├── secrets
└── .gitkeep
├── tests
├── integration
│ ├── shop_shop.go
│ ├── tools.go
│ ├── go.mod
│ ├── sock_shop_suite_test.go
│ ├── sock_shop_test.go
│ └── go.sum
└── policies
│ └── image.rego
├── .gitignore
├── helmfile.d
├── 00_repositories.yaml
└── jenkins.yaml
├── environments.yml
├── terraform
└── cluster
│ ├── outputs.tf
│ ├── variables.tf
│ ├── versions.tf
│ ├── vpc.tf
│ ├── security-groups.tf
│ ├── eks-cluster.tf
│ └── .terraform.lock.hcl
├── bootstrap
└── bootstrap.bash
├── JENKINS.md
├── pipelines
├── destroy.xml
├── push-git-branch.xml
├── destroy.Jenkinsfile
├── deploy.xml
└── deploy.Jenkinsfile
├── Dockerfile
├── config
└── default
│ └── jenkins.yaml
├── Makefile
├── deployments
└── sock-shop
│ └── manifest.yml
└── README.md
/secrets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/integration/shop_shop.go:
--------------------------------------------------------------------------------
1 | package sock_shop
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .terraform/
2 | *.tfstate
3 | *.tfstate.backup
4 | secrets/*
5 | build/*
6 | .envrc
--------------------------------------------------------------------------------
/helmfile.d/00_repositories.yaml:
--------------------------------------------------------------------------------
1 | repositories:
2 | - name: jenkins
3 | url: https://charts.jenkins.io
4 |
--------------------------------------------------------------------------------
/tests/integration/tools.go:
--------------------------------------------------------------------------------
1 | // +build tools
2 |
3 | package tools
4 |
5 | import _ "github.com/onsi/ginkgo/ginkgo"
6 |
--------------------------------------------------------------------------------
/environments.yml:
--------------------------------------------------------------------------------
1 | - name: prod
2 | promotes_from: passed_staging
3 | promotes_to: passed_prod
4 | - name: staging
5 | promotes_to: passed_staging
--------------------------------------------------------------------------------
/terraform/cluster/outputs.tf:
--------------------------------------------------------------------------------
1 | output "kubeconfig" {
2 | description = "kubectl config as generated by the module."
3 | value = module.eks.kubeconfig
4 | sensitive = true
5 | }
--------------------------------------------------------------------------------
/terraform/cluster/variables.tf:
--------------------------------------------------------------------------------
1 | variable "env_name" {
2 | description = "Name of the cluster to deploy"
3 | }
4 |
5 | variable "region" {
6 | default = "eu-west-2"
7 | description = "AWS region"
8 | }
--------------------------------------------------------------------------------
/tests/integration/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/EngineerBetter/iac-example/tests/integration
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/onsi/ginkgo v1.16.2
7 | github.com/onsi/gomega v1.10.1
8 | github.com/sclevine/agouti v3.0.0+incompatible
9 | )
10 |
--------------------------------------------------------------------------------
/helmfile.d/jenkins.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | helmDefaults:
3 | atomic: false
4 | force: false
5 | timeout: 480
6 | wait: true
7 |
8 | releases:
9 | - name: jenkins
10 | chart: jenkins/jenkins
11 | version: 3.3.18
12 | namespace: jenkins
13 | values: ["../config/default/jenkins.yaml"]
14 | set:
15 | - name: namespace
16 | value: jenkins
17 |
--------------------------------------------------------------------------------
/tests/policies/image.rego:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | has_a_sha256_hash_specified(image_path) {
4 | regex.match("@sha256:[0-9a-zA-Z]+$", image_path)
5 | }
6 |
7 | all_images_have_a_sha256_hash_specified(containers) {
8 | some i
9 | container := containers[i]
10 | not has_a_sha256_hash_specified(container.image)
11 | }
12 |
13 | deny[msg] {
14 | input.kind == "Deployment"
15 | all_images_have_a_sha256_hash_specified(input.spec.template.spec.containers)
16 |
17 | msg := "Container images must specify a SHA hash"
18 | }
--------------------------------------------------------------------------------
/terraform/cluster/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | backend "s3" {}
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = "3.40.0"
8 | }
9 |
10 | local = {
11 | source = "hashicorp/local"
12 | version = "2.0.0"
13 | }
14 |
15 | null = {
16 | source = "hashicorp/null"
17 | version = "3.0.0"
18 | }
19 |
20 | template = {
21 | source = "hashicorp/template"
22 | version = "2.2.0"
23 | }
24 |
25 | kubernetes = {
26 | source = "hashicorp/kubernetes"
27 | version = "2.1.0"
28 | }
29 |
30 | http = {
31 | source = "terraform-aws-modules/http"
32 | version = "2.4.1"
33 | }
34 | }
35 |
36 | required_version = "~> 0.15"
37 | }
38 |
--------------------------------------------------------------------------------
/tests/integration/sock_shop_suite_test.go:
--------------------------------------------------------------------------------
1 | package sock_shop_test
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | . "github.com/onsi/ginkgo"
8 | . "github.com/onsi/gomega"
9 | "github.com/sclevine/agouti"
10 | )
11 |
12 | var sockShopURL string
13 |
14 | func TestSockShop(t *testing.T) {
15 | RegisterFailHandler(Fail)
16 | RunSpecs(t, "SockShop Suite")
17 | }
18 |
19 | var agoutiDriver *agouti.WebDriver
20 |
21 | var _ = BeforeSuite(func() {
22 | sockShopURL = os.Getenv("SOCK_SHOP_URL")
23 | Expect(sockShopURL).NotTo(BeEmpty())
24 | sockShopURL = "http://" + sockShopURL
25 |
26 | agoutiDriver = agouti.ChromeDriver(agouti.ChromeOptions("args", []string{
27 | "--headless",
28 | "--no-sandbox",
29 | "--disable-dev-shm-usage",
30 | "--disable-gpu",
31 | "--detach",
32 | "--whitelisted-ips",
33 | }), agouti.Debug)
34 | Expect(agoutiDriver.Start()).To(Succeed())
35 | })
36 |
37 | var _ = AfterSuite(func() {
38 | Expect(agoutiDriver.Stop()).To(Succeed())
39 | })
40 |
--------------------------------------------------------------------------------
/terraform/cluster/vpc.tf:
--------------------------------------------------------------------------------
1 | provider "aws" {
2 | region = var.region
3 | }
4 |
5 | data "aws_availability_zones" "available" {}
6 |
7 | module "vpc" {
8 | source = "terraform-aws-modules/vpc/aws"
9 | version = "2.66.0"
10 |
11 | name = var.env_name
12 | cidr = "10.0.0.0/16"
13 | azs = data.aws_availability_zones.available.names
14 | private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
15 | public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"]
16 | enable_nat_gateway = true
17 | single_nat_gateway = true
18 | enable_dns_hostnames = true
19 |
20 | tags = {
21 | "kubernetes.io/cluster/${var.env_name}" = "shared"
22 | }
23 |
24 | public_subnet_tags = {
25 | "kubernetes.io/cluster/${var.env_name}" = "shared"
26 | "kubernetes.io/role/elb" = "1"
27 | }
28 |
29 | private_subnet_tags = {
30 | "kubernetes.io/cluster/${var.env_name}" = "shared"
31 | "kubernetes.io/role/internal-elb" = "1"
32 | }
33 | }
--------------------------------------------------------------------------------
/tests/integration/sock_shop_test.go:
--------------------------------------------------------------------------------
1 | package sock_shop_test
2 |
3 | import (
4 | "time"
5 |
6 | . "github.com/onsi/ginkgo"
7 | . "github.com/onsi/gomega"
8 | "github.com/sclevine/agouti"
9 | . "github.com/sclevine/agouti/matchers"
10 | )
11 |
12 | var _ = Describe("SockShop", func() {
13 | var page *agouti.Page
14 |
15 | BeforeEach(func() {
16 | var err error
17 | page, err = agoutiDriver.NewPage(agouti.Browser("chrome"))
18 | Expect(err).NotTo(HaveOccurred())
19 | })
20 |
21 | AfterEach(func() {
22 | Expect(page.Destroy()).To(Succeed())
23 | })
24 |
25 | It("renders the front-end", func() {
26 | By("navigating to the homepage", func() {
27 | Expect(page.Navigate(sockShopURL)).To(Succeed())
28 | })
29 |
30 | By("finding the advantages section", func() {
31 | Eventually(find(page, "#advantages"), time.Second*10).Should(BeFound())
32 | })
33 | })
34 | })
35 |
36 | func find(page *agouti.Page, text string) func() *agouti.Selection {
37 | return func() *agouti.Selection {
38 | return page.Find(text)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/terraform/cluster/security-groups.tf:
--------------------------------------------------------------------------------
1 | resource "aws_security_group" "snyk_security_group_one" {
2 | description = "Security group for worker group one"
3 | name_prefix = "snyk_security_group_one"
4 | vpc_id = module.vpc.vpc_id
5 |
6 | ingress {
7 | from_port = 22
8 | to_port = 22
9 | protocol = "tcp"
10 |
11 | cidr_blocks = [
12 | "10.0.0.0/8",
13 | ]
14 | }
15 | }
16 |
17 | resource "aws_security_group" "snyk_security_group_two" {
18 | description = "Security group for worker group two"
19 | name_prefix = "snyk_security_group_two"
20 | vpc_id = module.vpc.vpc_id
21 |
22 | ingress {
23 | from_port = 22
24 | to_port = 22
25 | protocol = "tcp"
26 |
27 | cidr_blocks = [
28 | "192.168.0.0/16",
29 | ]
30 | }
31 | }
32 |
33 | resource "aws_security_group" "snyk_security_group_all" {
34 | description = "Security group for all worker groups"
35 | name_prefix = "snyk_security_group_all"
36 | vpc_id = module.vpc.vpc_id
37 |
38 | ingress {
39 | from_port = 22
40 | to_port = 22
41 | protocol = "tcp"
42 |
43 | cidr_blocks = [
44 | "10.0.0.0/8",
45 | "172.16.0.0/12",
46 | "192.168.0.0/16",
47 | ]
48 | }
49 | }
--------------------------------------------------------------------------------
/bootstrap/bootstrap.bash:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -eu
3 |
4 | function create_bucket() {
5 | local region="$1"
6 | local name="$2"
7 |
8 | if aws s3 ls "s3://$name" 2>&1 | grep -q 'NoSuchBucket'; then
9 | echo 'Creating S3 bucket...'
10 | aws s3api create-bucket \
11 | --bucket "$name" \
12 | --create-bucket-configuration="{\"LocationConstraint\": \"${region}\"}" \
13 | 1>/dev/null
14 | aws s3api put-bucket-versioning \
15 | --bucket "$name" \
16 | --versioning-configuration Status=Enabled \
17 | 1>/dev/null
18 | fi
19 | }
20 |
21 | function create_table() {
22 | local region="$1"
23 | local name="$2"
24 |
25 | if aws dynamodb describe-table --region "$region" --table-name "$name" 2>&1 | grep -q 'not found'; then
26 | echo 'Creating DynamoDB table...'
27 | aws dynamodb create-table \
28 | --region "$region" \
29 | --table-name "$name" \
30 | --attribute-definitions AttributeName=LockID,AttributeType=S \
31 | --key-schema AttributeName=LockID,KeyType=HASH \
32 | --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \
33 | 1>/dev/null
34 | fi
35 | }
36 |
37 | create_bucket "$BOOTSTRAP_AWS_REGION" "$BOOTSTRAP_BUCKET_NAME"
38 | create_table "$BOOTSTRAP_AWS_REGION" "$BOOTSTRAP_DYNAMO_TABLE_NAME"
--------------------------------------------------------------------------------
/JENKINS.md:
--------------------------------------------------------------------------------
1 | # Jenkins in Kubernetes (optional)
2 |
3 | Firstly you'll need a Kubernetes cluster, we recommend [enabling the inbuilt one
4 | inside Docker Desktop](https://docs.docker.com/desktop/kubernetes/).
5 | Alternatively you could use
6 | [kind](https://kind.sigs.k8s.io/docs/user/quick-start/).
7 |
8 | After installation of the cluster, make sure you've selected the docker-desktop
9 | context so that kubectl is talking to the expected cluster. You'll find this by
10 | navigating to Docker -> kubernetes -> docker-desktop.
11 |
12 | If you're using Kind, please follow the
13 | [instructions to install a LoadBalancer](https://kind.sigs.k8s.io/docs/user/loadbalancer/)
14 | to your cluster.
15 |
16 | ## Following along
17 |
18 | ```terminal
19 | # In order to use the Jenkins CLI and access the UI you'll need to install an
20 | # ingress controller.
21 | kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.46.0/deploy/static/provider/cloud/deploy.yaml
22 |
23 | # Install Jenkins in a `jenkins` namespace.
24 | helmfile apply
25 |
26 | # Jenkins may take a few minutes to install the first time this command is run,
27 | # you can view the status of the `jenkins` pod with:
28 | kubectl describe pod jenkins-0 -n jenkins
29 | ```
30 |
31 | Once deployed, access the Jenkins UI by navigating to
32 | [http://localhost/](http://localhost/) and logging in with `admin`/`p4ssw0rd`.
--------------------------------------------------------------------------------
/terraform/cluster/eks-cluster.tf:
--------------------------------------------------------------------------------
1 | module "eks" {
2 | source = "terraform-aws-modules/eks/aws"
3 | version = "17.0.3"
4 | cluster_name = var.env_name
5 | cluster_version = "1.18"
6 | subnets = module.vpc.private_subnets
7 |
8 | tags = {
9 | Environment = var.env_name
10 | }
11 |
12 | vpc_id = module.vpc.vpc_id
13 |
14 | workers_group_defaults = {
15 | root_volume_type = "gp2"
16 | }
17 |
18 | worker_groups = [
19 | {
20 | name = "worker-group-1"
21 | instance_type = "t2.small"
22 | asg_desired_capacity = 2
23 | additional_security_group_ids = [aws_security_group.snyk_security_group_one.id]
24 | },
25 | {
26 | name = "worker-group-2"
27 | instance_type = "t2.medium"
28 | additional_security_group_ids = [aws_security_group.snyk_security_group_two.id]
29 | asg_desired_capacity = 1
30 | },
31 | ]
32 |
33 | map_roles = [
34 | {
35 | rolearn = data.aws_caller_identity.current.arn
36 | username = "creator"
37 | groups = ["system:masters"]
38 | },
39 | ]
40 |
41 | write_kubeconfig = false
42 | }
43 |
44 | data "aws_caller_identity" "current" {}
45 |
46 | data "aws_eks_cluster" "cluster" {
47 | name = module.eks.cluster_id
48 | }
49 |
50 | data "aws_eks_cluster_auth" "cluster" {
51 | name = module.eks.cluster_id
52 | }
53 |
54 | provider "kubernetes" {
55 | host = element(concat(data.aws_eks_cluster.cluster[*].endpoint, tolist([""])), 0)
56 | cluster_ca_certificate = base64decode(element(concat(data.aws_eks_cluster.cluster[*].certificate_authority.0.data, tolist([""])), 0))
57 | token = element(concat(data.aws_eks_cluster_auth.cluster[*].token, tolist([""])), 0)
58 | }
59 |
--------------------------------------------------------------------------------
/pipelines/destroy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | false
14 |
15 |
16 |
17 | SLACK_CHANNEL=REPLACE_ME_SLACK_CHANNEL
18 | ENV_NAME=REPLACE_ME_ENV_NAME
19 |
20 |
21 | false
22 |
23 | false
24 |
25 | true
26 | true
27 | true
28 | false
29 |
30 |
31 |
32 |
33 | 2
34 |
35 |
36 | REPLACE_ME_REPOSITORY_URL
37 | git
38 |
39 |
40 |
41 |
42 | refs/heads/REPLACE_ME_PROMOTES_TO
43 |
44 |
45 | false
46 |
47 |
48 |
49 | pipelines/destroy.Jenkinsfile
50 | true
51 |
52 |
53 | true
54 |
55 |
--------------------------------------------------------------------------------
/pipelines/push-git-branch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | false
6 |
7 |
8 |
9 |
10 | GIT_REVISION
11 |
12 |
13 | false
14 |
15 |
16 | REFERENCE
17 |
18 |
19 | false
20 |
21 |
22 |
23 |
24 |
25 | 2
26 |
27 |
28 | REPLACE_ME_REPOSITORY_URL
29 | git
30 |
31 |
32 |
33 |
34 | */main
35 |
36 |
37 | false
38 |
39 |
40 |
41 | true
42 | false
43 | false
44 | false
45 |
46 | false
47 |
48 |
49 | echo "Revision: $GIT_REVISION"
50 | echo "Branch: $REFERENCE"
51 |
52 | git push origin "$( git rev-parse "$GIT_REVISION" ):refs/heads/${REFERENCE}"
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | git
61 |
62 | false
63 |
64 |
65 |
--------------------------------------------------------------------------------
/pipelines/destroy.Jenkinsfile:
--------------------------------------------------------------------------------
1 | // engineerbetter/iac-example-ci:15-promote
2 | def ciImage = 'engineerbetter/iac-example-ci@sha256:0bb2dd8e86b418cd96a3371887bf8d7aa929ef4c58616c98a425ff22bddc0257'
3 |
4 | pipeline {
5 | agent {
6 | kubernetes {
7 | yaml """
8 | apiVersion: v1
9 | kind: Pod
10 | spec:
11 | containers:
12 | - name: iac
13 | image: ${ciImage}
14 | command:
15 | - cat
16 | tty: true
17 | """.stripIndent()
18 | defaultContainer 'iac'
19 | }
20 | }
21 |
22 | environment {
23 | TF_IN_AUTOMATION = 'true'
24 | BOOTSTRAP_AWS_REGION = credentials 'BOOTSTRAP_AWS_REGION'
25 | BOOTSTRAP_BUCKET_NAME = credentials 'BOOTSTRAP_BUCKET_NAME'
26 | BOOTSTRAP_DYNAMO_TABLE_NAME = credentials 'BOOTSTRAP_DYNAMO_TABLE_NAME'
27 | }
28 |
29 | stages {
30 | stage('Terraform init') {
31 | environment {
32 | AWS_ACCESS_KEY_ID = credentials 'AWS_ACCESS_KEY_ID'
33 | AWS_SECRET_ACCESS_KEY = credentials 'AWS_SECRET_ACCESS_KEY'
34 | }
35 |
36 | steps {
37 | sh 'make terraform-init'
38 | }
39 | }
40 |
41 | stage('Delete sock shop') {
42 | environment {
43 | AWS_ACCESS_KEY_ID = credentials 'AWS_ACCESS_KEY_ID'
44 | AWS_SECRET_ACCESS_KEY = credentials 'AWS_SECRET_ACCESS_KEY'
45 | }
46 |
47 | steps {
48 | sh 'make fetch-cluster-config'
49 | sh 'make delete-sock-shop'
50 |
51 | // Leave time for Kubernetes to remove resources like Load Balancers.
52 | // Since Kubernetes creates resources that terraform doesn't know
53 | // about, we need to wait for these to be removed otherwise there's a
54 | // risk that `terraform destroy` will hang and require manual cleanup.
55 | sh 'sleep 180'
56 | }
57 | }
58 |
59 | stage('Destroy cluster') {
60 | environment {
61 | AWS_ACCESS_KEY_ID = credentials 'AWS_ACCESS_KEY_ID'
62 | AWS_SECRET_ACCESS_KEY = credentials 'AWS_SECRET_ACCESS_KEY'
63 | TF_CLI_ARGS_destroy = '-input=false -auto-approve'
64 | }
65 |
66 | steps {
67 | sh 'make destroy-cluster'
68 | }
69 | }
70 | }
71 |
72 | post {
73 | always {
74 | cleanWs()
75 | }
76 |
77 | failure {
78 | slackSend(
79 | message: "Destroy failed: <${env.BUILD_URL}|${env.JOB_NAME}#${env.BUILD_NUMBER}>",
80 | color: 'danger',
81 | username: 'The Butler',
82 | tokenCredentialId: 'SLACK_WEBHOOK_CREDENTIAL',
83 | baseUrl: 'https://hooks.slack.com/services/',
84 | channel: env.SLACK_CHANNEL
85 | )
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # ubuntu:hirsute
2 | FROM ubuntu@sha256:9a5cc8359b220b9414e4dc6ec992f867b33f864c560a1e198fb833f98b8f7f3c
3 |
4 | RUN \
5 | apt update \
6 | && DEBIAN_FRONTEND=noninteractive \
7 | apt install --yes \
8 | git \
9 | wget \
10 | unzip \
11 | gnupg2 \
12 | build-essential \
13 | && rm -rf /var/lib/apt/lists/*
14 |
15 | # System test requirements
16 | RUN wget \
17 | -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub \
18 | | apt-key add - \
19 | && echo \
20 | "deb http://dl.google.com/linux/chrome/deb/ stable main" \
21 | >> /etc/apt/sources.list.d/google.list \
22 | && apt update --yes \
23 | && DEBIAN_FRONTEND=noninteractive \
24 | apt install --yes google-chrome-stable golang build-essential \
25 | && rm -rf /var/lib/apt/lists/* \
26 | && wget -q \
27 | -O /tmp/chromedriver_linux64.zip \
28 | https://chromedriver.storage.googleapis.com/90.0.4430.24/chromedriver_linux64.zip \
29 | && unzip /tmp/chromedriver_linux64.zip \
30 | && rm /tmp/chromedriver_linux64.zip \
31 | && chmod +rx ./chromedriver \
32 | && mv ./chromedriver /usr/local/bin/chromedriver \
33 | && mkdir /go && chmod a+rwx /go \
34 | && mkdir /gocache && chmod a+rwx /gocache
35 |
36 | ENV GOCACHE /gocache
37 |
38 | RUN \
39 | apt update \
40 | && DEBIAN_FRONTEND=noninteractive \
41 | apt install --yes awscli \
42 | && rm -rf /var/lib/apt/lists/*
43 |
44 | RUN \
45 | wget \
46 | -O /terraform.zip \
47 | https://releases.hashicorp.com/terraform/0.15.4/terraform_0.15.4_linux_amd64.zip \
48 | && unzip /terraform.zip \
49 | && rm /terraform.zip \
50 | && chmod +rx /terraform \
51 | && mv /terraform /usr/local/bin/terraform
52 |
53 | RUN \
54 | wget \
55 | -O /usr/local/bin/aws-iam-authenticator \
56 | https://amazon-eks.s3.us-west-2.amazonaws.com/1.19.6/2021-01-05/bin/linux/amd64/aws-iam-authenticator \
57 | && chmod +rx /usr/local/bin/aws-iam-authenticator
58 |
59 | RUN \
60 | wget \
61 | -O /usr/local/bin/kubectl \
62 | https://dl.k8s.io/release/v1.21.1/bin/linux/amd64/kubectl \
63 | && chmod +rx /usr/local/bin/kubectl
64 |
65 | RUN \
66 | wget \
67 | -O /tflint.zip \
68 | https://github.com/terraform-linters/tflint/releases/download/v0.28.1/tflint_linux_amd64.zip \
69 | && unzip /tflint.zip \
70 | && rm /tflint.zip \
71 | && chmod +rx /tflint \
72 | && mv /tflint /usr/local/bin/tflint
73 |
74 | RUN \
75 | wget \
76 | -O /tmp/conftest_0.25.0_Linux_x86_64.tar.gz \
77 | https://github.com/open-policy-agent/conftest/releases/download/v0.25.0/conftest_0.25.0_Linux_x86_64.tar.gz \
78 | && tar xzf /tmp/conftest_0.25.0_Linux_x86_64.tar.gz --directory=/tmp \
79 | && mv /tmp/conftest /usr/local/bin/conftest \
80 | && chmod +rx /usr/local/bin/conftest \
81 | && rm /tmp/conftest_0.25.0_Linux_x86_64.tar.gz
82 |
83 | RUN \
84 | wget \
85 | -O /usr/local/bin/snyk \
86 | https://github.com/snyk/snyk/releases/download/v1.616.0/snyk-linux \
87 | && chmod +rx /usr/local/bin/snyk
88 |
89 | RUN \
90 | wget \
91 | -O /usr/local/bin/yq \
92 | https://github.com/mikefarah/yq/releases/download/v4.9.1/yq_linux_amd64 \
93 | && chmod +rx /usr/local/bin/yq
94 |
--------------------------------------------------------------------------------
/pipelines/deploy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | false
14 |
15 |
16 |
17 | SLACK_CHANNEL=REPLACE_ME_SLACK_CHANNEL
18 | ENV_NAME=REPLACE_ME_ENV_NAME
19 | PROMOTES_TO=REPLACE_ME_PROMOTES_TO
20 |
21 |
22 | false
23 |
24 | false
25 |
26 | true
27 | true
28 | true
29 | false
30 |
31 |
32 |
33 | -1
34 | 200
35 | -1
36 | -1
37 |
38 |
39 |
40 |
41 |
42 |
43 | @hourly
44 |
45 |
46 | H/2 * * * *
47 | false
48 |
49 |
50 |
51 |
52 |
53 |
54 | 2
55 |
56 |
57 | REPLACE_ME_REPOSITORY_URL
58 | git
59 |
60 |
61 |
62 |
63 | refs/heads/REPLACE_ME_PROMOTES_FROM
64 |
65 |
66 | false
67 |
68 |
69 |
70 | pipelines/deploy.Jenkinsfile
71 | true
72 |
73 |
74 | false
75 |
76 |
--------------------------------------------------------------------------------
/pipelines/deploy.Jenkinsfile:
--------------------------------------------------------------------------------
1 | // engineerbetter/iac-example-ci:15-promote
2 | def ciImage = 'engineerbetter/iac-example-ci@sha256:0bb2dd8e86b418cd96a3371887bf8d7aa929ef4c58616c98a425ff22bddc0257'
3 |
4 | pipeline {
5 | agent {
6 | kubernetes {
7 | yaml """
8 | apiVersion: v1
9 | kind: Pod
10 | spec:
11 | containers:
12 | - name: iac
13 | image: ${ciImage}
14 | command:
15 | - cat
16 | tty: true
17 | """.stripIndent()
18 | defaultContainer 'iac'
19 | }
20 | }
21 |
22 | environment {
23 | TF_IN_AUTOMATION = 'true'
24 | BOOTSTRAP_AWS_REGION = credentials 'BOOTSTRAP_AWS_REGION'
25 | BOOTSTRAP_BUCKET_NAME = credentials 'BOOTSTRAP_BUCKET_NAME'
26 | BOOTSTRAP_DYNAMO_TABLE_NAME = credentials 'BOOTSTRAP_DYNAMO_TABLE_NAME'
27 | }
28 |
29 | stages {
30 | stage('Terraform bootstrap') {
31 | environment {
32 | AWS_ACCESS_KEY_ID = credentials 'AWS_ACCESS_KEY_ID'
33 | AWS_SECRET_ACCESS_KEY = credentials 'AWS_SECRET_ACCESS_KEY'
34 | }
35 |
36 | steps {
37 | sh 'make terraform-bootstrap'
38 | }
39 | }
40 |
41 | stage('Terraform init') {
42 | environment {
43 | AWS_ACCESS_KEY_ID = credentials 'AWS_ACCESS_KEY_ID'
44 | AWS_SECRET_ACCESS_KEY = credentials 'AWS_SECRET_ACCESS_KEY'
45 | }
46 |
47 | steps {
48 | sh 'make terraform-init'
49 | }
50 | }
51 |
52 | stage('Static checks') {
53 | parallel {
54 | stage('Validate terraform') {
55 | steps {
56 | sh 'make terraform-validate'
57 | }
58 | }
59 |
60 | stage('Lint terraform') {
61 | steps {
62 | sh 'make terraform-lint'
63 | }
64 | }
65 |
66 | stage('Check format') {
67 | steps {
68 | sh 'make terraform-fmt-check'
69 | }
70 | }
71 |
72 | stage('Policy test') {
73 | steps {
74 | sh 'make policy-test'
75 | }
76 | }
77 |
78 | stage('Snyk: test terraform') {
79 | environment {
80 | SNYK_TOKEN = credentials 'SNYK_TOKEN'
81 | }
82 |
83 | steps {
84 | retry(3) {
85 | sh 'make snyk-test-terraform'
86 | }
87 | }
88 | }
89 |
90 | stage('Snyk: test manifest') {
91 | environment {
92 | SNYK_TOKEN = credentials 'SNYK_TOKEN'
93 | IGNORE_SNYK_TEST_DEPLOYMENTS_FAILURE = 'true'
94 | }
95 |
96 | steps {
97 | retry(3) {
98 | sh 'make snyk-test-deployments'
99 | }
100 | }
101 | }
102 | }
103 | }
104 |
105 | stage('Deploy cluster') {
106 | environment {
107 | AWS_ACCESS_KEY_ID = credentials 'AWS_ACCESS_KEY_ID'
108 | AWS_SECRET_ACCESS_KEY = credentials 'AWS_SECRET_ACCESS_KEY'
109 | SNYK_TOKEN = credentials 'SNYK_TOKEN'
110 | IGNORE_SNYK_TEST_PLAN_FAILURE = 'true'
111 | TF_CLI_ARGS_apply = '-input=false -auto-approve'
112 | }
113 |
114 | steps {
115 | sh 'make deploy-cluster'
116 | }
117 | }
118 |
119 | stage('Deploy sock shop') {
120 | environment {
121 | AWS_ACCESS_KEY_ID = credentials 'AWS_ACCESS_KEY_ID'
122 | AWS_SECRET_ACCESS_KEY = credentials 'AWS_SECRET_ACCESS_KEY'
123 | }
124 |
125 | steps {
126 | sh 'make fetch-cluster-config'
127 | sh 'make deploy-sock-shop'
128 | }
129 | }
130 |
131 | stage('Integration tests') {
132 | environment {
133 | AWS_ACCESS_KEY_ID = credentials 'AWS_ACCESS_KEY_ID'
134 | AWS_SECRET_ACCESS_KEY = credentials 'AWS_SECRET_ACCESS_KEY'
135 | }
136 |
137 | steps {
138 | sh 'make fetch-cluster-config'
139 | sh 'make integration-test'
140 | }
141 | }
142 |
143 | stage('Promote') {
144 | steps {
145 | script {
146 | GIT_REVISION = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
147 | }
148 |
149 | build(
150 | job: 'Push Git Branch',
151 | propagate: false,
152 | wait: false,
153 | parameters: [
154 | string(name: 'GIT_REVISION', value: GIT_REVISION),
155 | string(name: 'REFERENCE', value: env.PROMOTES_TO)
156 | ]
157 | )
158 | }
159 | }
160 | }
161 |
162 | post {
163 | always {
164 | cleanWs()
165 | }
166 |
167 | failure {
168 | slackSend(
169 | message: "Deploy failed: <${env.BUILD_URL}|${env.JOB_NAME}#${env.BUILD_NUMBER}>",
170 | color: 'danger',
171 | username: 'The Butler',
172 | tokenCredentialId: 'SLACK_WEBHOOK_CREDENTIAL',
173 | baseUrl: 'https://hooks.slack.com/services/',
174 | channel: env.SLACK_CHANNEL
175 | )
176 | }
177 | }
178 | }
--------------------------------------------------------------------------------
/config/default/jenkins.yaml:
--------------------------------------------------------------------------------
1 | rbac:
2 | readSecrets: true
3 | persistence:
4 | enabled: true
5 | size: 100Gi
6 | controller:
7 | imagePullPolicy: IfNotPresent
8 | JCasC:
9 | enabled: true
10 | defaultConfig: false
11 | configScripts:
12 | no-executors: |
13 | jenkins:
14 | numExecutors: 2
15 | matrix-settings: |
16 | jenkins:
17 | authorizationStrategy:
18 | globalMatrix:
19 | permissions:
20 | - "Overall/Administer:admin"
21 | - "Credentials/Create:admin"
22 | - "Overall/SystemRead:authenticated"
23 | - "Overall/Read:authenticated"
24 | - "Agent/ExtendedRead:authenticated"
25 | - "Job/ExtendedRead:authenticated"
26 | - "Overall/Read:anonymous"
27 | - "Job/Read:anonymous"
28 | - "View/Read:anonymous"
29 | security-realm: |
30 | jenkins:
31 | securityRealm:
32 | local:
33 | allowsSignup: false
34 | users:
35 | - id: "admin"
36 | password: "p4ssw0rd"
37 | misc-jenkins: |
38 | jenkins:
39 | remotingSecurity:
40 | enabled: true
41 | slaveAgentPort: 50000
42 | crumb-issuer: |
43 | jenkins:
44 | crumbIssuer:
45 | standard:
46 | excludeClientIPFromCrumb: true
47 | misc-security: |
48 | security:
49 | apiToken:
50 | creationOfLegacyTokenEnabled: false
51 | tokenGenerationOnCreationEnabled: false
52 | usageStatisticsEnabled: true
53 | misc-unclassified: |
54 | unclassified:
55 | gitSCM:
56 | createAccountBasedOnEmail: false
57 | globalConfigEmail: "oscar@example.com"
58 | globalConfigName: "oscar"
59 | pollSCM:
60 | pollingThreadCount: 10
61 | ansiColorBuildWrapper:
62 | globalColorMapName: "xterm"
63 | # timestamper:
64 | # allPipelines: true
65 | location: |
66 | unclassified:
67 | location:
68 | adminAddress: "nobody@jenkins.io"
69 | url: "http://localhost"
70 | agent-settings: |
71 | jenkins:
72 | clouds:
73 | - kubernetes:
74 | containerCapStr: "100"
75 | jenkinsUrl: "http://jenkins:8080"
76 | maxRequestsPerHostStr: "300"
77 | webSocket: true
78 | name: "kubernetes"
79 | namespace: "jenkins"
80 | podRetention: "Never"
81 | serverUrl: "https://kubernetes.default"
82 | podLabels:
83 | # Required to be jenkins/-jenkins-slave as definede here
84 | # https://github.com/helm/charts/blob/ef0d749132ecfa61b2ea47ccacafeaf5cf1d3d77/stable/jenkins/templates/jenkins-master-networkpolicy.yaml#L27
85 | - key: "jenkins/jenkins-agent"
86 | value: "true"
87 | templates:
88 | - name: jnlp
89 | nodeSelector: "kubernetes.io/os=linux"
90 | containers:
91 | - name: jnlp
92 | image: "jenkins/inbound-agent:latest-jdk11"
93 | resourceLimitCpu: "500m"
94 | resourceLimitMemory: "512Mi"
95 | resourceRequestCpu: "500m"
96 | resourceRequestMemory: "512Mi"
97 | args: 1d
98 | alwaysPullImage: true
99 | overwritePlugins: true
100 | installPlugins:
101 | - ace-editor:1.1
102 | - ansicolor:1.0.0
103 | - authentication-tokens:1.4
104 | - authorize-project:1.4.0
105 | - bootstrap4-api:4.6.0-3
106 | - caffeine-api:2.9.1-23.v51c4e2c879c8
107 | - checks-api:1.7.0
108 | - cloudbees-folder:6.15
109 | - configuration-as-code:1.51
110 | - credentials:2.4.1
111 | - credentials-binding:1.24
112 | - display-url-api:2.3.5
113 | - durable-task:1.36
114 | - echarts-api:5.1.0-2
115 | - envinject:2.4.0
116 | - git:4.7.1
117 | - git-client:3.7.1
118 | - github:1.33.1
119 | - github-api:1.123
120 | - github-branch-source:2.10.4
121 | - jackson2-api:2.12.3
122 | - jjwt-api:0.11.2-9.c8b45b8bb173
123 | - junit:1.49
124 | - kubernetes:1.29.6
125 | - kubernetes-client-api:4.13.3-1
126 | - kubernetes-credentials:0.9.0
127 | - kubernetes-credentials-provider:0.18-1
128 | - lockable-resources:2.10
129 | - mailer:1.34
130 | - matrix-auth:2.6.7
131 | - metrics:4.0.2.7
132 | - pipeline-build-step:2.13
133 | - pipeline-graph-analysis:1.10
134 | - pipeline-graph-view:0.1
135 | - pipeline-input-step:2.12
136 | - pipeline-milestone-step:1.3.2
137 | - pipeline-model-api:1.8.4
138 | - pipeline-model-definition:1.8.4
139 | - pipeline-stage-step:2.5
140 | - pipeline-stage-view:2.19
141 | - plain-credentials:1.7
142 | - plugin-util-api:2.2.0
143 | - scm-api:2.6.4
144 | - script-security:1.77
145 | - slack:2.48
146 | - snakeyaml-api:1.27.0
147 | - ssh-agent:1.22
148 | - ssh-credentials:1.18.1
149 | - structs:1.23
150 | - timestamper:1.13
151 | - variant:1.4
152 | - workflow-aggregator:2.6
153 | - workflow-api:2.42
154 | - workflow-basic-steps:2.23
155 | - workflow-cps:2.92
156 | - workflow-cps-global-lib:2.19
157 | - workflow-durable-task-step:2.39
158 | - workflow-job:2.40
159 | - workflow-multibranch:2.24
160 | - workflow-scm-step:2.12
161 | - workflow-step-api:2.23
162 | - workflow-support:3.8
163 | - ws-cleanup:0.39
164 | sidecars:
165 | configAutoReload:
166 | env:
167 | # https://github.com/kiwigrid/k8s-sidecar#configuration-environment-variables
168 | - name: METHOD
169 | # Polling mode (instead of watching kube API)
170 | value: "SLEEP"
171 | # https://github.com/kiwigrid/k8s-sidecar#configuration-environment-variables
172 | - name: SLEEP_TIME
173 | # Time in seconds between two polls
174 | value: "60"
175 | ingress:
176 | enabled: true
177 | annotations:
178 | nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
179 | probes:
180 | startupProbe:
181 | initialDelaySeconds: 240
182 | livenessProbe:
183 | initialDelaySeconds: 240
184 | readinessProbe:
185 | initialDelaySeconds: 240
186 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # ===== Deployment ============================================================
2 |
3 | terraform-bootstrap: \
4 | guard-BOOTSTRAP_AWS_REGION \
5 | guard-BOOTSTRAP_BUCKET_NAME \
6 | guard-BOOTSTRAP_DYNAMO_TABLE_NAME
7 | @./bootstrap/bootstrap.bash
8 | @$(call print_success,OK)
9 |
10 | terraform-init: \
11 | load-env \
12 | guard-BOOTSTRAP_AWS_REGION \
13 | guard-BOOTSTRAP_BUCKET_NAME \
14 | guard-BOOTSTRAP_DYNAMO_TABLE_NAME
15 | terraform \
16 | -chdir=$(CLUSTER) \
17 | init \
18 | -backend=true \
19 | -reconfigure \
20 | -input=false \
21 | -backend-config=bucket=$(BOOTSTRAP_BUCKET_NAME) \
22 | -backend-config=region=$(BOOTSTRAP_AWS_REGION) \
23 | -backend-config=dynamodb_table=$(BOOTSTRAP_DYNAMO_TABLE_NAME) \
24 | -backend-config=key=$(ENV_NAME)
25 |
26 | terraform-plan: load-env terraform-init
27 | terraform -chdir=$(CLUSTER) plan -var="env_name=$(ENV_NAME)" -out "$$( pwd )/build/tfplan"
28 | terraform -chdir=$(CLUSTER) show -json "$$( pwd )/build/tfplan" > build/tfplan.json
29 |
30 | deploy-cluster: load-env terraform-plan snyk-test-plan
31 | rm build/tfplan.json
32 | terraform \
33 | -chdir=$(CLUSTER) \
34 | apply \
35 | "$$( pwd )/build/tfplan"
36 | rm build/tfplan
37 |
38 | deploy-sock-shop: load-env
39 | kubectl \
40 | --kubeconfig=secrets/config-$(ENV_NAME).yml \
41 | apply \
42 | --filename deployments/sock-shop/manifest.yml
43 |
44 | kubectl \
45 | --kubeconfig=secrets/config-$(ENV_NAME).yml \
46 | wait \
47 | --namespace sock-shop \
48 | --all=true \
49 | --for condition=ready \
50 | --timeout=600s \
51 | pod
52 |
53 | # ===== Destroy ===============================================================
54 |
55 | delete-sock-shop: load-env
56 | kubectl \
57 | --kubeconfig=secrets/config-$(ENV_NAME).yml \
58 | delete \
59 | --filename deployments/sock-shop/manifest.yml \
60 | --ignore-not-found=true \
61 | --wait=true
62 |
63 | destroy-cluster: load-env terraform-init
64 | terraform -chdir=$(CLUSTER) destroy -var="env_name=$(ENV_NAME)"
65 |
66 | # ===== Tests & Checks ========================================================
67 |
68 | test: \
69 | terraform-validate \
70 | terraform-lint \
71 | terraform-fmt-check \
72 | snyk-test-terraform \
73 | snyk-test-deployments
74 |
75 | terraform-validate:
76 | terraform -chdir=$(CLUSTER) validate
77 |
78 | terraform-lint:
79 | tflint $(CLUSTER)
80 |
81 | terraform-fmt-check:
82 | @if ! terraform -chdir=$(CLUSTER) fmt -check -diff; then \
83 | $(call print_fail,Run `terraform fmt $(CLUSTER)` to fix format errors); \
84 | fi
85 |
86 | snyk-test-terraform: guard-SNYK_TOKEN
87 | snyk iac test terraform/
88 |
89 | snyk-test-deployments: guard-SNYK_TOKEN
90 | ifdef IGNORE_SNYK_TEST_DEPLOYMENTS_FAILURE
91 | snyk iac test deployments/ || true
92 | else
93 | snyk iac test deployments/
94 | endif
95 |
96 | snyk-test-plan: guard-SNYK_TOKEN
97 | ifdef IGNORE_SNYK_TEST_PLAN_FAILURE
98 | snyk iac test --scan=planned-values build/tfplan.json || true
99 | else
100 | snyk iac test --scan=planned-values build/tfplan.json
101 | endif
102 |
103 | integration-test: load-env
104 | cd tests/integration && \
105 | SOCK_SHOP_URL="$$( kubectl \
106 | --kubeconfig=../../secrets/config-$(ENV_NAME).yml \
107 | -n sock-shop \
108 | get service/front-end \
109 | -o jsonpath="{.status.loadBalancer.ingress[0].hostname}" \
110 | )" $(GINKGO) --race --randomizeAllSpecs -r .
111 |
112 | policy-test:
113 | conftest test -p tests/policies deployments/sock-shop/manifest.yml
114 |
115 | # ===== Jenkins ===============================================================
116 |
117 | jenkins-update-pipelines: \
118 | jenkins-update-deploy-pipeline \
119 | jenkins-update-destroy-pipeline \
120 | jenkins-update-promote-pipeline
121 |
122 | jenkins-create-pipelines: \
123 | jenkins-create-deploy-pipeline \
124 | jenkins-create-destroy-pipeline \
125 | jenkins-create-promote-pipeline
126 |
127 | jenkins-%-deploy-pipeline: \
128 | load-env \
129 | guard-JENKINS_CLI \
130 | guard-JENKINS_URL \
131 | guard-JENKINS_PASSWORD \
132 | guard-JENKINS_USERNAME \
133 | guard-SLACK_CHANNEL
134 | @echo -n Setting deploy pipeline...
135 | @sed \
136 | -e 's#REPLACE_ME_REPOSITORY_URL#$(REPOSITORY_URL)#' \
137 | -e 's/REPLACE_ME_SLACK_CHANNEL/$(SLACK_CHANNEL)/' \
138 | -e 's/REPLACE_ME_PROMOTES_FROM/$(PROMOTES_FROM)/' \
139 | -e 's/REPLACE_ME_ENV_NAME/$(ENV_NAME)/' \
140 | -e 's/REPLACE_ME_PROMOTES_TO/$(PROMOTES_TO)/' \
141 | pipelines/deploy.xml \
142 | | java \
143 | -jar $(JENKINS_CLI) \
144 | -s $(JENKINS_URL) \
145 | -auth "$(JENKINS_USERNAME):$${JENKINS_PASSWORD}" \
146 | $*-job 'Deploy ($(ENV_NAME))'
147 | @$(call print_success, OK!)
148 |
149 | jenkins-%-destroy-pipeline: \
150 | load-env \
151 | guard-JENKINS_CLI \
152 | guard-JENKINS_URL \
153 | guard-JENKINS_PASSWORD \
154 | guard-JENKINS_USERNAME \
155 | guard-SLACK_CHANNEL
156 | @echo -n Setting destroy pipeline...
157 | @sed \
158 | -e 's/REPLACE_ME_SLACK_CHANNEL/$(SLACK_CHANNEL)/' \
159 | -e 's/REPLACE_ME_ENV_NAME/$(ENV_NAME)/' \
160 | -e 's#REPLACE_ME_REPOSITORY_URL#$(REPOSITORY_URL)#' \
161 | -e 's/REPLACE_ME_PROMOTES_TO/$(PROMOTES_TO)/' \
162 | pipelines/destroy.xml \
163 | | java \
164 | -jar $(JENKINS_CLI) \
165 | -s $(JENKINS_URL) \
166 | -auth "$(JENKINS_USERNAME):$${JENKINS_PASSWORD}" \
167 | $*-job 'Destroy ($(ENV_NAME))'
168 | @$(call print_success, OK!)
169 |
170 | jenkins-%-promote-pipeline: \
171 | guard-JENKINS_CLI \
172 | guard-JENKINS_URL \
173 | guard-JENKINS_PASSWORD \
174 | guard-JENKINS_USERNAME
175 | @echo -n Setting promote pipeline...
176 | @sed \
177 | -e 's#REPLACE_ME_REPOSITORY_URL#$(REPOSITORY_URL)#' \
178 | pipelines/push-git-branch.xml \
179 | | java \
180 | -jar $(JENKINS_CLI) \
181 | -s $(JENKINS_URL) \
182 | -auth "$(JENKINS_USERNAME):$${JENKINS_PASSWORD}" \
183 | $*-job 'Push Git Branch'
184 | @$(call print_success, OK!)
185 |
186 | # ===== Miscellaneous =========================================================
187 |
188 | GINKGO := go run github.com/onsi/ginkgo/ginkgo
189 |
190 | COLOUR_GREEN=\033[0;32m
191 | COLOUR_RED=\033[;31m
192 | COLOUR_NONE=\033[0m
193 |
194 | CLUSTER=terraform/cluster
195 |
196 | GITHUB_ORG ?= EngineerBetter
197 | GITHUB_REPOSITORY ?= iac-example
198 | REPOSITORY_URL=git@github.com:$(GITHUB_ORG)/$(GITHUB_REPOSITORY).git
199 |
200 | load-env:
201 | @if [ -z "$$(yq eval '.[] | select(.name == "$(ENV_NAME)") | .name' environments.yml)" ]; then \
202 | $(call print_fail,No such environment) \
203 | && exit 1; \
204 | fi
205 |
206 | $(eval PROMOTES_FROM := $(shell \
207 | yq \
208 | eval \
209 | '.[] | select(.name == "$(ENV_NAME)") | .promotes_from // "main"' \
210 | environments.yml \
211 | ))
212 |
213 | $(eval PROMOTES_TO := $(shell \
214 | yq \
215 | eval \
216 | '.[] | select(.name == "$(ENV_NAME)") | .promotes_to' \
217 | environments.yml \
218 | ))
219 |
220 | @if [ "$(PROMOTES_TO)" = "null" ]; then \
221 | $(call print_fail,Required field "promotes_to" not defined for $(ENV_NAME)) \
222 | && exit 1; \
223 | fi
224 |
225 | configure-pre-commit-hook:
226 | @echo \
227 | "make terraform-validate; make terraform-lint; make terraform-fmt-check" \
228 | > .git/hooks/pre-commit
229 | @chmod +rx .git/hooks/pre-commit
230 | @$(call print_success,Configured pre-commit hook)
231 |
232 | fetch-cluster-config: load-env terraform-init
233 | @terraform \
234 | -chdir=$(CLUSTER) \
235 | output \
236 | -raw \
237 | kubeconfig \
238 | > ./secrets/config-$(ENV_NAME).yml
239 | @$(call print_success,Config written to secrets/config-$(ENV_NAME).yml)
240 |
241 | guard-%:
242 | @if [ "${${*}}" = "" ]; then \
243 | $(call print_fail,Environment variable $* must be set) \
244 | && exit 1; \
245 | fi
246 |
247 | define print_success
248 | printf '$(COLOUR_GREEN)$1$(COLOUR_NONE)\n'
249 | endef
250 |
251 | define print_fail
252 | printf '$(COLOUR_RED)$1$(COLOUR_NONE)\n'
253 | endef
254 |
--------------------------------------------------------------------------------
/terraform/cluster/.terraform.lock.hcl:
--------------------------------------------------------------------------------
1 | # This file is maintained automatically by "terraform init".
2 | # Manual edits may be lost in future updates.
3 |
4 | provider "registry.terraform.io/hashicorp/aws" {
5 | version = "3.40.0"
6 | constraints = ">= 2.68.0, >= 3.40.0, 3.40.0"
7 | hashes = [
8 | "h1:0r9TS3qACD9xJhrfTPZR7ygoCKDWHRX4c0D5GCyfAu4=",
9 | "zh:2fd824991b19837e200d19b17d8188bf71efb92c36511809484549e77b4045dd",
10 | "zh:47250cb58b3bd6f2698ca17bfb962710542d6adf95637cd560f6119abf97dba2",
11 | "zh:515722a8c8726541b05362ec71331264977603374a2e4d4d64f89940873143ea",
12 | "zh:61b6b7542da2113278c987a0af9f230321f5ed605f1e3098824603cb09ac771b",
13 | "zh:66aad13ada6344b64adbc67abad4f35c414e62838a99f78626befb8b74c760d8",
14 | "zh:7d4436aeb53fa348d7fd3c2ab4a727b03c7c59bfdcdecef4a75237760f3bb3cf",
15 | "zh:a4583891debc49678491510574b1c28bb4fe3f83ed2bb353959c4c1f6f409f1f",
16 | "zh:b8badecea52f6996ae832144560be87e0b7c2da7fe1dcd6e6230969234b2fc55",
17 | "zh:cecf64a085f640c30437ccc31bd964c21004ae8ae00cfbd95fb04037e46b88ca",
18 | "zh:d81dbb9ad8ce5eca4d1fc5a7a06bbb9c47ea8691f1502e94760fa680e20e4afc",
19 | "zh:f0fc724a964c7f8154bc5911d572ee411f5d181414f9b1f09de7ebdacb0d884b",
20 | ]
21 | }
22 |
23 | provider "registry.terraform.io/hashicorp/cloudinit" {
24 | version = "2.2.0"
25 | hashes = [
26 | "h1:siiI0wK6/jUDdA5P8ifTO0yc9YmXHml4hz5K9I9N+MA=",
27 | "zh:76825122171f9ea2287fd27e23e80a7eb482f6491a4f41a096d77b666896ee96",
28 | "zh:795a36dee548e30ca9c9d474af9ad6d29290e0a9816154ad38d55381cd0ab12d",
29 | "zh:9200f02cb917fb99e44b40a68936fd60d338e4d30a718b7e2e48024a795a61b9",
30 | "zh:a33cf255dc670c20678063aa84218e2c1b7a67d557f480d8ec0f68bc428ed472",
31 | "zh:ba3c1b2cd0879286c1f531862c027ec04783ece81de67c9a3b97076f1ce7f58f",
32 | "zh:bd575456394428a1a02191d2e46af0c00e41fd4f28cfe117d57b6aeb5154a0fb",
33 | "zh:c68dd1db83d8437c36c92dc3fc11d71ced9def3483dd28c45f8640cfcd59de9a",
34 | "zh:cbfe34a90852ed03cc074601527bb580a648127255c08589bc3ef4bf4f2e7e0c",
35 | "zh:d6ffd7398c6d1f359b96f5b757e77b99b339fbb91df1b96ac974fe71bc87695c",
36 | "zh:d9c15285f847d7a52df59e044184fb3ba1b7679fd0386291ed183782683d9517",
37 | "zh:f7dd02f6d36844da23c9a27bb084503812c29c1aec4aba97237fec16860fdc8c",
38 | ]
39 | }
40 |
41 | provider "registry.terraform.io/hashicorp/kubernetes" {
42 | version = "2.1.0"
43 | constraints = ">= 1.11.1, 2.1.0"
44 | hashes = [
45 | "h1:L/3XfqLQ4bS1PjH/FksJPm+MYIOxCwn97ozbfSwg/VQ=",
46 | "zh:22e2bcef08fb7f97ed503a27e3725d9d14fdd09fe3aa144fae8a7f78ed27856a",
47 | "zh:2380cc2a91239b80ea380af8a7fcdcc7396f5213a71a251a5505c962ac6cb9c2",
48 | "zh:496ea2818d5480590ada763672be051f4e76dc12c6a61fde2faa0c909e174eb7",
49 | "zh:4e5b6c230d9a8da8a0f12e5db198f158f2c26432ad8e1c6ac22770ce7ec39118",
50 | "zh:55ad614beffda4cdc918ad87dca09bb7b961f12183c0923230301f73e23e9665",
51 | "zh:6849c52899091fa2f6714d8e5180a4affffc4b2ad03dc2250043d4b32049e16e",
52 | "zh:7a6f0d9da5172b3770af98d59263e142313a8b2c4048271893c6003493ad1c89",
53 | "zh:7c97fb24e60c41fa16f6305620d18ae51545c329f46f92988493a4c51a4e43e5",
54 | "zh:a08111c4898544c40c62437cc28798d1f4d7298f61ddaf3f48dddec042d3519f",
55 | "zh:be7493bff6b9f95fe203c295bfc5933111e7c8a5f3bd9e9ae143a0d699d516f8",
56 | "zh:e4c94adc65b5ad5551893f58c19e1c766f212f16220087ca3e940a89449ac285",
57 | ]
58 | }
59 |
60 | provider "registry.terraform.io/hashicorp/local" {
61 | version = "2.0.0"
62 | constraints = ">= 1.4.0, 2.0.0"
63 | hashes = [
64 | "h1:pO1ANXtOCRfecKsY9Hn4UsXoPBLv6LFiDIEiS1MZ09E=",
65 | "zh:34ce8b79493ace8333d094752b579ccc907fa9392a2c1d6933a6c95d0786d3f1",
66 | "zh:5c5a19c4f614a4ffb68bae0b0563f3860115cf7539b8adc21108324cfdc10092",
67 | "zh:67ddb1ca2cd3e1a8f948302597ceb967f19d2eeb2d125303493667388fe6330e",
68 | "zh:68e6b16f3a8e180fcba1a99754118deb2d82331b51f6cca39f04518339bfdfa6",
69 | "zh:8393a12eb11598b2799d51c9b0a922a3d9fadda5a626b94a1b4914086d53120e",
70 | "zh:90daea4b2010a86f2aca1e3a9590e0b3ddcab229c2bd3685fae76a832e9e836f",
71 | "zh:99308edc734a0ac9149b44f8e316ca879b2670a1cae387a8ae754c180b57cdb4",
72 | "zh:c76594db07a9d1a73372a073888b672df64adb455d483c2426cc220eda7e092e",
73 | "zh:dc09c1fb36c6a706bdac96cce338952888c8423978426a09f5df93031aa88b84",
74 | "zh:deda88134e9780319e8de91b3745520be48ead6ec38cb662694d09185c3dac70",
75 | ]
76 | }
77 |
78 | provider "registry.terraform.io/hashicorp/null" {
79 | version = "3.0.0"
80 | constraints = "3.0.0"
81 | hashes = [
82 | "h1:V1tzrSG6t3e7zWvUwRbGbhsWU2Jd/anrJpOl9XM+R/8=",
83 | "zh:05fb7eab469324c97e9b73a61d2ece6f91de4e9b493e573bfeda0f2077bc3a4c",
84 | "zh:1688aa91885a395c4ae67636d411475d0b831e422e005dcf02eedacaafac3bb4",
85 | "zh:24a0b1292e3a474f57c483a7a4512d797e041bc9c2fbaac42fe12e86a7fb5a3c",
86 | "zh:2fc951bd0d1b9b23427acc93be09b6909d72871e464088171da60fbee4fdde03",
87 | "zh:6db825759425599a326385a68acc6be2d9ba0d7d6ef587191d0cdc6daef9ac63",
88 | "zh:85985763d02618993c32c294072cc6ec51f1692b803cb506fcfedca9d40eaec9",
89 | "zh:a53186599c57058be1509f904da512342cfdc5d808efdaf02dec15f0f3cb039a",
90 | "zh:c2e07b49b6efa676bdc7b00c06333ea1792a983a5720f9e2233db27323d2707c",
91 | "zh:cdc8fe1096103cf5374751e2e8408ec4abd2eb67d5a1c5151fe2c7ecfd525bef",
92 | "zh:dbdef21df0c012b0d08776f3d4f34eb0f2f229adfde07ff252a119e52c0f65b7",
93 | ]
94 | }
95 |
96 | provider "registry.terraform.io/hashicorp/random" {
97 | version = "3.1.0"
98 | constraints = ">= 2.1.0"
99 | hashes = [
100 | "h1:rKYu5ZUbXwrLG1w81k7H3nce/Ys6yAxXhWcbtk36HjY=",
101 | "zh:2bbb3339f0643b5daa07480ef4397bd23a79963cc364cdfbb4e86354cb7725bc",
102 | "zh:3cd456047805bf639fbf2c761b1848880ea703a054f76db51852008b11008626",
103 | "zh:4f251b0eda5bb5e3dc26ea4400dba200018213654b69b4a5f96abee815b4f5ff",
104 | "zh:7011332745ea061e517fe1319bd6c75054a314155cb2c1199a5b01fe1889a7e2",
105 | "zh:738ed82858317ccc246691c8b85995bc125ac3b4143043219bd0437adc56c992",
106 | "zh:7dbe52fac7bb21227acd7529b487511c91f4107db9cc4414f50d04ffc3cab427",
107 | "zh:a3a9251fb15f93e4cfc1789800fc2d7414bbc18944ad4c5c98f466e6477c42bc",
108 | "zh:a543ec1a3a8c20635cf374110bd2f87c07374cf2c50617eee2c669b3ceeeaa9f",
109 | "zh:d9ab41d556a48bd7059f0810cf020500635bfc696c9fc3adab5ea8915c1d886b",
110 | "zh:d9e13427a7d011dbd654e591b0337e6074eef8c3b9bb11b2e39eaaf257044fd7",
111 | "zh:f7605bd1437752114baf601bdf6931debe6dc6bfe3006eb7e9bb9080931dca8a",
112 | ]
113 | }
114 |
115 | provider "registry.terraform.io/hashicorp/template" {
116 | version = "2.2.0"
117 | constraints = "2.2.0"
118 | hashes = [
119 | "h1:0wlehNaxBX7GJQnPfQwTNvvAf38Jm0Nv7ssKGMaG6Og=",
120 | "zh:01702196f0a0492ec07917db7aaa595843d8f171dc195f4c988d2ffca2a06386",
121 | "zh:09aae3da826ba3d7df69efeb25d146a1de0d03e951d35019a0f80e4f58c89b53",
122 | "zh:09ba83c0625b6fe0a954da6fbd0c355ac0b7f07f86c91a2a97849140fea49603",
123 | "zh:0e3a6c8e16f17f19010accd0844187d524580d9fdb0731f675ffcf4afba03d16",
124 | "zh:45f2c594b6f2f34ea663704cc72048b212fe7d16fb4cfd959365fa997228a776",
125 | "zh:77ea3e5a0446784d77114b5e851c970a3dde1e08fa6de38210b8385d7605d451",
126 | "zh:8a154388f3708e3df5a69122a23bdfaf760a523788a5081976b3d5616f7d30ae",
127 | "zh:992843002f2db5a11e626b3fc23dc0c87ad3729b3b3cff08e32ffb3df97edbde",
128 | "zh:ad906f4cebd3ec5e43d5cd6dc8f4c5c9cc3b33d2243c89c5fc18f97f7277b51d",
129 | "zh:c979425ddb256511137ecd093e23283234da0154b7fa8b21c2687182d9aea8b2",
130 | ]
131 | }
132 |
133 | provider "registry.terraform.io/terraform-aws-modules/http" {
134 | version = "2.4.1"
135 | constraints = ">= 2.4.1, 2.4.1"
136 | hashes = [
137 | "h1:FINkX7/X/cr5NEssB7dMqVWa6YtJtmwzvkfryuR39/k=",
138 | "zh:0111f54de2a9815ded291f23136d41f3d2731c58ea663a2e8f0fef02d377d697",
139 | "zh:0740152d76f0ccf54f4d0e8e0753739a5233b022acd60b5d2353d248c4c17204",
140 | "zh:569518f46809ec9cdc082b4dfd4e828236eee2b50f87b301d624cfd83b8f5b0d",
141 | "zh:7669f7691de91eec9f381e9a4be81aa4560f050348a86c6ea7804925752a01bb",
142 | "zh:81cd53e796ec806aca2d8e92a2aed9135661e170eeff6cf0418e54f98816cd05",
143 | "zh:82f01abd905090f978b169ac85d7a5952322a5f0f460269dd981b3596652d304",
144 | "zh:9a235610066e0f7e567e69c23a53327271a6fc568b06bf152d8fe6594749ed2b",
145 | "zh:aeabdd8e633d143feb67c52248c85358951321e35b43943aeab577c005abd30a",
146 | "zh:c20d22dba5c79731918e7192bc3d0b364d47e98a74f47d287e6cc66236bc0ed0",
147 | "zh:c4fea2cb18c31ed7723deec5ebaff85d6795bb6b6ed3b954794af064d17a7f9f",
148 | "zh:e21e88b6e7e55b9f29b046730d9928c65a4f181fd5f60a42f1cd41b46a0a938d",
149 | "zh:eddb888a74dea348a0acdfee13a08875bacddde384bd9c28342a534269665568",
150 | "zh:f46d5f1403b8d8dfafab9bdd7129d3080bb62a91ea726f477fd43560887b8c4a",
151 | ]
152 | }
153 |
--------------------------------------------------------------------------------
/tests/integration/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
5 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
6 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
7 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
8 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
9 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
10 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
11 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
12 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
13 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
14 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
15 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
16 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
17 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
18 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
19 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
20 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
21 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
22 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
23 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
24 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
25 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
26 | github.com/onsi/ginkgo v1.16.2 h1:HFB2fbVIlhIfCfOW81bZFbiC/RvnpXSdhbF2/DJr134=
27 | github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
28 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
29 | github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
30 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
33 | github.com/sclevine/agouti v3.0.0+incompatible h1:8IBJS6PWz3uTlMP3YBIR5f+KAldcGuOeFkFbUWfBgK4=
34 | github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
35 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
36 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
37 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
38 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
39 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
40 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
41 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
42 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
43 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
44 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
45 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
46 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
47 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
48 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
49 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
50 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
51 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
52 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
53 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
54 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
55 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
56 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
57 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
58 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
59 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
60 | golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
61 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
62 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
63 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
64 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
65 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
66 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
67 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
68 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
69 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
70 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
71 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
72 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
73 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
74 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
75 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
76 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
77 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
78 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
79 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
80 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
81 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
83 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
84 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
85 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
86 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
87 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
88 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
89 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
90 |
--------------------------------------------------------------------------------
/deployments/sock-shop/manifest.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata: {name: sock-shop}
4 | ---
5 | apiVersion: apps/v1
6 | kind: Deployment
7 | metadata:
8 | name: carts-db
9 | labels: {name: carts-db}
10 | namespace: sock-shop
11 | spec:
12 | replicas: 1
13 | selector:
14 | matchLabels: {name: carts-db}
15 | template:
16 | metadata:
17 | labels: {name: carts-db}
18 | spec:
19 | containers:
20 | - name: carts-db
21 | # mongo:latest
22 | image: mongo@sha256:401ae88210c708d54873af22df618b46a5f3d2da1c2676d115326b205ec78d3f
23 | ports:
24 | - name: mongo
25 | containerPort: 27017
26 | securityContext:
27 | capabilities:
28 | drop: [all]
29 | add: [CHOWN, SETGID, SETUID]
30 | readOnlyRootFilesystem: true
31 | volumeMounts:
32 | - mountPath: /tmp
33 | name: tmp-volume
34 | volumes:
35 | - name: tmp-volume
36 | emptyDir: {medium: Memory}
37 | nodeSelector:
38 | beta.kubernetes.io/os: linux
39 | ---
40 | apiVersion: v1
41 | kind: Service
42 | metadata:
43 | name: carts-db
44 | labels: {name: carts-db}
45 | namespace: sock-shop
46 | spec:
47 | ports:
48 | - port: 27017
49 | targetPort: 27017
50 | selector: {name: carts-db}
51 | ---
52 | apiVersion: apps/v1
53 | kind: Deployment
54 | metadata:
55 | name: carts
56 | labels: {name: carts}
57 | namespace: sock-shop
58 | spec:
59 | replicas: 1
60 | selector:
61 | matchLabels: {name: carts}
62 | template:
63 | metadata:
64 | labels:
65 | name: carts
66 | spec:
67 | containers:
68 | - name: carts
69 | # weaveworksdemos/carts:0.4.8
70 | image: weaveworksdemos/carts@sha256:434d2f5a6e0e8beef1f253fe96f45b8437a703125fc003434c5282ecf8969a4f
71 | ports:
72 | - containerPort: 80
73 | env:
74 | - name: ZIPKIN
75 | value: zipkin.jaeger.svc.cluster.local
76 | - name: JAVA_OPTS
77 | value: -Xms64m -Xmx128m -XX:PermSize=32m -XX:MaxPermSize=64m -XX:+UseG1GC -Djava.security.egd=file:/dev/urandom
78 | securityContext:
79 | runAsNonRoot: true
80 | runAsUser: 10001
81 | capabilities:
82 | drop: [all]
83 | add: [NET_BIND_SERVICE]
84 | readOnlyRootFilesystem: true
85 | volumeMounts:
86 | - mountPath: /tmp
87 | name: tmp-volume
88 | volumes:
89 | - name: tmp-volume
90 | emptyDir: {medium: Memory}
91 | nodeSelector:
92 | beta.kubernetes.io/os: linux
93 | ---
94 | apiVersion: v1
95 | kind: Service
96 | metadata:
97 | name: carts
98 | labels: {name: carts}
99 | namespace: sock-shop
100 | spec:
101 | ports:
102 | - port: 80
103 | targetPort: 80
104 | selector: {name: carts}
105 | ---
106 | apiVersion: apps/v1
107 | kind: Deployment
108 | metadata:
109 | name: catalogue-db
110 | labels: {name: catalogue-db}
111 | namespace: sock-shop
112 | spec:
113 | replicas: 1
114 | selector:
115 | matchLabels: {name: catalogue-db}
116 | template:
117 | metadata:
118 | labels: {name: catalogue-db}
119 | spec:
120 | containers:
121 | - name: catalogue-db
122 | # weaveworksdemos/catalogue-db:0.3.0
123 | image: weaveworksdemos/catalogue-db@sha256:7ba74ec9adf88f6625b8d85d3323d1ee5232b39877e1590021ea485cf9457251
124 | env:
125 | - name: MYSQL_ROOT_PASSWORD
126 | value: fake_password
127 | - name: MYSQL_DATABASE
128 | value: socksdb
129 | ports:
130 | - name: mysql
131 | containerPort: 3306
132 | nodeSelector:
133 | beta.kubernetes.io/os: linux
134 | ---
135 | apiVersion: v1
136 | kind: Service
137 | metadata:
138 | name: catalogue-db
139 | labels: {name: catalogue-db}
140 | namespace: sock-shop
141 | spec:
142 | ports:
143 | - port: 3306
144 | targetPort: 3306
145 | selector: {name: catalogue-db}
146 | ---
147 | apiVersion: apps/v1
148 | kind: Deployment
149 | metadata:
150 | name: catalogue
151 | labels: {name: catalogue}
152 | namespace: sock-shop
153 | spec:
154 | replicas: 1
155 | selector:
156 | matchLabels: {name: catalogue}
157 | template:
158 | metadata:
159 | labels: {name: catalogue}
160 | spec:
161 | containers:
162 | - name: catalogue
163 | # weaveworksdemos/catalogue:0.3.5
164 | image: weaveworksdemos/catalogue@sha256:0147a65b7116569439eefb1a6dbed455fe022464ef70e0c3cab75bc4a226b39b
165 | ports:
166 | - containerPort: 80
167 | securityContext:
168 | runAsNonRoot: true
169 | runAsUser: 10001
170 | capabilities:
171 | drop: [all]
172 | add: [NET_BIND_SERVICE]
173 | readOnlyRootFilesystem: true
174 | nodeSelector:
175 | beta.kubernetes.io/os: linux
176 | ---
177 | apiVersion: v1
178 | kind: Service
179 | metadata:
180 | name: catalogue
181 | labels: {name: catalogue}
182 | namespace: sock-shop
183 | spec:
184 | ports:
185 | - port: 80
186 | targetPort: 80
187 | selector: {name: catalogue}
188 | ---
189 | apiVersion: apps/v1
190 | kind: Deployment
191 | metadata:
192 | name: front-end
193 | namespace: sock-shop
194 | spec:
195 | replicas: 1
196 | selector:
197 | matchLabels: {name: front-end}
198 | template:
199 | metadata:
200 | labels: {name: front-end}
201 | spec:
202 | containers:
203 | - name: front-end
204 | # weaveworksdemos/front-end:0.3.12
205 | image: weaveworksdemos/front-end@sha256:26a2d9b6b291dee2dca32fca3f5bff6c2fa07bb5954359afcbc8001cc70eac71
206 | resources:
207 | requests:
208 | cpu: 100m
209 | memory: 100Mi
210 | ports:
211 | - containerPort: &front-end-port 8079
212 | securityContext:
213 | runAsNonRoot: true
214 | runAsUser: 10001
215 | capabilities:
216 | drop: [all]
217 | readOnlyRootFilesystem: true
218 | readinessProbe:
219 | httpGet:
220 | path: /
221 | port: *front-end-port
222 | periodSeconds: 20
223 | nodeSelector:
224 | beta.kubernetes.io/os: linux
225 | ---
226 | apiVersion: v1
227 | kind: Service
228 | metadata:
229 | name: front-end
230 | labels: {name: front-end}
231 | namespace: sock-shop
232 | spec:
233 | type: LoadBalancer
234 | ports:
235 | - port: 80
236 | targetPort: 8079
237 | protocol: TCP
238 | selector: {name: front-end}
239 | ---
240 | apiVersion: apps/v1
241 | kind: Deployment
242 | metadata:
243 | name: orders-db
244 | labels: {name: orders-db}
245 | namespace: sock-shop
246 | spec:
247 | replicas: 1
248 | selector:
249 | matchLabels: {name: orders-db}
250 | template:
251 | metadata:
252 | labels: {name: orders-db}
253 | spec:
254 | containers:
255 | - name: orders-db
256 | # mongo:latest
257 | image: mongo@sha256:401ae88210c708d54873af22df618b46a5f3d2da1c2676d115326b205ec78d3f
258 | ports:
259 | - name: mongo
260 | containerPort: 27017
261 | securityContext:
262 | capabilities:
263 | drop: [all]
264 | add: [CHOWN, SETGID, SETUID]
265 | readOnlyRootFilesystem: true
266 | volumeMounts:
267 | - mountPath: /tmp
268 | name: tmp-volume
269 | volumes:
270 | - name: tmp-volume
271 | emptyDir: {medium: Memory}
272 | nodeSelector:
273 | beta.kubernetes.io/os: linux
274 | ---
275 | apiVersion: v1
276 | kind: Service
277 | metadata:
278 | name: orders-db
279 | labels: {name: orders-db}
280 | namespace: sock-shop
281 | spec:
282 | ports:
283 | - port: 27017
284 | targetPort: 27017
285 | selector: {name: orders-db}
286 | ---
287 | apiVersion: apps/v1
288 | kind: Deployment
289 | metadata:
290 | name: orders
291 | labels: {name: orders}
292 | namespace: sock-shop
293 | spec:
294 | replicas: 1
295 | selector:
296 | matchLabels: {name: orders}
297 | template:
298 | metadata:
299 | labels: {name: orders}
300 | spec:
301 | containers:
302 | - name: orders
303 | # weaveworksdemos/orders:0.4.7
304 | image: weaveworksdemos/orders@sha256:b622e40e83433baf6374f15e076b53893f79958640fc6667dff597622eff03b9
305 | env:
306 | - name: ZIPKIN
307 | value: zipkin.jaeger.svc.cluster.local
308 | - name: JAVA_OPTS
309 | value: -Xms64m -Xmx128m -XX:PermSize=32m -XX:MaxPermSize=64m -XX:+UseG1GC -Djava.security.egd=file:/dev/urandom
310 | ports:
311 | - containerPort: 80
312 | securityContext:
313 | runAsNonRoot: true
314 | runAsUser: 10001
315 | capabilities:
316 | drop: [all]
317 | add: [NET_BIND_SERVICE]
318 | readOnlyRootFilesystem: true
319 | volumeMounts:
320 | - mountPath: /tmp
321 | name: tmp-volume
322 | volumes:
323 | - name: tmp-volume
324 | emptyDir: {medium: Memory}
325 | nodeSelector:
326 | beta.kubernetes.io/os: linux
327 | ---
328 | apiVersion: v1
329 | kind: Service
330 | metadata:
331 | name: orders
332 | labels: {name: orders}
333 | namespace: sock-shop
334 | spec:
335 | ports:
336 | - port: 80
337 | targetPort: 80
338 | selector: {name: orders}
339 | ---
340 | apiVersion: apps/v1
341 | kind: Deployment
342 | metadata:
343 | name: payment
344 | labels: {name: payment}
345 | namespace: sock-shop
346 | spec:
347 | replicas: 1
348 | selector:
349 | matchLabels: {name: payment}
350 | template:
351 | metadata:
352 | labels:
353 | name: payment
354 | spec:
355 | containers:
356 | - name: payment
357 | # weaveworksdemos/payment:0.4.3
358 | image: weaveworksdemos/payment@sha256:5ab1c9877480a018d4dda10d6dfa382776e6bca9fc1c60bacbb80903fde8cfe0
359 | ports:
360 | - containerPort: 80
361 | securityContext:
362 | runAsNonRoot: true
363 | runAsUser: 10001
364 | capabilities:
365 | drop: [all]
366 | add: [NET_BIND_SERVICE]
367 | readOnlyRootFilesystem: true
368 | nodeSelector:
369 | beta.kubernetes.io/os: linux
370 | ---
371 | apiVersion: v1
372 | kind: Service
373 | metadata:
374 | name: payment
375 | labels: {name: payment}
376 | namespace: sock-shop
377 | spec:
378 | ports:
379 | - port: 80
380 | targetPort: 80
381 | selector: {name: payment}
382 | ---
383 | apiVersion: apps/v1
384 | kind: Deployment
385 | metadata:
386 | name: queue-master
387 | labels: {name: queue-master}
388 | namespace: sock-shop
389 | spec:
390 | replicas: 1
391 | selector:
392 | matchLabels: {name: queue-master}
393 | template:
394 | metadata:
395 | labels: {name: queue-master}
396 | spec:
397 | containers:
398 | - name: queue-master
399 | # weaveworksdemos/queue-master:0.3.1
400 | image: weaveworksdemos/queue-master@sha256:6292d3095f4c7aeed8d863527f8ef6d7a75d3128f20fc61e57f398c100142712
401 | ports:
402 | - containerPort: 80
403 | nodeSelector:
404 | beta.kubernetes.io/os: linux
405 | ---
406 | apiVersion: v1
407 | kind: Service
408 | metadata:
409 | name: queue-master
410 | labels: {name: queue-master}
411 | annotations:
412 | prometheus.io/path: "/prometheus"
413 | namespace: sock-shop
414 | spec:
415 | ports:
416 | - port: 80
417 | targetPort: 80
418 | selector: {name: queue-master}
419 | ---
420 | apiVersion: apps/v1
421 | kind: Deployment
422 | metadata:
423 | name: rabbitmq
424 | labels: {name: rabbitmq}
425 | namespace: sock-shop
426 | spec:
427 | replicas: 1
428 | selector:
429 | matchLabels: {name: rabbitmq}
430 | template:
431 | metadata:
432 | labels: {name: rabbitmq}
433 | spec:
434 | containers:
435 | - name: rabbitmq
436 | # rabbitmq:3.6.8
437 | image: rabbitmq@sha256:a9f4923559bbcd00b93b02e61615aef5eb6f1d1c98db51053bab0fa6b680db26
438 | ports:
439 | - containerPort: 5672
440 | securityContext:
441 | capabilities:
442 | drop: [all]
443 | add: [CHOWN, SETGID, SETUID, DAC_OVERRIDE]
444 | readOnlyRootFilesystem: true
445 | nodeSelector:
446 | beta.kubernetes.io/os: linux
447 | ---
448 | apiVersion: v1
449 | kind: Service
450 | metadata:
451 | name: rabbitmq
452 | labels: {name: rabbitmq}
453 | namespace: sock-shop
454 | spec:
455 | ports:
456 | - port: 5672
457 | targetPort: 5672
458 | selector: {name: rabbitmq}
459 | ---
460 | apiVersion: apps/v1
461 | kind: Deployment
462 | metadata:
463 | name: shipping
464 | labels: {name: shipping}
465 | namespace: sock-shop
466 | spec:
467 | replicas: 1
468 | selector:
469 | matchLabels: {name: shipping}
470 | template:
471 | metadata:
472 | labels: {name: shipping}
473 | spec:
474 | containers:
475 | - name: shipping
476 | # weaveworksdemos/shipping:0.4.8
477 | image: weaveworksdemos/shipping@sha256:983305c948fded487f4a4acdeab5f898e89d577b4bc1ca3de7750076469ccad4
478 | env:
479 | - name: ZIPKIN
480 | value: zipkin.jaeger.svc.cluster.local
481 | - name: JAVA_OPTS
482 | value: -Xms64m -Xmx128m -XX:PermSize=32m -XX:MaxPermSize=64m -XX:+UseG1GC -Djava.security.egd=file:/dev/urandom
483 | ports:
484 | - containerPort: 80
485 | securityContext:
486 | runAsNonRoot: true
487 | runAsUser: 10001
488 | capabilities:
489 | drop: [all]
490 | add: [NET_BIND_SERVICE]
491 | readOnlyRootFilesystem: true
492 | volumeMounts:
493 | - mountPath: /tmp
494 | name: tmp-volume
495 | volumes:
496 | - name: tmp-volume
497 | emptyDir: {medium: Memory}
498 | nodeSelector:
499 | beta.kubernetes.io/os: linux
500 | ---
501 | apiVersion: v1
502 | kind: Service
503 | metadata:
504 | name: shipping
505 | labels: {name: shipping}
506 | namespace: sock-shop
507 | spec:
508 | ports:
509 | - port: 80
510 | targetPort: 80
511 | selector: {name: shipping}
512 | ---
513 | apiVersion: apps/v1
514 | kind: Deployment
515 | metadata:
516 | name: user-db
517 | labels: {name: user-db}
518 | namespace: sock-shop
519 | spec:
520 | replicas: 1
521 | selector:
522 | matchLabels: {name: user-db}
523 | template:
524 | metadata:
525 | labels: {name: user-db}
526 | spec:
527 | containers:
528 | - name: user-db
529 | # weaveworksdemos/user-db:0.4.0
530 | image: weaveworksdemos/user-db@sha256:b43f0f8a76e0c908805fcec74d1ad7f4af4d93c4612632bd6dc20a87508e0b68
531 | ports:
532 | - name: mongo
533 | containerPort: 27017
534 | securityContext:
535 | capabilities:
536 | drop: [all]
537 | add: [CHOWN, SETGID, SETUID]
538 | readOnlyRootFilesystem: true
539 | volumeMounts:
540 | - mountPath: /tmp
541 | name: tmp-volume
542 | volumes:
543 | - name: tmp-volume
544 | emptyDir: {medium: Memory}
545 | nodeSelector:
546 | beta.kubernetes.io/os: linux
547 | ---
548 | apiVersion: v1
549 | kind: Service
550 | metadata:
551 | name: user-db
552 | labels: {name: user-db}
553 | namespace: sock-shop
554 | spec:
555 | ports:
556 | - port: 27017
557 | targetPort: 27017
558 | selector: {name: user-db}
559 | ---
560 | apiVersion: apps/v1
561 | kind: Deployment
562 | metadata:
563 | name: user
564 | labels: {name: user}
565 | namespace: sock-shop
566 | spec:
567 | replicas: 1
568 | selector:
569 | matchLabels: {name: user}
570 | template:
571 | metadata:
572 | labels: {name: user}
573 | spec:
574 | containers:
575 | - name: user
576 | # weaveworksdemos/user:0.4.7
577 | image: weaveworksdemos/user@sha256:2ffccc332963c89e035fea52201012208bf62df43a55fe461ad6598a5c757ab7
578 | ports:
579 | - containerPort: 80
580 | env:
581 | - name: MONGO_HOST
582 | value: user-db:27017
583 | securityContext:
584 | runAsNonRoot: true
585 | runAsUser: 10001
586 | capabilities:
587 | drop: [all]
588 | add: [NET_BIND_SERVICE]
589 | readOnlyRootFilesystem: true
590 | nodeSelector:
591 | beta.kubernetes.io/os: linux
592 | ---
593 | apiVersion: v1
594 | kind: Service
595 | metadata:
596 | name: user
597 | labels: {name: user}
598 | namespace: sock-shop
599 | spec:
600 | ports:
601 | - port: 80
602 | targetPort: 80
603 | selector: {name: user}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Continuous Infrastructure-as-Code Examples
2 |
3 | This repository demonstrates the practices of Continuous Infrastructure as Code
4 | (IaC) from [the companion white
5 | paper](https://github.com/EngineerBetter/iac-paper).
6 |
7 | * **Each practice is implemented in a commit** in the history of this
8 | repository, so that you can see how the practices build on each other. We have
9 | tagged each commit for easy reference.
10 | * Each practice also a **section in this document** to explain _why_ changes
11 | were made in a particular way.
12 |
13 | Throughout the series of commits, we will build up a solid foundation for many
14 | Continuous IaC practices using [Make](https://www.gnu.org/software/make/),
15 | GitHub, Snyk and Jenkins, amongst other tools.
16 |
17 | ## How to use this repository
18 |
19 | You may wish to read the documentation and examples, and you may also wish to
20 | follow along and run them yourself.
21 |
22 | ### Reading
23 |
24 | If you wish to **understand each practice** in isolation, you should **read
25 | through the commits in order**. This is the **recommended** approach. Each
26 | commit will implement one practice, and add documentation for it. No commits
27 | will change documentation for prior practices.
28 |
29 | If you wish to see **only the finished pipeline**, then you should look at the
30 | latest commit.
31 |
32 | ### Following along
33 |
34 | We recommend that you [fork this
35 | repository](https://docs.github.com/en/github/getting-started-with-github/quickstart/fork-a-repo)
36 | so that you may make your own changes, and so that the Jenkins CI server that is
37 | introduced later can push commits to branches for the purpose of promoting
38 | changes between environments.
39 |
40 | Once you have forked the repository, make a local clone of it.
41 |
42 | You can list all tags and navigate to a
43 | particular tag by running `git log --oneline` and `git checkout {tag_name}` (for
44 | example, `git checkout 01-starting`) respectively.
45 |
46 | You can return to the latest commit with `git checkout main`.
47 |
48 | To get your Jenkins CI server to use your fork (rather than the original!) you
49 | will need to set an environment variable before setting your pipelines. This is
50 | because we can't know in advance what the Git URL of your fork will be. We'll
51 | assume that your fork is stored on GitHub.
52 |
53 | #### Dependencies
54 |
55 | To follow along you may need some or all of the tools used throughout the
56 | examples in this repository. Here's a list of everything used:
57 |
58 | - `aws-cli` (`>=2.2.7`) - Use
59 | [the official installation instructions](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html)
60 | - `aws-iam-authenticator` (`>=1.19.6`) - Use
61 | [the official installation instructions](https://docs.aws.amazon.com/eks/latest/userguide/install-aws-iam-authenticator.html)
62 | - `terraform` (`1.15.4`) - we've used
63 | [`tfenv`](https://github.com/tfutils/tfenv#installation) to install specific
64 | versions of Terraform
65 | - `kubectl` (`>=1.21.1`) - Use
66 | [the official installation instructions](https://kubernetes.io/docs/tasks/tools/)
67 | - `snyk` (`>=1.616.0`) - Use
68 | [the official installation instructions](https://support.snyk.io/hc/en-us/articles/360003812538-Install-the-Snyk-CLI)
69 | - `tflint` (`>=0.28.1`) - Use
70 | [the official installation instructions](https://github.com/terraform-linters/tflint#installation)
71 | - `docker` (latest version) - Use
72 | [the official installation instructions](https://docs.docker.com/get-docker/)
73 | - `jre` (Java Runtime Environment, version 8) - Use
74 | [the official installation instructions](https://www.oracle.com/uk/java/technologies/javase-jre8-downloads.html)
75 | - `golang` (`>=1.16.4`) - Use
76 | [the official installation instructions](https://golang.org/doc/install)
77 | - `chromedriver` (`>=90.0.4430.24`) - Use
78 | [the official installation instructions](https://chromedriver.chromium.org/downloads),
79 | make sure to install version `90.*` and not `91.*`.
80 | - `conftest` (`>=0.25.0`) - Use
81 | [the official installation instructions](https://www.conftest.dev/install/)
82 |
83 | You may wish to install all of these tools now. Alternatively, you may install
84 | the tools as you encounter them (you'll need the `aws-iam-authenticator` for any
85 | `kubectl` commands).
86 |
87 | #### Jenkins CLI
88 |
89 | The Jenkins CLI is required from step 7 onwards. It is installed by downloading
90 | the jar from your own Jenkins instance (instructions on setting up Jenkins
91 | follow if you don't have Jenkins CI yet).
92 |
93 | The jar is downloaded with:
94 |
95 | ```terminal
96 | curl -o jenkins-cli.jar {your_jenkins_host}/jnlpJars/jenkins-cli.jar
97 | ```
98 |
99 | Store this jar wherever is convenient for you on your machine, we'll be using it
100 | later.
101 |
102 | #### Dependencies for a local Jenkins CI
103 |
104 | If you don't already have a Jenkins CI installed the instructions for doing so
105 | are described in [JENKINS.md](/JENKINS.md) (although you won't need Jenkins
106 | until section 7). In addition to the steps in that section, you'll need the
107 | following tools installed:
108 |
109 | - `helm` (`>=3.0.0`) - Use
110 | [the official installation instructions](https://helm.sh/docs/intro/install/)
111 | - `helmfile` (`>=0.139.7`) - Use
112 | [the official installation instructions](https://github.com/roboll/helmfile#installation)
113 | - `helm-diff` - Use
114 | [the official installation instructions](https://github.com/databus23/helm-diff#install)
115 |
116 | #### Allowing Jenkins to authenticate with GitHub
117 |
118 | Later Jenkins will need to be able to both clone your fork of this repository,
119 | and also push changes back to branches in your fork. To do this it will need to
120 | be able to authenticate with GitHub via a [deploy
121 | key](https://docs.github.com/en/developers/overview/managing-deploy-keys#deploy-keys).
122 |
123 | Create a new keypair in the `$HOME/.ssh/` directory:
124 |
125 | ```terminal
126 | ssh-keygen -b 2048 -t rsa -f $HOME/.ssh/local-jenkins -q -N ""
127 | ```
128 |
129 | Next, tell GitHub about the public half of the keypair:
130 |
131 | * Copy the entire contents of `$HOME/.ssh/local-jenkins.pub` to your paste
132 | buffer
133 | * Visit your repository in GitHub
134 | * Visit the "Settings" page of your repository
135 | * Select "Deploy Keys" in the left-hand menu
136 | * Entire a title of "Local Jenkins" or any other name that will allow you to
137 | remember what this key is
138 | * Paste the contents of the public key file into the large text box
139 | * Ensure you tick the "Allow write access" box, otherwise Jenkins will not be
140 | able to push changes later
141 |
142 | Finally, tell Jenkins about the private half of the keypair:
143 |
144 | * Copy the entire contents of `$HOME/.ssh/local-jenkins` to your paste buffer
145 | * Log in to your Jenkins instance ([http://localhost](http://localhost)
146 | `admin`/`p4ssw0rd` if you use [JENKINS.md](/JENKINS.md))
147 | * Visit the "Manage Jenkins" page
148 | * Select "Manage Credentials"
149 | * Select the "Jenkins Credential Provider"
150 | * Select "Global credentials"
151 | * Select "Add credentials"
152 | * Provide a "Username" and "ID" of `git`
153 | * Paste the contents of the private key file into the large text box (you may
154 | need to click buttons for it to appear)
155 | * Click "Save"
156 |
157 | #### Snyk account and API token
158 |
159 | We'll be using Snyk's CLI tool to find misconfigurations. In addition to
160 | installing the CLI (instructions above), you'll need a Snyk account and a Snyk
161 | token.
162 | [Create a Snyk account](https://support.snyk.io/hc/en-us/articles/360017098237-Create-a-Snyk-account)
163 | and a
164 | [Snyk API token](https://support.snyk.io/hc/en-us/articles/360004008258-Authenticate-the-CLI-with-your-account)
165 | and make a note of your token, we'll need it later.
166 |
167 | ## Practices
168 |
169 | Not all practices are represented in this repository, as some are non-technical.
170 |
171 | The below links will take you to the appropriate point in Git history for each
172 | practice.
173 |
174 | 1. [The starting point](https://github.com/EngineerBetter/iac-example/tree/01-starting#the-starting-point)
175 | 1. [Store `.tfstate` appropriately](https://github.com/EngineerBetter/iac-example/tree/02-store-tf-state#store-tfstate-appropriately)
176 | 1. [Statically test IaC files](https://github.com/EngineerBetter/iac-example/tree/03-static-test#statically-test-iac-files)
177 | 1. [Write files in a standardized way](https://github.com/EngineerBetter/iac-example/tree/04-linting-formatting#write-files-in-a-standardized-way)
178 | 1. [Automatically format, lint and test before committing changes](https://github.com/EngineerBetter/iac-example/tree/05-pre-commit-hook#automatically-format-lint-and-test-before-committing-changes)
179 | 1. [Dynamically test against environments](https://github.com/EngineerBetter/iac-example/tree/06-dynamic-test#dynamically-test-against-environments)
180 | 1. [Automatically test and apply IaC](https://github.com/EngineerBetter/iac-example/tree/07-automatically-apply#automatically-test-and-apply-iac)
181 | 1. [Make all jobs idempotent](https://github.com/EngineerBetter/iac-example/tree/08-idempotent#make-all-jobs-idempotent)
182 | 1. [Continually apply IaC](https://github.com/EngineerBetter/iac-example/tree/09-converge#continually-apply-iac)
183 | 1. [Alert on failures](https://github.com/EngineerBetter/iac-example/tree/10-alert#alert-on-failures)
184 | 1. [Smoke-test deployed applications](https://github.com/EngineerBetter/iac-example/tree/11-smoke-test#smoke-test-deployed-applications)
185 | 1. [Test that everything works together](https://github.com/EngineerBetter/iac-example/tree/12-integration-test#test-that-everything-works-together)
186 | 1. [Record which versions work together](https://github.com/EngineerBetter/iac-example/tree/13-record-versions#record-which-versions-work-together)
187 | 1. [Parameterize differences between environments](https://github.com/EngineerBetter/iac-example/tree/14-parameterise-environments#parameterize-differences-between-environments)
188 | 1. [Promote change](https://github.com/EngineerBetter/iac-example/tree/15-promote#promote-change)
189 |
190 | ### The starting point
191 |
192 | Many of the practice implementations are series of commands, chained together in
193 | shell scripts. In order to make these easy to use for engineers as well as easy
194 | to invoke from Jenkins, we've used a [Makefile](/Makefile). This is a file that
195 | is interpreted by the [GNU Make tool](https://www.gnu.org/software/make/).
196 |
197 | We chose to use Make because it is common and convenient, but you don't have
198 | to. You could save scripts in individual files, or even embed them in the
199 | pipeline definition. Make will be available by default on many systems.
200 |
201 | This repository has assumed the existence of some simple deployments that we'll
202 | build automation around. The first commit in this repository includes Terraform
203 | files for deploying a Kubernetes cluster to AWS, and a Kubernetes manifest to
204 | deploy the [Sock Shop](https://microservices-demo.github.io/) microservices
205 | application to that cluster.
206 |
207 | #### Following along
208 |
209 | ```terminal
210 | # Set up environmental variables to authenticate to AWS.
211 | # You may instead store these in a `.envrc` file if using `direnv`.
212 | export AWS_ACCESS_KEY_ID={your_aws_access_key_id}
213 | export AWS_SECRET_ACCESS_KEY={your_aws_secret_access_key}
214 |
215 | # Deploy the Kubernetes cluster using Terraform. The first this this runs,
216 | # expect it to take between 15 - 25 minutes.
217 | make deploy-cluster
218 |
219 | # Get the Kubernetes config file you'll use to communicate with the Kubernetes
220 | # cluster.
221 | make fetch-cluster-config
222 |
223 | # Deploy the sock-shop microservice application to Kubernetes
224 | make deploy-sock-shop
225 |
226 | # Sock Shop may take a few minutes to deploy, but you can check its progress
227 | # with this command. You're finished with this section once Sock Shop reports
228 | # it is "Ready".
229 | kubectl --kubeconfig secrets/config-prod.yml get all -n sock-shop
230 | ```
231 |
232 | ### Store `.tfstate` appropriately
233 |
234 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/01-starting...02-store-tf-state)
235 |
236 | In the previous step, Terraform's state (where Terraform remembers what is
237 | deployed at the moment) was stored locally on disk in a `.tfstate` file that was
238 | ignored by Git. This change introduces a remote store for that state such that
239 | it is no longer kept on your workstation.
240 |
241 | A bootstrap Make target as been added (`make terraform-bootstrap`) that will
242 | create the following AWS resources to manage state remotely:
243 |
244 | 1. A S3 bucket to store the `.tfstate` file
245 | 1. A DynamoDB table that is used as a lock such that simultaneous changes to the
246 | infrastructure are prevented
247 |
248 | #### Following along
249 |
250 | ```terminal
251 | # Set up the following environmental variables for the remote state. You may store
252 | these in a `.envrc` file if using `direnv`.
253 |
254 | # The region where state resources are to be created, we used `eu-west-2`
255 | export BOOTSTRAP_AWS_REGION={your_aws_region}
256 |
257 | # The name of the S3 bucket to store state, such as `terraform_state`
258 | export BOOTSTRAP_BUCKET_NAME={your_aws_bucket_name}
259 |
260 | # The DynamoDB table name used for locking, such as `terraform_lock`
261 | export BOOTSTRAP_DYNAMO_TABLE_NAME={your_dynamodb_table}
262 |
263 | # Create the bucket and locking table, the AWS CLI may display output to the
264 | # screen that you can dismiss by pressing `q`. Note that this script will fail
265 | # if run multiple times, we'll address this later.
266 | make terraform-bootstrap
267 |
268 | # Initializing again will configure your remote Terraform backend. You should
269 | # be prompted to decide if you'd like to migrate your local state to the S3
270 | # bucket, which you absolutely do.
271 | make terraform-init
272 |
273 | # Remove the now unnecessary local state files.
274 | rm terraform/deployments/cluster-prod/*.tfstate
275 | rm terraform/deployments/cluster-prod/*.tfstate.backup
276 |
277 | # Running deploy again should validate that nothing is going to change even
278 | # though we've changed where state is stored.
279 | make deploy-cluster
280 |
281 | # You're now finished with this section.
282 | ```
283 |
284 | ### Statically test IaC files
285 |
286 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/02-store-tf-state...03-static-test)
287 |
288 | In order to improve confidence in the correctness and safety of the resources
289 | deployed by Terraform and Kubernetes, static testing is introduced. Make sure
290 | your have your Snyk API token to hand that
291 | [you created earlier](https://github.com/EngineerBetter/iac-example#snyk-account-and-api-token).
292 |
293 | #### Following along
294 |
295 | ```terminal
296 | # Used by the Snyk CLI for authentication when performing scans
297 | export SNYK_TOKEN={your_snyk_token}
298 |
299 | # Using the Terraform CLI, this task checks our Terraform files for correctness
300 | # and whether or not we're using deprecated Terraform features.
301 | make terraform-validate
302 |
303 | # This target uses Snyk to determine whether misconfigurations in the Terraform
304 | # definitions would result in issues (such as security issues leading to
305 | # exposure to risk).
306 | make snyk-test-terraform
307 |
308 | # This target uses the Snyk tool to perform a similar check against the
309 | # deployment manifest we used to deploy Sock Shop to Kubernetes. This target
310 | # will likely fail as there are issues with the Sock Shop manifest. We'll
311 | # ignore failure here for the time being but note that there are configuration
312 | # issues with this manifest identified by Snyk.
313 | make snyk-test-deployments
314 |
315 | # This is a convenience target that'll run the above three targets sequentially.
316 | make test
317 | ```
318 |
319 | It is advisable to run `make test` prior to applying any changes to your
320 | infrastructure or application to increase your confidence. Later, it will be
321 | demonstrated how this may be automated.
322 |
323 | ### Write files in a standardized way
324 |
325 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/03-static-test...04-linting-formatting)
326 |
327 | Keeping code standardized will improve readability of the files. We can enforce
328 | standardization using tools that are triggered by Make for convenience.
329 |
330 | #### Following along
331 |
332 | ```terminal
333 | # This target uses Terraform's `fmt` subcommand to indicate when files are
334 | # formatted in a non-standard way. If it finds a formatting issue then it'll
335 | # fail and tell you what you need to do to fix it.
336 | make terraform-fmt-check
337 |
338 | # There are no formatting errors in the files, so the tool exits without error.
339 | # You can prove this by printing the exit code of the last command, which will be
340 | # 0.
341 | echo $?
342 |
343 | # This target uses tflint to to provide faster feedback for errors that are
344 | # _syntactically_ correct but _semantically_ incorrect (such as asking AWS to
345 | # create an instance type that does not exist).
346 | make terraform-lint
347 | ```
348 |
349 | Try making edits to the files before running the checks again, to see if you can
350 | get them to output a warning.
351 |
352 | Seeing these commands pass successfully concludes this section. For convenience,
353 | both of these two tests are run within the existing target `make test`.
354 |
355 | ### Automatically format, lint and test before committing changes
356 |
357 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/04-linting-formatting...05-pre-commit-hook)
358 |
359 | Until now, the person making changes had to remember to to run tests and checks
360 | prior to applying changes to either Terraform or Kubernetes. In this change, [a
361 | pre-commit hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) is
362 | configured such that fast tests are run automatically when checking in new
363 | changes to Git.
364 |
365 | #### Following along
366 |
367 | ```terminal
368 | # Configure the pre-commit hook. After running this, each time a commit is made
369 | # Git will run the targets `terraform-validate`, `terraform-lint` and
370 | # `terraform-fmt-check`. Only these tests are run because they are fast.
371 | make configure-pre-commit-hook
372 |
373 | # Try making a simple commit to see the hook trigger.
374 | touch a-new-file
375 | git add a-new-file
376 | git commit -m "testing the pre-commit hook"
377 |
378 | # You'll see the tests running automatically.
379 | ```
380 |
381 | Having configured your pre-commit hook, you've finished this section.
382 |
383 | #### Removing the Git hook
384 |
385 | You may wish to remove the git hook in future or disable it temporarily. To
386 | remove the hook run `rm .git/hooks/pre-commit`. It can be re-enabled by running
387 | `make configure-pre-commit-hook` again.
388 |
389 | ### Dynamically test against environments
390 |
391 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/05-pre-commit-hook...06-dynamic-test)
392 |
393 | To further increase confidence in the infrastructure being deployed, this change
394 | makes use of Snyk's Terraform plan-scanning feature.
395 |
396 | Rather than immediately applying changes to infrastructure, a plan is now
397 | generated first and scanned by Snyk before applying those changes. If Snyk finds
398 | issues with the generated plan then the deployment is aborted.
399 |
400 | #### Following along
401 |
402 | ```terminal
403 | # Running this target now makes use of Snyk's IaC plan scanning. This will
404 | # likely fail when run and output useful information about properly configuring
405 | # the Terraform files.
406 | make deploy-cluster
407 |
408 | # Since we still need to deploy to production despite issues that may already
409 | # exist there, we've added a toggle to not consider finding configuration issues
410 | # an error, but merely report on issues found.
411 | IGNORE_SNYK_TEST_PLAN_FAILURE=true make deploy-cluster
412 | ```
413 |
414 | This concludes this section, where deploys with the feature toggle should report
415 | no deployment changes, but warn about configuration issues.
416 |
417 | ### Automatically test and apply IaC
418 |
419 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/06-dynamic-test...07-automatically-apply)
420 |
421 | Prior to this change, deployments and tests have been run locally on development
422 | machines. This presents a few issues, the most important of which being that an
423 | engineer making deploys could forget to run tests prior to deploying and that
424 | it's difficult for other contributors to see what was deployed recently.
425 |
426 | To address these issues, Jenkins CI pipelines have been introduced. The Make
427 | targets that have been used for deploys and tests are run automatically whenever
428 | Jenkins detects a code change.
429 |
430 | Each time a commit is pushed to this repository, the `Deploy` pipeline is
431 | triggered. This pipeline does the following each time it is run, in this order:
432 |
433 | 1. Run `make terraform-init`
434 | 2. In parallel, run:
435 | - `make terraform-validate`
436 | - `make terraform-lint`
437 | - `make terraform-fmt-check`
438 | - `make snyk-test-terraform`
439 | - `make snyk-test-deployments`
440 | 3. Run `make deploy-cluster`
441 | 4. Run `make deploy-sock-shop`
442 |
443 | At each push to this repository, all tests are run, the Kubernetes cluster is
444 | deployed and the Sock Shop application is deployed. By using this, we need not
445 | fear that we forget to run tests prior to making changes to the infrastructure.
446 | We also now have a view of the history of changes through Jenkins CI build
447 | history.
448 |
449 | A `Destroy` pipeline has also been created. This pipeline is disabled
450 | by default to prevent accidental running, as its role is to destroy the
451 | application and Kubernetes cluster. While you're unlikely to want to do this
452 | for your production deployments, this functionality will become useful in later
453 | commits when we look at using multiple environments.
454 |
455 | #### Following along
456 |
457 | You may already have access to a running Jenkins instance. If you don't, please
458 | follow the instructions in [JENKINS.md](/JENKINS.md) to deploy Jenkins locally.
459 |
460 | In order to run our Make targets in Jenkins, we'll need to let Jenkins know
461 | about a few credentials. Refer to the
462 | [Jenkins documentation for configuring credentials](https://www.jenkins.io/doc/book/using/using-credentials/).
463 | Below are a list of credentials we'll need, including their key and type. Their
464 | values are the same as those you've already configured in earlier steps.
465 |
466 | | Key | Type |
467 | |-----------------------------|-------------|
468 | | SNYK_TOKEN | Secret text |
469 | | AWS_ACCESS_KEY_ID | Secret text |
470 | | AWS_SECRET_ACCESS_KEY | Secret text |
471 | | BOOTSTRAP_AWS_REGION | Secret text |
472 | | BOOTSTRAP_BUCKET_NAME | Secret text |
473 | | BOOTSTRAP_DYNAMO_TABLE_NAME | Secret text |
474 |
475 | ```terminal
476 | # In order to create pipelines in our Jenkins instance, we'll need to set a few
477 | # environment variables.
478 | # You may instead store these in a `.envrc` file if using `direnv`.
479 |
480 | # If you've forked this repo, these variables are used to instruct Jenkins on
481 | # where it can find your fork.
482 | # If different from "EngineerBetter":
483 | export GITHUB_ORG={your_github_org}
484 | # If different from "iac-example":
485 | export GITHUB_REPOSITORY={your_github_repository}
486 |
487 | # The Make targets we're about to use need to use the Jenkins CLI so we set this
488 | # to enable the target to find it.
489 | export JENKINS_CLI={your_cli_path}
490 |
491 | # The following variables are used to communicate and authenticate with Jenkins.
492 |
493 | # The URL to your Jenkins instance, if you used JENKINS.md then its value should
494 | # be `http://localhost/`.
495 | export JENKINS_URL={your_jenkins_url}
496 |
497 | # The following are the Jenkins credentials, if you used JENKINS.md then their
498 | # values are `admin` and `p4ssw0rd` respectively.
499 | export JENKINS_USERNAME={your_jenkins_username}
500 | export JENKINS_PASSWORD={your_jenkins_password}
501 |
502 | # The following will create two declarative pipelines (see
503 | # https://www.jenkins.io/doc/book/pipeline/syntax/) in Jenkins - one for
504 | # deploying the infrastructure and one for destroying it. After running these
505 | # commands, this section is concluded.
506 | make jenkins-create-deploy-pipeline
507 | make jenkins-create-destroy-pipeline
508 | ```
509 |
510 | Explore the Jenkins web user interface to see your newly-created pipelines. You
511 | can trigger a build to see them in action.
512 |
513 | #### Notes: Updating the pipelines
514 |
515 | The first time we created the Jenkins pipelines, we used the commands just
516 | above. If you need to modify and set the pipelines again, you should use the
517 | Make targets `jenkins-update-deploy-pipeline` and
518 | `jenkins-update-destroy-pipeline`. This is because Jenkins' CLI has no way to
519 | "create a pipeline _or_ update it if it already exists" - they are separate
520 | operations.
521 |
522 | Pipeline metadata lives in [deploy.Jenkinsfile](/pipelines/deploy.Jenkinsfile)
523 | and [destroy.Jenkinsfile](/pipelines/destroy.Jenkinsfile). Since it can be
524 | difficult to read and change XML files, it's recommended you make any changes
525 | you need to make to pipeline metadata in the Jenkins UI and use the Jenkins CLI
526 | to get the pipeline definition, updating the XML files with the newly generated
527 | ones:
528 |
529 | ```terminal
530 | java -jar \
531 | $JENKINS_CLI \
532 | -s $JENKINS_URL \
533 | -auth "${JENKINS_USERNAME}:${JENKINS_PASSWORD}" \
534 | get-job 'Deploy (prod)' \
535 | > pipelines/deploy.xml
536 | ```
537 |
538 | #### Notes: Docker images
539 |
540 | Our pipelines use Kubernetes agents to run their workloads. We've provided a
541 | Docker image that the pipelines already know about (engineerbetter/iac-example).
542 |
543 | If you wish to create your own Docker images for use by modifying the Dockerfile
544 | in this repository, you can run
545 | `docker build . -t {your_image_repository}/{your_image_name}:{your_image_tag}`
546 | to build the image and
547 | `docker push {your_image_repository}/{your_image_name}:{your_image_tag}` to push
548 | the image.
549 |
550 | Refer to the
551 | [Dockerhub getting started guide](https://docs.docker.com/docker-hub/) (or the
552 | documentation for whatever image repository you use).
553 |
554 | If you have built your own image and wish to use it, make sure you replace the
555 | image references in both [deploy.Jenkinsfile](/pipelines/deploy.Jenkinsfile) and
556 | [destroy.Jenkinsfile](/pipelines/destroy.Jenkinsfile). You can retrieve the
557 | SHA256 of your image with
558 | `docker image inspect {your_image_repository}/{your_image_name}:{your_image_tag}`
559 | and looking for the "RepoDigests".
560 |
561 | ### Make all jobs idempotent
562 |
563 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/07-automatically-apply...08-idempotent)
564 |
565 | When Jenkins CI was introduced in the previous commit, we automated almost
566 | everything. One thing that was missing was the 'bootstrap' Make target which
567 | created the AWS resources required for the Terraform backend. That target only
568 | works the first time it is run: if the resources already exist, it errors.
569 |
570 | In this commit we make the bootstrap Make target idempotent so that it may be
571 | safely run in CI along with all other Make targets, automating what was
572 | previously a manual step.
573 |
574 | Prior to creating the bootstrap S3 bucket and DynamoDB table, the script now
575 | checks if they already exist, so that it does not fail if they are already
576 | there. With this change it is now safe to run that target repeatedly and be sure
577 | that the end state will be the same.
578 |
579 | Given this new safety, this step is introduced into the CI pipeline in Jenkins,
580 | removing a dependency on what was previously a manual step.
581 |
582 | #### Following along
583 |
584 | Our Jenkins pipeline is currently configured to build from a specific tag (the
585 | tag of the last step). We need to tell Jenkins that it should now be looking at
586 | _this_ tag.
587 |
588 | Unfortunately Jenkins has a bug which means that it might not 'notice' that the
589 | tag it is being told to watch has changed. Triggering a build does force it to
590 | recognise that it is configured to watch a new tag.
591 |
592 | You will need to update your pipeline definitions, and also trigger a build to
593 | force it to update.
594 |
595 | ```terminal
596 | # Update the pipelines
597 | make jenkins-update-deploy-pipeline
598 | make jenkins-update-destroy-pipeline
599 |
600 | # Trigger a build
601 | java -jar ${JENKINS_CLI} \
602 | -s ${JENKINS_URL} \
603 | -auth "${JENKINS_USERNAME}:${JENKINS_PASSWORD}" \
604 | build 'Deploy (prod)'
605 | ```
606 |
607 | ### Continually apply IaC
608 |
609 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/08-idempotent...09-converge)
610 |
611 | The pipeline is now triggered at least once per hour, even if nothing changes.
612 | This is safe to do since our tasks are now idempotent and running the pipeline
613 | repeatedly ought to result in the same outcome.
614 |
615 | This change ensures that manual changes are overwritten at least every hour,
616 | encouraging change to move through CI via Git commits.
617 |
618 | #### Following along
619 |
620 | ```terminal
621 | # Update the pipelines, and trigger a build to force Jenkins to notice the
622 | # change in tag.
623 | make jenkins-update-deploy-pipeline
624 | make jenkins-update-destroy-pipeline
625 | java -jar ${JENKINS_CLI} \
626 | -s ${JENKINS_URL} \
627 | -auth "${JENKINS_USERNAME}:${JENKINS_PASSWORD}" \
628 | build 'Deploy (prod)'
629 | ```
630 |
631 | Visit the [Deploy (prod)](http://localhost/job/Deploy%20(prod)/configure)
632 | configuration page, and see that there are now settings for an hourly build
633 | trigger.
634 |
635 | ### Alert on failures
636 |
637 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/09-converge...10-alert)
638 |
639 | Prior to this change, failures in CI would only be visible by inspecting the
640 | Jenkins build history. This is fine when people are constantly checking Jenkins
641 | but that isn't always a realistic approach. Important information ought to be
642 | _pushed_ to the those who need to know it rather than expecting those people to
643 | be constantly checking for it.
644 |
645 | In this commit, our pipelines are configured to alert to a Slack channel with a
646 | link to the failing build, grabbing the attention of those able to fix the issue
647 | and ensuring the pipeline is failing for a smaller window of time.
648 |
649 | To enable Jenkins to post to Slack, you'll need to configure a Jenkins
650 | credential `SLACK_WEBHOOK_CREDENTIAL` of type "secret text". Configuring Jenkins
651 | credentials was covered in
652 | [Automatically test and apply IaC](https://github.com/EngineerBetter/iac-example/tree/10-alert#automatically-test-and-apply-iac).
653 | The value for this secret is created by following
654 | [Slack's tutorial on setting up webhooks](https://slack.com/intl/en-gb/help/articles/115005265063-Incoming-webhooks-for-Slack).
655 |
656 | #### Following along
657 |
658 | You will need to update your pipeline definitions for the alerting configuration to take effect:
659 |
660 | ```terminal
661 | # Configure the slack channel that failures are reported to.
662 | # You may instead store these in a `.envrc` file if using `direnv`.
663 | export SLACK_CHANNEL={your_alerts_channel}
664 |
665 | # Update the pipelines, and trigger a build to force Jenkins to notice the
666 | # change in tag.
667 | make jenkins-update-deploy-pipeline
668 | make jenkins-update-destroy-pipeline
669 | java -jar ${JENKINS_CLI} \
670 | -s ${JENKINS_URL} \
671 | -auth "${JENKINS_USERNAME}:${JENKINS_PASSWORD}" \
672 | build 'Deploy (prod)'
673 | ```
674 |
675 | ### Smoke-test deployed applications
676 |
677 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/10-alert...11-smoke-test)
678 |
679 | Smoke tests are often used to get fast feedback on whether deployed systems are
680 | functioning as they expect. While not rigorous tests, they often answer the
681 | question "is something on fire?". In this commit, we introduce smoke tests after
682 | our infrastructure is deployed to increase our confidence that things are
683 | functioning as expected.
684 |
685 | We've leveraged functionality in Kubernetes to achieve rudimentary smoke tests -
686 | we've defined a readiness probe on the front end of Sock Shop that expects a
687 | `200 OK` response when fetching the landing page content.
688 |
689 | In addition, when deploying Sock Shop, we use `kubectl` to wait for all pods and
690 | resources to report that they are `ready`. Previously this wasn't checked for
691 | and we'd not have known that deploying Sock Shop had failed.
692 |
693 | #### Following along
694 |
695 | ```terminal
696 | # Update the pipelines, and trigger a build to force Jenkins to notice the
697 | # change in tag.
698 | make jenkins-update-deploy-pipeline
699 | make jenkins-update-destroy-pipeline
700 | java -jar ${JENKINS_CLI} \
701 | -s ${JENKINS_URL} \
702 | -auth "${JENKINS_USERNAME}:${JENKINS_PASSWORD}" \
703 | build 'Deploy (prod)'
704 | ```
705 |
706 | Triggering the "Deploy (prod)" pipeline and observing the "Deploy Sock
707 | Shop" stage should indicate that the pipeline waited until the pods reported
708 | they were ready (that pods were deployed and their readiness probes were
709 | successful).
710 |
711 | You can validate that the script waited for readiness probes to return
712 | successfully by looking for a number of lines containing `condition met` in the
713 | build output.
714 |
715 | ### Test that everything works together
716 |
717 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/11-smoke-test...12-integration-test)
718 |
719 | Our earlier smoke tests gave us some good assurances that we will catch issues
720 | with individual services, but only by testing one component of our deployment in
721 | isolation. In this section we introduce an example of slightly more rigorous
722 | testing. Ideally these tests would be maintained by the people who maintain the
723 | Sock Shop application code.
724 |
725 | Our new integration tests will load the front end and ensure that a particular
726 | section exists in that page content. Now we've guarded against more complicated
727 | failures such as the front end having no content but returning `200 OK`, or
728 | returning the wrong content.
729 |
730 | #### Following along
731 |
732 | You can run the integration tests locally, if you have all of the prerequisites
733 | installed:
734 |
735 | ```terminal
736 | # Ensure that our cluster config is up to date so that we can communicate with
737 | # Kubernetes.
738 | make fetch-cluster-config
739 |
740 | # Run the integration tests from our local machine
741 | make integration-test
742 | ```
743 |
744 | In this tag the integration tests are also run as part of the pipeline. To see
745 | them run automatically:
746 |
747 | ```terminal
748 | # Update the pipelines, and trigger a build to force Jenkins to notice the
749 | # change in tag.
750 | make jenkins-update-deploy-pipeline
751 | make jenkins-update-destroy-pipeline
752 | java -jar ${JENKINS_CLI} \
753 | -s ${JENKINS_URL} \
754 | -auth "${JENKINS_USERNAME}:${JENKINS_PASSWORD}" \
755 | build 'Deploy (prod)'
756 | ```
757 |
758 | ...and look for the integration test job.
759 |
760 | ### Record which versions work together
761 |
762 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/12-integration-test...13-record-versions)
763 |
764 | There are many ways to maintain a "bill of materials" that declares what is to
765 | be deployed and what is deployed at the moment. It's not quite true that we've
766 | been entirely declarative in our infrastructure and application deployments up
767 | to now. We're going to make sure that this repository contains specific version
768 | definitions for what is deployed right now.
769 |
770 | Our Kubernetes deployment manifest for Sock Shop has been referencing which
771 | images it needs by tag, for example `mongo:latest`. The image version that tags
772 | point to can be changed by the owner of the image. Indeed, some tags such as
773 | `latest` are _intended_ to be updated constantly. To be more confident in our
774 | deployment process being reproducible and idempotent, we'd like to make sure
775 | that the versions of images are not changing underneath us between deploys. This
776 | is achieved by referencing each image's SHA256, rather than their tags.
777 |
778 | For each image in the deployment manifest, we've updated the reference to the
779 | SHA256 of the image deployed at the moment.
780 |
781 | #### Following along
782 |
783 | You can run this test locally:
784 |
785 | ```terminal
786 | # To ensure that we continue to reference images by SHA256, we've added conftest
787 | # to inspect deployment manifests and fail if it finds an image not referenced
788 | # by SHA256.
789 | make policy-test
790 | # Verify that the previous command exited successfully
791 | echo $?
792 | ```
793 |
794 | You could try switching some of the versions to `latest` to see the test fail.
795 |
796 | Observe that the new Make target is also run by the latest version of the
797 | pipeline:
798 |
799 | ```terminal
800 | # Update the pipelines, and trigger a build to force Jenkins to notice the
801 | # change in tag.
802 | make jenkins-update-deploy-pipeline
803 | make jenkins-update-destroy-pipeline
804 | java -jar ${JENKINS_CLI} \
805 | -s ${JENKINS_URL} \
806 | -auth "${JENKINS_USERNAME}:${JENKINS_PASSWORD}" \
807 | build 'Deploy (prod)'
808 | ```
809 |
810 | ...and look for the "Policy Test" job.
811 |
812 | This section is now concluded.
813 |
814 | ### Parameterize differences between environments
815 |
816 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/13-record-versions...14-parameterise-environments)
817 |
818 | Until this commit, we've been assuming that there is only one environment: prod.
819 | One of the key benefits of IaC is that it becomes easy to create and destroy
820 | infrastructure. To unlock this power we need to make very little change to our
821 | repository.
822 |
823 | An environment variable, `TF_VAR_env_name`, has been introduced that will be
824 | used by Terraform to determine which environment it is to deploy.
825 |
826 | #### Following along
827 |
828 | ```terminal
829 | # Configure the environment we're interacting with. Make targets will fail if
830 | # run without an environment configured.
831 | # You may instead store this in a `.envrc` file if using `direnv`.
832 | export TF_VAR_env_name=prod
833 |
834 | # Update the pipelines, and trigger a build to force Jenkins to notice the
835 | # change in tag.
836 | make jenkins-update-deploy-pipeline
837 | make jenkins-update-destroy-pipeline
838 | java -jar ${JENKINS_CLI} \
839 | -s ${JENKINS_URL} \
840 | -auth "${JENKINS_USERNAME}:${JENKINS_PASSWORD}" \
841 | build 'Deploy (prod)'
842 |
843 | # Switch environments to "staging". Every operation is now run against a
844 | # different environment.
845 | export TF_VAR_env_name=staging
846 |
847 | # Create deploy and destroy pipelines for the staging environment. These new
848 | # pipelines will operate on "staging" and not interfere with "prod".
849 | make jenkins-create-deploy-pipeline
850 | make jenkins-create-destroy-pipeline
851 | ```
852 |
853 | You can create as many environment as you like, the only limit is your budget!
854 | Simply changing `TF_VAR_env_name`'s value will achieve that. This section is now concluded.
855 |
856 | ### Promote change
857 |
858 | * [See code changes](https://github.com/EngineerBetter/iac-example/compare/14-parameterise-environments...15-promote)
859 |
860 | Given how easy it is to create new environments now that they are parameterized,
861 | it is trivial to demonstrate another practice used in IaC: promotion.
862 |
863 | We've decided to use Git branches to implement promotion in Jenkins. When a
864 | deploy is successful, the branch called `passed_{env_name}` is rebased to the
865 | Git commit of what was just deployed. In practice we've created a `staging`
866 | environment and pipelines, and when a `staging` deploy is successful, it pushes
867 | the branch `passed_staging`. Our production environment is configured to deploy
868 | on change to the `passed_staging` branch and when successful, the `prod`
869 | pipeline pushes to `passed_prod`.
870 |
871 | So that knowledge of environment names exists outside of our heads, we've created
872 | an `environments.yml` file. The Make targets have been modified such that acting on an
873 | environment not referenced in that file will fail with a message explaining why.
874 |
875 | Each environment requires the `name` and `promotes_to` fields to be set in the
876 | `environments.yml` file. `promotes_to` is used to determine which branch to push
877 | _to_ when a deploy is successful. An optional `promotes_from` field may be set
878 | that determines which branch triggers the pipeline. It defaults to `main`.
879 |
880 | #### Notes
881 |
882 | Unfortunately the Jenkins Git plugin does not function correctly in declarative
883 | pipelines. A consequence of this is that we were unable to `git push` from our
884 | deploy pipelines. As a workaround we've configured the deploy pipeline to
885 | trigger a
886 | [freestyle project](https://docs.cloudbees.com/docs/admin-resources/latest/pipelines/learning-about-pipelines#_freestyle_projects)
887 | to perform `git push`.
888 |
889 | This made updating Jenkins pipelines a bit unwieldy since there are now at least
890 | three pipelines to set. As a convenience, `make jenkins-update-pipelines` and
891 | `make jenkins-create-pipelines` will set all pipelines for an environment.
892 |
893 | #### Following along
894 |
895 | ```terminal
896 | # Remove the no-longer used environment variable. If using `direnv` then remove
897 | # it from your .envrc instead.
898 | unset TF_VAR_env_name
899 |
900 | # Use ENV_NAME to indicate which environment we're operating on. It must match
901 | # an environment referenced in environments.yml.
902 | # You may instead store this in a `.envrc` file if using `direnv`.
903 | export ENV_NAME=prod
904 |
905 | # Create the promotion pipeline used to promote change between environments.
906 | make jenkins-create-promote-pipeline
907 |
908 | # To move the tutorial on, make sure we instruct Jenkins to look at the source
909 | # code at this tag.
910 | make jenkins-update-pipelines
911 |
912 | # Update the staging pipelines too.
913 | export ENV_NAME=staging
914 | make jenkins-update-pipelines
915 | ```
916 |
917 | This concludes this section. Feel free to push a trivial change and observe
918 | promotion in action. Pushes to `main` will trigger the staging pipeline, which
919 | will trigger the prod pipeline after success.
920 |
--------------------------------------------------------------------------------