diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index f43688128..1e25d4c51 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -6,7 +6,7 @@ version: '3.7' services: rover: - image: aztfmod/rover:2005.1314 + image: aztfmod/rover:2005.1510 labels: - "caf=Azure CAF" diff --git a/.github/workflows/ci-branches.yml b/.github/workflows/ci-branches.yml index b57b94406..bd015a7d4 100644 --- a/.github/workflows/ci-branches.yml +++ b/.github/workflows/ci-branches.yml @@ -33,7 +33,7 @@ jobs: convention: ["random"] container: - image: aztfmod/rover:2005.1314 + image: aztfmod/rover:2005.1510 options: --user 0 steps: @@ -67,7 +67,7 @@ jobs: environment: ["integration-tests"] container: - image: aztfmod/rover:2005.1314 + image: aztfmod/rover:2005.1510 options: --user 0 steps: @@ -115,7 +115,7 @@ jobs: environment: ["integration-tests"] container: - image: aztfmod/rover:2005.1314 + image: aztfmod/rover:2005.1510 options: --user 0 steps: @@ -179,7 +179,7 @@ jobs: environment: ["integration-tests"] container: - image: aztfmod/rover:2005.1314 + image: aztfmod/rover:2005.1510 options: --user 0 steps: @@ -226,7 +226,7 @@ jobs: convention: ["random"] container: - image: aztfmod/rover:2005.1314 + image: aztfmod/rover:2005.1510 options: --user 0 steps: diff --git a/_pictures/delivery/intro_ci_gha_ado/gha_secrets.png b/_pictures/delivery/intro_ci_gha_ado/gha_secrets.png new file mode 100644 index 000000000..4547eb953 Binary files /dev/null and b/_pictures/delivery/intro_ci_gha_ado/gha_secrets.png differ diff --git a/documentation/README.md b/documentation/README.md index 3d89ecfcb..230204618 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -24,6 +24,8 @@ Soon - [Developing landing zones, modules, blueprints]() [Introduction to delivery of landing zones](./delivery/delivery_landingzones.md) +[Deploying landing zones with GitHub Actions](./delivery/intro_ci_gha.md) + Soon - [Deployment guide for Azure CAF landing zones]() ## Operating an environment with landing zones diff --git a/documentation/code_architecture/hierarchy.md b/documentation/code_architecture/hierarchy.md index 595297b48..693366975 100644 --- a/documentation/code_architecture/hierarchy.md +++ b/documentation/code_architecture/hierarchy.md @@ -50,4 +50,6 @@ For a given "level" in the environment, each Agent VM will be assigned a managed - The target Azure Subscription - The Terraform state file: will be Read and Write permissions for the current level, will be Read only permissions for a "lower" level type of landing zone, avoiding alterations on more privileged environments. -In the example above, each pipeline will have its lifecycle management (typically, level 0 and 1 will be initiated at every new subscription creation, while level 4 could be initiated as many times a day you deploy code in your application environment). \ No newline at end of file +In the example above, each pipeline will have its lifecycle management (typically, level 0 and 1 will be initiated at every new subscription creation, while level 4 could be initiated as many times a day you deploy code in your application environment). + +[Back to summary](../README.md) \ No newline at end of file diff --git a/documentation/code_architecture/module_conventions.md b/documentation/code_architecture/module_conventions.md index 58720170f..099ae92a5 100644 --- a/documentation/code_architecture/module_conventions.md +++ b/documentation/code_architecture/module_conventions.md @@ -59,4 +59,6 @@ As a convention we will use the following minimal module outputs: | name | returns the object name | | object | returns the full resource object | -Any other resource specific outputs. \ No newline at end of file +Any other resource specific outputs. + +[Back to summary](../README.md) \ No newline at end of file diff --git a/documentation/delivery/delivery_landingzones.md b/documentation/delivery/delivery_landingzones.md index b94656d0a..a66c4fc3c 100644 --- a/documentation/delivery/delivery_landingzones.md +++ b/documentation/delivery/delivery_landingzones.md @@ -60,3 +60,5 @@ Changes in the different environments introduced and promoted following GitOps c - Operations are promoted from one environment to another via Git operations (Git commit, push, pull request). - Changes are promoted only once they have reached quality gates (provided by automation, CI mechanisms and test suites executions) to promote higher quality changes. - Changes are promoted only once they also have been validated by service owners in the environment (that can be declared in the DevOps configuration of the platform). + +[Back to summary](../README.md) \ No newline at end of file diff --git a/documentation/delivery/intro_ci_gha.md b/documentation/delivery/intro_ci_gha.md new file mode 100644 index 000000000..57af04d63 --- /dev/null +++ b/documentation/delivery/intro_ci_gha.md @@ -0,0 +1,252 @@ +# Deploying landing zones with GitHub Actions + +Starting in version 2005.xxxx, we introduced the support for the rover to run into GitHub Actions (GHA) and Azure DevOps, in this guide, you will find steps to deploy landing zones using rover. + +## Introduction + +Rover allows you to seamlessly run your landing zones in a team of developers and into Continuous Integration / Deployments environments, this decoupling of the versioning of the execution environnement with the CI/CD specifics allows versatility and if we tested only on GitHub Actions and Azure DevOps, it should be easy to deploy in other tools. We based this example of the [GitHub YAML workflow syntax](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions). + +## Authentication in the pipelines + +In the examples below we will use service principals to authenticate to the Azure subscriptions. + +## Getting started with GitHub Actions + +In GitHub Actions, you specify your pipelines configuration for your repo into the sub-folders ```/.github/worklows``` in our example, we specify it for pull requests validation as follow: + +```yaml +name: landingzones + +on: + pull_request: + branches: + - master +``` + +We define the set of variables to get the authentication context and a couple of parameters, to automate destroy and specify the environment path for the variable path. The variables ```ARM_CLIENT_SECRET```, ```ARM_CLIENT_ID```, ```ARM_SUBSCRIPTION_ID```, and ```ARM_TENANT_ID``` all have been defined into my GitHub **Settings**, **Secrets**, as follow: + +![GHA_Secrets](../../_pictures/delivery/intro_ci_gha_ado/gha_secrets.png) + +```yaml +env: + TF_CLI_ARGS: '-no-color' + TF_CLI_ARGS_destroy: '-auto-approve -refresh=false' + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} + TFVARS_PATH: '/tf/caf/environments' +``` + +### Launchpad deployment + +We then specify a set of **jobs**: first one to deploy launchpad. + +In the container strategy, notice we specify the container image to use the rover from Docker Hub, and we run as root using ```option: --user 0``` + +We configure the testing strategy using a matrix structure in order to be able to test multiple configurations during our tests, by default GitHub Actions will run them simultaneously but you can tune this behavior adding the ```max-parallel``` + +In the steps, note that we authenticate to Azure using the service principal, then select the right subscriptions. In the step ```Locate launchpad``` we first verify if there is a launchpad that might have been executed previously by locating the tags on the storage account, and if not, we deploy it. + +```yaml +jobs: + level0: + name: level0 + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + region: ["westus2"] + convention: ["random", "cafrandom"] + + container: + image: aztfmod/rover:2005.1510 + options: --user 0 + + steps: + - name: Login azure + run: | + az login --service-principal -u '${{ env.ARM_CLIENT_ID }}' -p '${{ env.ARM_CLIENT_SECRET }}' --tenant '${{ env.ARM_TENANT_ID }}' + az account set -s ${{ env.ARM_SUBSCRIPTION_ID }} + + echo "local user: $(whoami)" + + - name: Locate launchpad + run: | + id=$(az storage account list --query "[?tags.tfstate=='level0' && tags.workspace=='level0']" -o json | jq -r .[0].id) + if [ "${id}" == "null" ]; then + /tf/rover/launchpad.sh /tf/launchpads/launchpad_opensource plan -var location=${{ matrix.region }} + /tf/rover/launchpad.sh /tf/launchpads/launchpad_opensource apply -var location=${{ matrix.region }} + fi + +``` + +### CAF foundations deployment + +Once the launchpad has completed, we then deploy the caf_foundations landing zone. This block is relatively simple, we specify we need ```level0``` to be deployed before running, then specify a matrix for testing conditions (this could be specified as simple variables in this case, but we leave room to grow). This landing zone will also use the ```caffoundationsci``` workspace. + +```YAML +caf_foundations: + name: caf_foundations + runs-on: ubuntu-latest + needs: level0 + + strategy: + fail-fast: false + matrix: + landingzone: ["landingzone_caf_foundations"] + region: ["westus2"] + convention: ["random"] + environment: ["integration-tests"] + + container: + image: aztfmod/rover:2005.1510 + options: --user 0 + + steps: + - uses: actions/checkout@v2 + + - name: setup context + id: context + run: | + ln -s ${GITHUB_WORKSPACE} /tf/caf + echo "ls /tf/caf" && ls -lsa /tf/caf + ls -lsa /tmp + + workspace='caffoundationsci' + echo ::set-env name=TF_VAR_workspace::${workspace} + + - name: Login azure + run: | + az login --service-principal -u '${{ env.ARM_CLIENT_ID }}' -p '${{ env.ARM_CLIENT_SECRET }}' --tenant '${{ env.ARM_TENANT_ID }}' + az account set -s ${{ env.ARM_SUBSCRIPTION_ID }} + + echo "local user: $(whoami)" + + - name: workspace + run: | + /tf/rover/launchpad.sh workspace create ${TF_VAR_workspace} + + - name: deploy caf_foundations + run: | + /tf/rover/rover.sh /tf/caf/landingzones/${{ matrix.landingzone }} apply \ + '-var tags={testing-job-id="${{ github.run_id }}"}' \ + '-var-file ${{ env.TFVARS_PATH }}/${{ matrix.environment }}/${{ matrix.landingzone }}/${{ matrix.landingzone }}_${{ matrix.region }}_${{ matrix.convention }}.tfvars' +``` + +### Landing zones deployment + +Once the fundamentals are set, we can now proceed to deploy the test landing zones in our environments. We are now making more use of the matrix structure to test all the landing zones simultaneously: + +```yml + landingzones: + name: landingzones + runs-on: ubuntu-latest + + needs: [level0, caf_foundations] + + strategy: + fail-fast: false + matrix: + landingzone: ["landingzone_hub_spoke", "landingzone_secure_vnet_dmz", "landingzone_starter", "landingzone_vdc_demo"] + region: ["westus2"] + convention: ["cafrandom", "random"] + environment: ["integration-tests"] + + container: + image: aztfmod/rover:2005.1510 + options: --user 0 + + steps: + - uses: actions/checkout@v2 + + - name: setup context + id: context + run: | + ln -s ${GITHUB_WORKSPACE} /tf/caf + echo "ls /tf/caf" && ls -lsa /tf/caf + ls -lsa /tmp + + job_id=${{ job.container.id }} + workspace=${job_id:0:63} + echo ::set-env name=TF_VAR_workspace::${workspace} + + - name: Login azure + run: | + az login --service-principal -u '${{ env.ARM_CLIENT_ID }}' -p '${{ env.ARM_CLIENT_SECRET }}' --tenant '${{ env.ARM_TENANT_ID }}' + az account set -s ${{ env.ARM_SUBSCRIPTION_ID }} + + echo "local user: $(whoami)" + + - name: workspace + run: | + /tf/rover/launchpad.sh workspace create ${TF_VAR_workspace} + + - name: deploy landing_zone + run: | + /tf/rover/rover.sh /tf/caf/landingzones/${{ matrix.landingzone }} apply \ + '-var tags={testing-job-id="${{ github.run_id }}"}' \ + '-var-file ${{ env.TFVARS_PATH }}/${{ matrix.environment }}/${{ matrix.landingzone }}/${{ matrix.landingzone }}.tfvars' \ + '-var workspace=caffoundationsci' + + - name: destroy landing_zone + if: always() + run: | + /tf/rover/rover.sh /tf/caf/landingzones/${{ matrix.landingzone }} destroy \ + '-var tags={testing-job-id="${{ github.run_id }}"}' \ + '-var-file ${{ env.TFVARS_PATH }}/${{ matrix.environment }}/${{ matrix.landingzone }}/${{ matrix.landingzone }}.tfvars' \ + '-var workspace=caffoundationsci' + + - name: cleanup workspace + if: always() + run: | + stg_name=$(az storage account list --query "[?tags.tfstate=='level0']" -o json | jq -r .[0].name) + az storage container delete --account-name ${stg_name} --name ${TF_VAR_workspace} --auth-mode login + +``` + +### Destroy and purges + +In the rest of the scripts, we destroy the foundations and level0 landing zones and we also run a purge on the resource groups in the subscription in case there are any items remaining. + +```yaml +level0_destroy: + name: level0_destroy + runs-on: ubuntu-latest + + needs: caf_foundations_destroy + + strategy: + fail-fast: false + matrix: + region: ["westus2"] + convention: ["random", "cafrandom"] + + container: + image: aztfmod/rover:2005.1510 + options: --user 0 + + steps: + - name: Login azure + run: | + az login --service-principal -u '${{ env.ARM_CLIENT_ID }}' -p '${{ env.ARM_CLIENT_SECRET }}' --tenant '${{ env.ARM_TENANT_ID }}' + az account set -s ${{ env.ARM_SUBSCRIPTION_ID }} + + echo "local user: $(whoami)" + + - name: Remove launchpad + run: | + /tf/rover/launchpad.sh /tf/launchpads/launchpad_opensource destroy -var location=${{ matrix.region }} -auto-approve + + - name: Complete purge + run: | + for i in `az group list -o tsv --query '[].name'`; do az group delete -n $i -y --no-wait; done + for i in `az monitor log-profiles list -o tsv --query '[].name'`; do az monitor log-profiles delete --name $i -y; done +``` + +## Conclusion + +A lot can be improved in this script, but hopefully it gives you a good example on how to start with landing zones deployment in GitHub Actions! + +[Back to summary](../README.md) \ No newline at end of file