From 2aaf36f403dbb045e13096470f8c1cf40306dead Mon Sep 17 00:00:00 2001 From: IGE Clouds Project Operations <34292598+igewebs@users.noreply.github.com> Date: Mon, 25 Dec 2023 20:41:14 +0000 Subject: [PATCH] Add files via upload Signed-off-by: IGE Clouds Project Operations <34292598+igewebs@users.noreply.github.com> --- src/jetbrain-space/docker-compose.yml | 164 +++++++++++++++ src/prefect-docker-compose-main/README.md | 195 ++++++++++++++++++ .../agent/Dockerfile | 4 + .../agent/docker-compose.yml | 12 ++ .../agent_docker/Dockerfile | 7 + .../agent_docker/docker-compose.yml | 20 ++ .../client/Dockerfile | 6 + .../client/app/weather.py | 59 ++++++ .../client/docker-compose.yml | 12 ++ .../client_docker/Dockerfile | 10 + .../client_docker/app/weather.py | 111 ++++++++++ .../client_docker/docker-compose.yml | 42 ++++ .../client_docker/entrypoint.sh | 3 + .../client_docker/execution.Dockerfile | 4 + .../client_s3/Dockerfile | 6 + .../client_s3/app/weather.py | 148 +++++++++++++ .../client_s3/docker-compose.yml | 37 ++++ .../docker_interface.png | Bin 0 -> 13945 bytes .../prefect_schema_principle.drawio | 1 + .../prefect_schema_principle.jpg | Bin 0 -> 45790 bytes .../server/Dockerfile | 4 + .../server/docker-compose.yml | 48 +++++ src/vscode-dotnet-project/CODE_OF_CONDUCT.md | 9 + src/vscode-dotnet-project/LICENSE | 21 ++ src/vscode-dotnet-project/Program.cs | 34 +++ src/vscode-dotnet-project/README.md | 109 ++++++++++ src/vscode-dotnet-project/SECURITY.md | 41 ++++ .../appsettings.Development.json | 8 + .../appsettings.HttpsDevelopment.json | 9 + src/vscode-dotnet-project/appsettings.json | 9 + .../vscode-remote-try-dotnet.csproj | 10 + 31 files changed, 1143 insertions(+) create mode 100644 src/jetbrain-space/docker-compose.yml create mode 100644 src/prefect-docker-compose-main/README.md create mode 100644 src/prefect-docker-compose-main/agent/Dockerfile create mode 100644 src/prefect-docker-compose-main/agent/docker-compose.yml create mode 100644 src/prefect-docker-compose-main/agent_docker/Dockerfile create mode 100644 src/prefect-docker-compose-main/agent_docker/docker-compose.yml create mode 100644 src/prefect-docker-compose-main/client/Dockerfile create mode 100644 src/prefect-docker-compose-main/client/app/weather.py create mode 100644 src/prefect-docker-compose-main/client/docker-compose.yml create mode 100644 src/prefect-docker-compose-main/client_docker/Dockerfile create mode 100644 src/prefect-docker-compose-main/client_docker/app/weather.py create mode 100644 src/prefect-docker-compose-main/client_docker/docker-compose.yml create mode 100644 src/prefect-docker-compose-main/client_docker/entrypoint.sh create mode 100644 src/prefect-docker-compose-main/client_docker/execution.Dockerfile create mode 100644 src/prefect-docker-compose-main/client_s3/Dockerfile create mode 100644 src/prefect-docker-compose-main/client_s3/app/weather.py create mode 100644 src/prefect-docker-compose-main/client_s3/docker-compose.yml create mode 100644 src/prefect-docker-compose-main/docker_interface.png create mode 100644 src/prefect-docker-compose-main/prefect_schema_principle.drawio create mode 100644 src/prefect-docker-compose-main/prefect_schema_principle.jpg create mode 100644 src/prefect-docker-compose-main/server/Dockerfile create mode 100644 src/prefect-docker-compose-main/server/docker-compose.yml create mode 100644 src/vscode-dotnet-project/CODE_OF_CONDUCT.md create mode 100644 src/vscode-dotnet-project/LICENSE create mode 100644 src/vscode-dotnet-project/Program.cs create mode 100644 src/vscode-dotnet-project/README.md create mode 100644 src/vscode-dotnet-project/SECURITY.md create mode 100644 src/vscode-dotnet-project/appsettings.Development.json create mode 100644 src/vscode-dotnet-project/appsettings.HttpsDevelopment.json create mode 100644 src/vscode-dotnet-project/appsettings.json create mode 100644 src/vscode-dotnet-project/vscode-remote-try-dotnet.csproj diff --git a/src/jetbrain-space/docker-compose.yml b/src/jetbrain-space/docker-compose.yml new file mode 100644 index 0000000..f0bd2d3 --- /dev/null +++ b/src/jetbrain-space/docker-compose.yml @@ -0,0 +1,164 @@ +version: '3.8' + +services: + init-configs: + image: "public.registry.jetbrains.space/p/space-on-premises/docker/init-configs:2023.3.1" + volumes: + - config:/home/init-config/config + environment: + AUTOMATION_TAG: "2023.3.1.87" + SPACE_VERSION: "2023.3.1" + space: + image: "public.registry.jetbrains.space/p/space-on-premises/docker/space:2023.3.1" + volumes: + - config:/home/space/circlet-server-onprem/config + environment: + JAVA_OPTS: "-Dconfig.file=/home/space/circlet-server-onprem/config/space.on-premises.conf -Dconfig.override_with_env_vars=true" + depends_on: + init-configs: + condition: service_completed_successfully + vcs: + condition: service_started + redis: + condition: service_started + postgres: + condition: service_started + minio: + condition: service_started + elasticsearch: + condition: service_healthy + ports: + - "8084:8084" + networks: + - "frontend" + - "backend-apps" + - "backend-data" + + vcs: + image: "public.registry.jetbrains.space/p/space-on-premises/docker/vcs-hosting:2023.3.1" + volumes: + - config:/home/space/git/vcs-hosting/config + environment: + JAVA_OPTS: '-Dproperties.file=config/vcs.on-premises.properties' + depends_on: + init-configs: + condition: service_completed_successfully + redis: + condition: service_started + postgres: + condition: service_started + minio: + condition: service_started + elasticsearch: + condition: service_healthy + ports: + - "2222:2222" + - "8080:8080" + networks: + - "frontend" + - "backend-apps" + - "backend-data" + + packages: + image: "public.registry.jetbrains.space/p/space-on-premises/docker/packages:2023.3.1" + volumes: + - config:/home/space/packages-server/config + environment: + JAVA_OPTS: '-Dconfig.file=/home/space/packages-server/config/packages.on-premises.conf -Dconfig.override_with_env_vars=true' + depends_on: + init-configs: + condition: service_completed_successfully + redis: + condition: service_started + postgres: + condition: service_started + minio: + condition: service_started + elasticsearch: + condition: service_healthy + ports: + - "8390:8390" + - "9390:9390" + networks: + - "frontend" + - "backend-apps" + - "backend-data" + + langservice: + image: "public.registry.jetbrains.space/p/space-on-premises/docker/langservice:2023.3.1" + volumes: + - config:/home/space/langservice-server/config + environment: + JAVA_OPTS: "-Dconfig.file=/home/space/langservice-server/config/langservice.on-premises.conf -Dconfig.override_with_env_vars=true" + depends_on: + init-configs: + condition: service_completed_successfully + ports: + - "8095" + networks: + - "backend-apps" + + postgres: + image: "postgres:12.2" + volumes: + - db_data:/var/lib/postgresql/data + environment: + POSTGRES_USER: space + POSTGRES_PASSWORD: spacepassword + POSTGRES_DB: spacedb + ports: + - "5432" + networks: + - "backend-data" + + elasticsearch: + image: "elasticsearch:7.17.7" + volumes: + - elasticsearch_data:/usr/share/elasticsearch/data + environment: + ES_JAVA_OPTS: -Xms512m -Xmx1024m + discovery.type: single-node + xpack.security.enabled: "false" + ports: + - "9200" + - "9300" + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost:9200/_cat/health" ] + interval: 5s + timeout: 5s + retries: 10 + start_period: 20s + networks: + - "backend-data" + + redis: + image: "redis:7.0.2-alpine" + ports: + - "6379" + networks: + - "backend-data" + + minio: + image: minio/minio:RELEASE.2021-09-09T21-37-07Z + volumes: + - minio_data:/data + environment: + MINIO_ROOT_USER: space-access-key + MINIO_ROOT_PASSWORD: space-secret-key + command: server --address :9000 --console-address :9001 --compat /data + ports: + - "9000:9000" + - "9001" + networks: + - "backend-data" +volumes: + elasticsearch_data: {} + db_data: {} + minio_data: {} + config: {} + +networks: + frontend: {} + backend-apps: {} + backend-data: {} + diff --git a/src/prefect-docker-compose-main/README.md b/src/prefect-docker-compose-main/README.md new file mode 100644 index 0000000..58507f5 --- /dev/null +++ b/src/prefect-docker-compose-main/README.md @@ -0,0 +1,195 @@ +# Prefect - Docker Compose + +A simple guide to understand and make Prefect **2.x** work with your own docker-compose configuration. + +Interested in examples for Prefect **1.x** ? Switch to the [last 1.x branch of this repo](https://github.com/flavienbwk/prefect-docker-compose/tree/e758a498d5819550a9b926b0bf9bb4e9c85574d1). + +This allows you to package your Prefect instance for fully-containerized environments (e.g: docker-compose, Kubernetes) or offline use. + +![Operating principle of Prefect](./prefect_schema_principle.jpg) + +- [Prefect - Docker Compose](#prefect---docker-compose) + - [Run the server](#run-the-server) + - [Run one or multiple agents](#run-one-or-multiple-agents) + - [Run your first flow via the Prefect API](#run-your-first-flow-via-the-prefect-api) + - [Principles to understand](#principles-to-understand) + - [Flow with Local storage (easiest)](#flow-with-local-storage-easiest) + - [Flow with S3 Storage (recommended)](#flow-with-s3-storage-recommended) + - [Flow with Docker storage](#flow-with-docker-storage) + - [Preparing the Registry](#preparing-the-registry) + - [Start the Docker in Docker agent](#start-the-docker-in-docker-agent) + - [Registering the flow](#registering-the-flow) + +## Run the server + +1. Optionally open and edit the [`server/.env`](./server/.env) file + + :information_source: All `PREFECT_*` env variables can be [found in the Prefect settings.py file](https://github.com/PrefectHQ/prefect/blob/main/src/prefect/settings.py#L238). + +2. Start the server : + + ```bash + cd server && docker-compose up --build -d && cd - + ``` + +3. Access the Orion UI at [localhost:4200](http://localhost:4200) + +## Run one or multiple agents + +Agents are services that run your scheduled flows. + +1. Optionally open and edit the [`agent/docker-compose.yml`](./agent/docker-compose.yml) file. + + > :information_source: In each `docker-compose.yml`, you will find the `PREFECT_API_URL` env variable including the `172.17.0.1` IP address. This is the IP of the Docker daemon on which are exposed all exposed ports of your containers. This allows containers launched from different docker-compose networks to communicate. Change it if yours is different (check your daemon IP by typing `ip a | grep docker0`). + > + > ![Docker interface IP](./docker_interface.png) + > + > Here, mine is `192.168.254.1` but the default is generally to `172.17.0.1`. + +2. Start the agent : + + ```bash + docker-compose -f agent/docker-compose.yml up -d + ``` + + > :information_source: You can run the agent on another machine than the one with the Prefect server. Edit the `PREFECT_API_URL` env variable for that. + + Maybe you want to instanciate multiple agents ? + + ```bash + docker-compose -f agent/docker-compose.yml up -d --scale agent=3 agent + ``` + +3. Our agents are now starting listening the Orion server on the `flows-example-queue` queue ([see the `--work-queue` option](./agent/docker-compose.yml#L7)). + +## Run your first flow via the Prefect API + +### Principles to understand + +> :speech_balloon: [Execution in your cloud; orchestration in ours](https://medium.com/the-prefect-blog/the-prefect-hybrid-model-1b70c7fd296) + +This means the Prefect server never stores your code. It just orchestrates the running (optionally the scheduling) of it. + +1. After developing your flow, Prefect will register it to the Orion server [through a Deployment](./client/app/weather.py#L49). In that script, you may ask the server to run your flow 3 times a day, for example. +2. Your code never lies on the Prefect server : this means the code has to be stored somewhere accessible to the agents in order to be executed. + + Prefect has [a lot of storage options](https://docs.prefect.io/tutorials/storage) but the most famous are : Local, S3, Docker and git. + - Local : saves the flows to be run on disk. So the volume where you save the flows must be [shared among your client and your agent(s)](./client/docker-compose.yml#L9). Requires your agent to have the same environment than your client (Python modules, packages installed etc... (the same Dockerfile if your agent and client are containers)) + - S3 : similar to local, but saves the flows to be run in S3 objects. + - Docker : saves the flows to be run as Docker images to your Docker Registry so your agents can easily run the code. + +### Flow with Local storage (easiest) + +:information_source: If your agents are installed among multiple machines, I recommend you to mount a shared directory with SSHFS. + +1. Run the following command to register your deployment and run the flow : + + ```bash + docker-compose -f client/docker-compose.yml up # Executes weather.py + ``` + +2. Access the UI to see your flow correctly run + +### Flow with S3 Storage (recommended) + +
+Tutorial for S3 Storage +
+ +We will use [MinIO](https://www.github.com/minio/minio) as our S3 server. + +1. Optionally open and edit the [`client_s3/.env`](./client_s3/.env) file and start MinIO + + ```bash + docker-compose -f client_s3/docker-compose.yml up -d minio # Starts MinIO + ``` + +2. Register the flow : + + ```bash + docker-compose -f client_s3/docker-compose.yml up weather # Executes weather.py + ``` + +Now your flow is registered. You can access the UI to run it. + +
+ +### Flow with Docker storage + +This method requires our client AND agent containers to have access to Docker so they can package or load the image in which the flow will be executed. We use _Docker in Docker_ for that. + +
+Tutorial for (secure) Docker Storage + +#### Preparing the Registry + +A Docker Registry is needed in order to save images that are going to be used by our agents. + +1. Generate the authentication credentials for our registry + + ```bash + sudo apt install apache2-utils # required to generate basic_auth credentials + cd client_docker/registry/auth && htpasswd -B -c .htpasswd myusername && cd - + ``` + + > To add more users, re-run the previous command **without** the -c option + +2. Start the registry + + ```bash + docker-compose -f client_docker/docker-compose.yml up -d registry + ``` + +3. Login to the registry + + You need to allow your Docker daemon to push to this registry. Insert this in your `/etc/docker/daemon.json` (create if needed) : + + ```json + { + "insecure-registries": ["172.17.0.1:5000"] + } + ``` + +4. Start the registry + + ```bash + docker login http://172.17.0.1:5000 # with myusername and the password you typed + ``` + + You should see : _Login Succeeded_ + +#### Start the Docker in Docker agent + +Optionally edit registry credentials in [`./agent_docker/docker-compose.yml`](./agent_docker/docker-compose.yml) and run : + +```bash +docker-compose -f agent_docker/docker-compose.yml up --build -d +``` + +#### Registering the flow + +We're going to push our Docker image with Python dependencies and register our flow. + +1. Build, tag and push the image + + ```bash + docker build . -f ./client_docker/execution.Dockerfile -t 172.17.0.1:5000/weather/base_image:latest + ``` + + > You **must** prefix your image with the registry URI `172.17.0.1:5000` to push it + + ```bash + docker push 172.17.0.1:5000/weather/base_image:latest + ``` + +2. Register the flow + + Optionally edit registry credentials in `./client_docker/docker-compose.yml` and run : + + ```bash + docker-compose -f ./client_docker/docker-compose.yml up --build weather + ``` + +Now your flow is registered. You can access the UI to run it. + +
diff --git a/src/prefect-docker-compose-main/agent/Dockerfile b/src/prefect-docker-compose-main/agent/Dockerfile new file mode 100644 index 0000000..5cbbfd0 --- /dev/null +++ b/src/prefect-docker-compose-main/agent/Dockerfile @@ -0,0 +1,4 @@ +FROM python:3.10 + +RUN apt update && apt install uuid -y +RUN pip install prefect==2.4.2 psycopg2-binary==2.9.3 s3fs==2022.8.2 minio==7.1.11 diff --git a/src/prefect-docker-compose-main/agent/docker-compose.yml b/src/prefect-docker-compose-main/agent/docker-compose.yml new file mode 100644 index 0000000..b4e5155 --- /dev/null +++ b/src/prefect-docker-compose-main/agent/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.7" + +services: + + agent: + build: . + command: bash -c "prefect agent start --work-queue flows-example-queue" + environment: + PREFECT_API_URL: "http://172.17.0.1:4200/api" + volumes: + - /srv/docker/prefect/flows:/root/.prefect/flows + - /srv/docker/prefect/flows:/flows diff --git a/src/prefect-docker-compose-main/agent_docker/Dockerfile b/src/prefect-docker-compose-main/agent_docker/Dockerfile new file mode 100644 index 0000000..8cc2dce --- /dev/null +++ b/src/prefect-docker-compose-main/agent_docker/Dockerfile @@ -0,0 +1,7 @@ +FROM docker:20.10-dind-rootless +ENV TZ="Europe/Paris" + +USER root +RUN apk update && apk add build-base libffi-dev python3 python3-dev py3-pip gcc linux-headers musl-dev util-linux + +RUN pip3 install --upgrade pip && pip3 install prefect==2.4.2 diff --git a/src/prefect-docker-compose-main/agent_docker/docker-compose.yml b/src/prefect-docker-compose-main/agent_docker/docker-compose.yml new file mode 100644 index 0000000..5f373bb --- /dev/null +++ b/src/prefect-docker-compose-main/agent_docker/docker-compose.yml @@ -0,0 +1,20 @@ +version: "3.7" + +services: + + agent: + build: . + privileged: true # forced for Docker DinD + entrypoint: ["/bin/sh", "-c"] + command: + - | + docker login -u=myusername -p=test http://172.17.0.1:5000 + mkdir -p /opt/prefect/flows + prefect agent start --work-queue flows-example-queue-docker + environment: + PREFECT_API_URL: "http://172.17.0.1:4200/api" + volumes: + - type: bind + source: /var/run/docker.sock + target: /var/run/docker.sock + read_only: true diff --git a/src/prefect-docker-compose-main/client/Dockerfile b/src/prefect-docker-compose-main/client/Dockerfile new file mode 100644 index 0000000..c450a42 --- /dev/null +++ b/src/prefect-docker-compose-main/client/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.10 + +RUN apt update && apt install uuid -y +RUN pip install prefect==2.4.2 + +WORKDIR /usr/app diff --git a/src/prefect-docker-compose-main/client/app/weather.py b/src/prefect-docker-compose-main/client/app/weather.py new file mode 100644 index 0000000..88200b4 --- /dev/null +++ b/src/prefect-docker-compose-main/client/app/weather.py @@ -0,0 +1,59 @@ +# A simple example to demonstrate Prefect is working as expected +# Works with a local folder shared with the agents (/root/.prefect/flows by default). + +import json + +import requests +from prefect import flow, task, get_run_logger +from prefect.deployments import Deployment +from prefect.filesystems import LocalFileSystem + +# --- Flow definition + +@task +def get_city_coordinates(city: str): + logger = get_run_logger() + cities = { + "Paris": (2.3510, 48.8567) + } + logger.info("Getting {}'s coordinates".format(city)) + return cities[city] + + +@task +def get_weather(longitude: float, latitude: float): + logger = get_run_logger() + logger.info(f"Getting weather of latitude={latitude} and longitude={longitude}") + api_endpoint = f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&hourly=temperature_2m" + response = requests.get(api_endpoint) + if response.status_code == 200: + weather_data = json.loads(response.text) + logger.debug(weather_data) + return weather_data + else: + raise Exception("Failed to query " + api_endpoint) + + +@flow(name="get_paris_weather") +def get_paris_weather(): + city_coordinates = get_city_coordinates("Paris") + return get_weather(city_coordinates[0], city_coordinates[1]) + +# --- Deployment definition + +if __name__ == '__main__': + + block_storage = LocalFileSystem(basepath="/flows") + block_storage.save("local-storage", overwrite=True) + + deployment = Deployment.build_from_flow( + name="get_weather_local_example", + flow=get_paris_weather, + storage=LocalFileSystem.load("local-storage"), + work_queue_name="flows-example-queue" + ) + deployment.apply() + + # --- Execute the flow + + get_paris_weather() diff --git a/src/prefect-docker-compose-main/client/docker-compose.yml b/src/prefect-docker-compose-main/client/docker-compose.yml new file mode 100644 index 0000000..144b6c2 --- /dev/null +++ b/src/prefect-docker-compose-main/client/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.7" + +services: + + weather: + build: . + command: python3 /usr/app/weather.py + volumes: + - /srv/docker/prefect/flows:/flows + - ./app:/usr/app:ro + environment: + PREFECT_API_URL: "http://172.17.0.1:4200/api" diff --git a/src/prefect-docker-compose-main/client_docker/Dockerfile b/src/prefect-docker-compose-main/client_docker/Dockerfile new file mode 100644 index 0000000..b436b03 --- /dev/null +++ b/src/prefect-docker-compose-main/client_docker/Dockerfile @@ -0,0 +1,10 @@ +FROM docker:20.10-dind-rootless +ENV TZ="Europe/Paris" + +USER root +RUN apk update && apk add build-base libffi-dev python3 python3-dev py3-pip gcc linux-headers musl-dev util-linux + +RUN pip3 install --upgrade pip && pip3 install prefect==2.4.2 docker==6.0.0 + +COPY ./entrypoint.sh /entrypoint.sh +ENTRYPOINT [ "sh", "/entrypoint.sh" ] diff --git a/src/prefect-docker-compose-main/client_docker/app/weather.py b/src/prefect-docker-compose-main/client_docker/app/weather.py new file mode 100644 index 0000000..4f5ca72 --- /dev/null +++ b/src/prefect-docker-compose-main/client_docker/app/weather.py @@ -0,0 +1,111 @@ +# A simple example to demonstrate Prefect is working as expected +# Works with a local folder shared with the agents (/root/.prefect/flows by default). + +import json +import os +import shutil +import tarfile +import tempfile +import uuid +from datetime import datetime + +import requests +from prefect import flow, get_run_logger, task +from prefect.deployments import Deployment +from prefect.infrastructure.docker import DockerContainer, DockerRegistry + +# --- Flow definition + + +@task +def get_city_coordinates(city: str): + logger = get_run_logger() + cities = {"Paris": (2.3510, 48.8567)} + logger.info("Getting {}'s coordinates".format(city)) + return cities[city] + + +@task +def get_weather(longitude: float, latitude: float): + logger = get_run_logger() + logger.info(f"Getting weather of latitude={latitude} and longitude={longitude}") + api_endpoint = f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&hourly=temperature_2m" + response = requests.get(api_endpoint) + if response.status_code == 200: + weather_data = json.loads(response.text) + logger.debug(weather_data) + return weather_data + else: + raise Exception("Failed to query " + api_endpoint) + + +@flow(name="get_paris_weather_docker") +def get_paris_weather(): + city_coordinates = get_city_coordinates("Paris") + weather_content = get_weather(city_coordinates[0], city_coordinates[1]) + logger = get_run_logger() + logger.info(weather_content) + return True + + +# --- Deployment definition + +if __name__ == "__main__": + from io import BytesIO + from docker import APIClient + + flow_identifier = datetime.today().strftime("%Y%m%d%H%M%S-") + str(uuid.uuid4()) + + # Mimicking Prefect 1.x image build for Prefect 2.x + + ## 1. Creating Docker build context (including flow files) + base_image = f"{os.environ.get('REGISTRY_ENDPOINT')}/weather/base_image:latest" + flow_image = ( + f"{os.environ.get('REGISTRY_ENDPOINT')}/weather/flow_image:{flow_identifier}" + ) + dockerfile = f""" + FROM {base_image} + RUN mkdir -p /usr/app + COPY ./flow /usr/app + """ + with tempfile.TemporaryDirectory() as tmp_path: + ### a. Creating archive with context (flow files + Dockerfile) for Docker build API + os.makedirs(f"{tmp_path}/build") + with open(f"{tmp_path}/build/Dockerfile", "w+") as the_file: + the_file.write(dockerfile) + shutil.copytree("/usr/app", f"{tmp_path}/build/flow") + with tarfile.open(f"{tmp_path}/flow.tar", "w") as tar: + tar.add(f"{tmp_path}/build", arcname=".") + ### b. Build image with context + with open(f"{tmp_path}/flow.tar", "rb") as fh: + docker_build_archive = BytesIO(fh.read()) + cli = APIClient(base_url="unix:///var/run/docker.sock") + for line in cli.build( + fileobj=docker_build_archive, custom_context=True, rm=True, tag=flow_image + ): + print(line, flush=True) + for line in cli.push(flow_image, stream=True, decode=True): + print(line, flush=True) + + ## 2. Registering flow + dockerhub = DockerRegistry( + username=os.environ.get("REGISTRY_USERNAME"), + password=os.environ.get("REGISTRY_PASSWORD"), + reauth=True, + registry_url=f"{os.environ.get('REGISTRY_SCHEME')}://{os.environ.get('REGISTRY_ENDPOINT')}", + ) + dockerhub.save("docker-storage", overwrite=True) + docker_block = DockerContainer( + image=flow_image, + image_registry=dockerhub, + ) + docker_block.save("docker-storage", overwrite=True) + + deployment = Deployment.build_from_flow( + name="get_weather_docker_example", + flow=get_paris_weather, + infrastructure=docker_block, # storage block is automatically detected from https://github.com/PrefectHQ/prefect/pull/6574/files + work_queue_name="flows-example-queue-docker", + path="/usr/app", + ) + deployment.apply() diff --git a/src/prefect-docker-compose-main/client_docker/docker-compose.yml b/src/prefect-docker-compose-main/client_docker/docker-compose.yml new file mode 100644 index 0000000..5f1f724 --- /dev/null +++ b/src/prefect-docker-compose-main/client_docker/docker-compose.yml @@ -0,0 +1,42 @@ +version: "3.8" + +services: + + registry: + restart: always + image: registry:2.8.1 + ports: + - 5000:5000 + volumes: + - ./registry/auth:/auth + - registry_data:/data + environment: + REGISTRY_AUTH: htpasswd + REGISTRY_AUTH_HTPASSWD_REALM: Registry + REGISTRY_AUTH_HTPASSWD_PATH: /auth/.htpasswd + REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data + + # The script should have access to Docker in order to register + # the Dockerized script (`execution.Dockerfile`) in a + # Docker image later used by the Prefect agent. + weather: + build: . + privileged: true # forced for Docker DinD + environment: + PREFECT_API_URL: "http://172.17.0.1:4200/api" + REGISTRY_ENDPOINT: "172.17.0.1:5000" + REGISTRY_SCHEME: "http" + REGISTRY_USERNAME: myusername + REGISTRY_PASSWORD: test + volumes: + - ./app:/usr/app:ro + - type: bind + source: /var/run/docker.sock + target: /var/run/docker.sock + read_only: true + depends_on: + - registry + +volumes: + registry_data: + flows_docker: diff --git a/src/prefect-docker-compose-main/client_docker/entrypoint.sh b/src/prefect-docker-compose-main/client_docker/entrypoint.sh new file mode 100644 index 0000000..5828727 --- /dev/null +++ b/src/prefect-docker-compose-main/client_docker/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/sh +docker login -u=${REGISTRY_USERNAME} -p=${REGISTRY_PASSWORD} "${REGISTRY_SCHEME}://${REGISTRY_ENDPOINT}" +python3 /usr/app/weather.py diff --git a/src/prefect-docker-compose-main/client_docker/execution.Dockerfile b/src/prefect-docker-compose-main/client_docker/execution.Dockerfile new file mode 100644 index 0000000..60e049c --- /dev/null +++ b/src/prefect-docker-compose-main/client_docker/execution.Dockerfile @@ -0,0 +1,4 @@ +FROM python:3.10 + +RUN apt update && apt install uuid -y +RUN pip3 install --upgrade pip && pip3 install prefect==2.4.2 diff --git a/src/prefect-docker-compose-main/client_s3/Dockerfile b/src/prefect-docker-compose-main/client_s3/Dockerfile new file mode 100644 index 0000000..b526131 --- /dev/null +++ b/src/prefect-docker-compose-main/client_s3/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.10 + +RUN apt update && apt install uuid -y +RUN pip install prefect==2.4.2 psycopg2-binary==2.9.3 s3fs==2022.8.2 minio==7.1.11 + +WORKDIR /usr/app diff --git a/src/prefect-docker-compose-main/client_s3/app/weather.py b/src/prefect-docker-compose-main/client_s3/app/weather.py new file mode 100644 index 0000000..35dfb7a --- /dev/null +++ b/src/prefect-docker-compose-main/client_s3/app/weather.py @@ -0,0 +1,148 @@ +# A simple example to demonstrate Prefect is working as expected +# Works with a local folder shared with the agents (/root/.prefect/flows by default). + +import io +import json +import os +import uuid +from datetime import datetime + +import requests +from minio import Minio +from minio.error import S3Error +from prefect import flow, get_run_logger, task +from prefect.deployments import Deployment +from prefect.filesystems import RemoteFileSystem + +# --- Flow definition + + +@task +def create_bucket( + minio_endpoint, + minio_access_key, + minio_secret_key, + minio_use_ssl, + bucket_name, +): + client = Minio( + minio_endpoint, minio_access_key, minio_secret_key, secure=minio_use_ssl + ) + try: + client.make_bucket(bucket_name) + except S3Error as ex: + if ex.code != "BucketAlreadyOwnedByYou": + raise ex + print("Flows bucket already exist, skipping.", flush=True) + + +@task +def get_city_coordinates(city: str): + logger = get_run_logger() + cities = {"Paris": (2.3510, 48.8567)} + logger.info("Getting {}'s coordinates".format(city)) + return cities[city] + + +@task +def get_weather(longitude: float, latitude: float): + logger = get_run_logger() + logger.info(f"Getting weather of latitude={latitude} and longitude={longitude}") + api_endpoint = f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&hourly=temperature_2m" + response = requests.get(api_endpoint) + if response.status_code == 200: + weather_data = json.loads(response.text) + logger.debug(weather_data) + return weather_data + else: + raise Exception("Failed to query " + api_endpoint) + + +@task +def add_text_to_bucket( + content: str, + minio_endpoint, + minio_access_key, + minio_secret_key, + minio_use_ssl, + bucket_name, +): + client = Minio( + minio_endpoint, minio_access_key, minio_secret_key, secure=minio_use_ssl + ) + content_json = json.dumps(content) + client.put_object( + object_name=f"{datetime.today().strftime('%Y%m%d%H%M%S')}/weather.txt", + bucket_name=bucket_name, + data=io.BytesIO(str.encode(content_json)), + length=len(content_json), + content_type="text/plain", + ) + + +@flow(name="get_paris_weather_s3") +def get_paris_weather( + minio_endpoint: str, + minio_access_key: str, + minio_secret_key: str, + minio_use_ssl: bool, + artifacts_bucket_name: str, +): + city_coordinates = get_city_coordinates("Paris") + weather_content = get_weather(city_coordinates[0], city_coordinates[1]) + create_bucket( + minio_endpoint, + minio_access_key, + minio_secret_key, + minio_use_ssl, + artifacts_bucket_name, + ) + add_text_to_bucket( + weather_content, + minio_endpoint, + minio_access_key, + minio_secret_key, + minio_use_ssl, + artifacts_bucket_name, + ) + return True + + +# --- Deployment definition + +if __name__ == "__main__": + bucket_name = os.environ.get("MINIO_PREFECT_FLOWS_BUCKET_NAME") + artifacts_bucket_name = os.environ.get("MINIO_PREFECT_ARTIFACTS_BUCKET_NAME") + minio_endpoint = os.environ.get("MINIO_ENDPOINT") + minio_use_ssl = os.environ.get("MINIO_USE_SSL") == "true" + minio_scheme = "https" if minio_use_ssl else "http" + minio_access_key = os.environ.get("MINIO_ACCESS_KEY") + minio_secret_key = os.environ.get("MINIO_SECRET_KEY") + + flow_identifier = datetime.today().strftime("%Y%m%d%H%M%S-") + str(uuid.uuid4()) + block_storage = RemoteFileSystem( + basepath=f"s3://{bucket_name}/{flow_identifier}", + key_type="hash", + settings=dict( + use_ssl=minio_use_ssl, + key=minio_access_key, + secret=minio_secret_key, + client_kwargs=dict(endpoint_url=f"{minio_scheme}://{minio_endpoint}"), + ), + ) + block_storage.save("s3-storage", overwrite=True) + + deployment = Deployment.build_from_flow( + name="get_weather_s3_example", + flow=get_paris_weather, + storage=RemoteFileSystem.load("s3-storage"), + work_queue_name="flows-example-queue", + parameters={ + "minio_endpoint": minio_endpoint, + "minio_access_key": minio_access_key, + "minio_secret_key": minio_secret_key, + "minio_use_ssl": minio_use_ssl, + "artifacts_bucket_name": artifacts_bucket_name, + }, + ) + deployment.apply() diff --git a/src/prefect-docker-compose-main/client_s3/docker-compose.yml b/src/prefect-docker-compose-main/client_s3/docker-compose.yml new file mode 100644 index 0000000..494046a --- /dev/null +++ b/src/prefect-docker-compose-main/client_s3/docker-compose.yml @@ -0,0 +1,37 @@ +version: "3.7" + +services: + + minio: + image: minio/minio:RELEASE.2022-09-07T22-25-02Z + command: server --console-address ":9001" /data + ports: + - 9000:9000 + - 9001:9001 + volumes: + - /srv/docker/prefect/minio:/data + environment: + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 5s + timeout: 3s + retries: 10 + + weather: + build: . + command: python3 /usr/app/weather.py + volumes: + - ./app:/usr/app:ro + environment: + PREFECT_API_URL: "http://172.17.0.1:4200/api" + MINIO_USE_SSL: ${MINIO_USE_SSL} + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} + MINIO_ENDPOINT: ${MINIO_ENDPOINT} # Must be accessible from Agent + MINIO_PREFECT_FLOWS_BUCKET_NAME: ${MINIO_PREFECT_FLOWS_BUCKET_NAME} + MINIO_PREFECT_ARTIFACTS_BUCKET_NAME: ${MINIO_PREFECT_ARTIFACTS_BUCKET_NAME} + depends_on: + minio: + condition: service_healthy diff --git a/src/prefect-docker-compose-main/docker_interface.png b/src/prefect-docker-compose-main/docker_interface.png new file mode 100644 index 0000000000000000000000000000000000000000..0cb392212bc5e7a6b40f11c849c579e62125af7e GIT binary patch literal 13945 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 \ No newline at end of file diff --git a/src/prefect-docker-compose-main/prefect_schema_principle.jpg b/src/prefect-docker-compose-main/prefect_schema_principle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d067574424143b20692942c995841ab1d970ffcc GIT binary patch literal 45790 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 literal 0 HcmV?d00001 diff --git a/src/prefect-docker-compose-main/server/Dockerfile b/src/prefect-docker-compose-main/server/Dockerfile new file mode 100644 index 0000000..8d119f7 --- /dev/null +++ b/src/prefect-docker-compose-main/server/Dockerfile @@ -0,0 +1,4 @@ +FROM prefecthq/prefect:2.4.2-python3.10 + +RUN apt update && \ + pip install psycopg2-binary==2.9.3 s3fs==2022.8.2 diff --git a/src/prefect-docker-compose-main/server/docker-compose.yml b/src/prefect-docker-compose-main/server/docker-compose.yml new file mode 100644 index 0000000..c3c3d6a --- /dev/null +++ b/src/prefect-docker-compose-main/server/docker-compose.yml @@ -0,0 +1,48 @@ +version: "3.7" + +services: + + postgres: + command: + - postgres + - -c + - max_connections=150 + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + volumes: + - postgres:/var/lib/postgresql/data + healthcheck: + interval: 10s + retries: 60 + start_period: 2s + test: pg_isready -q -d $${POSTGRES_DB} -U $${POSTGRES_USER} | grep "accepting connections" || exit 1 + timeout: 2s + image: postgres:14 + restart: always + + orion: + restart: always + build: . + + command: prefect orion start + ports: + - 4200:4200 + depends_on: + postgres: + condition: service_started + volumes: + - prefect_data:/root/.prefect + - prefect_flows:/flows + environment: + PREFECT_ORION_API_HOST: 0.0.0.0 + PREFECT_ORION_DATABASE_CONNECTION_URL: ${DB_CONNECTION_URL} + PREFECT_ORION_ANALYTICS_ENABLED: "false" + PREFECT_LOGGING_SERVER_LEVEL: WARNING + PREFECT_API_URL: ${PREFECT_API_URL} + +volumes: + postgres: + prefect_data: + prefect_flows: \ No newline at end of file diff --git a/src/vscode-dotnet-project/CODE_OF_CONDUCT.md b/src/vscode-dotnet-project/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f9ba8cf --- /dev/null +++ b/src/vscode-dotnet-project/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/src/vscode-dotnet-project/LICENSE b/src/vscode-dotnet-project/LICENSE new file mode 100644 index 0000000..4b1ad51 --- /dev/null +++ b/src/vscode-dotnet-project/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/src/vscode-dotnet-project/Program.cs b/src/vscode-dotnet-project/Program.cs new file mode 100644 index 0000000..ff8b47e --- /dev/null +++ b/src/vscode-dotnet-project/Program.cs @@ -0,0 +1,34 @@ +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); + +app.Urls.Add("http://localhost:5000"); + +app.MapGet("/", () => "Hello World!"); + +app.MapGet("/{cityName}/weather", GetWeatherByCity); + +app.Run(); + + +Weather GetWeatherByCity(string cityName) +{ + app.Logger.LogInformation($"Weather requested for {cityName}."); + var weather = new Weather(cityName); + return weather; +} + +public record Weather +{ + public string City { get; set; } + + public Weather(string city) + { + City = city; + Conditions = "Cloudy"; + // Temperature here is in celsius degrees, hence the 0-40 range. + Temperature = new Random().Next(0,40).ToString(); + } + + public string Conditions { get; set; } + public string Temperature { get; set; } +} \ No newline at end of file diff --git a/src/vscode-dotnet-project/README.md b/src/vscode-dotnet-project/README.md new file mode 100644 index 0000000..d239cfa --- /dev/null +++ b/src/vscode-dotnet-project/README.md @@ -0,0 +1,109 @@ +# Try Out Development Containers: .NET + +[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode-remote-try-dotnet) + + +A **development container** is a running container with a well-defined tool/runtime stack and its prerequisites. You can try out development containers with **[GitHub Codespaces](https://github.com/features/codespaces)** or **[Visual Studio Code Dev Containers](https://aka.ms/vscode-remote/containers)**. + +This is a sample project that lets you try out either option in a few easy steps. We have a variety of other [vscode-remote-try-*](https://github.com/search?q=org%3Amicrosoft+vscode-remote-try-&type=Repositories) sample projects, too. + +> **Note:** If you already have a Codespace or dev container, you can jump to the [Things to try](#things-to-try) section. + +## Setting up the development container + +### GitHub Codespaces +Follow these steps to open this sample in a Codespace: +1. Click the **Code** drop-down menu. +2. Click on the **Codespaces** tab. +3. Click **Create codespace on main** . + +For more info, check out the [GitHub documentation](https://docs.github.com/en/free-pro-team@latest/github/developing-online-with-codespaces/creating-a-codespace#creating-a-codespace). + +### VS Code Dev Containers + +If you already have VS Code and Docker installed, you can click the badge above or [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode-remote-try-dotnet) to get started. Clicking these links will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use. + +Follow these steps to open this sample in a container using the VS Code Dev Containers extension: + +1. If this is your first time using a development container, please ensure your system meets the pre-reqs (i.e. have Docker installed) in the [getting started steps](https://aka.ms/vscode-remote/containers/getting-started). + +2. To use this repository, you can either open the repository in an isolated Docker volume: + + - Press F1 and select the **Dev Containers: Try a Sample...** command. + - Choose the ".NET Core" sample, wait for the container to start, and try things out! + > **Note:** Under the hood, this will use the **Dev Containers: Clone Repository in Container Volume...** command to clone the source code in a Docker volume instead of the local filesystem. [Volumes](https://docs.docker.com/storage/volumes/) are the preferred mechanism for persisting container data. + + Or open a locally cloned copy of the code: + + - Clone this repository to your local filesystem. + - Press F1 and select the **Dev Containers: Open Folder in Container...** command. + - Select the cloned copy of this folder, wait for the container to start, and try things out! + +3. If you want to enable **HTTPS**, see [enabling HTTPS](#enabling-https) to reuse your local development cert in the container. + +## Things to try + +Once you have this sample opened, you'll be able to work with it like you would locally. + +Some things to try: + +1. **Restore Packages:** When notified by the C# extension to install packages, click Restore to trigger the process from inside the container! You can also execute `dotnet restore` command in a terminal. + +2. **Edit:** + - Open `Program.cs` + - Try adding some code and check out the language features. + - Make a spelling mistake and notice it is detected. The [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) extension was automatically installed because it is referenced in `.devcontainer/devcontainer.json`. + - Also notice that the [C#](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp) extension is installed. Tools are installed in the `mcr.microsoft.com/devcontainers/dotnet` image and Dev Container settings and metadata are automatically picked up from [image labels](https://containers.dev/implementors/reference/#labels). + + +4. **Build, Run, and Debug:** + - Open `Program.cs` + - Add a breakpoint (e.g. on line 16). + - Press F5 to launch the app in the container. + - Navigate to the weather endpoint, for example, [http://localhost:5000/paris/weather](http://localhost:5000/paris/weather). + - Once the breakpoint is hit, try hovering over variables, examining locals, and more. + - Continue (F5). You can connect to the server in the container by either: + - Clicking on `Open in Browser` in the notification telling you: `Your service running on port 5000 is available`. + - Clicking the globe icon in the 'Ports' view. The 'Ports' view gives you an organized table of your forwarded ports, and you can access it with the command **Ports: Focus on Ports View**. + - Notice port 5000 in the 'Ports' view is labeled "Hello Remote World." In `devcontainer.json`, you can set `"portsAttributes"`, such as a label for your forwarded ports and the action to be taken when the port is autoforwarded. + + > **Note:** In Dev Containers, you can access your app at `http://localhost:5000` in a local browser. But in a browser-based Codespace, you must click the link from the notification or the `Ports` view so that the service handles port forwarding in the browser and generates the correct URL. + +5. **Rebuild or update your container** + + You may want to make changes to your container, such as installing a different version of a software or forwarding a new port. You'll rebuild your container for your changes to take effect. + + **Open browser automatically:** As an example change, let's update the `portsAttributes` in the `.devcontainer/devcontainer.json` file to open a browser when our port is automatically forwarded. + + - Open the `.devcontainer/devcontainer.json` file. + - Modify the `"onAutoForward"` attribute in your `portsAttributes` from `"notify"` to `"openBrowser"`. + - Press F1 and select the **Dev Containers: Rebuild Container** or **Codespaces: Rebuild Container** command so the modifications are picked up. + +5. **Install Node.js using a Dev Container Feature:** + - Press F1 and select the **Dev Containers: Configure Container Features...** or **Codespaces: Configure Container Features...** command. + - Type "node" in the text box at the top. + - Check the check box next to "Node.js (via nvm) and yarn" (published by devcontainers) + - Click OK + - Press F1 and select the **Dev Containers: Rebuild Container** or **Codespaces: Rebuild Container** command so the modifications are picked up. + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## Trademarks + +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft +trademarks or logos is subject to and must follow +[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). +Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. +Any use of third-party trademarks or logos are subject to those third-party's policies. diff --git a/src/vscode-dotnet-project/SECURITY.md b/src/vscode-dotnet-project/SECURITY.md new file mode 100644 index 0000000..e138ec5 --- /dev/null +++ b/src/vscode-dotnet-project/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + diff --git a/src/vscode-dotnet-project/appsettings.Development.json b/src/vscode-dotnet-project/appsettings.Development.json new file mode 100644 index 0000000..ff66ba6 --- /dev/null +++ b/src/vscode-dotnet-project/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/vscode-dotnet-project/appsettings.HttpsDevelopment.json b/src/vscode-dotnet-project/appsettings.HttpsDevelopment.json new file mode 100644 index 0000000..8f41d8b --- /dev/null +++ b/src/vscode-dotnet-project/appsettings.HttpsDevelopment.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} \ No newline at end of file diff --git a/src/vscode-dotnet-project/appsettings.json b/src/vscode-dotnet-project/appsettings.json new file mode 100644 index 0000000..4d56694 --- /dev/null +++ b/src/vscode-dotnet-project/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/vscode-dotnet-project/vscode-remote-try-dotnet.csproj b/src/vscode-dotnet-project/vscode-remote-try-dotnet.csproj new file mode 100644 index 0000000..06fd153 --- /dev/null +++ b/src/vscode-dotnet-project/vscode-remote-try-dotnet.csproj @@ -0,0 +1,10 @@ + + + + net7.0 + enable + enable + vscode_remote_try_dotnet + + +