diff --git a/.github/workflows/pull-request-docker.yml b/.github/workflows/pull-request-docker.yml new file mode 100644 index 0000000..3b07def --- /dev/null +++ b/.github/workflows/pull-request-docker.yml @@ -0,0 +1,55 @@ +name: Docker images validation +on: + - pull_request + +jobs: + validate-dockerfile-order-processor: + name: Validate dockerfile order processor + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + dockerfile: + - 'src/Keda.Samples.Dotnet.OrderProcessor/**' + + - name: Build images + if: steps.filter.outputs.dockerfile == 'true' + run: docker build ./src/ --file ./src/Keda.Samples.Dotnet.OrderProcessor/Dockerfile --tag ghcr.io/kedacore/sample-dotnet-worker-servicebus-queue:latest + + validate-dockerfile-portal: + name: Validate dockerfile portal + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + dockerfile: + - 'src/Keda.Samples.DotNet.Web/**' + + - name: Build tools + if: steps.filter.outputs.dockerfile == 'true' + run: docker build ./src/ --file ./src/Keda.Samples.DotNet.Web/Dockerfile --tag ghcr.io/kedacore/sample-dotnet-worker-servicebus-portal:latest + + validate-order-generator: + name: Validate dockerfile order-generator + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + dockerfile: + - 'src/Keda.Samples.Dotnet.OrderGenerator/**' + + - name: Build the Docker image + if: steps.filter.outputs.dockerfile == 'true' + run: docker build ./src/ --file ./src/Keda.Samples.Dotnet.OrderGenerator/Dockerfile --tag ghcr.io/kedacore/sample-dotnet-order-generator-servicebus:latest \ No newline at end of file diff --git a/.github/workflows/pull-request-terraform.yml b/.github/workflows/pull-request-terraform.yml new file mode 100644 index 0000000..3d52dc0 --- /dev/null +++ b/.github/workflows/pull-request-terraform.yml @@ -0,0 +1,28 @@ +name: Terraform validation +on: + - pull_request + +jobs: + validate-infra-as-code-connection-string: + name: Validate infra-as-code for connection-string scenario + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + infra-as-code: + - 'infra-as-code/connection-string/**' + + - uses: hashicorp/setup-terraform@v2 + if: steps.filter.outputs.infra-as-code == 'true' + with: + terraform_version: 1.3.2 + + - name: Terraform validate + if: steps.filter.outputs.infra-as-code == 'true' + run: | + terraform -chdir=infra-as-code/connection-string init + terraform -chdir=infra-as-code/connection-string validate diff --git a/.github/workflows/push-images.yml b/.github/workflows/push-images.yml index 3b4d300..4a549d8 100644 --- a/.github/workflows/push-images.yml +++ b/.github/workflows/push-images.yml @@ -34,3 +34,18 @@ jobs: run: docker build ./src/ --file ./src/Keda.Samples.DotNet.Web/Dockerfile --tag ghcr.io/kedacore/sample-dotnet-worker-servicebus-portal:latest - name: Push the Docker image run: docker push ghcr.io/kedacore/sample-dotnet-worker-servicebus-portal:latest + order_generator: + runs-on: ubuntu-latest + name: Order Generator + steps: + - uses: actions/checkout@v2 + - name: Docker Login + uses: docker/login-action@v1.6.0 + with: + registry: ghcr.io + username: kedacore + password: ${{ secrets.CONTAINER_REGISTRY_KEY }} + - name: Build the Docker image + run: docker build ./src/ --file ./src/Keda.Samples.Dotnet.OrderGenerator/Dockerfile --tag ghcr.io/kedacore/sample-dotnet-order-generator-servicebus:latest + - name: Push the Docker image + run: docker push ghcr.io/kedacore/sample-dotnet-order-generator-servicebus:latest diff --git a/.gitignore b/.gitignore index 4f59a06..6a2e841 100644 --- a/.gitignore +++ b/.gitignore @@ -328,4 +328,13 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ -/src/docker-compose.override.yml \ No newline at end of file +/src/docker-compose.override.yml + +# Terraform related files +*.tfstate +*.tfstate.backup +.terraform.tfstate.lock.info +.terraform +.terraform.lock.hcl +terraform.plan +terraform.destroy \ No newline at end of file diff --git a/connection-string-scenario.md b/connection-string-scenario.md index e6db19e..e43251a 100644 --- a/connection-string-scenario.md +++ b/connection-string-scenario.md @@ -1,4 +1,5 @@ # .NET Core worker processing Azure Service Bus Queue scaled by KEDA with connection strings + A simple Docker container written in .NET that will receive messages from a Service Bus queue and scale via KEDA with connection strings. The message processor will receive a single message at a time (per instance), and sleep for 2 second to simulate performing work. When adding a massive amount of queue messages, KEDA will drive the container to scale out according to the event source (Service Bus Queue). @@ -53,18 +54,20 @@ In this case, we are telling KEDA to read the `connection` parameter from a Kube This allows us to not only re-use this authentication resource but also assign different permissions to KEDA than our app itself. -## Pre-requisites +## Deploy manually + +### Pre-requisites - Azure CLI - Azure Subscription - .NET Core 3.0 - Kubernetes cluster with [KEDA v2.0+ installed](https://keda.sh/docs/2.0/deploy/) -## Setup +### Setup This setup will go through creating an Azure Service Bus queue and deploying this consumer with the `ScaledObject` to scale via KEDA. If you already have an Azure Service Bus namespace you can use your existing queues. -### Creating a new Azure Service Bus namespace & queue +#### Creating a new Azure Service Bus namespace & queue We will start by creating a new Azure Service Bus namespace: @@ -105,7 +108,7 @@ Create a base64 representation of the connection string and update our Kubernete ❯ echo -n "" | base64 ``` -### Deploying our order processor +#### Deploying our order processor We will start by creating a new Kubernetes namespace to run our order processor in: @@ -139,7 +142,7 @@ NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE CONTAIN order-processor 1 1 1 1 49s order-processor kedasamples/sample-dotnet-worker-servicebus-queue app=order-processor ``` -### Deploying our autoscaling +#### Deploying our autoscaling First things first, we will create a new authorization rule with `Management` permissions so that KEDA can monitor it. @@ -172,7 +175,7 @@ This is because our queue is empty and KEDA scaled it down until there is work t In that case, let's give generate some! -## Publishing messages to the queue +### Publishing messages to the queue The following job will send messages to the "orders" queue on which the order processor is listening to. As the queue builds up, KEDA will help the horizontal pod autoscaler add more and more pods until the queue is drained. The order generator will allow you to specify how many messages you want to queue. @@ -259,11 +262,186 @@ info: Keda.Samples.Dotnet.OrderProcessor.OrdersQueueProcessor[0] Message 9d24f13cd5ec44e884efdc9ed4a8842d processed at: 06/03/2019 12:32:19 +00:00 ``` +## Deploy with terraform + +### Pre-requisites + +- Terraform +- Azure Subscription +- .NET Core 3.0 +- Kubernetes cluster with [KEDA v2.0+ installed](https://keda.sh/docs/2.0/deploy/) + +### Setup + +This setup will go through creating an Azure Service Bus queue and the necessary permissions with terraform and deploying this consumer with the `ScaledObject` to scale via KEDA. It will use the [helm chart created specifically](./deploy/helm/keda-servicebus-connection-string/) to create the necessary resources in the cluster. + +1. It's necessary configure the necessary variables, modifying the file [terraform.tfvars](infra-as-code/connection-string/terraform.tfvars) with each of them: + 1. `location`: Azure location where the resources are going to be created + 2. `resource_group`: Azure resource group where the resources are going to be created + 3. `subscription_id`: Azure Account Subscription ID where the resources are going to be created +2. Download the necessary terraform dependencies: + + ```sh + > terraform -chdir=infra-as-code/connection-string init + ``` + +3. Calculate the terraform plan to view ther resources that are going to be created, using the file [terraform.tfvars](infra-as-code/connection-string/terraform.tfvars) after made the modifications as `--var-file`: + + ```sh + > terraform -chdir=infra-as-code/connection-string plan --var-file=terraform.tfvars -out=terraform.plan + ``` + +4. Apply the terraform plan calculated in the previous step to create the necessary resources: + + ```sh + > terraform -chdir=infra-as-code/connection-string apply "terraform.plan" + ``` + +5. Once the terraform apply process have finished, it can be seen that a job has been created to queue messages in the queue: + + ```sh + > kubectl -n keda-dotnet-sample get job -l app=order-processor -w + NAME COMPLETIONS DURATION AGE + order-generator-eqw54u4m 0/1 0s + order-generator-eqw54u4m 0/1 0s 0s + order-generator-eqw54u4m 0/1 3s 3s + order-generator-eqw54u4m 0/1 12s 12s + order-generator-eqw54u4m 0/1 14s 14s + order-generator-eqw54u4m 1/1 14s 14s + ``` + +6. If the logs of this job are obtained you can see how messages have been queued: + + ```sh + > kubectl -n keda-dotnet-sample logs order-generator-eqw54u4m-w2p6l + Let's queue some orders, how many do you want? + 100 orders are going to be generated + Queuing order c6d5ed3a-774c-451d-be86-578925f046d0 - A Chips for Melissa O'Reilly + Queuing order 80da03a7-c070-40b4-af60-3f35753a7cd9 - A Pizza for Johnny Welch + Queuing order 5cd01df3-b7af-4b28-a8b5-0821c0802d8d - A Sausages for Lee Roob + Queuing order f507c7af-3382-4f15-905c-f4931c548c74 - A Chair for Mary Heller + Queuing order 2c1684f2-91e7-4aab-ab01-c02194e25f39 - A Ball for Myrl McClure + Queuing order b6a8d667-7fb4-465b-8069-98869f11b211 - A Fish for Manuela Champlin + Queuing order 28bc7ca0-25cf-4e4d-978e-4ded66ed2890 - A Pants for Amelie Heathcote + Queuing order 92bc0d56-14b7-4f6f-98fb-ae4153f98394 - A Tuna for Anita Mitchell + Queuing order ec7362b3-f1f2-4f5e-a51c-3b2014643aa9 - A Shoes for Shanelle Botsford + Queuing order 8626abea-3994-4b44-8875-04ba0015c979 - A Soap for Jackie Hansen + Queuing order 4ae76dc1-a494-4111-b434-25874cd220be - A Ball for Kiarra Schuppe + Queuing order 794e06e7-3a86-452f-9a15-f97f19dbff5c - A Gloves for Branson Runte + Queuing order 41521b9d-77da-441e-9252-e807504617a0 - A Bacon for Geovany Heidenreich + Queuing order 29486511-e574-4a0a-b180-1dd07c2348a3 - A Salad for Destiny Quigley + Queuing order 90416461-8c1e-4d99-97e6-22b82c86257c - A Keyboard for Aryanna Nitzsche + Queuing order 008f1c81-d344-45b2-a9d5-baec7ee72f6c - A Bacon for Luigi Gutmann + Queuing order d2d44ea2-26f1-46ee-8f53-2c2ee7f13ecb - A Keyboard for Eusebio Hyatt + Queuing order 8274ceaa-6bbe-44ce-b465-1e0f1dfca969 - A Bike for Dario Collins + Queuing order 5e6d7539-a739-43ca-a570-379d6fb071fd - A Ball for Elenor Hane + Queuing order ee19a215-e47d-4226-80c3-676b7d083ce6 - A Bike for Juvenal Halvorson + Queuing order e5e6a883-fd36-412f-87ed-bd63307569da - A Hat for Nedra Schamberger + Queuing order b61b05a4-9e0c-4b7e-9eda-c449bbbdd89f - A Tuna for Tomasa Sawayn + Queuing order 5fb788c9-9ed3-41f3-8836-3ac9d778a027 - A Sausages for Rex Zieme + Queuing order 6f54693a-0caa-42ca-9d5b-c60ca002e28e - A Towels for Xander Tremblay + Queuing order f6818d61-356a-4408-be06-c88ff423323e - A Soap for Boyd Herman + Queuing order e4eee103-f935-4358-8d84-3c79ed62bdf8 - A Soap for Nolan Krajcik + Queuing order 97ea4dfa-7d3f-497d-ba02-f91e7e43cbda - A Car for Erna Lubowitz + Queuing order e957c5db-9978-4d3b-8694-3091325cc0a1 - A Bike for Kathleen Balistreri + Queuing order 76a78906-ccb9-4aed-8f16-6e23ed45cd9c - A Shirt for Ismael Adams + Queuing order 5b3f150c-99ce-4e4f-b87b-541148cff1df - A Ball for Elinor McDermott + Queuing order 0c709d86-d79a-449c-bb5a-3aa8cfc0e8fd - A Computer for Lou Gleason + Queuing order 14094363-79f4-4a22-960f-2ab69dfebbca - A Cheese for Gloria Jakubowski + Queuing order ffc4ee72-3954-4238-9887-703b3981abf8 - A Gloves for Jacklyn Carter + Queuing order 309df831-ea1e-4adb-b95d-1947fbc1b9b1 - A Shirt for Dorothy Lockman + Queuing order ecee4713-0102-41d3-8f78-fc8658673a03 - A Computer for Vicente Price + Queuing order 4711bf82-6df2-484a-a58e-60a684e9f7e3 - A Gloves for Bettie Lowe + Queuing order c5e8a228-2107-4684-af4e-94b776163805 - A Chips for Charley Bartoletti + Queuing order 99c6128b-dcaf-4e5f-9d5d-7ab0a03926c4 - A Chicken for Angus Cummings + Queuing order 5a725ebf-93c8-4d62-8ab1-d6f73debe35b - A Cheese for Burley Schoen + Queuing order 09ccbb57-1b87-46c2-9c68-e672469b5409 - A Computer for Ernie Pollich + Queuing order 2fded1e5-3883-4f66-8a45-1bc5d5ef171a - A Gloves for Nicholas Crona + Queuing order bcc444d5-5611-4f63-82fd-cb2607f1c568 - A Chips for Treva Johnson + Queuing order 6fa4a2e2-d26f-4176-9f1f-e6b08d85abe8 - A Soap for Rosella Lesch + Queuing order e5d7ac33-9d66-4c9d-aa5c-792b41976ae9 - A Bike for Helmer Thiel + Queuing order 73e7a762-e9ae-4cae-9cb2-1d9318bf4320 - A Ball for Benny Sporer + Queuing order b73668ce-e0b3-4bab-87e9-772e901f7366 - A Bacon for Candido Crooks + Queuing order 8fd39e05-1b5d-4974-8863-fc3155cdb270 - A Towels for Herbert Pfannerstill + Queuing order 9dac0a12-f936-4276-8836-9c9d008ea8f7 - A Chair for Aileen Fritsch + Queuing order 66f8593e-a04a-4dc5-aeb1-254a1fbdd213 - A Bacon for Luisa Walsh + Queuing order 5f8dee17-0985-4c5c-a88b-07d817152074 - A Pants for Leonor Heaney + Queuing order 7d34c8bb-00b5-4c95-ab96-aef7b6bf5eca - A Chair for Izabella Upton + Queuing order 197e1226-b68c-4ba9-9297-5113d3d10eea - A Keyboard for Sylvester Weber + Queuing order 84c1be12-0122-4f01-98cb-31643585850a - A Towels for Oren Anderson + Queuing order 468fe208-4539-47b9-be17-b9340dd86da5 - A Towels for Carolyn Streich + Queuing order 575b5f96-eb0f-4143-954e-424d0b0a2fd2 - A Soap for Cesar O'Kon + Queuing order 8e73a307-3c84-47b6-a35f-f7f66d752ee9 - A Tuna for Cheyanne Runolfsdottir + Queuing order 9cc57364-2a1f-4040-a507-af82580a21d0 - A Pants for Randal Zboncak + Queuing order fd17a112-e9bf-490f-a735-e2497f2a1736 - A Tuna for Joseph Schimmel + Queuing order b277f380-d68d-4211-b0dc-50fd3f79314f - A Ball for Arturo Klein + Queuing order acb18f83-fae4-41ee-81dd-71f1cbe81202 - A Tuna for Loren Bechtelar + Queuing order 6243ad1c-c416-411e-b854-cb9cb5113ba5 - A Mouse for Nathaniel Lebsack + Queuing order 727017e1-c436-415d-9292-8808021a6a8d - A Bacon for Petra Hauck + Queuing order 43ab6232-c76c-4eb4-898f-b5b2b8952ed7 - A Pants for Buddy Walsh + Queuing order 17eda05b-2744-4a86-8884-66857a1b5c41 - A Car for Pamela Koch + Queuing order e6a553ad-5ad0-4085-8ca0-3c768064aba7 - A Shoes for Frederique Cormier + Queuing order 45fd69ea-621d-4f46-b58c-f9fbb87014cd - A Chips for Miracle Donnelly + Queuing order 7a8c2b48-244f-49f6-b806-41078df6b12b - A Salad for Brant Larson + Queuing order d92890a3-c80b-44ed-ba98-55f96e7457df - A Towels for Einar Nitzsche + Queuing order cf948d5b-8518-4cbc-8f29-5a3da31ca243 - A Shoes for Chelsie Spencer + Queuing order 5bafb7c4-78b3-446d-a182-d26dc481af9f - A Shoes for Carmine Schulist + Queuing order f9f7acb2-0f67-4c34-b33a-3f82a44b803f - A Gloves for Chance Dooley + Queuing order fdf78669-9792-4803-9f6c-4be825faadc9 - A Table for Laurie Harris + Queuing order 9d8e4375-1054-4740-8a71-1052097a779b - A Chicken for Lavina Schumm + Queuing order 03ff3354-0fc4-4405-acd2-ad4d181f6b6a - A Car for Litzy MacGyver + Queuing order 46a7e326-8616-4a5d-b7af-e95558d95af4 - A Pizza for Buford Dickinson + Queuing order e95a993c-6f97-4494-9393-80f448880196 - A Gloves for Julianne Schaden + Queuing order bde68e04-c6c1-4b1f-a005-97a0e2a9bdfc - A Bacon for Sydni Stiedemann + Queuing order 55f861c2-9127-41cf-8398-c8b16c5fc3a2 - A Gloves for Timothy Rosenbaum + Queuing order aea85cf6-ea23-45b1-be8f-6b5012aef81f - A Sausages for Vickie Kuhic + Queuing order 1941d43d-2bf1-4bdd-9838-d9ed7ebb071a - A Pizza for Frank Mann + Queuing order 32c236cb-68a5-4a86-9480-ed8c38052d87 - A Gloves for Lavada Osinski + Queuing order b7de9d3d-2bec-4fee-9c00-f9d65a632e0c - A Cheese for Willa Dickinson + Queuing order 71ae5da2-0de1-46f7-80af-74ceda3d14fd - A Towels for Ramon West + Queuing order 03b0ebc9-987b-4b98-86ae-a808e7f87170 - A Cheese for Nannie Zboncak + Queuing order 8e8448e1-53ab-4665-b0c3-d14479cb6ab1 - A Computer for Santos Schinner + Queuing order cdfacf78-c775-48e5-9ea9-3035bf7cf283 - A Hat for Mafalda Rice + Queuing order a74afa0b-2749-4c84-ade3-3c7a8972c7af - A Fish for Vickie Schmidt + Queuing order 46829d2b-baff-4797-908b-f79ed34e94e2 - A Bacon for David Rice + Queuing order 8d408010-b6b5-47be-8037-c6f5bdb85560 - A Shoes for Murray Weissnat + Queuing order 6cc667e1-1fd4-44d7-a587-243c96887137 - A Mouse for Adrian Barrows + Queuing order 81683e8d-d05a-4ca3-bccf-26dc1f4909e9 - A Chips for Gail Kuvalis + Queuing order fe261dcc-0fb0-4d1d-b9e5-0450328a214b - A Chicken for Polly Reinger + Queuing order 1acac1cc-b13f-4ea2-b3bc-0502ec470077 - A Bacon for Delilah Mayer + Queuing order c3ae937c-40a7-48e0-bfa1-0666fcb94fc6 - A Gloves for Adele Ortiz + Queuing order 7ae03d51-2cfd-4c3e-94d8-4da557595afd - A Chair for Adalberto Schmeler + Queuing order 6fa33e30-b6a4-4588-a659-e193919fd0f2 - A Computer for Tito Rowe + Queuing order f803ebd3-cbe3-47c6-bc78-93ad7b7af3bb - A Keyboard for Lois Roob + Queuing order ed80672b-b590-428b-bec8-4253a6a0c72a - A Sausages for Jacques Jacobs + Queuing order d14e0129-6345-42f8-8536-64dc935557ce - A Towels for Johnson Leffler + Queuing order 51e92eac-f4fb-4067-86b5-84a71a06118d - A Shoes for Carolanne Cartwright + ``` + +7. If the deployment `order-processor` is checked you can see how the number of replicas is increasing: + + ```sh + > kubectl -n keda-dotnet-sample get deployment order-processor -w + NAME READY UP-TO-DATE AVAILABLE AGE + order-processor 2/2 2 2 2m47s + order-processor 2/0 2 2 3m + order-processor 2/0 2 2 3m + order-processor 0/0 0 0 3m + ``` + +8. Now it is possible to go to the section [visualizing the service bus queue](#visualizing-the-service-bus-queue) + +9. Once the tests have been completed, the resources must be deleted, using the following commands: + + ```sh + > terraform -chdir=infra-as-code/connection-string plan -destroy --var-file=terraform.tfvars -out=terraform.destroy + > terraform -chdir=infra-as-code/connection-string apply "terraform.destroy" + ``` + ## Visualizing the service bus queue There is also a web application included in the repository that shows a simple bar chart with the number of messages. The graph refreshes every 2 seconds, giving you a visualization how the queue initially builds up when orders are being sent to the service bus, and then when the autoscaler kicks in the queue will decrease in length quicker and quicker depending on how many replicas that have been created. - To build and run the web app locally, add the service bus connection string to appSettings.json and run the web application from Visual Studio. There is also a docker image available, so you can also run it locally with the following command: @@ -288,7 +466,7 @@ NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kedasampleweb LoadBalancer 10.0.37.60 52.157.87.179 80:30919/TCP 117s ``` -You'll need to wait a short while until the public IP is created and shown in the output. +You'll need to wait a short while until the public IP is created and shown in the output. ![Visualize message queue](/images/kedaweb.png) diff --git a/deploy/helm/keda-servicebus-connection-string/.helmignore b/deploy/helm/keda-servicebus-connection-string/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/deploy/helm/keda-servicebus-connection-string/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/deploy/helm/keda-servicebus-connection-string/Chart.yaml b/deploy/helm/keda-servicebus-connection-string/Chart.yaml new file mode 100644 index 0000000..2139c15 --- /dev/null +++ b/deploy/helm/keda-servicebus-connection-string/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: keda-servicebus-connection-string +description: A Helm chart to deploy KEDA service bus sample connection string scenario + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.3 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/deploy/helm/keda-servicebus-connection-string/README.md b/deploy/helm/keda-servicebus-connection-string/README.md new file mode 100644 index 0000000..230eb8b --- /dev/null +++ b/deploy/helm/keda-servicebus-connection-string/README.md @@ -0,0 +1,29 @@ +# keda-servicebus-connection-string + +![Version: 0.1.3](https://img.shields.io/badge/Version-0.1.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.16.0](https://img.shields.io/badge/AppVersion-1.16.0-informational?style=flat-square) + +A Helm chart to deploy KEDA service bus sample connection string scenario + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | | +| fullnameOverride | string | `""` | | +| imagePullSecrets | list | `[]` | | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| orderConsumer | object | `{"servicebusConnectionString":""}` | Order consumer configuration | +| orderConsumer.servicebusConnectionString | string | `""` | Service bus connection string with manage permission | +| orderGenerator | object | `{"image":"ghcr.io/kedacore/sample-dotnet-order-generator-servicebus:latest","orderAmount":100,"queueName":"orders"}` | Order generator configuration | +| orderGenerator.image | string | `"ghcr.io/kedacore/sample-dotnet-order-generator-servicebus:latest"` | Image used by Job order-generator | +| orderGenerator.orderAmount | int | `100` | Number of orders that is going to be created in service bus for orders queue | +| orderGenerator.queueName | string | `"orders"` | Queue where the orders are going to be generated | +| orderProcessor | object | `{"image":"ghcr.io/kedacore/sample-dotnet-worker-servicebus-queue:latest","servicebusConnectionString":""}` | Order process configuration | +| orderProcessor.image | string | `"ghcr.io/kedacore/sample-dotnet-worker-servicebus-queue:latest"` | Image used by Deployment order-processor | +| orderProcessor.servicebusConnectionString | string | `""` | Service bus connection string with listen and send permissions | +| resources | object | `{}` | Resources configuration | +| tolerations | list | `[]` | | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/deploy/helm/keda-servicebus-connection-string/templates/NOTES.txt b/deploy/helm/keda-servicebus-connection-string/templates/NOTES.txt new file mode 100644 index 0000000..8f69e3a --- /dev/null +++ b/deploy/helm/keda-servicebus-connection-string/templates/NOTES.txt @@ -0,0 +1 @@ +1. Keda test sample deployed \ No newline at end of file diff --git a/deploy/helm/keda-servicebus-connection-string/templates/_helpers.tpl b/deploy/helm/keda-servicebus-connection-string/templates/_helpers.tpl new file mode 100644 index 0000000..7f2dfc1 --- /dev/null +++ b/deploy/helm/keda-servicebus-connection-string/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "keda-servicebus-connection-string.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "keda-servicebus-connection-string.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "keda-servicebus-connection-string.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "keda-servicebus-connection-string.labels" -}} +helm.sh/chart: {{ include "keda-servicebus-connection-string.chart" . }} +{{ include "keda-servicebus-connection-string.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "keda-servicebus-connection-string.selectorLabels" -}} +app.kubernetes.io/name: {{ include "keda-servicebus-connection-string.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "keda-servicebus-connection-string.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "keda-servicebus-connection-string.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/deploy/helm/keda-servicebus-connection-string/templates/order_generator.yaml b/deploy/helm/keda-servicebus-connection-string/templates/order_generator.yaml new file mode 100644 index 0000000..08b0bf1 --- /dev/null +++ b/deploy/helm/keda-servicebus-connection-string/templates/order_generator.yaml @@ -0,0 +1,29 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: order-generator-{{ randAlphaNum 8 | lower }} + namespace: {{ .Release.Namespace }} + labels: + app: order-processor + {{- include "keda-servicebus-connection-string.labels" . | nindent 4 }} +spec: + template: + spec: + containers: + - name: order-generator + image: {{ .Values.orderGenerator.image }} + imagePullPolicy: Never + env: + - name: KEDA_SERVICEBUS_AUTH_MODE + value: ConnectionString + - name: KEDA_SERVICEBUS_QUEUE_NAME + value: "{{ .Values.orderGenerator.queueName }}" + - name: ORDER_AMOUNT + value: "{{ .Values.orderGenerator.orderAmount }}" + - name: KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING + valueFrom: + secretKeyRef: + name: secrets-order-consumer + key: servicebus-connectionstring + restartPolicy: Never + backoffLimit: 4 \ No newline at end of file diff --git a/deploy/helm/keda-servicebus-connection-string/templates/order_processor.yaml b/deploy/helm/keda-servicebus-connection-string/templates/order_processor.yaml new file mode 100644 index 0000000..ea8a850 --- /dev/null +++ b/deploy/helm/keda-servicebus-connection-string/templates/order_processor.yaml @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: order-processor + namespace: {{ .Release.Namespace }} + labels: + app: order-processor + {{- include "keda-servicebus-connection-string.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + app: order-processor + {{- include "keda-servicebus-connection-string.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + app: order-processor + {{- include "keda-servicebus-connection-string.selectorLabels" . | nindent 8 }} + spec: + containers: + - name: order-processor + image: {{ .Values.orderProcessor.image }} + env: + - name: KEDA_SERVICEBUS_AUTH_MODE + value: ConnectionString + - name: KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING + valueFrom: + secretKeyRef: + name: secrets-order-consumer + key: servicebus-connectionstring + - name: KEDA_SERVICEBUS_QUEUE_NAME + value: orders + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/deploy/helm/keda-servicebus-connection-string/templates/scaled_object.yaml b/deploy/helm/keda-servicebus-connection-string/templates/scaled_object.yaml new file mode 100644 index 0000000..415ac62 --- /dev/null +++ b/deploy/helm/keda-servicebus-connection-string/templates/scaled_object.yaml @@ -0,0 +1,20 @@ +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + name: order-processor-scaler + namespace: {{ .Release.Namespace }} +spec: + scaleTargetRef: + name: order-processor + minReplicaCount: 0 # Change to define how many minimum replicas you want + maxReplicaCount: 50 + # The period to wait after the last trigger reported active before scaling the resource back to 0. + # By default it’s 5 minutes (300 seconds). + cooldownPeriod: 5 + triggers: + - type: azure-servicebus + metadata: + queueName: orders + messageCount: '50' + authenticationRef: + name: trigger-auth-service-bus-orders \ No newline at end of file diff --git a/deploy/helm/keda-servicebus-connection-string/templates/secrets.yaml b/deploy/helm/keda-servicebus-connection-string/templates/secrets.yaml new file mode 100644 index 0000000..0f6430c --- /dev/null +++ b/deploy/helm/keda-servicebus-connection-string/templates/secrets.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secrets-order-consumer + namespace: {{ .Release.Namespace }} + labels: + app: order-processor + {{- include "keda-servicebus-connection-string.labels" . | nindent 4 }} +data: + servicebus-connectionstring: "{{ .Values.orderProcessor.servicebusConnectionString | b64enc }}" +--- +apiVersion: v1 +kind: Secret +metadata: + name: secrets-order-management + namespace: {{ .Release.Namespace }} + labels: + app: order-processor + {{- include "keda-servicebus-connection-string.labels" . | nindent 4 }} +data: + servicebus-order-management-connectionstring: "{{ .Values.orderConsumer.servicebusConnectionString | b64enc }}" \ No newline at end of file diff --git a/deploy/helm/keda-servicebus-connection-string/templates/trigger_auth.yaml b/deploy/helm/keda-servicebus-connection-string/templates/trigger_auth.yaml new file mode 100644 index 0000000..ceab1ab --- /dev/null +++ b/deploy/helm/keda-servicebus-connection-string/templates/trigger_auth.yaml @@ -0,0 +1,12 @@ +apiVersion: keda.sh/v1alpha1 +kind: TriggerAuthentication +metadata: + name: trigger-auth-service-bus-orders + namespace: {{ .Release.Namespace }} + labels: + {{- include "keda-servicebus-connection-string.labels" . | nindent 4 }} +spec: + secretTargetRef: + - parameter: connection + name: secrets-order-management + key: servicebus-order-management-connectionstring \ No newline at end of file diff --git a/deploy/helm/keda-servicebus-connection-string/values.yaml b/deploy/helm/keda-servicebus-connection-string/values.yaml new file mode 100644 index 0000000..9d30573 --- /dev/null +++ b/deploy/helm/keda-servicebus-connection-string/values.yaml @@ -0,0 +1,47 @@ +# Default values for keda-servicebus-connection-string. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +# -- Resources configuration +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# -- Order process configuration +orderProcessor: + # -- Service bus connection string with listen and send permissions + servicebusConnectionString: "" + # -- Image used by Deployment order-processor + image: ghcr.io/kedacore/sample-dotnet-worker-servicebus-queue:latest + +# -- Order generator configuration +orderGenerator: + # -- Number of orders that is going to be created in service bus for orders queue + orderAmount: 100 + # -- Queue where the orders are going to be generated + queueName: "orders" + # -- Image used by Job order-generator + image: ghcr.io/kedacore/sample-dotnet-order-generator-servicebus:latest + +# -- Order consumer configuration +orderConsumer: + # -- Service bus connection string with manage permission + servicebusConnectionString: "" \ No newline at end of file diff --git a/infra-as-code/.terraform-docs.yaml b/infra-as-code/.terraform-docs.yaml new file mode 100644 index 0000000..c94a55f --- /dev/null +++ b/infra-as-code/.terraform-docs.yaml @@ -0,0 +1,23 @@ +--- +formatter: markdown + +header-from: main.tf + +sort: + enabled: true + +settings: + indent: 4 + escape: false + default: false + required: false + type: true + +output: + file: README.md + mode: inject + template: |- + [//]: # (BEGIN_TF_DOCS) + {{ .Content }} + + [//]: # (END_TF_DOCS) \ No newline at end of file diff --git a/infra-as-code/README.md b/infra-as-code/README.md new file mode 100644 index 0000000..07261c5 --- /dev/null +++ b/infra-as-code/README.md @@ -0,0 +1,9 @@ +# Infra-as-code + +## Summary + +Folder where the creation of Azure resources through Terraform could be found: + +- **Process Azure Service Bus Queue by using Azure AD Pod Identity**: TBD +- **Process Azure Service Bus Queue by using Azure AD Workload Identity**: TBD +- [**Process Azure Service Bus Queue by using connection string authentication**](./connection-string/) \ No newline at end of file diff --git a/infra-as-code/connection-string/README.md b/infra-as-code/connection-string/README.md new file mode 100644 index 0000000..13cbcb8 --- /dev/null +++ b/infra-as-code/connection-string/README.md @@ -0,0 +1,7 @@ +# Connection string scenario + +## Summary + +In this folder is the terraform code to generate the [Service Bus](https://azure.microsoft.com/en-gb/products/service-bus) related resources for the aaa scenario, as well as the KEDA deployment and the [helm chart](../../deploy/helm/keda-servicebus-connection-string/) created specifically for the scenario. + +The detailed information could be found in [TFDOC.md](TFDOC.md). diff --git a/infra-as-code/connection-string/TFDOC.md b/infra-as-code/connection-string/TFDOC.md new file mode 100644 index 0000000..7f3ebf3 --- /dev/null +++ b/infra-as-code/connection-string/TFDOC.md @@ -0,0 +1,41 @@ +## Requirements + +| Name | Version | +|------|---------| +| [azurerm](#requirement\_azurerm) | ~> 3.52 | +| [helm](#requirement\_helm) | ~> 2.9 | + +## Providers + +| Name | Version | +|------|---------| +| [azurerm](#provider\_azurerm) | ~> 3.52 | +| [helm](#provider\_helm) | ~> 2.9 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [azurerm_servicebus_namespace.namespace](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_namespace) | resource | +| [azurerm_servicebus_queue.orders](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_queue) | resource | +| [azurerm_servicebus_queue_authorization_rule.keda_monitor](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_queue_authorization_rule) | resource | +| [azurerm_servicebus_queue_authorization_rule.listen_send](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/servicebus_queue_authorization_rule) | resource | +| [helm_release.keda](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [helm_release.keda_dotnet_sample](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [azurerm_resource_group.rg](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [location](#input\_location) | Azure location where the resources are going to be created | `string` | `"West Europe"` | no | +| [resource\_group](#input\_resource\_group) | Azure resource group where the resources are going to be created | `string` | n/a | yes | +| [subscription\_id](#input\_subscription\_id) | Azure Account Subscription ID where the resources are going to be created | `string` | n/a | yes | + +## Outputs + +No outputs. diff --git a/infra-as-code/connection-string/helm.tf b/infra-as-code/connection-string/helm.tf new file mode 100644 index 0000000..4473156 --- /dev/null +++ b/infra-as-code/connection-string/helm.tf @@ -0,0 +1,47 @@ +resource "helm_release" "keda" { + name = "keda" + repository = "https://kedacore.github.io/charts" + chart = "keda" + version = "2.6.2" + + namespace = "keda" + wait = true + create_namespace = true + + depends_on = [ + azurerm_servicebus_queue.orders, + azurerm_servicebus_queue_authorization_rule.listen_send, + azurerm_servicebus_queue_authorization_rule.keda_monitor + ] +} + +resource "helm_release" "keda_dotnet_sample" { + name = "orders-test" + chart = "${path.module}/../../deploy/helm/keda-servicebus-connection-string" + + namespace = "keda-dotnet-sample" + wait = true + create_namespace = true + + set_sensitive { + name = "orderProcessor.servicebusConnectionString" + value = azurerm_servicebus_queue_authorization_rule.listen_send.primary_connection_string + type = "string" + } + + set_sensitive { + name = "orderConsumer.servicebusConnectionString" + value = azurerm_servicebus_queue_authorization_rule.keda_monitor.primary_connection_string + type = "string" + } + + set { + name = "orderGenerator.queueName" + value = azurerm_servicebus_queue.orders.name + type = "string" + } + + depends_on = [ + helm_release.keda + ] +} \ No newline at end of file diff --git a/infra-as-code/connection-string/resource_group.tf b/infra-as-code/connection-string/resource_group.tf new file mode 100644 index 0000000..e3661a8 --- /dev/null +++ b/infra-as-code/connection-string/resource_group.tf @@ -0,0 +1,3 @@ +data "azurerm_resource_group" "rg" { + name = var.resource_group +} \ No newline at end of file diff --git a/infra-as-code/connection-string/servicebus.tf b/infra-as-code/connection-string/servicebus.tf new file mode 100644 index 0000000..55aa7af --- /dev/null +++ b/infra-as-code/connection-string/servicebus.tf @@ -0,0 +1,33 @@ +resource "azurerm_servicebus_namespace" "namespace" { + name = "keda-sample-servicebus" + location = data.azurerm_resource_group.rg.location + resource_group_name = data.azurerm_resource_group.rg.name + sku = "Basic" + + tags = { + source = "terraform" + } +} + +resource "azurerm_servicebus_queue" "orders" { + name = "orders" + namespace_id = azurerm_servicebus_namespace.namespace.id +} + +resource "azurerm_servicebus_queue_authorization_rule" "listen_send" { + name = "listen-send" + queue_id = azurerm_servicebus_queue.orders.id + + listen = true + send = true + manage = false +} + +resource "azurerm_servicebus_queue_authorization_rule" "keda_monitor" { + name = "keda-monitor" + queue_id = azurerm_servicebus_queue.orders.id + + listen = true + send = true + manage = true +} \ No newline at end of file diff --git a/infra-as-code/connection-string/terraform.tfvars b/infra-as-code/connection-string/terraform.tfvars new file mode 100644 index 0000000..7f434e0 --- /dev/null +++ b/infra-as-code/connection-string/terraform.tfvars @@ -0,0 +1,3 @@ +location = "" +resource_group = "" +subscription_id = "" \ No newline at end of file diff --git a/infra-as-code/connection-string/terraform_config.tf b/infra-as-code/connection-string/terraform_config.tf new file mode 100644 index 0000000..e21b416 --- /dev/null +++ b/infra-as-code/connection-string/terraform_config.tf @@ -0,0 +1,23 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.52" + } + helm = { + source = "hashicorp/helm" + version = "~> 2.9" + } + } +} + +provider "azurerm" { + features {} + subscription_id = var.subscription_id +} + +provider "helm" { + kubernetes { + config_path = "~/.kube/config" + } +} \ No newline at end of file diff --git a/infra-as-code/connection-string/variables.tf b/infra-as-code/connection-string/variables.tf new file mode 100644 index 0000000..f2a4321 --- /dev/null +++ b/infra-as-code/connection-string/variables.tf @@ -0,0 +1,15 @@ +variable "location" { + description = "Azure location where the resources are going to be created" + type = string + default = "West Europe" +} + +variable "resource_group" { + description = "Azure resource group where the resources are going to be created" + type = string +} + +variable "subscription_id" { + description = "Azure Account Subscription ID where the resources are going to be created" + type = string +} \ No newline at end of file diff --git a/src/Keda.Samples.Dotnet.OrderGenerator/Dockerfile b/src/Keda.Samples.Dotnet.OrderGenerator/Dockerfile new file mode 100644 index 0000000..6bcd99d --- /dev/null +++ b/src/Keda.Samples.Dotnet.OrderGenerator/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/core/runtime:3.1.1-alpine AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101-alpine AS build +WORKDIR /src +COPY ["Keda.Samples.Dotnet.OrderGenerator/Keda.Samples.Dotnet.OrderGenerator.csproj", "Keda.Samples.Dotnet.OrderGenerator/"] +RUN dotnet restore "Keda.Samples.Dotnet.OrderGenerator/Keda.Samples.Dotnet.OrderGenerator.csproj" +COPY . . +WORKDIR "/src/Keda.Samples.Dotnet.OrderGenerator" +RUN dotnet build "Keda.Samples.Dotnet.OrderGenerator.csproj" -c Release -o /app + +FROM build AS publish +RUN dotnet publish "Keda.Samples.Dotnet.OrderGenerator.csproj" -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "Keda.Samples.Dotnet.OrderGenerator.dll"] \ No newline at end of file diff --git a/src/Keda.Samples.Dotnet.OrderGenerator/Program.cs b/src/Keda.Samples.Dotnet.OrderGenerator/Program.cs index e21fdca..ad2a6d7 100644 --- a/src/Keda.Samples.Dotnet.OrderGenerator/Program.cs +++ b/src/Keda.Samples.Dotnet.OrderGenerator/Program.cs @@ -9,14 +9,15 @@ namespace Keda.Samples.Dotnet.OrderGenerator { class Program { - private const string QueueName = ""; - private const string ConnectionString = ""; + private static string QueueName = Environment.GetEnvironmentVariable("KEDA_SERVICEBUS_QUEUE_NAME"); + private static string ConnectionString = Environment.GetEnvironmentVariable("KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING"); static async Task Main(string[] args) { Console.WriteLine("Let's queue some orders, how many do you want?"); var requestedAmount = DetermineOrderAmount(); + Console.WriteLine($"{requestedAmount} orders are going to be queued in ${QueueName} queue"); await QueueOrders(requestedAmount); Console.WriteLine("That's it, see you later!"); @@ -55,6 +56,27 @@ private static Order GenerateOrder() private static int DetermineOrderAmount() { + var requestedAmountFromEnvVariable = DetermineOrderAmountFromEnvVariable(); + if (requestedAmountFromEnvVariable > 0) { + return requestedAmountFromEnvVariable; + } + var requestedAmountFromEnvVariableFromConsole = DetermineOrderAmountFromConsole(); + return requestedAmountFromEnvVariableFromConsole; + } + + private static int DetermineOrderAmountFromEnvVariable() + { + var OrderAmount = Environment.GetEnvironmentVariable("ORDER_AMOUNT"); + if (int.TryParse(OrderAmount, out int orderAmount)) + { + return orderAmount; + } + return 0; + } + + private static int DetermineOrderAmountFromConsole() + { + var rawAmount = Console.ReadLine(); if (int.TryParse(rawAmount, out int amount)) { @@ -62,7 +84,7 @@ private static int DetermineOrderAmount() } Console.WriteLine("That's not a valid amount, let's try that again"); - return DetermineOrderAmount(); + return DetermineOrderAmountFromConsole(); } } }