├── README.md ├── k8s ├── README.md ├── assets │ ├── BuildWorkflow.png │ └── PromoteWorkflow.png ├── deploy-demo-env.md ├── diagrams │ ├── BuildWorkflow.drawio │ └── PromoteWorkflow.drawio ├── run-demo.md └── scripts │ ├── clean-demo.sh │ └── deploy-demo.sh └── ocp ├── argocd ├── README.md ├── assets │ ├── BuildWorkflow.png │ └── PromoteWorkflow.png ├── deploy-demo-env.md ├── diagrams │ ├── BuildWorkflow.drawio │ └── PromoteWorkflow.drawio ├── run-demo.md └── scripts │ ├── clean-demo.sh │ └── deploy-demo.sh └── rhacm ├── README.md ├── assets ├── BuildWorkflow.png └── PromoteWorkflow.png ├── deploy-demo-env.md ├── diagrams ├── BuildWorkflow.drawio └── PromoteWorkflow.drawio ├── run-demo.md └── scripts ├── clean-demo.sh └── deploy-demo.sh /README.md: -------------------------------------------------------------------------------- 1 | # Code to Production Demo 2 | 3 | For running the demo on K8s look [here](k8s/) 4 | 5 | For running the demo on OCP look [here](ocp/) 6 | -------------------------------------------------------------------------------- /k8s/README.md: -------------------------------------------------------------------------------- 1 | # Code to Production Demo 2 | 3 | For the required steps to build a demo environment look [here](./deploy-demo-env.md) 4 | 5 | For the required steps to run the demo look [here](./run-demo.md) 6 | 7 | ## Scripts 8 | 9 | There are scripts provided that automate the demo environment creation and cleanup, you will need to edit them to match your environment. 10 | 11 | You will need to provide your git forks and custom ingress hostnames. 12 | 13 | The scripts can be found on the [scripts](./scripts) folder. 14 | -------------------------------------------------------------------------------- /k8s/assets/BuildWorkflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvazquezc/code-to-prod-demo/918bf017206708e68e9a1af1434c236640fa1fd4/k8s/assets/BuildWorkflow.png -------------------------------------------------------------------------------- /k8s/assets/PromoteWorkflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvazquezc/code-to-prod-demo/918bf017206708e68e9a1af1434c236640fa1fd4/k8s/assets/PromoteWorkflow.png -------------------------------------------------------------------------------- /k8s/deploy-demo-env.md: -------------------------------------------------------------------------------- 1 | # Create a cluster with KCli 2 | 3 | [KCli Project](https://github.com/karmab/kcli) 4 | 5 | 1. Deploy a K8s cluster using KCli 6 | 7 | ~~~sh 8 | kcli create kube generic -P masters=1 -P workers=1 -P master_memory=4096 -P numcpus=2 -P worker_memory=4096 -P sdn=calico -P version=1.18 -P ingress=true -P ingress_method=nginx -P metallb=true codetoprodcluster 9 | ~~~ 10 | 2. Once deployed configure a demo domain name in dnsmasq for the Ingress Controller 11 | 12 | > **NOTE**: I will use NetworkManager DNSMasq, feel free to use something different in your env 13 | 1. Get the NGinx Ingress SVC IP 14 | 15 | ~~~sh 16 | NGINX_CONTROLLER_IP=$(kubectl -n ingress-nginx get svc ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[*].ip}') 17 | ~~~ 18 | 19 | 2. Configure the DNS domain for the Ingress Controller 20 | ~~~sh 21 | sudo echo "address=/mario.lab/${NGINX_CONTROLLER_IP}" > /etc/NetworkManager/dnsmasq.d/mario.lab 22 | ~~~ 23 | 3. Deploy Argo CD 24 | 25 | ~~~sh 26 | kubectl create namespace argocd 27 | kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v1.6.1/manifests/install.yaml 28 | ~~~ 29 | 4. Get the Argo CD admin user password 30 | 31 | ~~~sh 32 | mkdir -p /var/tmp/code-to-prod-demo/ 33 | ARGOCD_PASSWORD=$(kubectl -n argocd get pods -l app.kubernetes.io/name=argocd-server -o name | awk -F "/" '{print $2}') 34 | echo $ARGOCD_PASSWORD > /var/tmp/code-to-prod-demo/argocd-password 35 | ~~~ 36 | 5. Patch the NGINX Ingress controller to support ssl-passthrough 37 | 38 | ~~~sh 39 | kubectl -n ingress-nginx patch deployment ingress-nginx-controller -p '{"spec":{"template":{"spec":{"$setElementOrder/containers":[{"name":"controller"}],"containers":[{"args":["/nginx-ingress-controller","--election-id=ingress-controller-leader","--ingress-class=nginx","--configmap=ingress-nginx/ingress-nginx-controller","--validating-webhook=:8443","--validating-webhook-certificate=/usr/local/certificates/cert","--validating-webhook-key=/usr/local/certificates/key","--publish-status-address=localhost","--enable-ssl-passthrough"],"name":"controller"}]}}}}' 40 | ~~~ 41 | 6. Create an ingress object for accessing Argo CD WebUI 42 | 43 | > **NOTE**: You need to use your own hostname for the Ingress hostname 44 | 45 | ~~~sh 46 | cat < **NOTE**: You need to fork these repositories and use your fork (so you have full-access) 78 | 79 | ~~~sh 80 | git clone git@github.com:mvazquezc/reverse-words.git /var/tmp/code-to-prod-demo/reverse-words 81 | git clone git@github.com:mvazquezc/reverse-words-cicd.git /var/tmp/code-to-prod-demo/reverse-words-cicd 82 | ~~~ 83 | 2. Go to the reverse-words-cicd repo and checkout the CI branch which contains our Tekton manifests 84 | 85 | ~~~sh 86 | cd /var/tmp/code-to-prod-demo/reverse-words-cicd 87 | git checkout ci 88 | ~~~ 89 | 3. Create a namespace for storing the configuration for our reversewords app pipeline 90 | 91 | ~~~sh 92 | kubectl create namespace tekton-reversewords 93 | ~~~ 94 | 4. Add the quay credentials to the credentials file 95 | 96 | ~~~sh 97 | QUAY_USER= 98 | read -s QUAY_PASSWORD 99 | sed -i "s//$QUAY_USER/" quay-credentials.yaml 100 | sed -i "s//$QUAY_PASSWORD/" quay-credentials.yaml 101 | ~~~ 102 | 5. Create a Secret containing the credentials to access our Git repository 103 | 104 | > **NOTE**: You need to provide a token with push access to the cicd repository 105 | 106 | ~~~sh 107 | read -s GIT_AUTH_TOKEN 108 | kubectl -n tekton-reversewords create secret generic image-updater-secret --from-literal=token=${GIT_AUTH_TOKEN} 109 | ~~~ 110 | 6. Import credentials into the cluster 111 | 112 | ~~~sh 113 | kubectl -n tekton-reversewords create -f quay-credentials.yaml 114 | ~~~ 115 | 7. Create a ServiceAccount with access to the credentials created in the previous step 116 | 117 | ~~~sh 118 | kubectl -n tekton-reversewords create -f pipeline-sa.yaml 119 | ~~~ 120 | 8. Create the Linter Task which will lint our code 121 | 122 | ~~~sh 123 | kubectl -n tekton-reversewords create -f lint-task.yaml 124 | ~~~ 125 | 9. Create the Tester Task which will run the tests in our app 126 | 127 | ~~~sh 128 | kubectl -n tekton-reversewords create -f test-task.yaml 129 | ~~~ 130 | 10. Create the Builder Task which will build a container image for our app 131 | 132 | ~~~sh 133 | kubectl -n tekton-reversewords create -f build-task.yaml 134 | ~~~ 135 | 11. Create the Image Update Task which will update the Deployment on a given branch after a successful image build 136 | 137 | ~~~sh 138 | kubectl -n tekton-reversewords create -f image-updater-task.yaml 139 | ~~~ 140 | 12. Edit some parameters from our Build Pipeline definition 141 | 142 | > **NOTE**: You need to use your forks address in the substitutions below 143 | 144 | ~~~sh 145 | sed -i "s||https://github.com/mvazquezc/reverse-words|" build-pipeline.yaml 146 | sed -i "s||quay.io/mavazque/tekton-reversewords|" build-pipeline.yaml 147 | sed -i "s||github.com/mvazquezc/reverse-words|" build-pipeline.yaml 148 | sed -i "s||mvazquezc/reverse-words-cicd|" build-pipeline.yaml 149 | ~~~ 150 | 13. Create the Build Pipeline definition which will be used to execute the previous tasks in an specific order with specific parameters 151 | 152 | ~~~sh 153 | kubectl -n tekton-reversewords create -f build-pipeline.yaml 154 | ~~~ 155 | 14. Create the curl task which will be used to query our apps on the promoter pipeline 156 | 157 | ~~~sh 158 | kubectl -n tekton-reversewords create -f curl-task.yaml 159 | ~~~ 160 | 15. Create the task that gets the stage release from the git cicd repository 161 | 162 | ~~~sh 163 | kubectl -n tekton-reversewords create -f get-stage-release-task.yaml 164 | ~~~ 165 | 16. Edit some parameters from our Promoter Pipeline definition 166 | 167 | > **NOTE**: You need to use your forks address/quay account in the substitutions below 168 | 169 | ~~~sh 170 | sed -i "s||https://github.com/mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 171 | sed -i "s||quay.io/mavazque/tekton-reversewords|" promote-to-prod-pipeline.yaml 172 | sed -i "s||mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 173 | sed -i "s||./deployment.yaml|" promote-to-prod-pipeline.yaml 174 | ~~~ 175 | 17. Create the Promoter Pipeline definition which will be used to execute the previous tasks in an specific order with specific parameters 176 | 177 | ~~~sh 178 | kubectl -n tekton-reversewords create -f promote-to-prod-pipeline.yaml 179 | ~~~ 180 | 18. Create the required Roles and RoleBindings for working with Webhooks 181 | 182 | ~~~sh 183 | kubectl -n tekton-reversewords create -f webhook-roles.yaml 184 | ~~~ 185 | 19. Create the TriggerBinding for reading data received by a webhook and pass it to the Pipeline 186 | 187 | ~~~sh 188 | kubectl -n tekton-reversewords create -f github-triggerbinding.yaml 189 | ~~~ 190 | 20. Create the TriggerTemplate and Event Listener to run the Pipeline when new commits hit the main branch of our app repository 191 | 192 | ~~~sh 193 | WEBHOOK_SECRET="v3r1s3cur3" 194 | kubectl -n tekton-reversewords create secret generic webhook-secret --from-literal=secret=${WEBHOOK_SECRET} 195 | sed -i "s//github-triggerbinding/" webhook.yaml 196 | kubectl -n tekton-reversewords create -f webhook.yaml 197 | ~~~ 198 | 21. We need to provide an ingress point for our EventListener, we want it to be TLS, so we need to generate some certs 199 | 200 | > **NOTE**: Use your own custom hostname for the tekton-events component when generating the key 201 | 202 | ~~~sh 203 | mkdir -p /var/tmp/code-to-prod-demo/tls-certs/ 204 | cd $_ 205 | openssl genrsa -out tls-certs/tekton-events.key 2048 206 | openssl req -new -key /var/tmp/code-to-prod-demo/tls-certs/tekton-events.key -out /var/tmp/code-to-prod-demo/tls-certs/tekton-events.csr -subj "/C=US/ST=TX/L=Austin/O=RedHat/CN=tekton-events.mario.lab" 207 | ~~~ 208 | 22. Send the CSR to the Kubernetes server to get it signed with the Kubernetes CA 209 | /var/tmp/code-to-prod-demo/ 210 | ~~~sh 211 | cat < /var/tmp/code-to-prod-demo/tls-certs/tekton-events.crt 229 | ~~~ 230 | 24. Create a secret with the TLS certificates 231 | 232 | ~~~sh 233 | cd /var/tmp/code-to-prod-demo/tls-certs/ 234 | kubectl -n tekton-reversewords create secret generic tekton-events-tls --from-file=tls.crt=tekton-events.crt --from-file=tls.key=tekton-events.key 235 | ~~~ 236 | 25. Configure a TLS ingress which uses the certs created 237 | 238 | > **NOTE**: Use your own custom hostname 239 | 240 | ~~~sh 241 | cat < **NOTE**: Use your own custom hostname for the tekton-dashboard component when generating the key 266 | 267 | ~~~sh 268 | cd /var/tmp/code-to-prod-demo/tls-certs/ 269 | openssl genrsa -out /var/tmp/code-to-prod-demo/tls-certs/tekton-dashboard.key 2048 270 | openssl req -new -key /var/tmp/code-to-prod-demo/tls-certs/tekton-dashboard.key -out /var/tmp/code-to-prod-demo/tls-certs/tekton-dashboard.csr -subj "/C=US/ST=TX/L=Austin/O=RedHat/CN=tekton-dashboard.mario.lab" 271 | ~~~ 272 | 27. Send the CSR to the Kubernetes server to get it signed with the Kubernetes CA 273 | 274 | ~~~sh 275 | cat < /var/tmp/code-to-prod-demo/tls-certs/tekton-dashboard.crt 293 | ~~~ 294 | 29. Create a secret with the TLS certificates 295 | 296 | ~~~sh 297 | cd /var/tmp/code-to-prod-demo/tls-certs/ 298 | kubectl -n tekton-pipelines create secret generic tekton-dashboard-tls --from-file=tls.crt=tekton-dashboard.crt --from-file=tls.key=tekton-dashboard.key 299 | ~~~ 300 | 30. Configure a TLS ingress which uses the certs created 301 | 302 | > **NOTE**: Use your own custom hostname 303 | 304 | ~~~sh 305 | cat < **NOTE**: Use your custom Argo CD ingress for login 340 | 341 | ~~~sh 342 | argocd login argocd.mario.lab --insecure --username admin --password $(cat /var/tmp/code-to-prod-demo/argocd-password) 343 | ~~~ 344 | 3. Update Argo CD password 345 | 346 | ~~~sh 347 | argocd account update-password --account admin --current-password $(cat /var/tmp/code-to-prod-demo/argocd-password) --new-password 'r3dh4t1!' 348 | ~~~ 349 | -------------------------------------------------------------------------------- /k8s/run-demo.md: -------------------------------------------------------------------------------- 1 | # Demo Workflows 2 | 3 | ## Build Workflow 4 | 5 | ![Build Workflow](./assets/BuildWorkflow.png) 6 | 7 | ## Image Promotion Workflow 8 | 9 | ![Promotion Workflow](./assets/PromoteWorkflow.png) 10 | 11 | # Argo CD 12 | 13 | ## Configuring the Argo CD Apps 14 | 15 | 1. Add the Git repository for our application to Argo CD 16 | 17 | > **NOTE**: Use your fork on the command below 18 | 19 | ~~~sh 20 | argocd repo add https://github.com/mvazquezc/reverse-words-cicd.git --name reversewords-cicd 21 | ~~~ 22 | 2. Edit the ingresses for our applications before creating them in Argo CD 23 | 24 | > **NOTE**: Update the ingresses on your fork to match your environment hostnames 25 | 26 | ~~~sh 27 | cd /var/tmp/code-to-prod-demo/reverse-words-cicd 28 | # Stash previous changes 29 | git stash 30 | # Update staging ingress 31 | git checkout stage 32 | sed -i "s/host: .*/host: reversewords-dev.mario.lab/" ingress.yaml 33 | # Push stage changes 34 | git commit -am "Added ingress hostname" 35 | git push origin stage 36 | # Update production ingress 37 | git checkout prod 38 | sed -i "s/host: .*/host: reversewords-prod.mario.lab/" ingress.yaml 39 | # Push prod changes 40 | git commit -am "Added ingress hostname" 41 | git push origin prod 42 | ~~~ 43 | 3. Define Development application 44 | 45 | > **NOTE**: Use your fork on the command below 46 | 47 | ~~~sh 48 | argocd app create --project default --name reverse-words-stage \ 49 | --repo https://github.com/mvazquezc/reverse-words-cicd.git \ 50 | --path . \ 51 | --dest-server https://kubernetes.default.svc \ 52 | --dest-namespace reverse-words-stage --revision stage \ 53 | --self-heal --sync-policy automated 54 | ~~~ 55 | 4. Define Production application 56 | 57 | > **NOTE**: Use your fork on the command below 58 | 59 | ~~~sh 60 | argocd app create --project default --name reverse-words-production \ 61 | --repo https://github.com/mvazquezc/reverse-words-cicd.git \ 62 | --path . \ 63 | --dest-server https://kubernetes.default.svc \ 64 | --dest-namespace reverse-words-production --revision prod \ 65 | --self-heal --sync-policy automated 66 | ~~~ 67 | 5. At this point the applications will be deployed automatically and Argo CD will poll the Git repository in order to detect configuration drifts every 3 minutes, when that happens, Argo CD will automatically apply the config stored in Git 68 | 69 | ## Triggering the Build Pipeline using the WebHook 70 | 71 | We are going to use WebHooks in order to run Pipelines automatically when new commits hit the branches of our app and cicd repositories. 72 | 73 | * Our first webhook will receive events from the application repository, when new code hits the main branch we will trigger the build pipeline. 74 | * Our second webhook will receive events from the cicd repository, when new code hits `stage` or `prod` branches we will trigger a new deployment using Argo CD. 75 | 76 | 1. We will configure the first webhook on the app repo 77 | 78 | > **NOTE**: Every Git server has its own properties, but basically you want to provide the ingress url for our webhook and when the Git server should send the hook. E.g: push events, PR events, etc. 79 | 80 | 1. Go to your application repository on GitHub, eg: https://github.com/mvazquezc/reverse-words 81 | 2. Click on `Settings` -> `Webhooks` 82 | 3. Create the following `Hook` 83 | 1. `Payload URL`: https://tekton-events.mario.lab 84 | 2. `Content type`: application/json 85 | 2. `Secret`: v3r1s3cur3 86 | 3. `Events`: Check **Push Events**, leave others blank 87 | 4. `Active`: Check it 88 | 5. `SSL verification`: Check **Disable** 89 | 6. Click on `Add webhook` 90 | 2. Now, we will configure the second webhook to react to changes on the cicd repository 91 | 92 | > **NOTE**: Argo CD comes with Webhooks enabled by default, that means that we just need to use the following url as Webhook endpoint, `https:///api/webhook` 93 | 94 | 1. Go to your cicd repository on GitHub, eg: https://github.com/mvazquezc/reverse-words-cicd 95 | 2. Click on `Settings` -> `Webhooks` 96 | 3. Create the following `Hook` 97 | 1. `Payload URL`: https://argocd.oss20.mario.lab/api/webhook 98 | 2. `Content type`: application/json 99 | 2. `Secret`: v3r1s3cur3 100 | 3. `Events`: Check **Push Events**, leave others blank 101 | 4. `Active`: Check it 102 | 5. `SSL verification`: Check **Disable** 103 | 6. Click on `Add webhook` 104 | 4. We need to configure our `Secret Token` on Argo CD 105 | ~~~sh 106 | WEBHOOK_SECRET="v3r1s3cur3" 107 | kubectl -n argocd patch secret argocd-secret -p "{\"data\":{\"webhook.github.secret\":\"$(echo -n $WEBHOOK_SECRET | base64)\"}}" --type=merge 108 | ~~~ 109 | 3. Now we should have a working Webhook, let's test it 110 | 111 | 1. Deploy tkn cli 112 | 113 | ~~~sh 114 | sudo curl -L https://github.com/tektoncd/cli/releases/download/v0.10.0/tkn_0.10.0_Linux_x86_64.tar.gz | tar xz tkn 115 | chown root: tkn && mv tkn /usr/bin/ 116 | ~~~ 117 | 2. We need to commit to the main branch, let's update the release number 118 | 119 | ~~~sh 120 | cd /var/tmp/code-to-prod-demo/reverse-words/ 121 | CURRENT_RELEASE=$(grep "var version" main.go | awk -F '"' '{print $2}' | awk -F "." 'BEGIN{FS=OFS="."}{NF--; print}') 122 | NEW_MINOR=$(grep "var version" main.go | awk -F '"' '{print $2}' | awk -F "." '{print $NF+1}') 123 | NEW_RELEASE="${CURRENT_RELEASE}.${NEW_MINOR}" 124 | sed -i "s|var version = .*|var version = \"${NEW_RELEASE}\"|" main.go 125 | git diff main.go 126 | git add main.go 127 | git commit -m "Release updated to $NEW_RELEASE" 128 | git push origin main 129 | ~~~ 130 | 3. Connect to the Tekton Dashboard (https://tekton-dashboard.) 131 | 1. You can see the PipelineRun on the dashboard and follow the log 132 | 4. We can check the running images for our application pod and see that when the pipeline finishes a new deployment is triggered on ArgoCD 133 | 5. When the Build pipeline finishes we can promote the new build to production 134 | 135 | > **NOTE**: Change the stageAppUrl to match your environment ingress 136 | 137 | ~~~sh 138 | tkn -n tekton-reversewords pipeline start reverse-words-promote-pipeline -r app-git=reverse-words-cicd-git -p pathToDeploymentFile=./deployment.yaml -p stageBranch=stage -p stageAppUrl=http://reversewords-dev.mario.lab 139 | ~~~ 140 | 141 | ## Tekton Polling Operator 142 | 143 | [Project Repository](https://github.com/bigkevmcd/tekton-polling-operator) 144 | 145 | Sometimes your clusters cannot be accessed from the Internet, thus, webhooks won't be a valid option for automatically run our pipelines upon git changes. In this scenario we can use the `Tekton Polling Operator` which will poll our repo in a given time interval and run the Pipeline when needed. 146 | 147 | You can run the polling operators in two ways: 148 | 149 | 1. Deploy a polling operator in the namespace where the Pipeline definitions are created 150 | 2. Deploy a global polling operator and give it access to the different namespaces with Pipelines definitions 151 | 152 | We are going to follow the second approach. 153 | 154 | > **NOTE**: Tekton Pipelines must be deployed in the cluster before deploying the operator 155 | 156 | 1. Create a namespace and deploy the operator 157 | 158 | ~~~sh 159 | kubectl create namespace tekton-polling-operator 160 | kubectl -n tekton-polling-operator apply -f https://github.com/bigkevmcd/tekton-polling-operator/releases/download/v0.2.0/release-v0.2.0.yaml 161 | ~~~ 162 | 2. Create the required ClusterRole and ClusterRoleBinding for the Operator to create PipelineRuns on the tekton-reversewords namespace 163 | 164 | ~~~sh 165 | cat < /tmp/test-secret.yaml 269 | ~~~ 270 | 4. Seal the test secret 271 | 272 | ~~~sh 273 | kubeseal -o yaml < /tmp/test-secret.yaml > test-secret-sealed.yaml 274 | ~~~ 275 | 5. Update the Kustomization and push the sealed secret to the git repository 276 | 277 | ~~~sh 278 | sed -i "s|patchesStrategicMerge:|- test-secret-sealed.yaml\npatchesStrategicMerge:|" kustomization.yaml 279 | git add kustomization.yaml test-secret-sealed.yaml 280 | git commit -m "Add Sealed Secret" 281 | git push origin stage 282 | ~~~ 283 | -------------------------------------------------------------------------------- /k8s/scripts/clean-demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -rf /var/tmp/code-to-prod-demo/ 3 | CLUSTER_NAME="codetoprod" 4 | kcli delete kube $CLUSTER_NAME 5 | -------------------------------------------------------------------------------- /k8s/scripts/deploy-demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -ne "Enter your quay.io username: " 4 | read QUAY_USER 5 | echo -ne "Enter your quay.io password: " 6 | read -s QUAY_PASSWORD 7 | echo -ne "\nEnter your Git Token: " 8 | read -s GIT_AUTH_TOKEN 9 | echo -ne "\nEnter the ingress controller domain (e.g: mario.lab): " 10 | read INGRESS_DOMAIN 11 | 12 | echo "Deploying K8s cluster" 13 | 14 | CLUSTER_NAME="codetoprod" 15 | kcli create kube generic -P masters=1 -P workers=1 -P master_memory=4096 -P numcpus=2 -P worker_memory=4096 -P sdn=calico -P version=1.18 -P ingress=true -P ingress_method=nginx -P metallb=true $CLUSTER_NAME 16 | 17 | export KUBECONFIG=$PWD/clusters/codetoprod/auth/kubeconfig 18 | 19 | echo "Patch Ingress Controller to support passthrough connections" 20 | kubectl -n ingress-nginx patch deployment ingress-nginx-controller -p '{"spec":{"template":{"spec":{"$setElementOrder/containers":[{"name":"controller"}],"containers":[{"args":["/nginx-ingress-controller","--election-id=ingress-controller-leader","--ingress-class=nginx","--configmap=ingress-nginx/ingress-nginx-controller","--validating-webhook=:8443","--validating-webhook-certificate=/usr/local/certificates/cert","--validating-webhook-key=/usr/local/certificates/key","--publish-status-address=localhost","--enable-ssl-passthrough"],"name":"controller"}]}}}}' 21 | kubectl wait --for=condition=available --timeout=600s deployment/ingress-nginx-controller -n ingress-nginx 22 | echo "Deploy Argo CD" 23 | kubectl create namespace argocd 24 | kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v1.6.1/manifests/install.yaml 25 | sleep 5 26 | echo -ne "Waiting for NGINX Controller to be ready" 27 | until [[ $(kubectl -n ingress-nginx get pods -l app.kubernetes.io/component=controller -o jsonpath='{.items[0].status.containerStatuses[0].ready}' 2>/dev/null) == "true" ]]; do echo -ne "."; sleep 5;done 28 | echo "done" 29 | echo "Create Ingress for Argo CD" 30 | cat </dev/null) == "true" ]]; do echo -ne "."; sleep 5;done 56 | echo "done" 57 | echo -ne "Waiting for Tekton Webhook to be ready" 58 | until [[ $(kubectl -n tekton-pipelines get pods -l app.kubernetes.io/component=webhook -o jsonpath='{.items[0].status.containerStatuses[0].ready}' 2>/dev/null) == "true" ]]; do echo -ne "."; sleep 5;done 59 | echo "done" 60 | mkdir -p /var/tmp/code-to-prod-demo/ 61 | git clone git@github.com:mvazquezc/reverse-words.git /var/tmp/code-to-prod-demo/reverse-words 62 | git clone git@github.com:mvazquezc/reverse-words-cicd.git /var/tmp/code-to-prod-demo/reverse-words-cicd 63 | cd ~/reverse-words-cicd 64 | git checkout ci 65 | echo "Create Tekton resources for the demo" 66 | kubectl create namespace tekton-reversewords 67 | sed -i "s//$QUAY_USER/" quay-credentials.yaml 68 | sed -i "s//$QUAY_PASSWORD/" quay-credentials.yaml 69 | kubectl -n tekton-reversewords create secret generic image-updater-secret --from-literal=token=${GIT_AUTH_TOKEN} 70 | kubectl -n tekton-reversewords create -f quay-credentials.yaml 71 | kubectl -n tekton-reversewords create -f pipeline-sa.yaml 72 | kubectl -n tekton-reversewords create -f lint-task.yaml 73 | kubectl -n tekton-reversewords create -f test-task.yaml 74 | kubectl -n tekton-reversewords create -f build-task.yaml 75 | kubectl -n tekton-reversewords create -f image-updater-task.yaml 76 | sed -i "s||https://github.com/mvazquezc/reverse-words|" build-pipeline.yaml 77 | sed -i "s||quay.io/mavazque/tekton-reversewords|" build-pipeline.yaml 78 | sed -i "s||github.com/mvazquezc/reverse-words|" build-pipeline.yaml 79 | sed -i "s||mvazquezc/reverse-words-cicd|" build-pipeline.yaml 80 | kubectl -n tekton-reversewords create -f build-pipeline.yaml 81 | kubectl -n tekton-reversewords create -f webhook-roles.yaml 82 | kubectl -n tekton-reversewords create -f github-triggerbinding.yaml 83 | WEBHOOK_SECRET="v3r1s3cur3" 84 | kubectl -n tekton-reversewords create secret generic webhook-secret --from-literal=secret=${WEBHOOK_SECRET} 85 | sed -i "s//github-triggerbinding/" webhook.yaml 86 | kubectl -n tekton-reversewords create -f webhook.yaml 87 | kubectl -n tekton-reversewords create -f curl-task.yaml 88 | kubectl -n tekton-reversewords create -f get-stage-release-task.yaml 89 | sed -i "s||https://github.com/mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 90 | sed -i "s||quay.io/mavazque/tekton-reversewords|" promote-to-prod-pipeline.yaml 91 | sed -i "s||mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 92 | sed -i "s||./deployment.yaml|" promote-to-prod-pipeline.yaml 93 | kubectl -n tekton-reversewords create -f promote-to-prod-pipeline.yaml 94 | mkdir -p /var/tmp/code-to-prod-demo/tls-certs/ 95 | cd /var/tmp/code-to-prod-demo/tls-certs/ 96 | openssl genrsa -out /var/tmp/code-to-prod-demo/tls-certs/tekton-events.key 2048 97 | openssl req -new -key /var/tmp/code-to-prod-demo/tls-certs/tekton-events.key -out /var/tmp/code-to-prod-demo/tls-certs/tekton-events.csr -subj "/C=US/ST=TX/L=Austin/O=RedHat/CN=tekton-events.${INGRESS_DOMAIN}" 98 | cat < /var/tmp/code-to-prod-demo/tls-certs/tekton-events.crt 112 | kubectl -n tekton-reversewords create secret generic tekton-events-tls --from-file=tls.crt=tekton-events.crt --from-file=tls.key=tekton-events.key 113 | until [[ $(kubectl -n ingress-nginx get pods -l app.kubernetes.io/component=controller -o jsonpath='{.items[0].status.containerStatuses[0].ready}') == "true" ]]; do echo "Waiting for nginx controller to be ready"; sleep 2;done 114 | cat < /var/tmp/code-to-prod-demo/tls-certs/tekton-dashboard.crt 151 | kubectl -n tekton-pipelines create secret generic tekton-dashboard-tls --from-file=tls.crt=tekton-dashboard.crt --from-file=tls.key=tekton-dashboard.key 152 | cat < **NOTE**: You need to fork these repositories and use your fork (so you have full-access) 90 | 91 | ~~~sh 92 | mkdir -p /var/tmp/code-to-prod-demo/ 93 | git clone git@github.com:mvazquezc/reverse-words.git /var/tmp/code-to-prod-demo/reverse-words 94 | git clone git@github.com:mvazquezc/reverse-words-cicd.git /var/tmp/code-to-prod-demo/reverse-words-cicd 95 | ~~~ 96 | 2. Go to the reverse-words-cicd repo and checkout the CI branch which contains our Tekton manifests 97 | 98 | ~~~sh 99 | cd /var/tmp/code-to-prod-demo/reverse-words-cicd 100 | git checkout ci 101 | ~~~ 102 | 3. Create a namespace for storing the configuration for our reversewords app pipeline 103 | 104 | ~~~sh 105 | oc create namespace reversewords-ci 106 | ~~~ 107 | 4. Add the quay credentials to the credentials file 108 | 109 | ~~~sh 110 | QUAY_USER= 111 | read -s QUAY_PASSWORD 112 | sed -i "s//$QUAY_USER/" quay-credentials.yaml 113 | sed -i "s//$QUAY_PASSWORD/" quay-credentials.yaml 114 | ~~~ 115 | 5. Create a Secret containing the credentials to access our Git repository 116 | 117 | > **NOTE**: You need to provide a token with push access to the cicd repository 118 | 119 | ~~~sh 120 | read -s GIT_AUTH_TOKEN 121 | oc -n reversewords-ci create secret generic image-updater-secret --from-literal=token=${GIT_AUTH_TOKEN} 122 | ~~~ 123 | 6. Import credentials into the cluster 124 | 125 | ~~~sh 126 | oc -n reversewords-ci create -f quay-credentials.yaml 127 | ~~~ 128 | 7. Create a ServiceAccount with access to the credentials created in the previous step 129 | 130 | ~~~sh 131 | oc -n reversewords-ci create -f pipeline-sa.yaml 132 | ~~~ 133 | 8. Create the Linter Task which will lint our code 134 | 135 | ~~~sh 136 | oc -n reversewords-ci create -f lint-task.yaml 137 | ~~~ 138 | 9. Create the Tester Task which will run the tests in our app 139 | 140 | ~~~sh 141 | oc -n reversewords-ci create -f test-task.yaml 142 | ~~~ 143 | 10. Create the Builder Task which will build a container image for our app 144 | 145 | ~~~sh 146 | oc -n reversewords-ci create -f build-task.yaml 147 | ~~~ 148 | 11. Create the Image Update Task which will update the Deployment on a given branch after a successful image build 149 | 150 | ~~~sh 151 | oc -n reversewords-ci create -f image-updater-task.yaml 152 | ~~~ 153 | 12. Edit some parameters from our Build Pipeline definition 154 | 155 | > **NOTE**: You need to use your forks address in the substitutions below 156 | 157 | ~~~sh 158 | sed -i "s||https://github.com/mvazquezc/reverse-words|" build-pipeline.yaml 159 | sed -i "s||quay.io/mavazque/tekton-reversewords|" build-pipeline.yaml 160 | sed -i "s||github.com/mvazquezc/reverse-words|" build-pipeline.yaml 161 | sed -i "s||mvazquezc/reverse-words-cicd|" build-pipeline.yaml 162 | ~~~ 163 | 13. Create the Build Pipeline definition which will be used to execute the previous tasks in an specific order with specific parameters 164 | 165 | ~~~sh 166 | oc -n reversewords-ci create -f build-pipeline.yaml 167 | ~~~ 168 | 14. Create the curl task which will be used to query our apps on the promoter pipeline 169 | 170 | ~~~sh 171 | oc -n reversewords-ci create -f curl-task.yaml 172 | ~~~ 173 | 15. Create the task that gets the stage release from the git cicd repository 174 | 175 | ~~~sh 176 | oc -n reversewords-ci create -f get-stage-release-task.yaml 177 | ~~~ 178 | 16. Edit some parameters from our Promoter Pipeline definition 179 | 180 | > **NOTE**: You need to use your forks address/quay account in the substitutions below 181 | 182 | ~~~sh 183 | sed -i "s||https://github.com/mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 184 | sed -i "s||quay.io/mavazque/tekton-reversewords|" promote-to-prod-pipeline.yaml 185 | sed -i "s||mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 186 | sed -i "s||./deployment.yaml|" promote-to-prod-pipeline.yaml 187 | ~~~ 188 | 17. Create the Promoter Pipeline definition which will be used to execute the previous tasks in an specific order with specific parameters 189 | 190 | ~~~sh 191 | oc -n reversewords-ci create -f promote-to-prod-pipeline.yaml 192 | ~~~ 193 | 18. Create the required Roles and RoleBindings for working with Webhooks 194 | 195 | ~~~sh 196 | oc -n reversewords-ci create -f webhook-roles.yaml 197 | ~~~ 198 | 19. Create the TriggerBinding for reading data received by a webhook and pass it to the Pipeline 199 | 200 | ~~~sh 201 | oc -n reversewords-ci create -f github-triggerbinding.yaml 202 | ~~~ 203 | 20. Create the TriggerTemplate and Event Listener to run the Pipeline when new commits hit the main branch of our app repository 204 | 205 | ~~~sh 206 | WEBHOOK_SECRET="v3r1s3cur3" 207 | oc -n reversewords-ci create secret generic webhook-secret --from-literal=secret=${WEBHOOK_SECRET} 208 | sed -i "s//github-triggerbinding/" webhook.yaml 209 | sed -i "/ref: github-triggerbinding/d" webhook.yaml 210 | sed -i "s/- name: pipeline-binding/- name: github-triggerbinding/" webhook.yaml 211 | oc -n reversewords-ci create -f webhook.yaml 212 | ~~~ 213 | 21. We need to provide an ingress point for our EventListener, we want it to be TLS, we will create a edge route 214 | 215 | ~~~sh 216 | oc -n reversewords-ci create route edge reversewords-webhook --service=el-reversewords-webhook --port=8080 --insecure-policy=Redirect 217 | ~~~ 218 | 219 | # Configure Argo CD 220 | 221 | 1. Install the Argo CD Cli to make things easier 222 | 223 | ~~~sh 224 | # Get the Argo CD Cli and place it in /usr/bin/ 225 | sudo curl -L https://github.com/argoproj/argo-cd/releases/download/v1.6.1/argocd-linux-amd64 -o /usr/bin/argocd 226 | sudo chmod +x /usr/bin/argocd 227 | ~~~ 228 | 2. Login into Argo CD from the Cli 229 | 230 | ~~~sh 231 | ARGOCD_PASSWORD=$(oc -n argocd get secret argocd-cluster -o jsonpath='{.data.admin\.password}' | base64 -d) 232 | ARGOCD_ROUTE=$(oc -n argocd get route argocd -o jsonpath='{.spec.host}') 233 | argocd login $ARGOCD_ROUTE --insecure --username admin --password $ARGOCD_PASSWORD 234 | ~~~ 235 | -------------------------------------------------------------------------------- /ocp/argocd/run-demo.md: -------------------------------------------------------------------------------- 1 | # Demo Workflows 2 | 3 | ## Build Workflow 4 | 5 | ![Build Workflow](./assets/BuildWorkflow.png) 6 | 7 | ## Image Promotion Workflow 8 | 9 | ![Promotion Workflow](./assets/PromoteWorkflow.png) 10 | 11 | # Argo CD 12 | 13 | ## Configuring the Argo CD Apps 14 | 15 | 1. Add the Git repository for our application to Argo CD 16 | 17 | > **NOTE**: Use your fork on the command below 18 | 19 | ~~~sh 20 | argocd repo add https://github.com/mvazquezc/reverse-words-cicd.git --name reversewords-cicd 21 | ~~~ 22 | 2. Edit the ingresses for our applications before creating them in Argo CD 23 | 24 | > **NOTE**: Update the ingresses on your fork to match your environment hostnames 25 | 26 | ~~~sh 27 | cd /var/tmp/code-to-prod-demo/reverse-words-cicd 28 | CLUSTER_WILDCARD=$(oc get ingresses.config.openshift.io cluster -o jsonpath='{ .spec.domain }') 29 | # Stash previous changes 30 | git stash 31 | # Update staging ingress 32 | git checkout stage 33 | sed -i "s/host: .*/host: reversewords-dev.${CLUSTER_WILDCARD}/" ingress.yaml 34 | # Push stage changes 35 | git commit -am "Added ingress hostname" 36 | git push origin stage 37 | # Update production ingress 38 | git checkout prod 39 | sed -i "s/host: .*/host: reversewords-prod.${CLUSTER_WILDCARD}/" ingress.yaml 40 | # Push prod changes 41 | git commit -am "Added ingress hostname" 42 | git push origin prod 43 | ~~~ 44 | 3. Define Development application 45 | 46 | > **NOTE**: Use your fork on the command below 47 | 48 | ~~~sh 49 | argocd app create --project default --name reverse-words-stage \ 50 | --repo https://github.com/mvazquezc/reverse-words-cicd.git \ 51 | --path . \ 52 | --dest-server https://kubernetes.default.svc \ 53 | --dest-namespace reverse-words-stage --revision stage \ 54 | --self-heal --sync-policy automated 55 | ~~~ 56 | 4. Define Production application 57 | 58 | > **NOTE**: Use your fork on the command below 59 | 60 | ~~~sh 61 | argocd app create --project default --name reverse-words-production \ 62 | --repo https://github.com/mvazquezc/reverse-words-cicd.git \ 63 | --path . \ 64 | --dest-server https://kubernetes.default.svc \ 65 | --dest-namespace reverse-words-production --revision prod \ 66 | --self-heal --sync-policy automated 67 | ~~~ 68 | 5. At this point the applications will be deployed automatically and Argo CD will poll the Git repository in order to detect configuration drifts every 3 minutes, when that happens, Argo CD will automatically apply the config stored in Git 69 | 70 | ## Triggering the Build Pipeline using the WebHook 71 | 72 | We are going to use WebHooks in order to run Pipelines automatically when new commits hit the branches of our app and cicd repositories. 73 | 74 | * Our first webhook will receive events from the application repository, when new code hits the main branch we will trigger the build pipeline. 75 | * Our second webhook will receive events from the cicd repository, when new code hits `stage` or `prod` branches we will trigger a new deployment using Argo CD. 76 | 77 | 1. We will configure the first webhook on the app repo 78 | 79 | > **NOTE**: Every Git server has its own properties, but basically you want to provide the ingress url for our webhook and when the Git server should send the hook. E.g: push events, PR events, etc. 80 | 81 | 1. Go to your application repository on GitHub, eg: https://github.com/mvazquezc/reverse-words 82 | 2. Click on `Settings` -> `Webhooks` 83 | 3. Create the following `Hook` 84 | 1. `Payload URL`: Output of command `oc -n reversewords-ci get route reversewords-webhook -o jsonpath='https://{.spec.host}'` 85 | 2. `Content type`: application/json 86 | 2. `Secret`: v3r1s3cur3 87 | 3. `Events`: Check **Push Events**, leave others blank 88 | 4. `Active`: Check it 89 | 5. `SSL verification`: Check **Disable** 90 | 6. Click on `Add webhook` 91 | 2. Now, we will configure the second webhook to react to changes on the cicd repository 92 | 93 | > **NOTE**: Argo CD comes with Webhooks enabled by default, that means that we just need to use the following url as Webhook endpoint, `https:///api/webhook` 94 | 95 | 1. Go to your cicd repository on GitHub, eg: https://github.com/mvazquezc/reverse-words-cicd 96 | 2. Click on `Settings` -> `Webhooks` 97 | 3. Create the following `Hook` 98 | 1. `Payload URL`: Output of command `oc -n argocd get route argocd-server -o jsonpath='https://{.spec.host}'/api/webhook` 99 | 2. `Content type`: application/json 100 | 2. `Secret`: v3r1s3cur3 101 | 3. `Events`: Check **Push Events**, leave others blank 102 | 4. `Active`: Check it 103 | 5. `SSL verification`: Check **Disable** 104 | 6. Click on `Add webhook` 105 | 4. We need to configure our `Secret Token` on Argo CD 106 | ~~~sh 107 | WEBHOOK_SECRET="v3r1s3cur3" 108 | oc -n argocd patch secret argocd-secret -p "{\"data\":{\"webhook.github.secret\":\"$(echo -n $WEBHOOK_SECRET | base64)\"}}" --type=merge 109 | ~~~ 110 | 3. Now we should have a working Webhook, let's test it 111 | 112 | 1. Deploy tkn cli 113 | 114 | ~~~sh 115 | sudo curl -L https://github.com/tektoncd/cli/releases/download/v0.10.0/tkn_0.10.0_Linux_x86_64.tar.gz | tar xz tkn 116 | chown root: tkn && mv tkn /usr/bin/ 117 | ~~~ 118 | 2. We need to commit to the main branch, let's update the release number 119 | 120 | ~~~sh 121 | cd /var/tmp/code-to-prod-demo/reverse-words/ 122 | CURRENT_RELEASE=$(grep "var version" main.go | awk -F '"' '{print $2}' | awk -F "." 'BEGIN{FS=OFS="."}{NF--; print}') 123 | NEW_MINOR=$(grep "var version" main.go | awk -F '"' '{print $2}' | awk -F "." '{print $NF+1}') 124 | NEW_RELEASE="${CURRENT_RELEASE}.${NEW_MINOR}" 125 | sed -i "s|var version = .*|var version = \"${NEW_RELEASE}\"|" main.go 126 | git diff main.go 127 | git add main.go 128 | git commit -m "Release updated to $NEW_RELEASE" 129 | git push origin main 130 | ~~~ 131 | 3. Connect to the OpenShift Developer Console and navigate to the `reversewords-ci` namespace 132 | 1. You can see the PipelineRun on the console and follow the log 133 | 4. We can check the running images for our application pod and see that when the pipeline finishes a new deployment is triggered on ArgoCD 134 | 5. When the Build pipeline finishes we can promote the new build to production 135 | 136 | ~~~sh 137 | tkn -n reversewords-ci pipeline start reverse-words-promote-pipeline -r app-git=reverse-words-cicd-git -p pathToDeploymentFile=./deployment.yaml -p stageBranch=stage -p stageAppUrl=$(oc -n reverse-words-stage get route -l app=reversewords-stage -o jsonpath='{.items[*].spec.host}') 138 | ~~~ 139 | 140 | ## Tekton Polling Operator 141 | 142 | [Project Repository](https://github.com/bigkevmcd/tekton-polling-operator) 143 | 144 | Sometimes your clusters cannot be accessed from the Internet, thus, webhooks won't be a valid option for automatically run our pipelines upon git changes. In this scenario we can use the `Tekton Polling Operator` which will poll our repo in a given time interval and run the Pipeline when needed. 145 | 146 | You can run the polling operators in two ways: 147 | 148 | 1. Deploy a polling operator in the namespace where the Pipeline definitions are created 149 | 2. Deploy a global polling operator and give it access to the different namespaces with Pipelines definitions 150 | 151 | We are going to follow the second approach. 152 | 153 | > **NOTE**: Tekton Pipelines must be deployed in the cluster before deploying the operator 154 | 155 | 1. Create a namespace and deploy the operator 156 | 157 | ~~~sh 158 | oc create namespace tekton-polling-operator 159 | oc -n tekton-polling-operator apply -f https://github.com/bigkevmcd/tekton-polling-operator/releases/download/v0.2.0/release-v0.2.0.yaml 160 | ~~~ 161 | 2. Create the required ClusterRole and ClusterRoleBinding for the Operator to create PipelineRuns on the reversewords-ci namespace 162 | 163 | ~~~sh 164 | cat < /tmp/test-secret.yaml 268 | ~~~ 269 | 4. Seal the test secret 270 | 271 | ~~~sh 272 | kubeseal -o yaml < /tmp/test-secret.yaml > test-secret-sealed.yaml 273 | ~~~ 274 | 5. Update the Kustomization and push the sealed secret to the git repository 275 | 276 | ~~~sh 277 | sed -i "s|patchesStrategicMerge:|- test-secret-sealed.yaml\npatchesStrategicMerge:|" kustomization.yaml 278 | git add kustomization.yaml test-secret-sealed.yaml 279 | git commit -m "Add Sealed Secret" 280 | git push origin stage 281 | ~~~ 282 | -------------------------------------------------------------------------------- /ocp/argocd/scripts/clean-demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -rf /var/tmp/code-to-prod-demo/ 3 | oc delete ns argocd 4 | oc delete ns reversewords-ci 5 | oc delete ns reverse-words-stage 6 | oc delete ns reverse-words-production 7 | oc -n openshift-operators delete subscription openshift-pipelines-operator-rh 8 | oc -n openshift-operators get csv -o name | grep openshift-pipelines-operator | xargs oc -n openshift-operators delete 9 | -------------------------------------------------------------------------------- /ocp/argocd/scripts/deploy-demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -ne "Enter your quay.io username: " 4 | read QUAY_USER 5 | echo -ne "Enter your quay.io password: " 6 | read -s QUAY_PASSWORD 7 | echo -ne "\nEnter your Git Token: " 8 | read -s GIT_AUTH_TOKEN 9 | 10 | echo "" 11 | rm -rf /var/tmp/code-to-prod-demo/ 12 | mkdir -p /var/tmp/code-to-prod-demo/ 13 | echo "Deploy Argo CD" 14 | oc create namespace argocd 15 | cat </$QUAY_USER/" quay-credentials.yaml 97 | sed -i "s//$QUAY_PASSWORD/" quay-credentials.yaml 98 | oc -n reversewords-ci create secret generic image-updater-secret --from-literal=token=${GIT_AUTH_TOKEN} 99 | oc -n reversewords-ci create -f quay-credentials.yaml 100 | oc -n reversewords-ci create -f pipeline-sa.yaml 101 | oc -n reversewords-ci create -f lint-task.yaml 102 | oc -n reversewords-ci create -f test-task.yaml 103 | oc -n reversewords-ci create -f build-task.yaml 104 | oc -n reversewords-ci create -f image-updater-task.yaml 105 | sed -i "s||https://github.com/mvazquezc/reverse-words|" build-pipeline.yaml 106 | sed -i "s||quay.io/mavazque/tekton-reversewords|" build-pipeline.yaml 107 | sed -i "s||github.com/mvazquezc/reverse-words|" build-pipeline.yaml 108 | sed -i "s||mvazquezc/reverse-words-cicd|" build-pipeline.yaml 109 | oc -n reversewords-ci create -f build-pipeline.yaml 110 | oc -n reversewords-ci create -f webhook-roles.yaml 111 | oc -n reversewords-ci create -f github-triggerbinding.yaml 112 | WEBHOOK_SECRET="v3r1s3cur3" 113 | oc -n reversewords-ci create secret generic webhook-secret --from-literal=secret=${WEBHOOK_SECRET} 114 | sed -i "s//github-triggerbinding/" webhook.yaml 115 | sed -i "s/- name: pipeline-binding/- name: github-triggerbinding/" webhook.yaml 116 | oc -n reversewords-ci create -f webhook.yaml 117 | oc -n reversewords-ci create -f curl-task.yaml 118 | oc -n reversewords-ci create -f get-stage-release-task.yaml 119 | sed -i "s||https://github.com/mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 120 | sed -i "s||quay.io/mavazque/tekton-reversewords|" promote-to-prod-pipeline.yaml 121 | sed -i "s||mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 122 | sed -i "s||./deployment.yaml|" promote-to-prod-pipeline.yaml 123 | oc -n reversewords-ci create -f promote-to-prod-pipeline.yaml 124 | oc -n reversewords-ci create route edge reversewords-webhook --service=el-reversewords-webhook --port=8080 --insecure-policy=Redirect 125 | sleep 15 126 | ARGOCD_ROUTE=$(oc -n argocd get route argocd-server -o jsonpath='{.spec.host}') 127 | argocd login $ARGOCD_ROUTE --insecure --username admin --password $ARGOCD_PASSWORD 128 | CONSOLE_ROUTE=$(oc -n openshift-console get route console -o jsonpath='{.spec.host}') 129 | echo "Argo CD Console: $ARGOCD_ROUTE" 130 | echo "Argo CD Admin Password: $ARGOCD_PASSWORD" 131 | echo "OCP Console: $CONSOLE_ROUTE" 132 | -------------------------------------------------------------------------------- /ocp/rhacm/README.md: -------------------------------------------------------------------------------- 1 | # Code To Production Demo 2 | 3 | For the required steps to build a demo environment look [here](./deploy-demo-env.md) 4 | 5 | For the required steps to run the demo look [here](./run-demo.md) 6 | 7 | ## Scripts 8 | 9 | There are scripts provided that automate the demo environment creation and cleanup, you will need to edit them to match your environment. 10 | 11 | You will need to provide your git forks and custom ingress hostnames. 12 | 13 | The scripts can be found on the [scripts](./scripts) folder. 14 | -------------------------------------------------------------------------------- /ocp/rhacm/assets/BuildWorkflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvazquezc/code-to-prod-demo/918bf017206708e68e9a1af1434c236640fa1fd4/ocp/rhacm/assets/BuildWorkflow.png -------------------------------------------------------------------------------- /ocp/rhacm/assets/PromoteWorkflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvazquezc/code-to-prod-demo/918bf017206708e68e9a1af1434c236640fa1fd4/ocp/rhacm/assets/PromoteWorkflow.png -------------------------------------------------------------------------------- /ocp/rhacm/deploy-demo-env.md: -------------------------------------------------------------------------------- 1 | # Deploy OpenShift and the required Operators 2 | 3 | 1. Deploy an OpenShift Cluster. For this demo we used OpenShift Container Platform v4.5.14 4 | 2. Deploy RH ACM from the OperatorHub. For this demo we used RH ACM v2.1 5 | 3. Deploy OpenShift Pipelines Operator 6 | 7 | ~~~sh 8 | cat < **NOTE**: You need to fork these repositories and use your fork (so you have full-access) 27 | 28 | ~~~sh 29 | mkdir -p /var/tmp/code-to-prod-demo/ 30 | git clone git@github.com:mvazquezc/reverse-words.git /var/tmp/code-to-prod-demo/reverse-words 31 | git clone git@github.com:mvazquezc/reverse-words-cicd.git /var/tmp/code-to-prod-demo/reverse-words-cicd 32 | ~~~ 33 | 2. Go to the reverse-words-cicd repo and checkout the CI branch which contains our Tekton manifests 34 | 35 | ~~~sh 36 | cd /var/tmp/code-to-prod-demo/reverse-words-cicd 37 | git checkout ci 38 | ~~~ 39 | 3. Create a namespace for storing the configuration for our reversewords app pipeline 40 | 41 | ~~~sh 42 | oc create namespace reversewords-ci 43 | ~~~ 44 | 4. Add the quay credentials to the credentials file 45 | 46 | ~~~sh 47 | QUAY_USER= 48 | read -s QUAY_PASSWORD 49 | sed -i "s//$QUAY_USER/" quay-credentials.yaml 50 | sed -i "s//$QUAY_PASSWORD/" quay-credentials.yaml 51 | ~~~ 52 | 5. Create a Secret containing the credentials to access our Git repository 53 | 54 | > **NOTE**: You need to provide a token with push access to the cicd repository 55 | 56 | ~~~sh 57 | read -s GIT_AUTH_TOKEN 58 | oc -n reversewords-ci create secret generic image-updater-secret --from-literal=token=${GIT_AUTH_TOKEN} 59 | ~~~ 60 | 6. Import credentials into the cluster 61 | 62 | ~~~sh 63 | oc -n reversewords-ci create -f quay-credentials.yaml 64 | ~~~ 65 | 7. Create a ServiceAccount with access to the credentials created in the previous step 66 | 67 | ~~~sh 68 | oc -n reversewords-ci create -f pipeline-sa.yaml 69 | ~~~ 70 | 8. Create the Linter Task which will lint our code 71 | 72 | ~~~sh 73 | oc -n reversewords-ci create -f lint-task.yaml 74 | ~~~ 75 | 9. Create the Tester Task which will run the tests in our app 76 | 77 | ~~~sh 78 | oc -n reversewords-ci create -f test-task.yaml 79 | ~~~ 80 | 10. Create the Builder Task which will build a container image for our app 81 | 82 | ~~~sh 83 | oc -n reversewords-ci create -f build-task.yaml 84 | ~~~ 85 | 11. Create the Image Update Task which will update the Deployment on a given branch after a successful image build 86 | 87 | ~~~sh 88 | oc -n reversewords-ci create -f image-updater-task.yaml 89 | ~~~ 90 | 12. Edit some parameters from our Build Pipeline definition 91 | 92 | > **NOTE**: You need to use your forks address in the substitutions below 93 | 94 | ~~~sh 95 | sed -i "s||https://github.com/mvazquezc/reverse-words|" build-pipeline.yaml 96 | sed -i "s||quay.io/mavazque/tekton-reversewords|" build-pipeline.yaml 97 | sed -i "s||github.com/mvazquezc/reverse-words|" build-pipeline.yaml 98 | sed -i "s||mvazquezc/reverse-words-cicd|" build-pipeline.yaml 99 | ~~~ 100 | 13. Create the Build Pipeline definition which will be used to execute the previous tasks in an specific order with specific parameters 101 | 102 | ~~~sh 103 | oc -n reversewords-ci create -f build-pipeline.yaml 104 | ~~~ 105 | 14. Create the curl task which will be used to query our apps on the promoter pipeline 106 | 107 | ~~~sh 108 | oc -n reversewords-ci create -f curl-task.yaml 109 | ~~~ 110 | 15. Create the task that gets the stage release from the git cicd repository 111 | 112 | ~~~sh 113 | oc -n reversewords-ci create -f get-stage-release-task.yaml 114 | ~~~ 115 | 16. Edit some parameters from our Promoter Pipeline definition 116 | 117 | > **NOTE**: You need to use your forks address/quay account in the substitutions below 118 | 119 | ~~~sh 120 | sed -i "s||https://github.com/mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 121 | sed -i "s||quay.io/mavazque/tekton-reversewords|" promote-to-prod-pipeline.yaml 122 | sed -i "s||mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 123 | sed -i "s||./deployment.yaml|" promote-to-prod-pipeline.yaml 124 | ~~~ 125 | 17. Create the Promoter Pipeline definition which will be used to execute the previous tasks in an specific order with specific parameters 126 | 127 | ~~~sh 128 | oc -n reversewords-ci create -f promote-to-prod-pipeline.yaml 129 | ~~~ 130 | 18. Create the required Roles and RoleBindings for working with Webhooks 131 | 132 | ~~~sh 133 | oc -n reversewords-ci create -f webhook-roles.yaml 134 | ~~~ 135 | 19. Create the TriggerBinding for reading data received by a webhook and pass it to the Pipeline 136 | 137 | ~~~sh 138 | oc -n reversewords-ci create -f github-triggerbinding.yaml 139 | ~~~ 140 | 20. Create the TriggerTemplate and Event Listener to run the Pipeline when new commits hit the main branch of our app repository 141 | 142 | ~~~sh 143 | WEBHOOK_SECRET="v3r1s3cur3" 144 | oc -n reversewords-ci create secret generic webhook-secret --from-literal=secret=${WEBHOOK_SECRET} 145 | sed -i "s//github-triggerbinding/" webhook.yaml 146 | sed -i "/ref: github-triggerbinding/d" webhook.yaml 147 | sed -i "s/- name: pipeline-binding/- name: github-triggerbinding/" webhook.yaml 148 | oc -n reversewords-ci create -f webhook.yaml 149 | ~~~ 150 | 21. We need to provide an ingress point for our EventListener, we want it to be TLS, we will create a edge route 151 | 152 | ~~~sh 153 | oc -n reversewords-ci create route edge reversewords-webhook --service=el-reversewords-webhook --port=8080 --insecure-policy=Redirect 154 | ~~~ -------------------------------------------------------------------------------- /ocp/rhacm/run-demo.md: -------------------------------------------------------------------------------- 1 | # Demo Workflows 2 | 3 | ## Build Workflow 4 | 5 | ![Build Workflow](./assets/BuildWorkflow.png) 6 | 7 | ## Image Promotion Workflow 8 | 9 | ![Promotion Workflow](./assets/PromoteWorkflow.png) 10 | 11 | # Red Hat Advanced Cluster Management 12 | 13 | ## Configuring the ACM Apps 14 | 15 | 1. Expose ACM to received WebHooks 16 | 17 | ~~~sh 18 | oc create route passthrough --service=multicluster-operators-subscription -n open-cluster-management 19 | ~~~ 20 | 1. Create a namespace for storing some of the ACM manifests 21 | 22 | ~~~sh 23 | oc create namespace gitops-demo 24 | ~~~ 25 | 2. Add the Git repository for our application to ACM 26 | 27 | > **NOTE**: Use your fork on the command below 28 | 29 | ~~~sh 30 | cat < **NOTE**: Update the ingresses on your fork to match your environment hostnames 85 | 86 | ~~~sh 87 | cd /var/tmp/code-to-prod-demo/reverse-words-cicd 88 | CLUSTER_DEV_NAME=spoke1 89 | CLUSTER_PROD_NAME=spoke2 90 | CLUSTER_DEV_WILDCARD=apps.$(oc -n ${CLUSTER_DEV_NAME} get managedclusterinfos ${CLUSTER_DEV_NAME} -o jsonpath='{.status.consoleURL}' | awk -F "apps." '{print $2}') 91 | CLUSTER_PROD_WILDCARD=apps.$(oc -n ${CLUSTER_PROD_NAME} get managedclusterinfos ${CLUSTER_PROD_NAME} -o jsonpath='{.status.consoleURL}' | awk -F "apps." '{print $2}') 92 | # Stash previous changes 93 | git stash 94 | # Update staging ingress 95 | git checkout stage 96 | sed -i "s/host: .*/host: reversewords.${CLUSTER_DEV_WILDCARD}/" ingress.yaml 97 | # Push stage changes 98 | git commit -am "Added ingress hostname" 99 | git push origin stage 100 | # Update production ingress 101 | git checkout prod 102 | sed -i "s/host: .*/host: reversewords.${CLUSTER_PROD_WILDCARD}/" ingress.yaml 103 | # Push prod changes 104 | git commit -am "Added ingress hostname" 105 | git push origin prod 106 | ~~~ 107 | 3. Since our subscriptions will create their own namespaces, we need to assign the ClusterRole `subscription-admin` to the user we are using for the demo 108 | 109 | ~~~sh 110 | oc adm policy add-cluster-role-to-user open-cluster-management:subscription-admin $(oc whoami) 111 | ~~~ 112 | 113 | 3. Define Development application 114 | 115 | ~~~sh 116 | cat < **NOTE**: Every Git server has its own properties, but basically you want to provide the ingress url for our webhook and when the Git server should send the hook. E.g: push events, PR events, etc. 223 | 224 | 1. Go to your application repository on GitHub, eg: https://github.com/mvazquezc/reverse-words 225 | 2. Click on `Settings` -> `Webhooks` 226 | 3. Create the following `Hook` 227 | 1. `Payload URL`: Output of command `oc -n reversewords-ci get route reversewords-webhook -o jsonpath='https://{.spec.host}'` 228 | 2. `Content type`: application/json 229 | 2. `Secret`: v3r1s3cur3 230 | 3. `Events`: Check **Push Events**, leave others blank 231 | 4. `Active`: Check it 232 | 5. `SSL verification`: Check **Disable** 233 | 6. Click on `Add webhook` 234 | 2. Now, we will configure the second webhook to react to changes on the cicd repository 235 | 236 | 1. Go to your cicd repository on GitHub, eg: https://github.com/mvazquezc/reverse-words-cicd 237 | 2. Click on `Settings` -> `Webhooks` 238 | 3. Create the following `Hook` 239 | 1. `Payload URL`: Output of command `oc -n open-cluster-management get route multicluster-operators-subscription -o jsonpath='https://{.spec.host}'/webhook` 240 | 2. `Content type`: application/json 241 | 2. `Secret`: v3r1s3cur3 242 | 3. `Events`: Check **Push Events**, leave others blank 243 | 4. `Active`: Check it 244 | 5. `SSL verification`: Check **Disable** 245 | 6. Click on `Add webhook` 246 | 247 | 3. Now we should have a working Webhook, let's test it 248 | 249 | 1. Deploy tkn cli 250 | 251 | ~~~sh 252 | sudo curl -L https://github.com/tektoncd/cli/releases/download/v0.10.0/tkn_0.10.0_Linux_x86_64.tar.gz | tar xz tkn 253 | chown root: tkn && mv tkn /usr/bin/ 254 | ~~~ 255 | 2. We need to commit to the main branch, let's update the release number 256 | 257 | ~~~sh 258 | cd /var/tmp/code-to-prod-demo/reverse-words/ 259 | CURRENT_RELEASE=$(grep "var version" main.go | awk -F '"' '{print $2}' | awk -F "." 'BEGIN{FS=OFS="."}{NF--; print}') 260 | NEW_MINOR=$(grep "var version" main.go | awk -F '"' '{print $2}' | awk -F "." '{print $NF+1}') 261 | NEW_RELEASE="${CURRENT_RELEASE}.${NEW_MINOR}" 262 | sed -i "s|var version = .*|var version = \"${NEW_RELEASE}\"|" main.go 263 | git diff main.go 264 | git add main.go 265 | git commit -m "Release updated to $NEW_RELEASE" 266 | git push origin main 267 | ~~~ 268 | 3. Connect to the OpenShift Developer Console and navigate to the `reversewords-ci` namespace 269 | 1. You can see the PipelineRun on the console and follow the log 270 | 4. We can check the running images for our application pod and see that when the pipeline finishes a new deployment is triggered on ArgoCD 271 | 5. When the Build pipeline finishes we can promote the new build to production 272 | 273 | ~~~sh 274 | tkn -n reversewords-ci pipeline start reverse-words-promote-pipeline -r app-git=reverse-words-cicd-git -p pathToDeploymentFile=./deployment.yaml -p stageBranch=stage -p stageAppUrl=$(oc -n gitops-demo get deployables reverse-words-stage-reverse-words-ingress -o jsonpath='{.spec.template.spec.rules[0].host}') 275 | ~~~ 276 | 277 | ## Tekton Polling Operator 278 | 279 | [Project Repository](https://github.com/bigkevmcd/tekton-polling-operator) 280 | 281 | Sometimes your clusters cannot be accessed from the Internet, thus, webhooks won't be a valid option for automatically run our pipelines upon git changes. In this scenario we can use the `Tekton Polling Operator` which will poll our repo in a given time interval and run the Pipeline when needed. 282 | 283 | You can run the polling operators in two ways: 284 | 285 | 1. Deploy a polling operator in the namespace where the Pipeline definitions are created 286 | 2. Deploy a global polling operator and give it access to the different namespaces with Pipelines definitions 287 | 288 | We are going to follow the second approach. 289 | 290 | > **NOTE**: Tekton Pipelines must be deployed in the cluster before deploying the operator 291 | 292 | 1. Create a namespace and deploy the operator 293 | 294 | ~~~sh 295 | oc create namespace tekton-polling-operator 296 | oc -n tekton-polling-operator apply -f https://github.com/bigkevmcd/tekton-polling-operator/releases/download/v0.2.0/release-v0.2.0.yaml 297 | ~~~ 298 | 2. Create the required ClusterRole and ClusterRoleBinding for the Operator to create PipelineRuns on the reversewords-ci namespace 299 | 300 | ~~~sh 301 | cat < /tmp/test-secret.yaml 405 | ~~~ 406 | 4. Seal the test secret 407 | 408 | ~~~sh 409 | kubeseal -o yaml < /tmp/test-secret.yaml > test-secret-sealed.yaml 410 | ~~~ 411 | 5. Update the Kustomization and push the sealed secret to the git repository 412 | 413 | ~~~sh 414 | sed -i "s|patchesStrategicMerge:|- test-secret-sealed.yaml\npatchesStrategicMerge:|" kustomization.yaml 415 | git add kustomization.yaml test-secret-sealed.yaml 416 | git commit -m "Add Sealed Secret" 417 | git push origin stage 418 | ~~~ 419 | -------------------------------------------------------------------------------- /ocp/rhacm/scripts/clean-demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -rf /var/tmp/code-to-prod-demo/ 3 | oc delete ns argocd 4 | oc delete ns reversewords-ci 5 | oc delete ns reverse-words-stage 6 | oc delete ns reverse-words-production 7 | oc -n openshift-operators delete subscription openshift-pipelines-operator-rh 8 | oc -n openshift-operators get csv -o name | grep openshift-pipelines-operator | xargs oc -n openshift-operators delete 9 | -------------------------------------------------------------------------------- /ocp/rhacm/scripts/deploy-demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -ne "Enter your quay.io username: " 4 | read QUAY_USER 5 | echo -ne "Enter your quay.io password: " 6 | read -s QUAY_PASSWORD 7 | echo -ne "\nEnter your Git Token: " 8 | read -s GIT_AUTH_TOKEN 9 | 10 | echo "" 11 | mkdir -p /var/tmp/code-to-prod-demo/ 12 | echo "Deploy Argo CD" 13 | oc create namespace argocd 14 | oc apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v1.6.1/manifests/install.yaml 15 | echo "Create Ingress for Argo CD" 16 | oc -n argocd create route passthrough argocd --service=argocd-server --port=https --insecure-policy=Redirect 17 | ARGOCD_PASSWORD=$(oc -n argocd get pods -l app.kubernetes.io/name=argocd-server -o name | awk -F "/" '{print $2}') 18 | oc -n argocd patch configmap argocd-cm -p '{"data":{"resource.customizations":"extensions/Ingress:\n health.lua: |\n hs = {}\n hs.status = \"Healthy\"\n return hs\n"}}' 19 | echo "Deploy Tekton Pipelines and Events" 20 | cat </$QUAY_USER/" quay-credentials.yaml 43 | sed -i "s//$QUAY_PASSWORD/" quay-credentials.yaml 44 | oc -n reversewords-ci create secret generic image-updater-secret --from-literal=token=${GIT_AUTH_TOKEN} 45 | oc -n reversewords-ci create -f quay-credentials.yaml 46 | oc -n reversewords-ci create -f pipeline-sa.yaml 47 | oc -n reversewords-ci create -f lint-task.yaml 48 | oc -n reversewords-ci create -f test-task.yaml 49 | oc -n reversewords-ci create -f build-task.yaml 50 | oc -n reversewords-ci create -f image-updater-task.yaml 51 | sed -i "s||https://github.com/mvazquezc/reverse-words|" build-pipeline.yaml 52 | sed -i "s||quay.io/mavazque/tekton-reversewords|" build-pipeline.yaml 53 | sed -i "s||github.com/mvazquezc/reverse-words|" build-pipeline.yaml 54 | sed -i "s||mvazquezc/reverse-words-cicd|" build-pipeline.yaml 55 | oc -n reversewords-ci create -f build-pipeline.yaml 56 | oc -n reversewords-ci create -f webhook-roles.yaml 57 | oc -n reversewords-ci create -f github-triggerbinding.yaml 58 | WEBHOOK_SECRET="v3r1s3cur3" 59 | oc -n reversewords-ci create secret generic webhook-secret --from-literal=secret=${WEBHOOK_SECRET} 60 | sed -i "s//github-triggerbinding/" webhook.yaml 61 | sed -i "/ref: github-triggerbinding/d" webhook.yaml 62 | sed -i "s/- name: pipeline-binding/- name: github-triggerbinding/" webhook.yaml 63 | oc -n reversewords-ci create -f webhook.yaml 64 | oc -n reversewords-ci create -f curl-task.yaml 65 | oc -n reversewords-ci create -f get-stage-release-task.yaml 66 | sed -i "s||https://github.com/mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 67 | sed -i "s||quay.io/mavazque/tekton-reversewords|" promote-to-prod-pipeline.yaml 68 | sed -i "s||mvazquezc/reverse-words-cicd|" promote-to-prod-pipeline.yaml 69 | sed -i "s||./deployment.yaml|" promote-to-prod-pipeline.yaml 70 | oc -n reversewords-ci create -f promote-to-prod-pipeline.yaml 71 | oc -n reversewords-ci create route edge reversewords-webhook --service=el-reversewords-webhook --port=8080 --insecure-policy=Redirect 72 | sleep 15 73 | ARGOCD_ROUTE=$(oc -n argocd get route argocd -o jsonpath='{.spec.host}') 74 | argocd login $ARGOCD_ROUTE --insecure --username admin --password $ARGOCD_PASSWORD 75 | argocd account update-password --account admin --current-password $ARGOCD_PASSWORD --new-password 'r3dh4t1!' 76 | CONSOLE_ROUTE=$(oc -n openshift-console get route console -o jsonpath='{.spec.host}') 77 | echo "Argo CD Console: $ARGOCD_ROUTE" 78 | echo "OCP Console: $CONSOLE_ROUTE" 79 | --------------------------------------------------------------------------------