├── jenkins ├── data │ └── .git_keep ├── jobs │ └── outyet │ │ └── config.xml └── docker │ └── Dockerfile ├── postgres └── data │ └── .git_keep ├── registry ├── data │ └── .git_keep ├── htpasswd ├── certs │ ├── README.md │ ├── san.cnf │ ├── domain.crt │ └── domain.key └── config.yaml ├── README.md ├── outyet ├── go.mod ├── README.md ├── docker-compose.yaml ├── Dockerfile.single ├── Dockerfile ├── Dockerfile.minimal ├── main_test.go └── main.go ├── terraform ├── data.tf ├── files │ ├── startup_options.conf │ └── daemon.json ├── key-pairs.tf ├── bin │ ├── local-ip.sh │ ├── ssh_server.sh │ ├── setup_workers.sh │ ├── setup_managers.sh │ ├── self_healing.sh │ └── ip_vars.sh ├── outputs.tf ├── variables.tf ├── security-group.tf ├── README.md └── app-instances.tf ├── .gitignore ├── scripts ├── docker-workshop │ ├── clean-jenkins-data.sh │ ├── clean-jenkins-data.ps1 │ ├── clean-all-data.sh │ └── clean-all-data.ps1 └── class-docker-cicd │ ├── clean-jenkins-data.sh │ ├── clean-jenkins-data.ps1 │ ├── clean-all-data.sh │ └── clean-all-data.ps1 ├── gogs └── conf │ └── app.ini └── compose └── docker-compose.yaml /jenkins/data/.git_keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /postgres/data/.git_keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /registry/data/.git_keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker CI/CD class 2 | -------------------------------------------------------------------------------- /outyet/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/spkane/outyet 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /terraform/data.tf: -------------------------------------------------------------------------------- 1 | data "aws_vpc" "default" { 2 | default = true 3 | } 4 | -------------------------------------------------------------------------------- /registry/htpasswd: -------------------------------------------------------------------------------- 1 | myuser:$2y$05$biU9Wlzj6YwrPRtL4uZlG.DvKLfbpfFpYAxBv.g9yJVQzHXv6C//y 2 | -------------------------------------------------------------------------------- /terraform/files/startup_options.conf: -------------------------------------------------------------------------------- 1 | [Service] 2 | ExecStart= 3 | ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375 4 | -------------------------------------------------------------------------------- /terraform/files/daemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimes": { 3 | "runsc": { 4 | "path": "/usr/local/bin/runsc" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /terraform/key-pairs.tf: -------------------------------------------------------------------------------- 1 | resource "aws_key_pair" "deployer" { 2 | key_name = "trainer_sean_kane" 3 | public_key = file(var.ssh_public_key_path) 4 | } 5 | -------------------------------------------------------------------------------- /terraform/bin/local-ip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | #ifconfig.me returns the ipv4 address instead of an ipv6 address 6 | echo "{\"public_ip\":\"$(curl -sL4 ifconfig.me)\"}" 7 | -------------------------------------------------------------------------------- /terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "manager-ips" { 2 | value = join(",", aws_instance.manager.*.public_ip) 3 | } 4 | 5 | output "worker-ips" { 6 | value = join(",", aws_instance.worker.*.public_ip) 7 | } 8 | -------------------------------------------------------------------------------- /registry/certs/README.md: -------------------------------------------------------------------------------- 1 | openssl req -out sslcert.csr -newkey rsa:2048 -nodes -keyout domain.key -config san.cnf 2 | openssl req -noout -text -in sslcert.csr | grep DNS 3 | openssl x509 -signkey domain.key -in sslcert.csr -req -days 3650 -out domain.crt 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sslcert.csr 2 | .env 3 | outyet/outyet 4 | postgres/data/* 5 | registry/data/* 6 | jenkins/data/* 7 | terraform/terraform.tfstate* 8 | terraform/.terraform 9 | terraform/.terraform.lock.hcl 10 | !postgres/data/.git_keep 11 | !registry/data/.git_keep 12 | !jenkins/data/.git_keep 13 | 14 | -------------------------------------------------------------------------------- /outyet/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | Build Status: [![Build Status](http://127.0.0.1:10091/buildStatus/icon?job=outyet)](http://127.0.0.1:10091/job/outyet/) 4 | 5 | Forked from: [github.com/golang/example](https://github.com/golang/example) 6 | 7 | ## Build 8 | 9 | - `docker image build .` 10 | 11 | -------------------------------------------------------------------------------- /terraform/bin/ssh_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -u 5 | 6 | source ./bin/ip_vars.sh 1> /dev/null 7 | 8 | ssh -o "IdentitiesOnly=yes" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/oreilly_aws ubuntu@${primary_manager_ip} 9 | 10 | echo 11 | echo "Test your setup with:" 12 | echo 13 | echo "docker -H ${primary_manager_ip}:2375 node list" 14 | echo 15 | -------------------------------------------------------------------------------- /outyet/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | outyet: 3 | #build: 4 | # context: . 5 | # We are just going to use the upstream image for demos 6 | # however, it would be more accurate to build it from the local code. 7 | image: spkane/outyet:latest 8 | ports: 9 | - "10088:8080" 10 | networks: 11 | - my-net 12 | networks: 13 | my-net: 14 | driver: bridge 15 | -------------------------------------------------------------------------------- /terraform/bin/setup_workers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -u 5 | 6 | source ./bin/ip_vars.sh 1> /dev/null 7 | 8 | for i in "${worker_ips[@]}" 9 | do 10 | ssh -o "IdentitiesOnly=yes" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o "BatchMode=yes" -i ~/.ssh/oreilly_aws ubuntu@${i} "sudo docker swarm join --token ${SWARM_WORKER_TOKEN} ${primary_manager_ip}:2377" 11 | done 12 | 13 | echo 14 | echo "Test your setup with:" 15 | echo 16 | echo "docker -H ${primary_manager_ip}:2375 node list" 17 | echo 18 | -------------------------------------------------------------------------------- /scripts/docker-workshop/clean-jenkins-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE="${HOME}/docker-workshop" 4 | 5 | echo "This will reset all the Jenkins data under ${BASE} !!!" 6 | echo 7 | echo "Are you sure this is what you want to do?" 8 | read -p "You must type 'yes' to confirm: " -r 9 | echo 10 | if [[ $REPLY == "yes" ]]; then 11 | rm -rf ${BASE}/layout/jenkins/data/* 12 | rm -rf ${BASE}/layout/jenkins/data/.groovy 13 | rm -rf ${BASE}/layout/jenkins/data/.java 14 | echo "completed" 15 | else 16 | echo "aborted" 17 | fi 18 | 19 | -------------------------------------------------------------------------------- /outyet/Dockerfile.single: -------------------------------------------------------------------------------- 1 | FROM golang:1.22.0 AS builder 2 | # Build Image 3 | 4 | COPY . /go/src/outyet 5 | WORKDIR /go/src/outyet 6 | 7 | ENV CGO_ENABLED=0 8 | ENV GOOS=linux 9 | 10 | RUN go get -v -d && \ 11 | go install -v && \ 12 | go test -v && \ 13 | go build -ldflags "-s" -a -installsuffix cgo -o outyet . 14 | 15 | RUN apt-get install -y ca-certificates 16 | 17 | WORKDIR / 18 | RUN cp /go/src/outyet/outyet . 19 | 20 | EXPOSE 8080 21 | 22 | CMD ["/outyet", "-version", "1.9.4", "-poll", "600s", "-http", ":8080"] 23 | -------------------------------------------------------------------------------- /scripts/class-docker-cicd/clean-jenkins-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE="${HOME}/class-docker-cicd" 4 | 5 | echo "This will reset all the Jenkins data under ${BASE} !!!" 6 | echo 7 | echo "Are you sure this is what you want to do?" 8 | read -p "You must type 'yes' to confirm: " -r 9 | echo 10 | if [[ $REPLY == "yes" ]]; then 11 | rm -rf ${BASE}/layout/jenkins/data/* 12 | rm -rf ${BASE}/layout/jenkins/data/.groovy 13 | rm -rf ${BASE}/layout/jenkins/data/.java 14 | echo "completed" 15 | else 16 | echo "aborted" 17 | fi 18 | 19 | -------------------------------------------------------------------------------- /terraform/bin/setup_managers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -u 5 | 6 | source ./bin/ip_vars.sh 1> /dev/null 7 | 8 | for i in "${secondary_manager_ips[@]}" 9 | do 10 | ssh -o "IdentitiesOnly=yes" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o "BatchMode=yes" -i ~/.ssh/oreilly_aws ubuntu@${i} "sudo docker swarm join --token ${SWARM_MANAGER_TOKEN} ${primary_manager_ip}:2377" 11 | done 12 | 13 | echo 14 | echo "Test your setup with:" 15 | echo 16 | echo "docker -H ${primary_manager_ip}:2375 node list" 17 | echo 18 | -------------------------------------------------------------------------------- /outyet/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22.0 AS builder 2 | # Build Image 3 | 4 | COPY . /go/src/outyet 5 | WORKDIR /go/src/outyet 6 | 7 | ENV CGO_ENABLED=0 8 | ENV GOOS=linux 9 | 10 | RUN go get -v -d && \ 11 | go install -v && \ 12 | go test -v && \ 13 | go build -ldflags "-s" -a -installsuffix cgo -o outyet . 14 | 15 | FROM alpine:latest AS deploy 16 | # Deploy Image 17 | 18 | RUN apk --no-cache add ca-certificates 19 | 20 | WORKDIR / 21 | COPY --from=builder /go/src/outyet/outyet . 22 | # (or) COPY --from=0 /go/src/outyet/outyet . 23 | 24 | EXPOSE 8080 25 | 26 | CMD ["/outyet", "-version", "1.22.0", "-poll", "600s", "-http", ":8080"] 27 | -------------------------------------------------------------------------------- /scripts/docker-workshop/clean-jenkins-data.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env powershell 2 | 3 | $BASE = "$HOME\docker-workshop" 4 | 5 | Write-Host "This will reset all the Jenkins data under $BASE !!!" 6 | Write-Host "" 7 | Write-Host "Are you sure this is what you want to do?" 8 | 9 | $confirmation = Read-Host "You must type 'yes' to confirm: " 10 | if ($confirmation -eq 'yes') { 11 | rm "$BASE/layout/jenkins/data/*" -r -fo 12 | rm "$BASE/layout/jenkins/data/.groovy" -r -fo 13 | rm "$BASE/layout/jenkins/data/.java" -r -fo 14 | cp "$BASE/layout/postgres/data/.git_keep" "$BASE/layout/jenkins/data/.git_keep" 15 | Write-Host "completed" 16 | } else { 17 | Write-Host "aborted" 18 | } 19 | 20 | -------------------------------------------------------------------------------- /scripts/class-docker-cicd/clean-jenkins-data.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env powershell 2 | 3 | $BASE = "$HOME\class-docker-cicd" 4 | 5 | Write-Host "This will reset all the Jenkins data under $BASE !!!" 6 | Write-Host "" 7 | Write-Host "Are you sure this is what you want to do?" 8 | 9 | $confirmation = Read-Host "You must type 'yes' to confirm: " 10 | if ($confirmation -eq 'yes') { 11 | rm "$BASE/layout/jenkins/data/*" -r -fo 12 | rm "$BASE/layout/jenkins/data/.groovy" -r -fo 13 | rm "$BASE/layout/jenkins/data/.java" -r -fo 14 | cp "$BASE/layout/postgres/data/.git_keep" "$BASE/layout/jenkins/data/.git_keep" 15 | Write-Host "completed" 16 | } else { 17 | Write-Host "aborted" 18 | } 19 | 20 | -------------------------------------------------------------------------------- /scripts/docker-workshop/clean-all-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE="${HOME}/docker-workshop" 4 | 5 | echo "This will reset all the class data under ${BASE} !!!" 6 | echo 7 | echo "Are you sure this is what you want to do?" 8 | read -p "You must type 'yes' to confirm: " -r 9 | echo 10 | if [[ $REPLY == "yes" ]]; then 11 | rm -rf ${BASE}/layout/postgres/data/data 12 | rm -rf ${BASE}/layout/registry/data/docker 13 | rm -rf ${BASE}/layout/jenkins/data/* 14 | rm -rf ${BASE}/layout/jenkins/data/.groovy 15 | rm -rf ${BASE}/layout/jenkins/data/.java 16 | rm -rf ${BASE}/layout/gogs/data/git 17 | rm -rf ${BASE}/layout/gogs/data/gogs 18 | rm -rf ${BASE}/layout/gogs/data/ssh 19 | echo "completed" 20 | else 21 | echo "aborted" 22 | fi 23 | 24 | -------------------------------------------------------------------------------- /scripts/class-docker-cicd/clean-all-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE="${HOME}/class-docker-cicd" 4 | 5 | echo "This will reset all the class data under ${BASE} !!!" 6 | echo 7 | echo "Are you sure this is what you want to do?" 8 | read -p "You must type 'yes' to confirm: " -r 9 | echo 10 | if [[ $REPLY == "yes" ]]; then 11 | rm -rf ${BASE}/layout/postgres/data/data 12 | rm -rf ${BASE}/layout/registry/data/docker 13 | rm -rf ${BASE}/layout/jenkins/data/* 14 | rm -rf ${BASE}/layout/jenkins/data/.groovy 15 | rm -rf ${BASE}/layout/jenkins/data/.java 16 | rm -rf ${BASE}/layout/gogs/data/git 17 | rm -rf ${BASE}/layout/gogs/data/gogs 18 | rm -rf ${BASE}/layout/gogs/data/ssh 19 | echo "completed" 20 | else 21 | echo "aborted" 22 | fi 23 | 24 | -------------------------------------------------------------------------------- /outyet/Dockerfile.minimal: -------------------------------------------------------------------------------- 1 | FROM golang:1.22.0 AS builder 2 | # Build Image 3 | 4 | COPY . /go/src/outyet 5 | WORKDIR /go/src/outyet 6 | 7 | ENV CGO_ENABLED=0 8 | ENV GOOS=linux 9 | 10 | RUN go get -v -d && \ 11 | go install -v && \ 12 | go test -v && \ 13 | go build -ldflags "-s" -a -installsuffix cgo -o outyet . 14 | 15 | FROM scratch AS deploy 16 | # Deploy Image 17 | 18 | WORKDIR / 19 | COPY --from=builder /go/src/outyet/outyet . 20 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 21 | # (or) COPY --from=0 /go/src/outyet/outyet . 22 | # (or) COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 23 | 24 | EXPOSE 8080 25 | 26 | CMD ["/outyet", "-version", "1.22.0", "-poll", "600s", "-http", ":8080"] 27 | -------------------------------------------------------------------------------- /terraform/bin/self_healing.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -u 5 | 6 | source ./bin/ip_vars.sh 1> /dev/null 7 | 8 | curl -s -o /dev/null -w "%{http_code}" http://${primary_manager_ip}:80/ 9 | echo; echo 10 | docker -H ${primary_manager_ip}:2375 service ls --format "{{.ID}}: {{.Name}} {{.Replicas}}" 11 | echo 12 | set +e 13 | curl -s -o /dev/null -w "%{http_code}" http://${primary_manager_ip}:80/die 14 | set -e 15 | echo; echo 16 | docker -H ${primary_manager_ip}:2375 service ls --format "{{.ID}}: {{.Name}} {{.Replicas}}" 17 | echo 18 | for i in {1..4}; do 19 | sleep 1 20 | curl -s -o /dev/null -w "%{http_code}" http://${primary_manager_ip}:80/ 21 | echo; echo 22 | docker -H ${primary_manager_ip}:2375 service ls --format "{{.ID}}: {{.Name}} {{.Replicas}}" 23 | echo 24 | done 25 | 26 | -------------------------------------------------------------------------------- /registry/config.yaml: -------------------------------------------------------------------------------- 1 | version: 0.1 2 | log: 3 | accesslog: 4 | disabled: false 5 | level: info 6 | fields: 7 | service: registry 8 | environment: development 9 | storage: 10 | delete: 11 | enabled: true 12 | cache: 13 | blobdescriptor: inmemory 14 | filesystem: 15 | rootdirectory: /var/lib/registry 16 | http: 17 | addr: :5000 18 | headers: 19 | X-Content-Type-Options: [nosniff] 20 | auth: 21 | htpasswd: 22 | realm: class-realm 23 | path: /htpasswd 24 | notifications: 25 | endpoints: 26 | - name: local-8083 27 | url: http://localhost:8083/callback 28 | timeout: 1s 29 | threshold: 10 30 | backoff: 1s 31 | disabled: true 32 | health: 33 | storagedriver: 34 | enabled: true 35 | interval: 10s 36 | threshold: 3 37 | -------------------------------------------------------------------------------- /terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aws_profile" { 2 | description = "AWS Profile for credentials" 3 | default = "oreilly-aws" 4 | } 5 | 6 | variable "ssh_private_key_path" { 7 | description = "Path to EC2 SSH private key" 8 | default = "/Users/spkane/.ssh/oreilly_aws" 9 | } 10 | 11 | variable "ssh_public_key_path" { 12 | description = "Path to EC2 SSH public key" 13 | default = "/Users/spkane/.ssh/oreilly_aws.pub" 14 | } 15 | 16 | #variable "public_ip_path" { 17 | # description = "Path to file containing public IP" 18 | # default = "/Users/spkane/.public_home_ip" 19 | #} 20 | 21 | variable "swarm_manager_count" { 22 | description = "Number of swarm managers" 23 | default = 3 24 | } 25 | 26 | variable "swarm_worker_count" { 27 | description = "Number of swarm workers" 28 | default = 4 29 | } 30 | 31 | -------------------------------------------------------------------------------- /registry/certs/san.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | default_bits = 2048 3 | distinguished_name = req_distinguished_name 4 | req_extensions = v3_req 5 | 6 | [req_distinguished_name] 7 | countryName = Country Name (2 letter code) 8 | countryName_default = US 9 | stateOrProvinceName = State or Province Name (full name) 10 | stateOrProvinceName_default = OR 11 | localityName = Locality Name (eg, city) 12 | localityName_default = Portland 13 | organizationalUnitName = Organizational Unit Name (eg, section) 14 | organizationalUnitName_default = example 15 | commonName = example.org 16 | commonName_max = 64 17 | 18 | [ v3_req ] 19 | # Extensions to add to a certificate request 20 | basicConstraints = CA:FALSE 21 | subjectAltName = @alt_names 22 | 23 | [alt_names] 24 | DNS.1 = private-registry.localdomain 25 | DNS.2 = localhost.localdomain 26 | DNS.3 = localhost 27 | IP.1 = 127.0.0.1 28 | -------------------------------------------------------------------------------- /scripts/docker-workshop/clean-all-data.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env powershell 2 | 3 | $BASE = "$HOME\docker-workshop" 4 | 5 | Write-Host "This will reset all the class data under $BASE !!!" 6 | Write-Host "" 7 | Write-Host "Are you sure this is what you want to do?" 8 | 9 | $confirmation = Read-Host "You must type 'yes' to confirm: " 10 | if ($confirmation -eq 'yes') { 11 | rm "$BASE/layout/postgres/data/data" -r -fo 12 | rm "$BASE/layout/registry/data/docker" -r -fo 13 | rm "$BASE/layout/jenkins/data/*" -r -fo 14 | rm "$BASE/layout/jenkins/data/.groovy" -r -fo 15 | rm "$BASE/layout/jenkins/data/.java" -r -fo 16 | rm "$BASE/layout/gogs/data/git" -r -fo 17 | rm "$BASE/layout/gogs/data/gogs" -r -fo 18 | rm "$BASE/layout/gogs/data/ssh" -r -fo 19 | cp "$BASE/layout/postgres/data/.git_keep" "$BASE/layout/jenkins/data/.git_keep" 20 | Write-Host "completed" 21 | } else { 22 | Write-Host "aborted" 23 | } 24 | 25 | -------------------------------------------------------------------------------- /scripts/class-docker-cicd/clean-all-data.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env powershell 2 | 3 | $BASE = "$HOME\class-docker-cicd" 4 | 5 | Write-Host "This will reset all the class data under $BASE !!!" 6 | Write-Host "" 7 | Write-Host "Are you sure this is what you want to do?" 8 | 9 | $confirmation = Read-Host "You must type 'yes' to confirm: " 10 | if ($confirmation -eq 'yes') { 11 | rm "$BASE/layout/postgres/data/data" -r -fo 12 | rm "$BASE/layout/registry/data/docker" -r -fo 13 | rm "$BASE/layout/jenkins/data/*" -r -fo 14 | rm "$BASE/layout/jenkins/data/.groovy" -r -fo 15 | rm "$BASE/layout/jenkins/data/.java" -r -fo 16 | rm "$BASE/layout/gogs/data/git" -r -fo 17 | rm "$BASE/layout/gogs/data/gogs" -r -fo 18 | rm "$BASE/layout/gogs/data/ssh" -r -fo 19 | cp "$BASE/layout/postgres/data/.git_keep" "$BASE/layout/jenkins/data/.git_keep" 20 | Write-Host "completed" 21 | } else { 22 | Write-Host "aborted" 23 | } 24 | 25 | -------------------------------------------------------------------------------- /terraform/bin/ip_vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then 4 | echo "The script ${BASH_SOURCE[0]} is being sourced..." 5 | echo 6 | else 7 | echo "You must source this bash script, not run it directly." 8 | echo 9 | exit 1 10 | fi 11 | 12 | export managers=$(terraform output | grep manager | cut -d " " -f 3) 13 | export workers=$(terraform output | grep worker | cut -d " " -f 3) 14 | num=0 15 | for i in $(echo $managers | sed "s/,/ /g" | sed 's/"/ /g' ) 16 | do 17 | all_manager_ips[${num}]=${i} 18 | echo "\${all_manager_ips[${num}]}: ${all_manager_ips[${num}]}" 19 | let num+=1 20 | done 21 | export primary_manager_ip=(${all_manager_ips[0]}) 22 | export secondary_manager_ips=(${all_manager_ips[@]:1}) 23 | export worker_ips=() 24 | num=0 25 | for i in $(echo $workers | sed "s/,/ /g" | sed 's/"/ /g' ) 26 | do 27 | worker_ips[${num}]=${i} 28 | echo "\${worker_ips[${num}]}: ${worker_ips[${num}]}" 29 | let num+=1 30 | done 31 | 32 | -------------------------------------------------------------------------------- /registry/certs/domain.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+jCCAeICCQCHPFP6qP40xzANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQGEwJV 3 | UzELMAkGA1UECAwCT1IxETAPBgNVBAcMCFBvcnRsYW5kMRAwDgYDVQQLDAdleGFt 4 | cGxlMB4XDTIwMDExMjE3NDgzOFoXDTMwMDEwOTE3NDgzOFowPzELMAkGA1UEBhMC 5 | VVMxCzAJBgNVBAgMAk9SMREwDwYDVQQHDAhQb3J0bGFuZDEQMA4GA1UECwwHZXhh 6 | bXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALKch+w4MDM46fpz 7 | BwUGysJA5xSKznjfr+9nSTLZ48k3kVXvwJ+1yYxJwa/yt8937ZTlQnQSjxphSu7l 8 | Br80VnjEAvO5Eq+xY7IfubPiSRHtTmExvRdRAmhEN00EF+Ib5cz5fcgiy1LSAfs4 9 | mo+jSc0N0+Y6mKm/08VjCraJs1XfchkmQlLItyeR14CA4x/Jevol/DiNCCH9Mi1y 10 | YK3VyY8gHeW0HE2VvhFj7D+H/L+OzNeh63GMzrYVOm60Q7aPlTtcL0nwAOMZcaVg 11 | FvnEWmR5uPHffDqZkW7z6xxc2O2q05xC4ZAOg5Q8g3bh7QmbGvMlSvJlBfk/9VD8 12 | I9NCijUCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAeQiO0S3c5BphvjR43wP5dxy8 13 | qoPHZFAioHAiwipwHj1VapS4O1hKdj7WAnV0gJ/VfyMZzChe35V4Q83sRGNil6dA 14 | 2OTX4ql7V5TNE5txvb48n09txWTFk5th1Uvw9JvrW7gqBpmy1rJ/3iSFU5+kX8TD 15 | wtX1hZp81gtkTW0MijVSwkcy+cRMtsXPUy69DbO1A7Ik3W34KLMnA0uJ01rytrI4 16 | kn4z4BL+YaNJpBOIb44CpsrXxh4rRF0lyYggtrvKQrdNFacM4aM7uGaqNXH50pFq 17 | +UNf1IWcr+4qvqFGblnEwRVTyedfL3d0mM2gia3YVUQK/JrH3GsRrcX5kKziUQ== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /gogs/conf/app.ini: -------------------------------------------------------------------------------- 1 | BRAND_NAME = Gogs 2 | RUN_USER = git 3 | RUN_MODE = prod 4 | 5 | [repository] 6 | DEFAULT_BRANCH = main 7 | ROOT = /data/git/gogs-repositories 8 | 9 | [security] 10 | LOCAL_NETWORK_ALLOWLIST = jenkins 11 | INSTALL_LOCK = true 12 | SECRET_KEY = 1HianFdxykORQym 13 | 14 | [database] 15 | TYPE = postgres 16 | HOST = postgres:5432 17 | NAME = gogs 18 | SCHEMA = public 19 | USER = postgres 20 | PASSWORD = myuser-pw! 21 | SSL_MODE = disable 22 | PATH = /app/gogs/data/gogs.db 23 | 24 | [server] 25 | DOMAIN = localhost 26 | HTTP_PORT = 3000 27 | EXTERNAL_URL = http://127.0.0.1:10090/ 28 | DISABLE_SSH = false 29 | SSH_PORT = 10022 30 | START_SSH_SERVER = false 31 | OFFLINE_MODE = false 32 | 33 | [mailer] 34 | ENABLED = false 35 | 36 | [auth] 37 | REQUIRE_EMAIL_CONFIRMATION = false 38 | DISABLE_REGISTRATION = false 39 | ENABLE_REGISTRATION_CAPTCHA = true 40 | REQUIRE_SIGNIN_VIEW = false 41 | 42 | [user] 43 | ENABLE_EMAIL_NOTIFICATION = false 44 | 45 | [picture] 46 | DISABLE_GRAVATAR = false 47 | ENABLE_FEDERATED_AVATAR = false 48 | 49 | [session] 50 | PROVIDER = file 51 | 52 | [log] 53 | MODE = file 54 | LEVEL = Info 55 | ROOT_PATH = /app/gogs/log 56 | -------------------------------------------------------------------------------- /terraform/security-group.tf: -------------------------------------------------------------------------------- 1 | /* Default security group */ 2 | resource "aws_security_group" "swarm" { 3 | name = "swarm-training-spkane" 4 | description = "Default security group that allows inbound and outbound traffic from all instances in the VPC" 5 | vpc_id = data.aws_vpc.default.id 6 | 7 | ingress { 8 | from_port = "0" 9 | to_port = "0" 10 | protocol = "-1" 11 | cidr_blocks = ["0.0.0.0/0"] 12 | self = true 13 | } 14 | 15 | ingress { 16 | from_port = 22 17 | to_port = 22 18 | protocol = "tcp" 19 | cidr_blocks = ["0.0.0.0/0"] 20 | } 21 | 22 | ingress { 23 | from_port = 80 24 | to_port = 80 25 | protocol = "tcp" 26 | cidr_blocks = ["${data.external.public_ip.result.public_ip}/32"] 27 | } 28 | 29 | ingress { 30 | from_port = 8080 31 | to_port = 8080 32 | protocol = "tcp" 33 | cidr_blocks = ["${data.external.public_ip.result.public_ip}/32"] 34 | } 35 | 36 | ingress { 37 | from_port = 2375 38 | to_port = 2375 39 | protocol = "tcp" 40 | cidr_blocks = ["${data.external.public_ip.result.public_ip}/32"] 41 | } 42 | 43 | egress { 44 | from_port = "0" 45 | to_port = "0" 46 | protocol = "-1" 47 | cidr_blocks = ["0.0.0.0/0"] 48 | self = true 49 | } 50 | egress { 51 | from_port = 22 52 | to_port = 22 53 | protocol = "tcp" 54 | cidr_blocks = ["0.0.0.0/0"] 55 | } 56 | 57 | tags = { 58 | Name = "swarm-training-spkane" 59 | Trainer = "Sean P. Kane" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /registry/certs/domain.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCynIfsODAzOOn6 3 | cwcFBsrCQOcUis5436/vZ0ky2ePJN5FV78CftcmMScGv8rfPd+2U5UJ0Eo8aYUru 4 | 5Qa/NFZ4xALzuRKvsWOyH7mz4kkR7U5hMb0XUQJoRDdNBBfiG+XM+X3IIstS0gH7 5 | OJqPo0nNDdPmOpipv9PFYwq2ibNV33IZJkJSyLcnkdeAgOMfyXr6Jfw4jQgh/TIt 6 | cmCt1cmPIB3ltBxNlb4RY+w/h/y/jszXoetxjM62FTputEO2j5U7XC9J8ADjGXGl 7 | YBb5xFpkebjx33w6mZFu8+scXNjtqtOcQuGQDoOUPIN24e0JmxrzJUryZQX5P/VQ 8 | /CPTQoo1AgMBAAECggEBALChT2Fw52mgPPKp0iVMZDLZ0CtmYWzzeJ79u0uIYlTY 9 | mOctI4ZNVbVgMyR2mQOYi+CVH7R9rfregCXjY1quvVzMKkcvnKUWfYrnCNQ1USFj 10 | VgGUGHT4bbMC0tVc2OmHwSeFCrhigEO03rS7c03cot0fcY/aHqKvyxMGMM/3ONwP 11 | ZrmvqtH41+n66kELvLEjhB+th55bF8DPzCVUj3aDFQ+GbwECT0jsLYFsqALy8zes 12 | 6lztwnvVVDfTZDukPaz6EGgkz30PSA8sos/nHDPdVUitcfCjCMqb3EaUxyswypBW 13 | rBanKhZTV6uKgoZZnQM3xcYHiyY4PJy2zMHpeFdI+zkCgYEA4o37Mham4BEzTmvC 14 | esHSbXBctqn6U5rXNHb1/KY56HnYAGRU0CaXHRWRlpc/wfAEfGZm3mD0DtF/AM4a 15 | j7WvfpqNVTMp1u6a1DZpqNwY07lQojHetvh3TqUeaHieZ8CSioEb8q4cRZkWZXIo 16 | R5DZDCH1+b0Te1KlqoLj2rMZfpsCgYEAydNdaoaNOb1RKOoPXjpVzPl+affy1Qdq 17 | Mi4nDThZj28fZ4XWplWDFjKhnApHLamgqcjBI+vSpgodCeQw6YFiy+y+/XlArEOW 18 | OGPfDbdPtSDoSFiSeQhRpkuWbf878VVpYqxVZ+/B+x6mnl/gAOMtEUxrfKrNVOEo 19 | 8eM39rWtv28CgYAaf0Ke5BlWYYyfomjXlK240Qh28MBvYM/EuhkhGIvzTbTert1g 20 | jvLjmu8xLrVtSFYt+ogTgEFrLkNtY62lmzlQVGEtUtOU6MlBArAu6LcapDTzE2Qa 21 | AEtr7lH1JA5a0iIRgvIy2vxBg5sj+EsPu7g2/A3ZzKxnow3vOGnP3Vyg+wKBgHHa 22 | zgkpLIRVG3LUT6UdjYebe8f1+0RX8X2rcZZQop1616GD7CpbdR7Roz+uwKau2ZbO 23 | TL6h62yFqgoJc9pWhlB6FuhRPb2wRw86r/tB3TJWBRPC+6ZRnhYM8CjPYpZrU485 24 | ntiKdcjfaqlPdsOpZKQskzp59Gmct4d1Yf3IUeL7AoGBAL1WZ3kwRK+/YoWmbGJN 25 | FDftUroAbUbVP0pf2sKFKlG7WsO4veIKcIbGO9yGmQwUGxo9T4nSsJULDNkVRcO0 26 | Zg2AetO9AsHTrjF0Zbvxa3EJrQNZbPEOGP4Mxf6aYDyFw5ewTnzhbqECdbw/QYBk 27 | OBhH9OI8/3vz4neaspBQrA1p 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /compose/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | container_name: class_postgres_4 4 | image: docker.io/postgres:15 5 | restart: unless-stopped 6 | networks: 7 | - my-net 8 | ports: 9 | - "15432:5432" 10 | environment: 11 | POSTGRES_USER: "postgres" 12 | POSTGRES_PASSWORD: "${MY_PG_PASS}" 13 | POSTGRES_DB: "gogs" 14 | healthcheck: 15 | test: ["CMD", "pg_isready", "-U", "postgres", "-d", "gogs"] 16 | interval: 10s 17 | timeout: 5s 18 | retries: 5 19 | gogs: 20 | container_name: class_gogs_4 21 | image: docker.io/gogs/gogs:0.13 22 | restart: unless-stopped 23 | networks: 24 | - my-net 25 | depends_on: 26 | # https://docs.docker.com/compose/compose-file/compose-file-v2/#depends_on 27 | # https://docs.docker.com/compose/compose-file/compose-file-v2/#healthcheck 28 | postgres: 29 | condition: service_healthy 30 | ports: 31 | - "10022:22" 32 | - "10090:3000" 33 | volumes: 34 | - "../gogs/conf:/data/gogs/conf" 35 | registry: 36 | container_name: class_registry_4 37 | image: docker.io/registry:2.8.2 38 | restart: unless-stopped 39 | networks: 40 | - my-net 41 | ports: 42 | - "5000:5000" 43 | environment: 44 | REGISTRY_HTTP_TLS_CERTIFICATE: "/certs/domain.crt" 45 | REGISTRY_HTTP_TLS_KEY: "/certs/domain.key" 46 | REGISTRY_HTTP_SECRET: "iuehfio73bt8dobq" 47 | volumes: 48 | - "../registry/config.yaml:/etc/docker/registry/config.yaml" 49 | - "../registry/htpasswd:/htpasswd" 50 | - "../registry/data:/var/lib/registry" 51 | - "../registry/certs:/certs" 52 | jenkins: 53 | container_name: class_jenkins_4 54 | #build: 55 | # context: ../../jenkins/docker 56 | # If you can't build, use this image: `spkane/dc-201-jenkins:latest` 57 | image: docker.io/spkane/dc-201-jenkins:latest 58 | restart: unless-stopped 59 | networks: 60 | - my-net 61 | ports: 62 | - "10091:8080" 63 | - "50000:50000" 64 | volumes: 65 | - "../jenkins/data:/var/jenkins_home" 66 | - "/var/run/docker.sock:/var/run/docker.sock" 67 | networks: 68 | my-net: 69 | driver: bridge 70 | -------------------------------------------------------------------------------- /jenkins/jobs/outyet/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | build and test outyet 5 | false 6 | 7 | 8 | 2 9 | 10 | 11 | http://gogs:3000/myuser/outyet.git 12 | 13 | 14 | 15 | 16 | ** 17 | 18 | 19 | false 20 | 21 | 22 | 23 | true 24 | false 25 | false 26 | false 27 | 28 | false 29 | 30 | 31 | docker login --username=myuser --password=${DOCKER_PW} 127.0.0.1:5000 32 | docker build -t 127.0.0.1:5000/myuser/outyet:build_${GIT_COMMIT} -f Dockerfile.build . 33 | export CONTAINER=`docker create 127.0.0.1:5000/myuser/outyet:build_${GIT_COMMIT}` 34 | docker cp ${CONTAINER}:/go/src/outyet/outyet . 35 | docker build -t 127.0.0.1:5000/myuser/outyet:deploy_${GIT_COMMIT} -f Dockerfile.deploy . 36 | docker push 127.0.0.1:5000/myuser/outyet:deploy_${GIT_COMMIT} 37 | 38 | 39 | 40 | 41 | 42 | false 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /terraform/README.md: -------------------------------------------------------------------------------- 1 | Forked from: https://github.com/Praqma/terraform-aws-docker 2 | 3 | # Terraform + AWS + Docker Swarm setup 4 | 5 | Here is the basic setup to run up docker swarm cluster in AWS using the Terraform. 6 | [Terraform](https://www.terraform.io) is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions. Using Terraform helps to create the infrastructure you can change and trace, safely and efficiently. A medium-sized swarm cluster will be created during startup. Three swarm managers and ten swarm workers. In the *app-instances.tf* you will find the core configuration. 7 | 8 | ## Installation 9 | Directions on how to install terraform can be found [here](https://www.terraform.io/intro/getting-started/install.html). Or you can using a Docker image to keep your environment clear. For example, [this one](https://hub.docker.com/r/amontaigu/terraform/). 10 | 11 | ## Preparations 12 | ### AWS account 13 | If you don't have account, you may get a free AWS account. In the setup will be used free t2.micro instances. 14 | #### SSH keys 15 | Before you start, you need to create your ssh keys. Terraform will create a key-pair in AWS, based on these keys. See [how to create ssh keys](https://confluence.atlassian.com/bitbucketserver/creating-ssh-keys-776639788.html) 16 | Create a .pem file with private ssh key you generated. Terraform will need to the .pem file to connect to instances for provisioning. 17 | #### Update the project file with new information 18 | There are three files that need your credentials to be successfully setup. First of all, update *key-pair.tf* and set the path to the public ssh key, generated earlier. In *variables.tf* update your AWS account information. In *app-instances.tf* update the connection block for each resource with the path to the ssh private key. 19 | 20 | ## How to use 21 | After all configuration files are ready, you can do check to see if there are any mistakes. 22 | ``` 23 | terraform plan 24 | ``` 25 | This command will either show a syntax error or a list of resources that will be created. After this you can run: 26 | ``` 27 | terraform apply 28 | ``` 29 | This command will build and run all resources in the *.tf files. If you run this command many times, Terraform may destroy previous instances before creating new ones. 30 | 31 | That is it. Now you have fully functioned docker swarm cluster in AWS. 32 | 33 | If you want to terminate instances and destroy the configuration you may call: 34 | ``` 35 | terraform destroy 36 | ``` 37 | -------------------------------------------------------------------------------- /outyet/main_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "net/http" 21 | "net/http/httptest" 22 | "strings" 23 | "testing" 24 | "time" 25 | ) 26 | 27 | // statusHandler is an http.Handler that writes an empty response using itself 28 | // as the response status code. 29 | type statusHandler int 30 | 31 | func (h *statusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 32 | w.WriteHeader(int(*h)) 33 | } 34 | 35 | func TestIsTagged(t *testing.T) { 36 | // Set up a fake "Google Code" web server reporting 404 not found. 37 | status := statusHandler(http.StatusNotFound) 38 | s := httptest.NewServer(&status) 39 | defer s.Close() 40 | 41 | if isTagged(s.URL) { 42 | t.Fatal("isTagged == true, want false") 43 | } 44 | 45 | // Change fake server status to 200 OK and try again. 46 | status = http.StatusOK 47 | 48 | if !isTagged(s.URL) { 49 | t.Fatal("isTagged == false, want true") 50 | } 51 | } 52 | 53 | func TestIntegration(t *testing.T) { 54 | status := statusHandler(http.StatusNotFound) 55 | ts := httptest.NewServer(&status) 56 | defer ts.Close() 57 | 58 | // Replace the pollSleep with a closure that we can block and unblock. 59 | sleep := make(chan bool) 60 | pollSleep = func(time.Duration) { 61 | sleep <- true 62 | sleep <- true 63 | } 64 | 65 | // Replace pollDone with a closure that will tell us when the poller is 66 | // exiting. 67 | done := make(chan bool) 68 | pollDone = func() { done <- true } 69 | 70 | // Put things as they were when the test finishes. 71 | defer func() { 72 | pollSleep = time.Sleep 73 | pollDone = func() {} 74 | }() 75 | 76 | s := NewServer("1.x", ts.URL, "localhost", 1*time.Millisecond, "") 77 | 78 | <-sleep // Wait for poll loop to start sleeping. 79 | 80 | // Make first request to the server. 81 | r, _ := http.NewRequest("GET", "/", nil) 82 | w := httptest.NewRecorder() 83 | s.ServeHTTP(w, r) 84 | if b := w.Body.String(); !strings.Contains(b, "No.") { 85 | t.Fatalf("body = %s, want no", b) 86 | } 87 | 88 | status = http.StatusOK 89 | 90 | <-sleep // Permit poll loop to stop sleeping. 91 | <-done // Wait for poller to see the "OK" status and exit. 92 | 93 | // Make second request to the server. 94 | w = httptest.NewRecorder() 95 | s.ServeHTTP(w, r) 96 | if b := w.Body.String(); !strings.Contains(b, "YES!") { 97 | t.Fatalf("body = %q, want yes", b) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /terraform/app-instances.tf: -------------------------------------------------------------------------------- 1 | /* Setup our aws provider */ 2 | provider "aws" { 3 | profile = var.aws_profile 4 | region = "us-east-1" 5 | } 6 | 7 | data "external" "public_ip" { 8 | program = ["./bin/local-ip.sh"] 9 | } 10 | 11 | resource "aws_instance" "manager" { 12 | count = var.swarm_manager_count 13 | ami = "ami-5c66ea23" 14 | instance_type = "m4.large" 15 | security_groups = [aws_security_group.swarm.name] 16 | key_name = aws_key_pair.deployer.key_name 17 | connection { 18 | host = self.public_ip 19 | user = "ubuntu" 20 | private_key = file(var.ssh_private_key_path) 21 | } 22 | provisioner "file" { 23 | source = "./files/startup_options.conf" 24 | destination = "/tmp/startup_options.conf" 25 | } 26 | provisioner "file" { 27 | source = "./files/daemon.json" 28 | destination = "/tmp/daemon.json" 29 | } 30 | provisioner "remote-exec" { 31 | inline = [ 32 | "sudo apt-get update", 33 | "sudo apt-get install -y linux-image-extra-$(uname -r) linux-image-extra-virtual", 34 | "sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common", 35 | "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -", 36 | "sudo add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\"", 37 | "sudo apt-get update", 38 | "sudo apt-get install -y docker-ce", 39 | "sudo usermod -aG docker ubuntu", 40 | "wget https://storage.googleapis.com/gvisor/releases/nightly/latest/runsc", 41 | "chmod +x runsc", 42 | "sudo mv runsc /usr/local/bin", 43 | "sudo cp /tmp/daemon.json /etc/docker/daemon.json", 44 | "sudo mkdir -p /etc/systemd/system/docker.service.d/", 45 | "sudo cp /tmp/startup_options.conf /etc/systemd/system/docker.service.d/startup_options.conf", 46 | "sudo systemctl daemon-reload", 47 | "sudo service docker restart", 48 | ] 49 | } 50 | tags = { 51 | Name = "swarm-manager-${count.index}-spkane" 52 | Trainer = "Sean P. Kane" 53 | } 54 | } 55 | 56 | resource "aws_instance" "worker" { 57 | count = var.swarm_worker_count 58 | ami = "ami-5c66ea23" 59 | instance_type = "m4.large" 60 | security_groups = [aws_security_group.swarm.name] 61 | key_name = aws_key_pair.deployer.key_name 62 | connection { 63 | host = self.public_ip 64 | user = "ubuntu" 65 | private_key = file(var.ssh_private_key_path) 66 | } 67 | provisioner "file" { 68 | source = var.ssh_private_key_path 69 | destination = "/home/ubuntu/key.pem" 70 | } 71 | provisioner "file" { 72 | source = "./files/daemon.json" 73 | destination = "/tmp/daemon.json" 74 | } 75 | provisioner "remote-exec" { 76 | inline = [ 77 | "sudo apt-get update", 78 | "sudo apt-get install -y linux-image-extra-$(uname -r) linux-image-extra-virtual", 79 | "sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common", 80 | "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -", 81 | "sudo add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\"", 82 | "sudo apt-get update", 83 | "sudo apt-get install -y docker-ce", 84 | "sudo chmod 400 /home/ubuntu/key.pem", 85 | "sudo usermod -aG docker ubuntu", 86 | "wget https://storage.googleapis.com/gvisor/releases/nightly/latest/runsc", 87 | "chmod +x runsc", 88 | "sudo mv runsc /usr/local/bin", 89 | "sudo cp /tmp/daemon.json /etc/docker/daemon.json", 90 | "sudo systemctl restart docker", 91 | ] 92 | } 93 | tags = { 94 | Name = "swarm-worker-${count.index}-spkane" 95 | Trainer = "Sean P. Kane" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /jenkins/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/jenkins/jenkins:2.405 2 | USER root 3 | 4 | RUN apt-get -y update && \ 5 | apt -y remove \ 6 | docker \ 7 | docker.io \ 8 | containerd \ 9 | runc && \ 10 | apt-get -y install \ 11 | ca-certificates \ 12 | curl \ 13 | gnupg && \ 14 | install -m 0755 -d /etc/apt/keyrings && \ 15 | curl -fsSL https://download.docker.com/linux/debian/gpg | \ 16 | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \ 17 | chmod a+r /etc/apt/keyrings/docker.gpg && \ 18 | echo \ 19 | "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ 20 | "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ 21 | tee /etc/apt/sources.list.d/docker.list > /dev/null && \ 22 | apt-get -y update && \ 23 | apt-get -y install \ 24 | docker-ce \ 25 | docker-ce-cli \ 26 | containerd.io \ 27 | docker-buildx-plugin \ 28 | docker-compose-plugin && \ 29 | gpasswd -a jenkins staff && \ 30 | gpasswd -a jenkins users && \ 31 | gpasswd -a jenkins docker && \ 32 | apt-get clean 33 | 34 | USER jenkins 35 | 36 | # Get from a running Jenkins: http://127.0.0.1:8080/script 37 | # Jenkins.instance.pluginManager.plugins.each{plugin -> println ("${plugin.getShortName()}:${plugin.getVersion()}")} 38 | # We add `embeddable-build-status`, `gogs-webhook` and `mask-passwords` to the defaults 39 | RUN jenkins-plugin-cli --plugins \ 40 | ant:487.vd79d090d4ea_e \ 41 | antisamy-markup-formatter:159.v25b_c67cd35fb_ \ 42 | apache-httpcomponents-client-4-api:4.5.14-150.v7a_b_9d17134a_5 \ 43 | bootstrap5-api:5.2.2-3 \ 44 | bouncycastle-api:2.27 \ 45 | branch-api:2.1092.vda_3c2a_a_f0c11 \ 46 | build-timeout:1.30 \ 47 | caffeine-api:3.1.6-115.vb_8b_b_328e59d8 \ 48 | checks-api:2.0.0 \ 49 | cloudbees-folder:6.815.v0dd5a_cb_40e0e \ 50 | commons-lang3-api:3.12.0-36.vd97de6465d5b_ \ 51 | commons-text-api:1.10.0-36.vc008c8fcda_7b_ \ 52 | credentials-binding:604.vb_64480b_c56ca_ \ 53 | credentials:1254.vb_96f366e7b_a_d \ 54 | display-url-api:2.3.7 \ 55 | durable-task:507.v050055d0cb_dd \ 56 | echarts-api:5.4.0-3 \ 57 | email-ext:2.97 \ 58 | embeddable-build-status:369.vb_a_68a_575a_b_11 \ 59 | font-awesome-api:6.3.0-2 \ 60 | git-client:4.2.0 \ 61 | git:5.0.2 \ 62 | github-api:1.314-431.v78d72a_3fe4c3 \ 63 | github-branch-source:1703.vd5a_2b_29c6cdc \ 64 | github:1.37.1 \ 65 | gogs-webhook:1.0.15 \ 66 | gradle:2.7 \ 67 | instance-identity:142.v04572ca_5b_265 \ 68 | ionicons-api:45.vf54fca_5d2154 \ 69 | jackson2-api:2.15.1-344.v6eb_55303dc3e \ 70 | jakarta-activation-api:2.0.1-3 \ 71 | jakarta-mail-api:2.0.1-3 \ 72 | javax-activation-api:1.2.0-6 \ 73 | javax-mail-api:1.6.2-9 \ 74 | jaxb:2.3.8-1 \ 75 | jjwt-api:0.11.5-77.v646c772fddb_0 \ 76 | jquery3-api:3.6.4-1 \ 77 | junit:1202.v79a_986785076 \ 78 | ldap:682.v7b_544c9d1512 \ 79 | mailer:448.v5b_97805e3767 \ 80 | mask-passwords:150.vf80d33113e80 \ 81 | matrix-auth:3.1.7 \ 82 | matrix-project:789.v57a_725b_63c79 \ 83 | mina-sshd-api-common:2.10.0-69.v28e3e36d18eb_ \ 84 | mina-sshd-api-core:2.10.0-69.v28e3e36d18eb_ \ 85 | okhttp-api:4.10.0-132.v7a_7b_91cef39c \ 86 | pam-auth:1.10 \ 87 | pipeline-build-step:491.v1fec530da_858 \ 88 | pipeline-github-lib:42.v0739460cda_c4 \ 89 | pipeline-graph-analysis:202.va_d268e64deb_3 \ 90 | pipeline-groovy-lib:656.va_a_ceeb_6ffb_f7 \ 91 | pipeline-input-step:468.va_5db_051498a_4 \ 92 | pipeline-milestone-step:111.v449306f708b_7 \ 93 | pipeline-model-api:2.2131.vb_9788088fdb_5 \ 94 | pipeline-model-definition:2.2131.vb_9788088fdb_5 \ 95 | pipeline-model-extensions:2.2131.vb_9788088fdb_5 \ 96 | pipeline-rest-api:2.32 \ 97 | pipeline-stage-step:305.ve96d0205c1c6 \ 98 | pipeline-stage-tags-metadata:2.2131.vb_9788088fdb_5 \ 99 | pipeline-stage-view:2.32 \ 100 | plain-credentials:143.v1b_df8b_d3b_e48 \ 101 | plugin-util-api:3.2.1 \ 102 | resource-disposer:0.22 \ 103 | scm-api:672.v64378a_b_20c60 \ 104 | script-security:1244.ve463715a_f89c \ 105 | snakeyaml-api:1.33-95.va_b_a_e3e47b_fa_4 \ 106 | ssh-credentials:305.v8f4381501156 \ 107 | ssh-slaves:2.877.v365f5eb_a_b_eec \ 108 | sshd:3.303.vefc7119b_ec23 \ 109 | structs:324.va_f5d6774f3a_d \ 110 | timestamper:1.25 \ 111 | token-macro:359.vb_cde11682e0c \ 112 | trilead-api:2.84.v72119de229b_7 \ 113 | variant:59.vf075fe829ccb \ 114 | workflow-aggregator:596.v8c21c963d92d \ 115 | workflow-api:1213.v646def1087f9 \ 116 | workflow-basic-steps:1017.vb_45b_302f0cea_ \ 117 | workflow-cps:3659.v582dc37621d8 \ 118 | workflow-durable-task-step:1246.v5524618ea_097 \ 119 | workflow-job:1295.v395eb_7400005 \ 120 | workflow-multibranch:746.v05814d19c001 \ 121 | workflow-scm-step:408.v7d5b_135a_b_d49 \ 122 | workflow-step-api:639.v6eca_cd8c04a_a_ \ 123 | workflow-support:839.v35e2736cfd5c \ 124 | ws-cleanup:0.45 125 | 126 | ENV no_proxy gogs 127 | 128 | # We really want this to run as the Jenkins user 129 | # but for class it is challenging to get the docker socket 130 | # permissions correct for every student, so we use root instead. 131 | USER root 132 | -------------------------------------------------------------------------------- /outyet/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // outyet is a web server that announces whether or not a particular Go version 18 | // has been tagged. 19 | package main 20 | 21 | import ( 22 | "errors" 23 | "expvar" 24 | "flag" 25 | "fmt" 26 | "html/template" 27 | "log" 28 | "net/http" 29 | "os" 30 | "strings" 31 | "sync" 32 | "time" 33 | ) 34 | 35 | // Command-line flags. 36 | var ( 37 | httpAddr = flag.String("http", ":8088", "Listen address") 38 | pollPeriod = flag.Duration("poll", 5*time.Second, "Poll period") 39 | version = flag.String("version", "1.22.0", "Go version") 40 | ) 41 | 42 | const baseChangeURL = "https://go.googlesource.com/go/+/" 43 | 44 | func main() { 45 | flag.Parse() 46 | hostname, err := os.Hostname() 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | changeURL := fmt.Sprintf("%sgo%s", baseChangeURL, *version) 52 | http.Handle("/", NewServer(*version, changeURL, hostname, *pollPeriod, "")) 53 | http.Handle("/die", NewServer(*version, changeURL, hostname, *pollPeriod, "die")) 54 | log.Fatal(http.ListenAndServe(*httpAddr, nil)) 55 | } 56 | 57 | func die() { 58 | os.Exit(99) 59 | } 60 | 61 | // Exported variables for monitoring the server. 62 | // These are exported via HTTP as a JSON object at /debug/vars. 63 | var ( 64 | hitCount = expvar.NewInt("hitCount") 65 | pollCount = expvar.NewInt("pollCount") 66 | pollError = expvar.NewString("pollError") 67 | pollErrorCount = expvar.NewInt("pollErrorCount") 68 | ) 69 | 70 | // Server implements the outyet server. 71 | // It serves the user interface (it's an http.Handler) 72 | // and polls the remote repository for changes. 73 | type Server struct { 74 | version string 75 | url string 76 | hostname string 77 | logic string 78 | period time.Duration 79 | config_message string 80 | secret_message string 81 | 82 | mu sync.RWMutex // protects the yes variable 83 | yes bool 84 | } 85 | 86 | // NewServer returns an initialized outyet server. 87 | func NewServer(version, url string, hostname string, period time.Duration, logic string) *Server { 88 | s := &Server{version: version, url: url, hostname: hostname, period: period, logic: logic} 89 | go s.poll() 90 | return s 91 | } 92 | 93 | // getEnvVar return the env var value. 94 | func getEnvVar() string { 95 | config_message := "Note: envvar is not set in the environment." 96 | if _, err := os.Stat("/etc/config/variable.env"); errors.Is(err, os.ErrNotExist) { 97 | // Do nothing 98 | } else { 99 | cfile, err := os.ReadFile("/etc/config/variable.env") 100 | if err != nil { 101 | fmt.Print(err) 102 | } 103 | config_message = fmt.Sprintf("envar (via file) is set to: %s", cfile) 104 | } 105 | // Prefer variable over file. 106 | envvar, exists := os.LookupEnv("envvar") 107 | if exists != false { 108 | config_message = fmt.Sprintf("envar is set to: %s", envvar) 109 | } 110 | return config_message 111 | } 112 | 113 | // getSecret return the env var value. 114 | func getSecret() string { 115 | secret_message := "Note: secret is not set in the environment." 116 | if _, err := os.Stat("/etc/config/secret.env"); errors.Is(err, os.ErrNotExist) { 117 | // Do nothing 118 | } else { 119 | sfile, err := os.ReadFile("/etc/config/secret.env") 120 | if err != nil { 121 | fmt.Print(err) 122 | } 123 | length := len(sfile) 124 | redacted_secret := strings.Repeat("*", length) 125 | secret_message = fmt.Sprintf("secret (via file) is set to: %s", redacted_secret) 126 | } 127 | // Prefer variable over file. 128 | secret, s_exists := os.LookupEnv("secret") 129 | if s_exists != false { 130 | length := len(secret) 131 | redacted_secret := strings.Repeat("*", length) 132 | secret_message = fmt.Sprintf("secret is set to: %s", redacted_secret) 133 | } 134 | return secret_message 135 | } 136 | 137 | // poll polls the change URL for the specified period until the tag exists. 138 | // Then it sets the Server's yes field true and exits. 139 | func (s *Server) poll() { 140 | for !isTagged(s.url) { 141 | pollSleep(s.period) 142 | } 143 | s.mu.Lock() 144 | s.yes = true 145 | s.mu.Unlock() 146 | pollDone() 147 | } 148 | 149 | // Hooks that may be overridden for integration tests. 150 | var ( 151 | pollSleep = time.Sleep 152 | pollDone = func() {} 153 | ) 154 | 155 | // isTagged makes an HTTP HEAD request to the given URL and reports whether it 156 | // returned a 200 OK response. 157 | func isTagged(url string) bool { 158 | pollCount.Add(1) 159 | r, err := http.Head(url) 160 | if err != nil { 161 | log.Print(err) 162 | pollError.Set(err.Error()) 163 | pollErrorCount.Add(1) 164 | return false 165 | } 166 | return r.StatusCode == http.StatusOK 167 | } 168 | 169 | // ServeHTTP implements the HTTP user interface. 170 | func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 171 | hitCount.Add(1) 172 | s.mu.RLock() 173 | 174 | config_message := getEnvVar() 175 | secret_message := getSecret() 176 | 177 | data := struct { 178 | ConfigMessage string 179 | SecretMessage string 180 | URL string 181 | Version string 182 | Hostname string 183 | Logic string 184 | Yes bool 185 | }{ 186 | config_message, 187 | secret_message, 188 | s.url, 189 | s.version, 190 | s.hostname, 191 | s.logic, 192 | s.yes, 193 | } 194 | s.mu.RUnlock() 195 | if s.logic == "die" { 196 | die() 197 | } 198 | err := tmpl.Execute(w, data) 199 | if err != nil { 200 | log.Print(err) 201 | } 202 | } 203 | 204 | // tmpl is the HTML template that drives the user interface. 205 | var tmpl = template.Must(template.New("tmpl").Parse(` 206 | 207 | 208 |
209 |

Is Go {{.Version}} out yet?

210 |

211 | {{if .Yes}} 212 | YES! 213 | {{else}} 214 | No. :-(

215 | But you can see a list of all releases here:

216 | https://github.com/golang/go/releases 217 | {{end}} 218 |

219 |

Hostname: {{.Hostname}}

220 |
221 |

{{.ConfigMessage}}
222 | {{.SecretMessage}}

223 |
224 |
225 | `)) 226 | --------------------------------------------------------------------------------