diff --git a/docs/gitops-quickstart.md b/docs/gitops-quickstart.md index 6ba3b92..e1671bb 100644 --- a/docs/gitops-quickstart.md +++ b/docs/gitops-quickstart.md @@ -28,7 +28,7 @@ The best way to start learning about GitOps in Bedrock is to walkthrough a simpl --- ## Step 2: Setup a sample GitOps environment with `bedrock` -Next `bedrock` will do the heavy lifting of creating [repositories](https://github.com/microsoft/bedrock/blob/master/gitops/azure-devops/ADORepos.md), [pipelines](https://github.com/microsoft/bedrock/blob/master/gitops/azure-devops/ManifestGeneration.md), and [cloud resources](https://github.com/microsoft/bedrock/blob/master/gitops/azure-devops/ImageTagRelease.md#create-a-service-connection-to-acr) for you. Please read about Bedrock Concepts [**TODO LINK**] to get more familiar. +Next `bedrock` will do the heavy lifting of creating [repositories](https://github.com/microsoft/bedrock/blob/master/gitops/docs/ADORepos.md), [pipelines](https://github.com/microsoft/bedrock/blob/master/gitops/azure-devops/ManifestGeneration.md), and [cloud resources](https://github.com/microsoft/bedrock/blob/master/gitops/azure-devops/ImageTagRelease.md#create-a-service-connection-to-acr) for you. Please read about Bedrock Concepts [**TODO LINK**] to get more familiar. 1. Run `bedrock setup` 2. Hit enter to confirm the preselected value that is in parenthesis. Otherwise a new value for the prompt diff --git a/gitops/azure-devops/ImageTagRelease.md b/gitops/azure-devops/ImageTagRelease.md index 4207335..74de45a 100644 --- a/gitops/azure-devops/ImageTagRelease.md +++ b/gitops/azure-devops/ImageTagRelease.md @@ -24,8 +24,8 @@ We will be focusing on the first step in this example. ### 1. Create Repositories and Personal Access Tokens Create both high level definition (HLD) and resource manifest repos and the personal access tokens that you'll use for the two ends of this CI/CD pipeline. We have instructions for how to do that in two flavors: -* [Azure DevOps](ADORepos.md) -* [GitHub](GitHubRepos.md) +* [Azure DevOps](../docs/ADORepos.md) +* [GitHub](../docs/GitHubRepos.md) ### 2. Create Application Code Pipeline diff --git a/gitops/azure-devops/ManifestGeneration.md b/gitops/azure-devops/ManifestGeneration.md index f285f60..496eed7 100644 --- a/gitops/azure-devops/ManifestGeneration.md +++ b/gitops/azure-devops/ManifestGeneration.md @@ -12,8 +12,8 @@ This section describes how to configure Azure Devops to be your CI/CD orchestrat ### 1. Create Repositories and Personal Access Tokens Create both high level definition (HLD) and resource manifest repos and the personal access tokens that you'll use for the two ends of this CI/CD pipeline. We have instructions for how to do that in two flavors: -* [Azure DevOps](ADORepos.md) -* [GitHub](GitHubRepos.md) +* [Azure DevOps](../docs/ADORepos.md) +* [GitHub](../docs/GitHubRepos.md) #### Add Azure Pipelines Build YAML If you are using your own high level description, add the following `azure-pipelines.yml` file to its root to defines the build rules for your Azure Devops pipeline. diff --git a/gitops/azure-devops/ADORepos.md b/gitops/docs/ADORepos.md similarity index 100% rename from gitops/azure-devops/ADORepos.md rename to gitops/docs/ADORepos.md diff --git a/gitops/azure-devops/GitHubRepos.md b/gitops/docs/GitHubRepos.md similarity index 71% rename from gitops/azure-devops/GitHubRepos.md rename to gitops/docs/GitHubRepos.md index 68f98a3..b73e0be 100644 --- a/gitops/azure-devops/GitHubRepos.md +++ b/gitops/docs/GitHubRepos.md @@ -6,13 +6,16 @@ If you have not already, create your own high level definition repository. (We provide a [sample GitHub repo](https://github.com/andrebriggs/fabrikate-sample-app) that you can fork and modify for your own application.) -Also, make sure you have an [azure-pipelines.yml](README.md#azure-pipelines-build-yaml) file at the root of your repository, as we will use this later to setup the build rules in Azure Devops. +Also, make sure you have (Depending if you want an AzDO pipeline or Github action workflow) +1) an [azure-pipelines.yml](../azure-devops/README.md#azure-pipelines-build-yaml) file at the root of your repository, as we will use this later to setup the build rules in Azure Devops. +OR +2) a Github workflows directory at the root of your repository, as we will use this later to setup the build pipeline in Github actions. ## 2. Create Manifest Repository You will also need a destination repository on GitHub where the Kubernetes resource manifests will be pushed to. Create this as well if you haven't already. -Next, generate a [deploy key](https://developer.github.com/v3/guides/managing-deploy-keys/) for your new repository on GitHub. Keep the contents of yor public SSH key and local path to your private SSH key handy for the next step. +Next, generate a [deploy key](https://developer.github.com/v3/guides/managing-deploy-keys/) for your new repository on GitHub. Keep the contents of your public SSH key and local path to your private SSH key handy for the next step. ## 3. Create a Flux enabled AKS Cluster diff --git a/gitops/github/ManifestGeneration.md b/gitops/github/ManifestGeneration.md new file mode 100644 index 0000000..ec84e23 --- /dev/null +++ b/gitops/github/ManifestGeneration.md @@ -0,0 +1,163 @@ +# Guide: Manifest Generation Pipeline + +This section describes how to configure Github actions to be your CI/CD orchestrator for your GitOps Workflow. You will create a manifest generation pipeline using Fabrikate. + +## Prerequisites + +1. _Permissions_: The ability to create Projects in your Github Organization. +2. _High Level Deployment Description_: Either your own [Fabrikate](https://github.com/Microsoft/fabrikate) high level definition for your deployment or a sample one of ours. We provide a [sample HLD repo](https://github.com/andrebriggs/fabrikate-sample-app) that builds upon the [cloud-native](https://github.com/timfpark/fabrikate-cloud-native) Fabrikate definition. + +## Setup + +### 1. Create Repositories and Personal Access Tokens + +Create both high level definition (HLD) and resource manifest repos and the personal access tokens that you'll use for the two ends of this CI/CD pipeline. We have instructions for how to do that in two flavors: +* [Azure DevOps](../docs/ADORepos.md) +* [GitHub](../docs/GitHubRepos.md) + +#### Add Github actions workflow YAML +If you are using your own high level description, add the following [`workflow.yml`](./workflow.yml) file to the .github/workflows directory to defines the build steps for your Github actions workflow. + +``` +on: + push: + branches: + - main + - master + + pull_request: + branches: + - main + - master + +jobs: + FabrikateToManifest: + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Download Bedrock orchestration script + run: | + curl $BEDROCK_BUILD_SCRIPT > build.sh + chmod +x ./build.sh + shell: bash + env: + BEDROCK_BUILD_SCRIPT: https://raw.githubusercontent.com/Microsoft/bedrock/master/gitops/azure-devops/build.sh + + + - uses: azure/setup-helm@v1 + with: + version: '2.17.0' # default is latest stable + id: install + + - name: Validate fabrikate definitions + run: | + chmod +x ./build.sh + ./build.sh + shell: bash + env: + MAJOR: 1 + VERIFY_ONLY: 1 + + - name: Get branch name + shell: bash + run: echo "##[set-output name=branch_name;]$(echo ${GITHUB_REF#refs/heads/})" + id: get_branch_name + + - name: Transform fabrikate definitions and publish to YAML manifests to repo + run: | + ./build.sh + shell: bash + env: + MAJOR: 1 + ACCESS_TOKEN_SECRET: ${{ secrets.ACCESS_TOKEN }} + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + REPO: ${{ secrets.MANIFEST_REPO }} + BRANCH_NAME: ${{ steps.get_branch_name.outputs.branch_name }} +``` + +### 2. Create workflow + +We use an [Github workflow](https://github.com/features/actions) to build your high level description into resource manifests: + +1. On a pull request (pre push to master) it executes a simple validation on proposed changes to infrastructure definition in the HLD repo. +1. On a merge to master branch (post push to master) it executes a script to transform the high level definition to YAML using [Fabrikate](https://github.com/Microsoft/fabrikate) and pushes the generated results into the resource manifest repo. + +__Note__: If you would like to trigger a build from a pipeline not linked to the high level definition repo, you can define a variable `HLD_PATH` and pass it into the script with other variables as shown above in `workflow.yml`. You need to set this to a git URL, such as `git://github.com/Microsoft/fabrikate-production-cluster-demo.git`. + +#### Create Build for your Definition Repo + +With Github actions you do not need to setup the workflow. Commit and push the yml to the workflows directory and its set. + +#### Configure Build + +1. Click the "Secrets" tab. + +5. Add two variables that are used by the `build.sh` script referenced in `workflow.yml`: + 1. __Name__: `ACCESS_TOKEN` (_mandatory_) __Value__: Personal Access Token ([Azure DevOps](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops) or [GitHub](https://www.help.github.com/articles/creating-a-personal-access-token-for-the-command-line)) for your repo type. + 2. __Name__: `MANIFEST_REPO` (_mandatory_) __Value__: The full URL to your manifest repo (i.e. https://github.com/andrebriggs/acme-company-yaml.git) + 3. __Name__: `FAB_ENVS` (_optional_) __Value__: Comma-separated list of environments for which you have specified a config in your high level definition repo. If this variable is not created in the pipeline, the script will generate manifest files for a generic `prod` environment. For example, you may set this variable to `prod-east, prod-west` depending on your configuration. + +7. You should now see the build run and complete successfully. + +### 3. Configure Flux + +Once you have your Github workflow is working, you will need to retrieve the SSH public key you used to [set up your cluster](../../cluster/common/flux/README.md). + +1. Copy the SSH key to your clipboard. + +2. In Github actions, under Settings > Deploy kets, click on `Add deploy key` and add the Flux deploy key. + ![ssh](images/ssh-key.png) + +3. On your cluster find the name of your pod by executing `kubectl get pods -n flux` + ``` + $ kubectl get pods -n flux + NAME READY STATUS RESTARTS AGE + flux-7d459f5f9-c2wtd 1/1 Running 0 24h + flux-memcached-59947476d9-49xs6 1/1 Running 0 24h + ``` + +4. Monitor the logs of your running Flux instance using the command `kubectl logs POD_NAME -n flux` to ensure that the initial manifest YAML files are being applied to your cluster. +``` +$ kubectl logs flux-7d459f5f9-c2wtd -n flux +ts=2019-02-14T19:37:55.332948174Z caller=main.go:156 version=1.10.1 +ts=2019-02-14T19:37:55.408911845Z caller=main.go:247 component=cluster identity=/etc/fluxd/ssh/identity +ts=2019-02-14T19:37:55.414659575Z caller=main.go:417 url=git@github.com:andrebriggs/aks-feb-manifest.git user="Weave Flux" email=support@weave.works sync-tag=flux-sync notes-ref=flux set-author=false +... +... +``` +Now, when a change is commited to the resource manifest repo, Flux should acknowledge the commit and make changes to the state of your cluster as necessary. You can monitor Flux by viewing the logs by running `kubectl logs POD_NAME -n flux -f` in stream mode. + +### 4. Make a Pull Request + +1. Create a new branch in your HLD repo and make a commit to the high level definition. + +1. For example, let's say we wanted to make a change that dropped the `cloud-native` stack and instead added directly a Elasticsearch / Fluentd / Kibana logging stack and Prometheus / Grafana metrics monitoring stack to your definition. We would make a commit that made this change: + ![ADO Build](images/definition-change.png) + +1. Then, create a pull request to merge your changes into master/main branch. + +1. Once these checks have passed and the PR has been approved by your team process, you can merge it into master. + +### 5. Monitor Repository Changes +1. Once merged, you can monitor the progress of the HLD transformation in the Actions tab. + +1. When the commit is merged into master/main, your workflow will build the resource manifests for this definition and check them into the resource manifest repo. + +1. Once the build is successful, navigate to your manifest repository. You should see a very recent commit to the main branch. + +### 6. Monitor Cluster Changes + +1. Next, [Flux](https://github.com/weaveworks/flux/blob/master/site/get-started.md#confirm-the-change-landed) will automatically apply the build resource manifest changes to your cluster. You can watch this with the following `kubectl` command: + +``` +$ kubectl logs POD_NAME -n flux -f +``` + +2. You can also use [Kubediff](https://github.com/weaveworks/kubediff) to make sure the applied resource manifests in your cluster match your resource manifest repo by cloning your resource manifest repo and then running: + +``` +$ kubediff ./cloned-resource-manifest-repo +``` + +3. Finally, you should watch your normal operational metrics to make sure the change was successful. \ No newline at end of file diff --git a/gitops/github/README.md b/gitops/github/README.md new file mode 100644 index 0000000..faf4cd4 --- /dev/null +++ b/gitops/github/README.md @@ -0,0 +1,5 @@ +# GitOps CI/CD with GitHub actions + +This section provides examples of how to configure Github actions to be your CI/CD orchestrator for your GitOps Workflow. If you haven't already please learn about how we [design GitOps pipelines](../PipelineThinking.md). + +[Manifest Generation Pipeline](./ManifestGeneration.md) \ No newline at end of file diff --git a/gitops/github/images/github_deploy_key.png b/gitops/github/images/github_deploy_key.png new file mode 100644 index 0000000..8251152 Binary files /dev/null and b/gitops/github/images/github_deploy_key.png differ diff --git a/gitops/github/workflow.yml b/gitops/github/workflow.yml new file mode 100644 index 0000000..7e09450 --- /dev/null +++ b/gitops/github/workflow.yml @@ -0,0 +1,55 @@ +on: + push: + branches: + - main + - master + + pull_request: + branches: + - main + - master + +jobs: + FabrikateToManifest: + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Download Bedrock orchestration script + run: | + curl $BEDROCK_BUILD_SCRIPT > build.sh + chmod +x ./build.sh + shell: bash + env: + BEDROCK_BUILD_SCRIPT: https://raw.githubusercontent.com/Microsoft/bedrock/master/gitops/azure-devops/build.sh + + + - uses: azure/setup-helm@v1 + with: + version: '2.17.0' # default is latest stable + id: install + + - name: Validate fabrikate definitions + run: | + chmod +x ./build.sh + ./build.sh + shell: bash + env: + MAJOR: 1 + VERIFY_ONLY: 1 + + - name: Get branch name + shell: bash + run: echo "##[set-output name=branch_name;]$(echo ${GITHUB_REF#refs/heads/})" + id: get_branch_name + + - name: Transform fabrikate definitions and publish to YAML manifests to repo + run: | + ./build.sh + shell: bash + env: + MAJOR: 1 + ACCESS_TOKEN_SECRET: ${{ secrets.ACCESS_TOKEN }} + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + REPO: ${{ secrets.MANIFEST_REPO }} + BRANCH_NAME: ${{ steps.get_branch_name.outputs.branch_name }} diff --git a/pipelines/github-cicd/.ci/az-login.sh b/pipelines/github-cicd/.ci/az-login.sh new file mode 100755 index 0000000..8a19d8b --- /dev/null +++ b/pipelines/github-cicd/.ci/az-login.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euox pipefail + +az login --service-principal -u "$ARM_CLIENT_ID" -p "$ARM_CLIENT_SECRET" --tenant "$ARM_TENANT_ID" diff --git a/pipelines/github-cicd/.ci/go-lint.sh b/pipelines/github-cicd/.ci/go-lint.sh new file mode 100755 index 0000000..5a72dc3 --- /dev/null +++ b/pipelines/github-cicd/.ci/go-lint.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# Note: the omission of the `pipefail` flag is intentional. It allows this +# step to succeede in the case that there are no `*.go` files in the +# infrastructure repository. +set -euox + +echo "Linting Go Files... If this fails, run 'go fmt ./...' to fix" + +# This runs a go fmt on each file without using the 'go fmt ./...' syntax. +# This is advantageous because it avoids having to download all of the go +# dependencies that would have been triggered by using the './...' syntax. +FILES_WITH_FMT_ISSUES=$(find . -name "*.go" | grep -v '.terraform' | xargs gofmt -l | wc -l) + +# convert to integer... +FILES_WITH_FMT_ISSUES=$(($FILES_WITH_FMT_ISSUES + 0)) + +# set exit code accordingly +exit $FILES_WITH_FMT_ISSUES diff --git a/pipelines/github-cicd/.ci/tf-apply.sh b/pipelines/github-cicd/.ci/tf-apply.sh new file mode 100755 index 0000000..efa6493 --- /dev/null +++ b/pipelines/github-cicd/.ci/tf-apply.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euox pipefail + +terraform apply -input=false -auto-approve $PLAN_FILE diff --git a/pipelines/github-cicd/.ci/tf-init-for-stage.sh b/pipelines/github-cicd/.ci/tf-init-for-stage.sh new file mode 100755 index 0000000..c68a539 --- /dev/null +++ b/pipelines/github-cicd/.ci/tf-init-for-stage.sh @@ -0,0 +1,4 @@ +# #!/usr/bin/env bash +set -euox pipefail + +terraform init -backend-config "storage_account_name=$AZURE_STORAGE_ACCOUNT_NAME" -backend-config "container_name=$AZURE_STORAGE_ACCOUNT_CONTAINER" diff --git a/pipelines/github-cicd/.ci/tf-init-without-backend.sh b/pipelines/github-cicd/.ci/tf-init-without-backend.sh new file mode 100755 index 0000000..c8e4755 --- /dev/null +++ b/pipelines/github-cicd/.ci/tf-init-without-backend.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euox pipefail + +terraform init -backend=false diff --git a/pipelines/github-cicd/.ci/tf-lint.sh b/pipelines/github-cicd/.ci/tf-lint.sh new file mode 100755 index 0000000..5a9c234 --- /dev/null +++ b/pipelines/github-cicd/.ci/tf-lint.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euox pipefail + +echo "Linting Terraform Files... If this fails, run 'terraform fmt -recursive' to fix" +terraform fmt -recursive -check diff --git a/pipelines/github-cicd/.ci/tf-plan.sh b/pipelines/github-cicd/.ci/tf-plan.sh new file mode 100755 index 0000000..f4a1e7c --- /dev/null +++ b/pipelines/github-cicd/.ci/tf-plan.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euox pipefail + +terraform plan -var-file="${VAR_FILE_NAME}" -out "$PLAN_FILE" diff --git a/pipelines/github-cicd/.ci/tf-validate.sh b/pipelines/github-cicd/.ci/tf-validate.sh new file mode 100755 index 0000000..299a8b8 --- /dev/null +++ b/pipelines/github-cicd/.ci/tf-validate.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euox pipefail + +terraform validate diff --git a/pipelines/github-cicd/.ci/tf-workspace-select.sh b/pipelines/github-cicd/.ci/tf-workspace-select.sh new file mode 100755 index 0000000..feee5eb --- /dev/null +++ b/pipelines/github-cicd/.ci/tf-workspace-select.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euox pipefail + +terraform workspace new $ENVIRONMENT || terraform workspace select $ENVIRONMENT diff --git a/pipelines/github-cicd/.github/workflows/.gh-actions-ci.yml b/pipelines/github-cicd/.github/workflows/.gh-actions-ci.yml new file mode 100644 index 0000000..9b04b10 --- /dev/null +++ b/pipelines/github-cicd/.github/workflows/.gh-actions-ci.yml @@ -0,0 +1,167 @@ +# Extension points not yet implemented: +# (1) Run infrastructure unit tests in precheck stage +# (2) Run infrastructure integration tests + +# Configure the base image for CI jobs here. +# +# To use a custom image, use the following syntax: +# name: "$CI_REGISTRY/:" + +on: + workflow_dispatch: + +jobs: + init: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: TF init without backend + run: | + ./.ci/tf-init-without-backend.sh + ./.ci/tf-lint.sh + ./.ci/tf-validate.sh + + - name: Upload terraform init results + uses: actions/upload-artifact@v2 + with: + name: tf_init + path: .terraform/ + + lint_go: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Lint Go + run: ./.ci/go-lint.sh + + dev_build_release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: plan + env: + ARM_CLIENT_ID: ${{ secrets.DEV_ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.DEV_ARM_CLIENT_SECRET }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + ARM_ACCESS_KEY: ${{ secrets.ARM_ACCESS_KEY }} + AZURE_STORAGE_ACCOUNT_NAME: ${{ secrets.DEV_AZURE_STORAGE_ACCOUNT_NAME }} + AZURE_STORAGE_ACCOUNT_CONTAINER: ${{ secrets.DEV_AZURE_STORAGE_ACCOUNT_CONTAINER }} + AZURE_STORAGE_ACCOUNT_SUBSCRIPTION: ${{ secrets.DEV_AZURE_STORAGE_ACCOUNT_SUBSCRIPTION }} + VAR_FILE_NAME: 'DEV_TF_VARS' + ENVIRONMENT: 'dev' + PLAN_FILE: 'tfplan-dev.out' + run: | + ./.ci/az-login.sh + ./.ci/tf-init-for-stage.sh + ./.ci/tf-workspace-select.sh + ./.ci/tf-plan.sh + + - name: apply + env: + ARM_CLIENT_ID: ${{ secrets.DEV_ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.DEV_ARM_CLIENT_SECRET }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + ARM_ACCESS_KEY: ${{ secrets.ARM_ACCESS_KEY }} + AZURE_STORAGE_ACCOUNT_NAME: ${{ secrets.DEV_AZURE_STORAGE_ACCOUNT_NAME }} + AZURE_STORAGE_ACCOUNT_CONTAINER: ${{ secrets.DEV_AZURE_STORAGE_ACCOUNT_CONTAINER }} + AZURE_STORAGE_ACCOUNT_SUBSCRIPTION: ${{ secrets.DEV_AZURE_STORAGE_ACCOUNT_SUBSCRIPTION }} + VAR_FILE_NAME: 'DEV_TF_VARS' + ENVIRONMENT: 'dev' + PLAN_FILE: 'tfplan-dev.out' + run: | + ./.ci/az-login.sh + ./.ci/tf-init-for-stage.sh + ./.ci/tf-workspace-select.sh + ./.ci/tf-apply.sh + + integration_build_release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: plan + env: + ARM_CLIENT_ID: ${{ secrets.INTEGRATION_ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.INTEGRATION_ARM_CLIENT_SECRET }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + ARM_ACCESS_KEY: ${{ secrets.ARM_ACCESS_KEY }} + AZURE_STORAGE_ACCOUNT_NAME: ${{ secrets.INTEGRATION_AZURE_STORAGE_ACCOUNT_NAME }} + AZURE_STORAGE_ACCOUNT_CONTAINER: ${{ secrets.INTEGRATIONAZURE_STORAGE_ACCOUNT_CONTAINER }} + AZURE_STORAGE_ACCOUNT_SUBSCRIPTION: ${{ secrets.INTEGRATION_AZURE_STORAGE_ACCOUNT_SUBSCRIPTION }} + VAR_FILE_NAME: 'INTEGRATION_TF_VARS' + ENVIRONMENT: 'integration' + PLAN_FILE: 'tfplan-integration.out' + run: | + ./.ci/az-login.sh + ./.ci/tf-init-for-stage.sh + ./.ci/tf-workspace-select.sh + ./.ci/tf-plan.sh + + - name: apply + env: + ARM_CLIENT_ID: ${{ secrets.INTEGRATION_ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.INTEGRATION_ARM_CLIENT_SECRET }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + ARM_ACCESS_KEY: ${{ secrets.ARM_ACCESS_KEY }} + AZURE_STORAGE_ACCOUNT_NAME: ${{ secrets.INTEGRATION_AZURE_STORAGE_ACCOUNT_NAME }} + AZURE_STORAGE_ACCOUNT_CONTAINER: ${{ secrets.INTEGRATION_AZURE_STORAGE_ACCOUNT_CONTAINER }} + AZURE_STORAGE_ACCOUNT_SUBSCRIPTION: ${{ secrets.INTEGRATION_AZURE_STORAGE_ACCOUNT_SUBSCRIPTION }} + VAR_FILE_NAME: 'INTEGRATION_TF_VARS' + ENVIRONMENT: 'integration' + PLAN_FILE: 'tfplan-integration.out' + run: | + ./.ci/az-login.sh + ./.ci/tf-init-for-stage.sh + ./.ci/tf-workspace-select.sh + ./.ci/tf-apply.sh + + prod_build_release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: plan + env: + ARM_CLIENT_ID: ${{ secrets.PROD_ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.PROD_ARM_CLIENT_SECRET }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + ARM_ACCESS_KEY: ${{ secrets.ARM_ACCESS_KEY }} + AZURE_STORAGE_ACCOUNT_NAME: ${{ secrets.PROD_AZURE_STORAGE_ACCOUNT_NAME }} + AZURE_STORAGE_ACCOUNT_CONTAINER: ${{ secrets.PROD_AZURE_STORAGE_ACCOUNT_CONTAINER }} + AZURE_STORAGE_ACCOUNT_SUBSCRIPTION: ${{ secrets.PROD_AZURE_STORAGE_ACCOUNT_SUBSCRIPTION }} + VAR_FILE_NAME: 'PROD_TF_VARS' + ENVIRONMENT: 'prod' + PLAN_FILE: 'tfplan.out' + run: | + ./.ci/az-login.sh + ./.ci/tf-init-for-stage.sh + ./.ci/tf-workspace-select.sh + ./.ci/tf-plan.sh + + - name: apply + env: + ARM_CLIENT_ID: ${{ secrets.PROD_ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.PROD_ARM_CLIENT_SECRET }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + ARM_ACCESS_KEY: ${{ secrets.ARM_ACCESS_KEY }} + AZURE_STORAGE_ACCOUNT_NAME: ${{ secrets.PROD_AZURE_STORAGE_ACCOUNT_NAME }} + AZURE_STORAGE_ACCOUNT_CONTAINER: ${{ secrets.PROD_AZURE_STORAGE_ACCOUNT_CONTAINER }} + AZURE_STORAGE_ACCOUNT_SUBSCRIPTION: ${{ secrets.PROD_AZURE_STORAGE_ACCOUNT_SUBSCRIPTION }} + VAR_FILE_NAME: 'PROD_TF_VARS' + ENVIRONMENT: 'prod' + PLAN_FILE: 'tfplan.out' + run: | + ./.ci/az-login.sh + ./.ci/tf-init-for-stage.sh + ./.ci/tf-workspace-select.sh + ./.ci/tf-apply.sh diff --git a/pipelines/github-cicd/Dockerfile.sample b/pipelines/github-cicd/Dockerfile.sample new file mode 100644 index 0000000..a260007 --- /dev/null +++ b/pipelines/github-cicd/Dockerfile.sample @@ -0,0 +1,13 @@ +FROM hashicorp/terraform:0.12.29 + +# Add common dependencies +RUN apk update && \ + apk add openssl curl tar gzip bash ca-certificates coreutils + +# Add Azure CLI +RUN \ + apk add py3-pip && \ + apk add --virtual=build gcc libffi-dev musl-dev openssl-dev python3-dev make && \ + pip3 --no-cache-dir install -U pip && \ + pip3 --no-cache-dir install azure-cli && \ + apk del --purge build diff --git a/pipelines/github-cicd/README.md b/pipelines/github-cicd/README.md new file mode 100644 index 0000000..c3c6b95 --- /dev/null +++ b/pipelines/github-cicd/README.md @@ -0,0 +1,72 @@ +# Terraform Deployments on Github actions + +This repository contains CICD templates that can deploy [Terraform](https://www.terraform.io/) templates for production systems into Azure using Github actions. + +## Automated CICD + +Deployment of Terraform Templates are fully automated via the [Github actions workflow](./.gh-actions-ci.yml). + +**Terminology** + +* `init` causes Terraform modules to be downloaded/cached +* `lint` applies lint checks to the IAC templates and any Go files +* `build` is a Terraform plan +* `release` is a Terraform apply + +The table below outlines when a deployment will happen to each stage: + +> **Note**: Manual approvals are **always required** before deploying to non-`dev` environments + +| Action | Pipeline Stages (sequential) | +| --- | --- | +| Manual branch build | `dev::build`, `dev::release` | +| Create/Update PR | `init`, `lint`, `dev::build`, `dev::release` | +| Merge PR | `init`, `lint`, `dev::build`, `dev::release`, `integration::build`, `integration::release`, `prod::build`, `prod::release` | +| Master Branch Build | Same as above | + +## Infrastructure Rollbacks + +It is possible that Terraform deployments will need to be rolled back. To rollback use `git revert` commands or simply make another commit to return your configuration to a previous state in the infrastructure as code repository. + +Be sure to create a local git branch, then commit, push, and generate a pull request on Github. + +## Usage + +**Step 1: Azure & Github Configuration** + +All Azure and Github configuration required to use these templates should be provisioned using the [`github-bootstrap-iac-cicd`](https://github.com/microsoft/cobalt/tree/master/infra/templates/github-bootstrap-iac-cicd) template from project [Cobalt](https://github.com/microsoft/cobalt). No other manual configuration is necessary. + +**Step 2: Build and Push Github Runner Base Image** + +The CICD templates in this folder assume the following tools are installed. +* Terraform v0.12.x +* Golang +* Azure CLI + +The included [`Dockerfile.sample`](Dockerfile.sample) can be used as a starting point. Use these commands to push the base image to the ACR: + +```bash +# Configure environment +$ ACR_NAME="..." +$ IMAGE="..." +$ TAG="latest" + +# Build and push image +$ az acr login -n "$ACR_NAME" +$ docker build -f Dockerfile.sample . -t "$ACR_NAME.azurecr.io/$IMAGE:$TAG" +$ docker push "$ACR_NAME.azurecr.io/$IMAGE:$TAG" +``` + +**Step 3: Configure Github Workflow to Use Base Image** + +Now insert a custom value for the base image reference/property in `.gh-actions-ci.yml`: + +> **Note**: The use of `\$CI_REGISTRY` in the command below is intentional. When this pipeline is exercised, the value of `CI_REGISTRY` will be resolved because the [`github-bootstrap-iac-cicd`](https://github.com/microsoft/cobalt/tree/master/infra/templates/github-bootstrap-iac-cicd) template from step 1 configured it to point to the correct container registry. + +```bash +$ sed -i '' "s/{{IMAGE_SLUG}}/\$CI_REGISTRY\/$IMAGE:$TAG/g" .gh-actions-ci.yml +``` + +**Step 4: Write Some Terraform!** + +At this point, you can begin writing a Terraform template for your deployment. The [`sample.tf`](./sample.tf) file is a sample Terraform template that shows a simple but working Terraform file that uses the backend state and variables configured through the Github/Azure bootstrapping process referenced above. diff --git a/pipelines/github-cicd/sample.tf b/pipelines/github-cicd/sample.tf new file mode 100644 index 0000000..7522b7e --- /dev/null +++ b/pipelines/github-cicd/sample.tf @@ -0,0 +1,44 @@ +# This file in intended to show how to leverage the following, which are provisioned +# through the Azure/Github bootstrapping process. +# (1) Backend State +# (2) TF Vars +# +# See README.me for more + +provider "azurerm" { + version = "=2.22" + features {} +} + +terraform { + backend "azurerm" { + key = "terraform.tfstate" + } +} + +variable "env" { + type = string + description = "The name of the environment to provision. Examples: dev, qa, prod" +} + +variable "resource_group" { + type = string + description = "The resource group to deploy into" +} + +variable "acr_id" { + type = string + description = "The resource identifier for AKS to attach to" +} + +output "echo_env" { + value = var.env +} + +output "echo_resource_group" { + value = var.resource_group +} + +output "echo_acr_id" { + value = var.acr_id +} \ No newline at end of file