├── 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 | --------------------------------------------------------------------------------