{"id":13626487,"url":"https://github.com/jonashackt/tekton-argocd-eks","last_synced_at":"2025-03-19T01:30:58.326Z","repository":{"id":37616363,"uuid":"424553655","full_name":"jonashackt/tekton-argocd-eks","owner":"jonashackt","description":"How to install and configure ArgoCD, Tekton (incl. Tekton Triggers) \u0026 a Cloud Native Buildpacks powered Pipeline on Amazon EKS and integrate with GitLab \u0026 GitHub","archived":false,"fork":false,"pushed_at":"2023-12-21T00:31:05.000Z","size":17392,"stargazers_count":23,"open_issues_count":15,"forks_count":9,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-02-28T13:47:27.912Z","etag":null,"topics":["argocd","argocd-applications","aws","eks","github","gitlab","kubernetes","openshift-pipelines","tekton","tekton-dashboard","tekton-pipelines","tekton-triggers","tekton-tutorial","traefik"],"latest_commit_sha":null,"homepage":"http://tekton.tekton-argocd.de/#/pipelineruns","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonashackt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-11-04T10:25:02.000Z","updated_at":"2024-08-07T19:58:29.000Z","dependencies_parsed_at":"2024-01-14T09:00:26.923Z","dependency_job_id":null,"html_url":"https://github.com/jonashackt/tekton-argocd-eks","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Ftekton-argocd-eks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Ftekton-argocd-eks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Ftekton-argocd-eks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Ftekton-argocd-eks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonashackt","download_url":"https://codeload.github.com/jonashackt/tekton-argocd-eks/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243960361,"owners_count":20375102,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["argocd","argocd-applications","aws","eks","github","gitlab","kubernetes","openshift-pipelines","tekton","tekton-dashboard","tekton-pipelines","tekton-triggers","tekton-tutorial","traefik"],"created_at":"2024-08-01T21:02:20.316Z","updated_at":"2025-03-19T01:30:56.326Z","avatar_url":"https://github.com/jonashackt.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# tekton-argocd-eks\n[![Build Status](https://github.com/jonashackt/aws-eks-tekton-gitlab/workflows/provision/badge.svg)](https://github.com/jonashackt/aws-eks-tekton-gitlab/actions)\n[![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/jonashackt/aws-eks-tekton-gitlab/blob/master/LICENSE)\n[![renovateenabled](https://img.shields.io/badge/renovate-enabled-yellow)](https://renovatebot.com)\n[![Configured with Kustomize](https://img.shields.io/badge/configured_by-Kustomize-455EA3.svg?logo=kubernetes\u0026logoColor=455EA3)](https://kustomize.io/)\n[![Ingress with Traefik](https://img.shields.io/badge/Traefik-traefik.tekton--argocd.de-5AA8C4.svg?logo=traefikmesh\u0026logoColor=5AA8C4)](http://traefik.tekton-argocd.de/dashboard/#)\n[![K8s deployment with ArgoCD](https://img.shields.io/badge/ArgoCD-argocd.tekton--argocd.de-E17F55.svg?logo=octopusdeploy\u0026logoColor=E17F55)](https://argocd.tekton-argocd.de/applications)\n[![CICD with Tekton](https://img.shields.io/badge/Tekton-tekton.tekton--argocd.de-7A4572.svg?logo=tekton\u0026logoColor=DD5C5E)](http://tekton.tekton-argocd.de/#/namespaces/default/pipelineruns)\n[![Trigger EventListener for GitLab](https://img.shields.io/badge/Trigger_EventListener_for_GitLab-gitlab--listener.tekton--argocd.de-7A4572.svg?logo=gitlab\u0026logoColor=DD5C5E)](http://gitlab-listener.tekton-argocd.de/)\n\n\nThis repository shows how to:\n\n* [create an ephemeral EKS cluster using Pulumi](#eks-with-pulumi) \u0026 install [Traefik as Ingress Controller the CRD way](#kubernetes-ingress-for-services-using-traefik-v2) using `IngressRoute` objects\n* [configure a Route53 domain record dynamically to provide sub domain based routing](#automatically-creating-the-route53-a-record-based-on-the-traefik-elb-in-github-actions) through Traefik for all services (base services \u0026 application services)\n* prepare [ArgoCD for application deployment in the GitOps style](#gitops-with-argocd)\n* [install \u0026 configure Tekton on EKS](#tekton-on-eks) and run [a Cloud Native Buildpacks powered Pipeline](#cloud-native-buildpacks-with-tekton)\n* [integrate Tekton with GitLab](#integrate-tekton-on-eks-with-gitlab) (application https://gitlab.com/jonashackt/microservice-api-spring-boot) using direct trigger via `tkn` CLI (via [aws-kubectl-tkn Docker image](https://gitlab.com/jonashackt/aws-kubectl-tkn)) or [Tekton Triggers](https://tekton.dev/docs/triggers/) incl. GitLab Webhooks \u0026 reporting Tekton pipeline status back to GitLab using [gitlab-set-status](https://hub.tekton.dev/tekton/task/gitlab-set-status) task\n* [application deployment using ArgoCD](#argocd-application-deployment) based on the application configuration repo https://gitlab.com/jonashackt/microservice-api-spring-boot-config\n\nIt is structured according to all tools used:\n\n```\n.\n├── argocd\n│   └── ArgoCD related configuration\n├── eks-deployment\n│   └── Pulumi configuration (TypeScript style)\n├── tekton\n│   ├── install\n│   │   └── Tekton CRDs, Pipelines, Dashboard, Triggers etc. installation\n│   ├── misc\n│   │   └── ServiceAccounts, PVCs, Secrets\n│   ├── pipelines\n│   │   └── Tekton Pipelines\n│   ├── tasks\n│   │   └── Tekton Tasks\n│   └── triggers\n│       └── Tekton Triggers Event Listener configuration\n├── traefik\n│   └── Traefik IngressRoute configurations\n```\n\n\n# EKS with Pulumi\n\nLet's simply roll out a AWS EKS cluster with Pulumi:\n\nhttps://www.pulumi.com/docs/guides/crosswalk/aws/eks/\n\nOur [eks-deployment/index.ts](eks-deployment/index.ts) looks like this:\n\n```typescript\nimport * as eks from \"@pulumi/eks\";\n\n// Create an EKS cluster with the default configuration.\nconst cluster = new eks.Cluster(\"eks-for-tekton\");\n\n// Export the cluster's kubeconfig.\nexport const kubeconfig = cluster.kubeconfig;\nexport const eksUrl = cluster.eksCluster.endpoint;\n```\n\nTo execute our Pulumi program, be sure to be logged into the correct Account at https://app.pulumi.com/your-account-here via `pulumi login` using your Pulumi account's token (do a `pulumi logout` before, if you're already logged into another Pulumi account).\n\nNow select the correct stack and fire up Pulumi with:\n\n```shell\npulumi stack select dev\npulumi up\n```\n\n### Accessing the Pulumi created EKS cluster\n\nAfter your EKS cluster has been setup correctly, use the `kubeconfig` const exported inside our Pulumi program to create the `kubeconfig.yml`:\n\n```shell\npulumi stack output kubeconfig \u003e kubeconfig.yml\n```\n\nTo access the cluster be sure to have `kubectl` installed. Try accessing it with:\n\n```shell\nkubectl --kubeconfig kubeconfig.yml get nodes\n```\n\nFor merging the new kubeconfig into your systems profile see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/\n\nFor example you could do:\n\n```shell\npulumi stack output kubeconfig \u003e ~/.kube/config-eks-for-tekton\nexport KUBECONFIG=~/.kube/config:~/.kube/config-eks-for-tekton\n```\n\nNow access via `kubectx` is also possible.\n\n\n### GitHub Actions using Pulumi to provision AWS EKS\n\nFirst we need to create GitHub repository secrets containing our AWS API key id \u0026 access key (`AWS_ACCESS_KEY_ID` \u0026 `AWS_SECRET_ACCESS_KEY`) and our Pulumi access token (`PULUMI_ACCESS_TOKEN`):\n\n![aws-pulumi-repo-secrets](screenshots/aws-pulumi-repo-secrets.png)\n\nOur [provision.yml](.github/workflows/provision.yml) workflow uses Pulumi like we did locally:\n\n```yaml\nname: provision\n\non: [push]\n\njobs:\n  provision-aws-eks:\n    runs-on: ubuntu-latest\n    env:\n      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n      PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}\n      AWS_DEFAULT_REGION: 'eu-central-1'\n    # Create an GitHub environment referencing our EKS cluster endpoint\n    environment:\n      name: tekton-flux-eks-pulumi-dev\n      url: ${{ steps.pulumi-up.outputs.eks_url }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@master\n\n      - name: Setup node env\n        uses: actions/setup-node@v2.4.1\n        with:\n          node-version: '14'\n\n      - name: Cache node_modules\n        uses: actions/cache@v2\n        with:\n          path: ~/.npm\n          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}\n          restore-keys: |\n            ${{ runner.os }}-node-\n\n      - name: Install Pulumi dependencies before npm run generate to prevent it from breaking the build\n        run: npm install\n        working-directory: ./eks-deployment\n\n      - name: Install Pulumi CLI\n        uses: pulumi/action-install-pulumi-cli@v2.0.0\n\n      - name: Provision AWS EKS cluster with Pulumi\n        id: pulumi-up\n        run: |\n          pulumi stack select dev\n          pulumi preview\n          echo \"lets use --suppress-outputs here in order to prevent Pulumi from logging the kubeconfig into public GitHub Action logs\"\n          pulumi up --yes --suppress-outputs\n          pulumi stack output kubeconfig \u003e kubeconfig.yml\n          echo \"::set-output name=eks_url::$(pulumi stack output eksUrl)/api/hello\"\n        working-directory: ./eks-deployment\n\n      - name: Try to connect to our EKS cluster using kubectl\n        run: kubectl --kubeconfig kubeconfig.yml get nodes\n        working-directory: ./eks-deployment\n\n\n```\n\nMind to use `--suppress-outputs` flag for our `pulumi up` to prevent the `kubeconfig` from getting logged unmasked. \n\nWe also export our `eks endpoint url` as an GitHub Environment ([as described here](https://stackoverflow.com/a/67385569/4964553)).\n\n\n#### Prevent the ' getting credentials: exec: executable aws failed with exit code 255' error\n\nI got this error ([see log](https://github.com/jonashackt/tekton-flux-eks-pulumi/runs/4105712645?check_suite_focus=true)): \n\n```\n...\n\u003cbotocore.awsrequest.AWSRequest object at 0x7f067c580670\u003e\nUnable to connect to the server: getting credentials: exec: executable aws failed with exit code 255\nError: Process completed with exit code 1.\n```\n\nLuckily this answer brought me into the right direction: https://stackoverflow.com/a/59184490/4964553\n\nI needed to define the `AWS_DEFAULT_REGION: 'eu-central-1'` also solely for `kubectl` in GitHub Actions. With this the error was gone, since the other two variables for `aws-cli` (which is already installed in the GitHub Actions virtual environment) were defined properly. \n\n\n### EKS with more power\n\nPer default Pulumi creates an EKS cluster with only 2 worker nodes - doing all this fancy Tekton \u0026 Argo stuff we should upgrade to at least 3 or 4 worker nodes. \n\nTherefore we can [tell Pulumi to use more using the `desiredCapacity` configuration](https://www.pulumi.com/registry/packages/eks/api-docs/cluster/#desiredcapacity_nodejs):\n\n```yaml\n// Create an EKS cluster with the default configuration.\n  const cluster = new eks.Cluster(\"eks-for-tekton\", {\n      desiredCapacity: 3,\n      minSize: 3,\n      maxSize: 4,\n});\n```\n\nIn order to prevent errors like `error waiting for CloudFormation Stack update: failed to update CloudFormation stack (UPDATE_ROLLBACK_COMPLETE): [\"Desired capacity:3 must be between the specified min size:1 and max size:2` we should also update the `minSize`, `maxSize` parameters.\n\nSee also the examples - e.g. https://github.com/pulumi/pulumi-eks/blob/master/examples/cluster/index.ts\n\n\n\n\n# Kubernetes Ingress for Services using Traefik v2\n\nI've started to have the Tekton Dashboard, the ArgoCD server/dashboard \u0026 Tekton Trigger EventListener exposed as their own separate Services of type `LoadBalancer`, which leads to the creation of multiple classic Elastic Load Balancers in AWS:\n\n![elastic-loadbalancers](screenshots/elastic-loadbalancers.png)\n\nIn the section `LoadBalancer for every http service` of https://blog.pipetail.io/posts/2020-05-04-most-common-mistakes-k8s/ the problem is described as:\n\n\u003e resources might get expensive (external static ipv4 address, compute, per-second pricing ,...)\n\nTo prevent that one could use the concept of an api gateway or Ingress in K8s terms. One of the best solutions out there is Traefik https://traefik.io/\n\n\n## Choose one of 3 ways to install \u0026 use Traefik in K8s\n\nAs of the docs there are 3 ways on how to use Traefik in Kubernetes:\n\n1. IngressRoute Custom Resource Definition (CRD) for Kubernetes: https://doc.traefik.io/traefik/providers/kubernetes-crd/\n2. \"old familiar\" Ingress Controller as the Kubernetes Ingress provider: https://doc.traefik.io/traefik/providers/kubernetes-ingress/\n3. Experimental Kubernetes Gateway API: https://doc.traefik.io/traefik/providers/kubernetes-gateway/\n\nCRDs seem to be the current defacto standard way to extend Kubernetes (by extending the Kubernetes API): https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/\n\nSo let's go with the IngressRoute CRD. There's also a full guide including Let's Encrypt in the Traefik docs https://doc.traefik.io/traefik/user-guides/crd-acme/\n\n\n## Install Traefik as KubernetesCRD with Helm\n\nInstall Traefik via Helm: https://doc.traefik.io/traefik/getting-started/install-traefik/#use-the-helm-chart from it's chart at https://github.com/traefik/traefik-helm-chart:\n\n\u003e This chart bootstraps Traefik version 2 as a Kubernetes ingress controller, using Custom Resources IngressRoute: https://docs.traefik.io/providers/kubernetes-crd/\n\nWe do all this right inside our GitHub Actions workflow [provision.yml](.github/workflows/provision.yml):\n\n```yaml\n      - name: Install Traefik via Helm\n        run: |\n          echo \"--- Install Traefik via Helm (which is already installed in GitHub Actions environment https://github.com/actions/virtual-environments)\n          helm repo add traefik https://helm.traefik.io/traefik\n          helm repo update\n          helm upgrade -i traefik traefik/traefik\n```\n\nBut instead of `helm install traefik traefik/traefik` we use `helm upgrade -i traefik traefik/traefik` to prevent the error `Error: INSTALLATION FAILED: cannot re-use a name that is still in use`(see https://stackoverflow.com/a/70465191/4964553).\n\nNow Traefik is already deployed and we can see it's Service (aka the Traefik Ingress Controller) in k9s for example:\n\n![traefik-k9s-service](screenshots/traefik-k9s-service.png)\n\nYou may temporarily expose the dashboard with a local `kubectl port-forward` like this (but we will create a nice domain later also):\n\n```\nkubectl port-forward $(kubectl get pods --selector \"app.kubernetes.io/name=traefik\" --output=name) 9000:9000\n```\n\nAnd access it at http://127.0.0.1:9000/dashboard/\n\n\n#### Install Traefik using Helm with pinned version manageble through Renovate\n\nRight now our Traefik installation uses no pinned version and every new GitHub Actions workflow run simply uses the newest version of Traefik.\n\nSo how can we use a pinned version with Helm? Simply [using `--version` isn't enough for us](https://stackoverflow.com/questions/51200917/how-to-install-a-specific-chart-version), since Renovate needs a dependency file to look at: https://docs.renovatebot.com/modules/manager/helm-values/\n\nBut [there's another way](https://mjpitz.com/blog/2020/12/03/renovate-your-gitops/) to use a simple [Chart.yaml](traefik/install/Chart.yaml) to pin our version and have a manageble file for Renovate:\n\n```yaml\napiVersion: v2\ntype: application\nname: traefik\nversion: 0.0.0 # unused\nappVersion: 0.0.0 # unused\ndependencies:\n  - name: traefik\n    repository: https://helm.traefik.io/traefik\n    version: 10.19.4\n```\n\nNow with the commands:\n\n```shell\nhelm dependency update traefik/install\nhelm upgrade -i traefik traefik/install\n```\n\nWe can now install Traefik in a Renovate-ready way.\n\n\n## IngressRoutes for Services to be available via Traefik\n\nNow let's configure the `IngressRoute` objects to get our Services accessible through Traefik\n\nhttps://doc.traefik.io/traefik/user-guides/crd-acme/#traefik-routers\n\nhttps://doc.traefik.io/traefik/routing/routers/#rule\n\nSo start by creating our first `IngressRoute` definition - right now only statically to see it working inside [traefik-application-ingress-routes.yml](traefik/traefik-application-ingress-routes.yml):\n\n```yaml\napiVersion: traefik.containo.us/v1alpha1\nkind: IngressRoute\nmetadata:\n  name: microservice-api-spring-boot-ingressroute\n  namespace: default\nspec:\n  entryPoints:\n    - web\n  routes:\n    - match: Host(`microservice-api-spring-boot-main`)\n      kind: Rule\n      services:\n        - name: microservice-api-spring-boot-main\n          port: 80\n```\n\nApply it with `kubectl apply -f traefik/traefik-application-ingress-routes.yml`\n\nFinally use a REST client like Postman to access our Service:\n\n![traefik-postman-first-ingressroute-service-call](screenshots/traefik-postman-first-ingressroute-service-call.png)\n\nYou need to provide the `Host:microservice-api-spring-boot-main` header in your request in order to make the call work.\n\n\n\n## Testing DNS-based Service availability on AWS EKS with Traefik\n\nFirst create a Domain with AWS Route53 - this will take a while \u0026 you should finally receive a mail, that your domain was registered successfully (this took around 20mins for me).\n\nWhen we have our domain ready - for me this is tekton-argocd.de - we can configure the Route53 hosted zone with the correct records.\n\nSee https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-elb-load-balancer.html#routing-to-elb-load-balancer-configuring\n\n![route53-hostedzone-record](screenshots/route53-hostedzone-record.png)\n\n\nLet's test it by enhancing our `IngressRoute` inside [traefik-application-ingress-routes.yml](traefik/traefik-application-ingress-routes.yml):\n\n```yaml\napiVersion: traefik.containo.us/v1alpha1\nkind: IngressRoute\nmetadata:\n  name: microservice-api-spring-boot-ingressroute\n  namespace: default\nspec:\n  entryPoints:\n    - web\n  routes:\n    - match: Host(`microservice-api-spring-boot-main.tekton-argocd.de`)\n      kind: Rule\n      services:\n        - name: microservice-api-spring-boot-main\n          port: 80\n```\n\nAnd apply it with `kubectl apply -f traefik/traefik-application-ingress-routes.yml`\n\n\n## Automatically creating the Route53 A record based on the Traefik ELB in GitHub Actions\n\nNow creating the Route53 record manually isn't what we should aim for. Instead let's use AWS CLI to do that for us.\n\n\u003e see https://stackoverflow.com/questions/71438625/create-route53-hosted-zone-a-record-dynamically-from-ci-based-on-previously-prov/71438626#71438626\n\nHere's a starting point https://aws.amazon.com/premiumsupport/knowledge-center/alias-resource-record-set-route53-cli/\n\nBut we don't want to do this using a static file like with the proposed `--change-batch file://sample.json` - instead we want to have it more dynamic so we can use a command inside our GitHub Actions workflow.\n\nThe idea is derived from this so answer https://stackoverflow.com/a/49228748/4964553, where we can simply use the json snippet inline without an extra file.\n\nWe also want to have an idempotent solution which we can run 1 or many times in our GitHub Actions CI. Therefore we use the `\"Action\" : \"UPSERT\"` (see https://aws.amazon.com/premiumsupport/knowledge-center/simple-resource-record-route53-cli/).\n\n```yaml\n          echo \"--- Creating or updating ('UPSERT') Route53 hosted zone A record to point to ELB Traefik (see https://aws.amazon.com/premiumsupport/knowledge-center/simple-resource-record-route53-cli/)\"\n          echo \"--- Creating Route53 hosted zone record (mind to wrap the variables in double quotes in order to get them evaluated, see https://stackoverflow.com/a/49228748/4964553)\"\n          aws route53 change-resource-record-sets \\\n            --hosted-zone-id $ROUTE53_DOMAIN_HOSTED_ZONE_ID \\\n            --change-batch '\n            {\n              \"Comment\": \"Create or update Route53 hosted zone A record to point to ELB Traefik is configured to\"\n              ,\"Changes\": [{\n                \"Action\"              : \"UPSERT\"\n                ,\"ResourceRecordSet\"  : {\n                  \"Name\"              : \"*.'\"$ROUTE53_DOMAIN_NAME\"'\"\n                  ,\"Type\"             : \"A\"\n                  ,\"AliasTarget\": {\n                      \"HostedZoneId\": \"'\"$ELB_HOSTED_ZONE_ID\"'\",\n                      \"DNSName\": \"dualstack.'\"$ELB_URL\"'\",\n                      \"EvaluateTargetHealth\": true\n                  }\n                }\n              }]\n            }\n            '\n```\n\n\u003e Using variables inside the json provided to the `--change-batch` parameter, we need to use single quotes and open them up immediately after (also see https://stackoverflow.com/a/49228748/4964553)\n\nAs you can see, we need to configure 4 variables to make this command run:\n\n1. `$ROUTE53_DOMAIN_HOSTED_ZONE_ID`: This is the hosted zone id of your Route53 domain you need to register before (the registration itself is a manual step)\n2. `$ROUTE53_DOMAIN_NAME`: Your Route53 registered domain name. As we want all routing to be done by Traefik, we can configure a wildcard record here using `*.$ROUTE53_DOMAIN_NAME`\n3. `$ELB_HOSTED_ZONE_ID`: [A different hosted zone id than your domain!](https://stackoverflow.com/a/59584444/4964553). This is the hosted zone id of the Elastic Load Balancer, which gets provisioned through the Traefik Service deployment (via Helm).\n4. `$ELB_URL`: The ELB url of the Traefik Service. We need to preface it with `dualstack.` in order to make it work (see https://docs.aws.amazon.com/Route53/latest/APIReference/API_AliasTarget.html)\n\nObtaining all those variables isn't trivial. We can start with the Route53 domain name, we need to configure as a static GitHub Actions environment varialbe at the top of our [provision.yml](.github/workflows/provision.yml):\n\n```yaml\nname: provision\n\non: [push]\n\nenv:\n  ...\n  ROUTE53_DOMAIN_NAME: tekton-argocd.de\n...\n\n      - name: Create or update Route53 hosted zone A record to point to ELB Traefik is configured to\n        run: |\n          echo \"--- Obtaining the Route53 domain's hosted zone id\"\n          ROUTE53_DOMAIN_HOSTED_ZONE_ID=\"$(aws route53 list-hosted-zones-by-name | jq --arg name \"$ROUTE53_DOMAIN_NAME.\" -r '.HostedZones | .[] | select(.Name==\"\\($name)\") | .Id')\"\n\n          echo \"--- Obtaining the ELB hosted zone id\"\n          echo \"Therefore cutting the ELB url from the traefik k8s Service using cut (see https://stackoverflow.com/a/29903172/4964553)\"\n          ELB_NAME=\"$(kubectl get service traefik -n default --output=jsonpath='{.status.loadBalancer.ingress[0].hostname}' | cut -d \"-\" -f 1)\"\n          echo \"Extracting the hosted zone it using aws cli and jq (see https://stackoverflow.com/a/53230627/4964553)\"\n          ELB_HOSTED_ZONE_ID=\"$(aws elb describe-load-balancers | jq --arg name \"$ELB_NAME\" -r '.LoadBalancerDescriptions | .[] | select(.LoadBalancerName==\"\\($name)\") | .CanonicalHostedZoneNameID')\"\n\n          echo \"--- Obtaining the Elastic Load Balancer url as the A records AliasTarget\"\n          ELB_URL=\"$(kubectl get service traefik -n default --output=jsonpath='{.status.loadBalancer.ingress[0].hostname}')\"\n\n```\n\n\n## Expose Traefik dashboard as traefik.tekton-argocd.de\n\nhttps://doc.traefik.io/traefik/operations/dashboard/\n\nAs we now have our Route53 record configuration in place to access our apps, we can also create a nice access to our Traefik dashboard to avoid the need of a manually started local `port-forward`:\n\nhttps://doc.traefik.io/traefik/getting-started/install-traefik/#exposing-the-traefik-dashboard\n\nTherefore let't create a `IngressRoute` for the Traefik dashboard at [traefik/traefik-dashboard.yml](traefik/traefik-dashboard.yml):\n\n```yaml\napiVersion: traefik.containo.us/v1alpha1\nkind: IngressRoute\nmetadata:\n  name: dashboard\nspec:\n  entryPoints:\n    - web\n  routes:\n    - match: Host(`traefik.tekton-argocd.de`)\n      kind: Rule\n      services:\n        - name: api@internal\n          kind: TraefikService\n```\n\nNow install it with:\n\n```shell\nkubectl apply -f traefik/traefik-dashboard.yml\n```\n\n\nWe also directly expose our nice Traefik url traefik.tekton-argocd.de as GitHub Actions Environment:\n\n```yaml\n    environment:\n      name: traefik-eks-url\n      url: ${{ steps.traefik-expose.outputs.traefik_url }}\n\n...\n\n      - name: Expose Traefik url as GitHub environment\n        id: traefik-expose\n        run: |\n          echo \"--- Apply Traefik-ception IngressRule\"\n          kubectl apply -f traefik/traefik-dashboard.yml\n          \n          echo \"--- Wait until Loadbalancer url is present (see https://stackoverflow.com/a/70108500/4964553)\"\n          until kubectl get service/traefik -n default --output=jsonpath='{.status.loadBalancer}' | grep \"ingress\"; do : ; done\n\n          TRAEFIK_URL=\"http://traefik.$ROUTE53_DOMAIN_NAME\"\n          echo \"All Services should be accessible through Traefik Ingress at $TRAEFIK_URL - creating GitHub Environment\"\n          echo \"::set-output name=traefik_url::$TRAEFIK_URL\"\n```\n\nNow Traefik should be accessible at http://traefik.tekton-argocd.de also through our pipeline.\n\n\n\n\n\n# Tekton on EKS\n\nhttps://tekton.dev/docs/getting-started/\n\n## Tekton Dashboard\n\nhttps://tekton.dev/docs/dashboard/\n\nInstall it with:\n\n```shell\nkubectl apply --filename https://github.com/tektoncd/dashboard/releases/latest/download/tekton-dashboard-release.yaml\n```\n\nNow as we already ran some Tasks let's have a look into the Tekton dashboard:\n\n```shell\nkubectl proxy --port=8080\n```\n\nThen open your Browser at http://localhost:8080/api/v1/namespaces/tekton-pipelines/services/tekton-dashboard:http/proxy/\n\n\n### Expose Tekton Dashboard through Traefik\n\nSo let's use Ingress with our Traefik and the nice Route53 domain \u0026 wildcard record to route from tekton.tekton-argocd.de. Simply create an Traefik `IngressRoute` as described in  [traefik/tekton-dashboard.yml](traefik/tekton-dashboard.yml):\n\n```yaml\napiVersion: traefik.containo.us/v1alpha1\nkind: IngressRoute\nmetadata:\n  name: tekton-dashboard\n  namespace: tekton-pipelines\nspec:\n  entryPoints:\n    - web\n  routes:\n    - kind: Rule\n      match: Host(`tekton.tekton-argocd.de`)\n      services:\n        - name: tekton-dashboard\n          port: 9097\n```\n\nApply it with `kubectl apply -f traefik/tekton-dashboard.yml` and you should be able to access the Tekton dashboard already at http://tekton.tekton-argocd.de:\n\n![tekton-dashboard-traefik](screenshots/tekton-dashboard-traefik.png)\n\n\n### Grab the Tekton Dashboard URL and populate as GitHub Environment\n\nNow that our AWS ELB is provisioned we can finally grad it's URL with:\n\n```shell\nkubectl get service tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer.ingress[0].hostname}'\n```\n\nThen it's easy to use the output and create a GitHub step variable, which is used in the GitHub Environment definition at the top of the job:\n\n```yaml\necho \"--- Create GitHub environment var\"\nDASHBOARD_HOST=$(kubectl get service tekton-dashboard-external-svc-manual -n tekton-pipelines --output=jsonpath='{.status.loadBalancer.ingress[0].hostname}')\necho \"The Tekton dashboard is accessible at $DASHBOARD_HOST - creating GitHub Environment\"\necho \"::set-output name=dashboard_host::http://$DASHBOARD_HOST\"\n```\n\n\n\n## Tekton Pipelines\n\nhttps://tekton.dev/docs/getting-started/#installation\n\nSo let's add the installation and wait for Tekton to become available:\n\n```yaml\n...\n      - name: Install Tekton Pipelines\n        run: kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml\n\n      - name: Wait for Tekton to become ready \u0026 show running Tekton pods\n        run: |\n          kubectl wait --for=condition=ready pod -l app=tekton-pipelines-controller --namespace tekton-pipelines\n          kubectl get pods --namespace tekton-pipelines\n```\n\n### Persistent Volumes (Optional)\n\nhttps://tekton.dev/docs/getting-started/#persistent-volumes\n\nLet's check if our EKS cluster [already has a `StorageClass` defined](https://docs.aws.amazon.com/eks/latest/userguide/storage-classes.html) with `kubectl get storageclasses`:\n\n```shell\n$ kubectl get storageclasses\nNAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE\ngp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  179m\n```\nBefore creating it let's check if there is already a `ConfigMap` defined:\n\n```shell\nkubectl describe configmap config-artifact-pvc -n tekton-pipelines\n```\n\nFrom the docs:\n\n\u003e Your Kubernetes cluster, such as one from Google Kubernetes Engine, may have persistent volumes set up at the time of creation, thus no extra step is required\n\nIf there's no Persistens Volume defined, we need to create a `ConfigMap` which defines the Persistent Volume Tekton will request:\n\n```shell\nkubectl create configmap config-artifact-pvc \\\n                         --from-literal=size=10Gi \\\n                         --from-literal=storageClassName=gp2 \\\n                         -o yaml -n tekton-pipelines \\\n                         --dry-run=client | kubectl replace -f -\n```\n\n\n### Tekton CLI\n\nInstall the Tekton CLI e.g. via homebrew:\n\n```shell\nbrew tap tektoncd/tools\nbrew install tektoncd/tools/tektoncd-cli\n```\n\n### Run first Tekton Task\n\nSee the [task-hello-world.yaml](tekton/tasks/task-hello-world.yaml):\n\n```yaml\napiVersion: tekton.dev/v1beta1\nkind: Task\nmetadata:\n  name: hello\nspec:\n  steps:\n    - name: hello\n      image: ubuntu\n      command:\n        - echo\n      args:\n        - \"Hello World!\"\n```\n\nLet's apply it to our cluster:\n\n```shell\nkubectl apply -f tekton/tasks/task-hello-world.yaml\n```\n\nLet's show our newly created task:\n\n```shell\n$ tkn task list\nNAMESPACE   NAME    DESCRIPTION   AGE\ndefault     hello                 24 seconds ago\n```\n\nNow this is only a Tekton Task definition. We need another Tekton object - the `TaskRun` - in order to run our Task. Create it with:\n\n```shell\ntkn task start hello\n```\n\nFollow the logs of the TaskRun with:\n\n```shell\ntkn taskrun logs --last -f \n```\n\n\n\n## Cloud Native Buildpacks with Tekton\n\nhttps://buildpacks.io/docs/tools/tekton/\n\n### Install Tasks\n\nInstall [git clone](https://hub.tekton.dev/tekton/task/git-clone) and [buildpacks](https://hub.tekton.dev/tekton/task/buildpacks) Task:\n```shell\nkubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/master/task/git-clone/0.4/git-clone.yaml\nkubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/master/task/buildpacks/0.3/buildpacks.yaml\n```\n\n### Create Secret for GitLab Container Registry authorization\n\nTo access the GitLab Container Registry we need to first create a PAT or deploy token (see https://docs.gitlab.com/ee/user/packages/container_registry/#authenticate-with-the-container-registry)\n\nGo to `Settings/Repository` inside your GitLab repo - for me this is https://gitlab.com/jonashackt/microservice-api-spring-boot/-/settings/repository\n\nThere create a token `TektonBuildpacksToken` under `Deploy tokens` with a username `gitlab-token` and `read_registry` \u0026 `write_registry` access. \n\nNow create GitHub Repository Secrets called `GITLAB_CR_USER` and `GITLAB_CR_PASSWORD` accordingly with the Tokens username and token.\n\nFinally we can create our Secret inside our GitHub Actions pipeline:\n\nhttps://buildpacks.io/docs/tools/tekton/#42-authorization\n\n```shell\nkubectl create secret docker-registry docker-user-pass \\\n    --docker-server=registry.gitlab.com \\\n    --docker-username=${{ secrets.GITLAB_CR_USER }} \\\n    --docker-password=${{ secrets.GITLAB_CR_PASSWORD }} \\\n    --namespace default\n```\n\nAfter the first successful secret creation, we sadly get the error `error: failed to create secret secrets \"docker-user-pass\" already exists` - which is correct, since the secret already exists.\n\nBut there's help (see https://stackoverflow.com/a/45881259/4964553): We add `--save-config --dry-run=client -o yaml | kubectl apply -f -` to our command like this:\n\n```shell\nkubectl create secret docker-registry gitlab-container-registry \\\n    --docker-server=registry.gitlab.com \\\n    --docker-username=${{ secrets.GITLAB_CR_USER }} \\\n    --docker-password=${{ secrets.GITLAB_CR_PASSWORD }} \\\n    --namespace default \\\n    --save-config --dry-run=client -o yaml | kubectl apply -f -\n```\n\nNow we made an `apply` out of our `create` kubectl command, which we can use repetitively :)\n\n\nWe also need to create a `ServiceAccount` that uses this secret as [buildpacks-service-account-gitlab.yml](tekton/misc/buildpacks-service-account-gitlab.yml)\n\n```yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: buildpacks-service-account-gitlab\nsecrets:\n  - name: gitlab-container-registry\n```\n\n### Create buildpacks PVC \n\nhttps://buildpacks.io/docs/tools/tekton/#41-pvcs\n\nCreate new [buildpacks-source-pvc.yml](tekton/misc/buildpacks-source-pvc.yml):\n\n```yaml\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: buildpacks-source-pvc\nspec:\n  accessModes:\n    - ReadWriteOnce\n  resources:\n    requests:\n      storage: 500Mi\n```\n\n### Create Pipeline\n\nCreate [pipeline.yml](tekton/pipelines/pipeline.yml):\n\n```yaml\napiVersion: tekton.dev/v1beta1\nkind: Pipeline\nmetadata:\n  name: buildpacks-test-pipeline\nspec:\n  params:\n    - name: image\n      type: string\n      description: image URL to push\n  workspaces:\n    - name: source-workspace # Directory where application source is located. (REQUIRED)\n    - name: cache-workspace # Directory where cache is stored (OPTIONAL)\n  tasks:\n    - name: fetch-repository # This task fetches a repository from github, using the `git-clone` task you installed\n      taskRef:\n        name: git-clone\n      workspaces:\n        - name: output\n          workspace: source-workspace\n      params:\n        - name: url\n          value: https://github.com/buildpacks/samples\n        - name: subdirectory\n          value: \"\"\n        - name: deleteExisting\n          value: \"true\"\n    - name: buildpacks # This task uses the `buildpacks` task to build the application\n      taskRef:\n        name: buildpacks\n      runAfter:\n        - fetch-repository\n      workspaces:\n        - name: source\n          workspace: source-workspace\n        - name: cache\n          workspace: cache-workspace\n      params:\n        - name: APP_IMAGE\n          value: \"$(params.image)\"\n        - name: SOURCE_SUBPATH\n          value: \"apps/java-maven\" # This is the path within the samples repo you want to build (OPTIONAL, default: \"\")\n        - name: BUILDER_IMAGE\n          value: paketobuildpacks/builder:base # This is the builder we want the task to use (REQUIRED)\n    - name: display-results\n      runAfter:\n        - buildpacks\n      taskSpec:\n        steps:\n          - name: print\n            image: docker.io/library/bash:5.1.4@sha256:b208215a4655538be652b2769d82e576bc4d0a2bb132144c060efc5be8c3f5d6\n            script: |\n              #!/usr/bin/env bash\n              set -e\n              echo \"Digest of created app image: $(params.DIGEST)\"              \n        params:\n          - name: DIGEST\n      params:\n        - name: DIGEST\n          value: $(tasks.buildpacks.results.APP_IMAGE_DIGEST)\n```\n\nAnd now apply all three configs with:\n\n```shell\nkubectl apply -f tekton/misc/buildpacks-source-pvc.yml\nkubectl apply -f tekton/misc/buildpacks-service-account-gitlab.yml\nkubectl apply -f tekton/pipelines/pipeline.yml\n```\n\n### Create PipelineRun\n\nCreate [pipeline-run.yml](tekton/pipelines/pipeline-run.yml):\n\n```yaml\napiVersion: tekton.dev/v1beta1\nkind: PipelineRun\nmetadata:\n  generateName: buildpacks-test-pipeline-run-\nspec:\n  serviceAccountName: buildpacks-service-account-gitlab # Only needed if you set up authorization\n  pipelineRef:\n    name: buildpacks-test-pipeline\n  workspaces:\n    - name: source-workspace\n      subPath: source\n      persistentVolumeClaim:\n        claimName: buildpacks-source-pvc\n    - name: cache-workspace\n      subPath: cache\n      persistentVolumeClaim:\n        claimName: buildpacks-source-pvc\n  params:\n    - name: image\n      value: registry.gitlab.com/jonashackt/microservice-api-spring-boot # This defines the name of output image\n```\n\nA crucial point here is to change the `metadata: name: buildpacks-test-pipeline-run` into `metadata: generateName: buildpacks-test-pipeline-run-`. Why? Because if we use the `name` parameter every `kubectl apply` tries to create the `PipelineRun` object with the same name which results in errors like this:\n\n```shell\nError from server (AlreadyExists): error when creating \"tekton/pipelines/pipeline-run.yml\": pipelineruns.tekton.dev \"buildpacks-test-pipeline-run\" already exists\n```\n\nUsing the `generateName` field fixes our problem (see https://stackoverflow.com/questions/69880096/how-to-restart-tekton-pipelinerun-having-a-pipeline-run-yml-defined-in-git-e-g/69880097#69880097), although we should implement a kind of garbage collection for our PipelineRun objects...\n\n\nAlso mind the `params: name: image` and insert an image name containing the correct namespace of your Container Registry you created a Secret for! \n\nAlso apply with\n\n```shell\nkubectl apply -f tekton/pipelines/pipeline-run.yml\n```\n\nLooking into the Tekton dashboard we should now finally see a successful Pipeline run:\n\n![successful-tekton-buildpacks-pipeline-run](screenshots/successful-tekton-buildpacks-pipeline-run.png)\n\n\n\n### Cache Buildpacks builds with cache image\n\nIt's extremey easy to leverage the Buildpacks cache image feature, that will create a separate cache image for building and will store it alongside the resulting app image in our Container Registry.\n\nTherefore simply add the `CACHE_IMAGE` parameter using our `$(params.IMAGE)` definition and appending `:paketo-build-cache` like this inside our Pipeline:\n\n```yaml\n      params:\n        - name: APP_IMAGE\n          value: \"$(params.IMAGE)\"\n        - name: CACHE_IMAGE\n          value: \"$(params.IMAGE):paketo-build-cache\"\n        - name: BUILDER_IMAGE\n          value: paketobuildpacks/builder:base # This is the builder we want the task to use (REQUIRED)\n```\n\nNow the next build should produce a separate image inside our Container Registry:\n\n![paketo-cache-image](screenshots/paketo-cache-image.png)\n\n\n### Add Maven Task to Pipeline\n\nWhat about extending our Tekton Pipeline with a Maven Task, that initiates a test run before we build our app container using Buildpacks.\n\nTherefore we can simply use the https://hub.tekton.dev/tekton/task/maven Task from Tekton Hub.\n\nFirst we need to install the Task in our GitHub Actions [provision.yml](.github/workflows/provision.yml):\n\n```yaml\nkubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/main/task/maven/0.2/maven.yaml\n```\n\nNow we need to enhance our [pipeline.yml](tekton/pipelines/pipeline.yml) with a new workspace `maven-settings` and the Task definition `maven-test`, which should also be defined as `runAfter` target in the `buildpacks` Task:  \n\n```yaml\n  workspaces:\n    - name: maven-settings # Maven settings, see https://hub.tekton.dev/tekton/task/maven\n      ...\n  tasks:\n    ...\n    - name: maven-test\n      taskRef:\n        name: maven\n      runAfter:\n        - fetch-repository\n      params:\n        - name: GOALS\n          value:\n            - verify\n      workspaces:\n        - name: maven-settings\n          workspace: maven-settings\n        - name: source\n          workspace: source-workspace\n    - name: buildpacks # This task uses the `buildpacks` task to build the application\n      taskRef:\n        name: buildpacks\n      runAfter:\n        - maven-test\n```\n\nWe also should enhance our [pipeline-run.yml](tekton/pipelines/pipeline-run.yml) and [gitlab-push-listener.yml](tekton/triggers/gitlab-push-listener.yml) to define the additional workspace:\n\n```yaml\n  pipelineRef:\n    name: buildpacks-test-pipeline\n  workspaces:\n    - name: maven-settings\n      emptyDir: {}\n```\n\nFinally we should also add the new workspace with `--workspace name=maven-settings,emptyDir=` to our `tkn pipeline start` command in the project's branch https://gitlab.com/jonashackt/microservice-api-spring-boot/-/tree/trigger-tekton-via-gitlabci inside the `.gitlab-ci.yml`:\n\n```shell\ntkn pipeline start buildpacks-test-pipeline \\\n  --serviceaccount buildpacks-service-account-gitlab \\\n  --workspace name=maven-settings,emptyDir= \\\n  --workspace name=source-workspace,subPath=source,claimName=buildpacks-source-pvc \\\n  --workspace name=cache-workspace,subPath=cache,claimName=buildpacks-source-pvc \\\n  --param IMAGE=registry.gitlab.com/jonashackt/microservice-api-spring-boot \\\n  --param SOURCE_URL=https://gitlab.com/jonashackt/microservice-api-spring-boot \\\n  --param REPO_PATH_ONLY=jonashackt/microservice-api-spring-boot \\\n  --param SOURCE_REVISION=main \\\n  --param GITLAB_HOST=gitlab.com \\\n  --param TEKTON_DASHBOARD_HOST=\"http://abd1c6f235c9642bf9d4cdf632962298-1232135946.eu-central-1.elb.amazonaws.com\" \\\n  --timeout 240s \\\n  --showlog\n```\n\n\n### Add caching to Maven Task\n\nIt seems that the [Tekton Hub's Maven Task](https://hub.tekton.dev/tekton/task/maven) doesn't implement caching for us. Our builds tend to download all Maven dependencies over and over again:\n\n![maven-task-without-repo-cache](screenshots/maven-task-without-repo-cache.png)\n\nAs stated in https://developers.redhat.com/blog/2020/02/26/speed-up-maven-builds-in-tekton-pipelines#maven_task_with_a_workspace we need to define our own Task for that.\n\nAs we don't need the `settings.xml` configuration (e.g. for Proxy settings), which is the main point of the Tekton Hub's Maven Task, we can simply create our own - see [task-maven-with-cache.yml](tasks/task-maven-with-cache.yml):\n\n```yaml\napiVersion: tekton.dev/v1alpha1\nkind: Task\nmetadata:\n  name: maven-with-cache\nspec:\n  workspaces:\n    - name: source\n      description: The workspace consisting of maven project.\n    - name: maven-repo-cache\n      description: The workspace holding the Maven repository for caching.\n  params:\n    - name: GOALS\n      description: The Maven goals to run\n      type: array\n      default:\n        - \"package\"\n  steps:\n    - name: mvn\n      image: gcr.io/cloud-builders/mvn\n      workingDir: $(workspaces.source.path)\n      command: [\"/usr/bin/mvn\"]\n      args:\n        - -Dmaven.repo.local=$(workspaces.maven-repo-cache.path)\n        - \"$(params.GOALS)\"\n```\n\nWe need to `kubectl apply` our Task `maven-with-cache` with:\n\n```shell\nkubectl apply -f tasks/task-maven-with-cache.yml\n```\n\n\nWe also need to use our new Maven Task inside our [pipeline.yml](tekton/pipelines/pipeline.yml):\n\n```yaml\nworkspaces:\n  - name: maven-repo-cache # Maven repository cahce, see https://developers.redhat.com/blog/2020/02/26/speed-up-maven-builds-in-tekton-pipelines#run_a_maven_pipeline\n\n...\n\n    - name: maven-test\n      taskRef:\n        name: maven-with-cache\n      runAfter:\n        - fetch-repository\n      params:\n        - name: GOALS\n          value:\n            - verify\n      workspaces:\n        - name: source\n          workspace: source-workspace\n        - name: maven-repo-cache\n          workspace: maven-repo-cache\n```\n\nNow we could try to also create a new `PersistentVolumeClaim` as stated in https://developers.redhat.com/blog/2020/02/26/speed-up-maven-builds-in-tekton-pipelines#run_a_maven_pipeline\n\n```yaml\n    - name: maven-repo-cache\n      persistentVolumeClaim:\n        claimName: maven-repo-cache-pvc\n```\n\nBut we would run into errors like:\n\n```shell\ncompletionTime: '2021-12-06T09:13:49Z'\nconditions:  - lastTransitionTime: '2021-12-06T09:13:49Z'\n    message: more than one PersistentVolumeClaim is bound\n    reason: TaskRunValidationFailed\n    status: 'False'\n    type: \n```\n\nIt seems that Tekton doesn't like to have multiple PVC inside one Task: https://github.com/tektoncd/pipeline/issues/3480 and https://github.com/tektoncd/pipeline/issues/3085\n\n\u003e In general, try to only use a single PVC for each task.\n\nSo we need to use a separate `subPath` inside our already existing PVC `buildpacks-source-pvc` (which doesn't have a matching name any more it seems :) ).\n\nAs stated in https://buildpacks.io/docs/tools/tekton/#43-pipeline __a Tekton workspace could be simply seen as a shared directory__ (see https://tekton.dev/docs/pipelines/workspaces/, where I didn't get this first).\n\nSo we finally simply provide a new workspace using the existing PVC but a different `subPath` to our [pipeline-run.yml](tekton/pipelines/pipeline-run.yml) \u0026 [gitlab-push-listener.yml](tekton/triggers/gitlab-push-listener.yml):\n\n```yaml\n    - name: maven-repo-cache\n      subPath: maven-repo-cache\n      persistentVolumeClaim:\n        claimName: buildpacks-source-pvc\n```\n\n\nAnd we can even optimize our solution by simply using the `maven-settings` workspace definition of the [standard Tekton Hub's Maven Task](https://hub.tekton.dev/tekton/task/maven) inside our Pipeline\n\n```yaml\nworkspaces:\n  - name: maven-repo-cache # Maven repository cahce, see https://developers.redhat.com/blog/2020/02/26/speed-up-maven-builds-in-tekton-pipelines#run_a_maven_pipeline\n\n...\n    - name: maven-test\n      taskRef:\n        name: maven\n      runAfter:\n        - fetch-repository\n      params:\n        - name: GOALS\n          value:\n            - -Dmaven.repo.local=$(workspaces.maven-settings.path)\n            - verify\n      workspaces:\n        - name: source\n          workspace: source-workspace\n        - name: maven-settings\n          workspace: maven-repo-cache\n```\n\nNow our Pipeline should run faster then before, since the Maven cache is used:\n\n![maven-task-with-repo-cache](screenshots/maven-task-with-repo-cache.png)\n\n\n\n\n\n# Integrate Tekton on EKS with GitLab\n\nHow to trigger Tekton `PipelineRun` from GitLab?\n\n\n## Trigger Tekton directly from GitLab CI\n\nThe simplest possible solution is to leverage GitLab CI and trigger Tekton from there.\n\nSee https://stackoverflow.com/a/69991508/4964553\n\nHave a look at this example gitlab.com project https://gitlab.com/jonashackt/microservice-api-spring-boot/-/tree/trigger-tekton-via-gitlabci\n\n\n\n\n\n## Tekton Triggers\n\nFull getting-started guide: https://github.com/tektoncd/triggers/tree/v0.17.0/docs/getting-started\n\n__BUT FIRST__ Examples are a great inspiration - for GitLab this is especially:\n\nhttps://github.com/tektoncd/triggers/tree/main/examples/v1beta1/gitlab\n\n\n### Install Tekton Triggers\n\nhttps://tekton.dev/docs/triggers/install/\n\n```shell\nkubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml\nkubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers/latest/interceptors.yaml\n```\n\n\n### ServiceAccount, RoleBinding \u0026 ClusterRoleBinding\n\nSee https://github.com/tektoncd/triggers/blob/v0.17.0/examples/rbac.yaml\n\nSo we also create [serviceaccount-rb-crb.yml](tekton/triggers/serviceaccount-rb-crb.yml):\n\n```yaml\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: tekton-triggers-example-sa\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: triggers-example-eventlistener-binding\nsubjects:\n- kind: ServiceAccount\n  name: tekton-triggers-example-sa\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: tekton-triggers-eventlistener-roles\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: triggers-example-eventlistener-clusterbinding\nsubjects:\n- kind: ServiceAccount\n  name: tekton-triggers-example-sa\n  namespace: default\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: tekton-triggers-eventlistener-clusterroles\n```\n\n```shell\nkubectl apply -f tekton/triggers/serviceaccount-rb-crb.yml\n```\n\n\n### Tekton Trigger Secret\n\nAs our Tekton Trigger API will be setup as a public API in the end, we need to secure our Trigger API somehow.\n\nOne way is to create a secret ID the calling JSON must contain. So let's create [tekton-trigger-secret.yml](tekton/triggers/tekton-trigger-secret.yml):\n\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: gitlab-secret\ntype: Opaque\nstringData:\n  secretToken: \"1234567\"\n```\n\n```shell\nkubectl apply -f tekton/triggers/tekton-trigger-secret.yml\n```\n\n### EventListener\n\nSo let's start with the `EventListener` . We'll adapt the `EventListener` from the example (see https://github.com/tektoncd/triggers/blob/main/examples/v1beta1/gitlab/gitlab-push-listener.yaml) to use our Buildpacks Pipeline defined in [pipeline.yml](tekton/pipelines/pipeline.yml).\n\nTherefore let's create a new file called [gitlab-push-listener.yml](tekton/triggers/gitlab-push-listener.yml):\n\n```yaml\napiVersion: triggers.tekton.dev/v1beta1\nkind: EventListener\nmetadata:\n  name: gitlab-listener\nspec:\n  serviceAccountName: tekton-triggers-example-sa\n  triggers:\n    - name: gitlab-push-events-trigger\n      interceptors:\n        - name: \"verify-gitlab-payload\"\n          ref:\n            name: \"gitlab\"\n            kind: ClusterInterceptor\n          params:\n            - name: secretRef\n              value:\n                secretName: \"gitlab-secret\"\n                secretKey: \"secretToken\"\n            - name: eventTypes\n              value:\n                - \"Push Hook\"\n      bindings:\n        - name: gitrevision\n          value: $(body.checkout_sha)\n        - name: gitrepositoryurl\n          value: $(body.repository.git_http_url)\n      template:\n        spec:\n          params:\n            - name: gitrevision\n            - name: gitrepositoryurl\n            - name: message\n              description: The message to print\n              default: This is the default message\n            - name: contenttype\n              description: The Content-Type of the event\n          resourcetemplates:\n            - apiVersion: tekton.dev/v1beta1\n              kind: PipelineRun\n              metadata:\n                generateName: buildpacks-test-pipeline-run-\n                #name: buildpacks-test-pipeline-run\n              spec:\n                serviceAccountName: buildpacks-service-account-gitlab # Only needed if you set up authorization\n                pipelineRef:\n                  name: buildpacks-test-pipeline\n                workspaces:\n                  - name: source-workspace\n                    subPath: source\n                    persistentVolumeClaim:\n                      claimName: buildpacks-source-pvc\n                  - name: cache-workspace\n                    subPath: cache\n                    persistentVolumeClaim:\n                      claimName: buildpacks-source-pvc\n                params:\n                  - name: IMAGE\n                    value: registry.gitlab.com/jonashackt/microservice-api-spring-boot # This defines the name of output image\n                  - name: SOURCE_URL\n                    value: https://gitlab.com/jonashackt/microservice-api-spring-boot\n                  - name: SOURCE_REVISION\n                    value: main\n\n```\n\nNow apply it to our cluster via:\n\n```shell\nkubectl apply -f tekton/triggers/gitlab-push-listener.yml\n```\n\n\u003e Tekton Triggers creates a new Deployment and Service for the EventListener\n\nAs stated in https://tekton.dev/docs/triggers/eventlisteners/#understanding-the-deployment-of-an-eventlistener \n\n\u003e Tekton Triggers uses the EventListener name prefixed with el- to name the Deployment and Service when instantiating them.\n\nthis will also deploy a K8s `Service` called `el-gitlab-listener` and a `Deployment` also called `el-gitlab-listener`:\n\n```shell\n$ k get deployment\nNAME                 READY   UP-TO-DATE   AVAILABLE   AGE\nel-gitlab-listener   1/1     1            1           25h\n\n$ k get service\nNAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE\nel-gitlab-listener   ClusterIP   10.100.101.207   \u003cnone\u003e        8080/TCP,9000/TCP   12m\n...\n```\n\n\n\n### Create GitLab Webhook \u0026 Craft a Push test event .json\n\nWhat we need here is an example test event as `.json` file. For an example see https://github.com/tektoncd/triggers/blob/main/examples/v1beta1/gitlab/gitlab-push-event.json\n\nBut we can craft this file ourselves while creating the GitLab Webhook.\n\nTherefore go to your project (in my example here this is https://gitlab.com/jonashackt/microservice-api-spring-boot) and head over to __Settings/Webhooks__.\n\nNow insert our a fake URL like `http://www.google.com` into the __URL__ field (we need a semantically correct url here in order to be able to save the Webhook, but will change it later to the EventListeners Ingress URL).\n\nAlso insert our Secret Token `1234567` in the __Secret Token__ field.\n\nFinally choose __Push events__, deselect SSL verification and scroll down and hit __Add webhook__. \n\nThis should result in the Webhook beeing created and listed right at the bottom of this page (scroll down again) in a list called `Project Hooks`.\n\nClick on __Test__ and choose __Push events__. This will fire a test event (which will produce a HTTP error). Now scroll down again an click on __edit__. Scroll down inside our WebHook (don't get confused with the UI :)) and you should see the __Recent events__ list:\n\n![gitlab-webhook-recent-events](screenshots/gitlab-webhook-recent-events.png)\n\nChoose the latest event and click on `View details`. Scroll down again and you should see the __Request__ frame with the generated request json:\n\n![gitlab-webhook-request](screenshots/gitlab-webhook-request.png)\n\nCopy the whole part into a local file. In this example project this file is called [gitlab-push-test-event.json](tekton/triggers/gitlab-push-test-event.json):\n\n```json\n{\n  \"object_kind\": \"push\",\n  \"event_name\": \"push\",\n  \"before\": \"5bbc8580432fc7a16f50be27eb513db42aad0860\",\n  \"after\": \"c25a74c8f919a72e3f00928917dc4ab2944ab061\",\n  \"ref\": \"refs/heads/main\",\n  \"checkout_sha\": \"c25a74c8f919a72e3f00928917dc4ab2944ab061\",\n  \"message\": null,\n  \"user_id\": 2351133,\n  \"user_name\": \"Jonas Hecht\",\n  \"user_username\": \"jonashackt\",\n  \"user_email\": \"\",\n  \"user_avatar\": \"https://secure.gravatar.com/avatar/a5c83d481ac20557b775703761aef7dc?s=80\u0026d=identicon\",\n  \"project_id\": 30444286,\n  \"project\": {\n    \"id\": 30444286,\n    \"name\": \"microservice-api-spring-boot\",\n    \"description\": \"Forked from https://github.com/jonashackt/microservice-api-spring-boot\",\n    \"web_url\": \"https://gitlab.com/jonashackt/microservice-api-spring-boot\",\n    \"avatar_url\": null,\n    \"git_ssh_url\": \"git@gitlab.com:jonashackt/microservice-api-spring-boot.git\",\n    \"git_http_url\": \"https://gitlab.com/jonashackt/microservice-api-spring-boot.git\",\n    \"namespace\": \"Jonas Hecht\",\n    \"visibility_level\": 20,\n    \"path_with_namespace\": \"jonashackt/microservice-api-spring-boot\",\n    \"default_branch\": \"main\",\n    \"ci_config_path\": \"\",\n    \"homepage\": \"https://gitlab.com/jonashackt/microservice-api-spring-boot\",\n    \"url\": \"git@gitlab.com:jonashackt/microservice-api-spring-boot.git\",\n    \"ssh_url\": \"git@gitlab.com:jonashackt/microservice-api-spring-boot.git\",\n    \"http_url\": \"https://gitlab.com/jonashackt/microservice-api-spring-boot.git\"\n  },\n  \"commits\": [\n    {\n      \"id\": \"c25a74c8f919a72e3f00928917dc4ab2944ab061\",\n      \"message\": \"Fixing cache image naming\\n\",\n      \"title\": \"Fixing cache image naming\",\n      \"timestamp\": \"2021-10-19T10:32:58+02:00\",\n      \"url\": \"https://gitlab.com/jonashackt/microservice-api-spring-boot/-/commit/c25a74c8f919a72e3f00928917dc4ab2944ab061\",\n      \"author\": {\n        \"name\": \"Jonas Hecht\",\n        \"email\": \"jonas.hecht@codecentric.de\"\n      },\n      \"added\": [\n\n      ],\n      \"modified\": [\n        \".gitlab-ci.yml\",\n        \"README.md\"\n      ],\n      \"removed\": [\n\n      ]\n    },\n    {\n      \"id\": \"06a7f1d2ad646acef149b1aad4600eb2b1268f0c\",\n      \"message\": \"Merge branch 'refactor-ci' into 'main'\\n\\nRefactor ci\\n\\nSee merge request jonashackt/microservice-api-spring-boot!2\",\n      \"title\": \"Merge branch 'refactor-ci' into 'main'\",\n      \"timestamp\": \"2021-10-19T08:30:46+00:00\",\n      \"url\": \"https://gitlab.com/jonashackt/microservice-api-spring-boot/-/commit/06a7f1d2ad646acef149b1aad4600eb2b1268f0c\",\n      \"author\": {\n        \"name\": \"Jonas Hecht\",\n        \"email\": \"jonas.hecht@codecentric.de\"\n      },\n      \"added\": [\n\n      ],\n      \"modified\": [\n        \".gitlab-ci.yml\",\n        \"README.md\"\n      ],\n      \"removed\": [\n\n      ]\n    },\n    {\n      \"id\": \"5bbc8580432fc7a16f50be27eb513db42aad0860\",\n      \"message\": \"Add cache image\\n\",\n      \"title\": \"Add cache image\",\n      \"timestamp\": \"2021-10-19T10:05:31+02:00\",\n      \"url\": \"https://gitlab.com/jonashackt/microservice-api-spring-boot/-/commit/5bbc8580432fc7a16f50be27eb513db42aad0860\",\n      \"author\": {\n        \"name\": \"Jonas Hecht\",\n        \"email\": \"jonas.hecht@codecentric.de\"\n      },\n      \"added\": [\n\n      ],\n      \"modified\": [\n        \".gitlab-ci.yml\",\n        \"README.md\"\n      ],\n      \"removed\": [\n\n      ]\n    }\n  ],\n  \"total_commits_count\": 3,\n  \"push_options\": {\n  },\n  \"repository\": {\n    \"name\": \"microservice-api-spring-boot\",\n    \"url\": \"git@gitlab.com:jonashackt/microservice-api-spring-boot.git\",\n    \"description\": \"Forked from https://github.com/jonashackt/microservice-api-spring-boot\",\n    \"homepage\": \"https://gitlab.com/jonashackt/microservice-api-spring-boot\",\n    \"git_http_url\": \"https://gitlab.com/jonashackt/microservice-api-spring-boot.git\",\n    \"git_ssh_url\": \"git@gitlab.com:jonashackt/microservice-api-spring-boot.git\",\n    \"visibility_level\": 20\n  }\n}\n```\n\n\n### Port forward locally \u0026 Trigger Tekton EventListener via curl\n\nPort forward with a new `Service` locally:\n\n```shell\nkubectl port-forward service/el-gitlab-listener 8080\n```\n\nNow test-drive the trigger via curl:\n\n```shell\ncurl -v \\\n-H 'X-GitLab-Token: 1234567' \\\n-H 'X-Gitlab-Event: Push Hook' \\\n-H 'Content-Type: application/json' \\\n--data-binary \"@tekton/triggers/gitlab-push-test-event.json\" \\\nhttp://localhost:8080\n```\n\n\n## Expose Tekton Trigger API publicly through Traefik\n\nLet's use our Ingress with Traefik and the nice Route53 domain \u0026 wildcard record to route from gitlab-listener.tekton-argocd.de. Simply create an Traefik `IngressRoute` as described in  [traefik/gitlab-listener.yml](traefik/gitlab-listener.yml):\n\n```yaml\napiVersion: traefik.containo.us/v1alpha1\nkind: IngressRoute\nmetadata:\n  name: gitlab-listener\nspec:\n  entryPoints:\n    - web\n  routes:\n    - kind: Rule\n      match: Host(`gitlab-listener.tekton-argocd.de`)\n      services:\n        - name: el-gitlab-listener\n          port: 8080\n```\n\nApply it with `kubectl apply -f traefik/gitlab-listener.yml` and it should be ready for access at http://gitlab-listener.tekton-argocd.de\n\n\n\n\n### Testdrive Trigger via curl\n\nNow let's try our `curl` using the predefined [gitlab-push-test-event.json](tekton/triggers/gitlab-push-test-event.json):\n\n```shell\nTEKTON_TRIGGER_GITLAB_LISTENER=\"http://gitlab-listener.tekton-argocd.de\"\n\ncurl -v \\\n-H 'X-GitLab-Token: 1234567' \\\n-H 'X-Gitlab-Event: Push Hook' \\\n-H 'Content-Type: application/json' \\\n--data-binary \"@tekton/triggers/gitlab-push-test-event.json\" \\\n$TEKTON_TRIGGER_GITLAB_LISTENER\n```\n\n\nFinally we can implement all this inside our GitHub Action workflow [.github/workflows/provision.yml](.github/workflows/provision.yml):\n\n```yaml\n      - name: Expose Tekton Triggers EventListener as Traefik IngressRoute \u0026 testdrive Trigger\n        run: |\n          echo \"--- Apply Tekton EventListener Traefik IngressRoute\"\n          kubectl apply -f traefik/gitlab-listener.yml\n\n          echo \"--- Testdrive Trigger via curl\"\n          curl -v \\\n          -H 'X-GitLab-Token: 1234567' \\\n          -H 'X-Gitlab-Event: Push Hook' \\\n          -H 'Content-Type: application/json' \\\n          --data-binary \"@tekton/triggers/gitlab-push-test-event.json\" \\\n          http://gitlab-listener.$ROUTE53_DOMAIN_NAME\n```\n\n\n\n### Parameterize PipelineRun in Tekton Triggers EventListener to use values from Webhook send json \n\nWe now should extend our [gitlab-push-listener.yml](tekton/triggers/gitlab-push-listener.yml) to use the values send by the GitLab Webhook via json.\n\nOur file now looks like this:\n\n```yaml\napiVersion: triggers.tekton.dev/v1beta1\nkind: EventListener\nmetadata:\n  name: gitlab-listener\nspec:\n  serviceAccountName: tekton-triggers-example-sa\n  triggers:\n    - name: gitlab-push-events-trigger\n      interceptors:\n        - name: \"verify-gitlab-payload\"\n          ref:\n            name: \"gitlab\"\n            kind: ClusterInterceptor\n          params:\n            - name: secretRef\n              value:\n                secretName: \"gitlab-secret\"\n                secretKey: \"secretToken\"\n            - name: eventTypes\n              value:\n                - \"Push Hook\"\n      bindings:\n        - name: gitrevision\n          value: $(body.checkout_sha)\n        - name: gitrepositoryurl\n          value: $(body.repository.git_http_url)\n        - name: gitrepository_pathonly\n          value: $(body.project.path_with_namespace)\n      template:\n        spec:\n          params:\n            - name: gitrevision\n            - name: gitrepositoryurl\n            - name: gitrepository_pathonly\n            - name: message\n              description: The message to print\n              default: This is the default message\n            - name: contenttype\n              description: The Content-Type of the event\n          resourcetemplates:\n            - apiVersion: tekton.dev/v1beta1\n              kind: PipelineRun\n              metadata:\n                generateName: buildpacks-test-pipeline-run-\n              spec:\n                serviceAccountName: buildpacks-service-account-gitlab\n                pipelineRef:\n                  name: buildpacks-test-pipeline\n                workspaces:\n                  - name: source-workspace\n                    subPath: source\n                    persistentVolumeClaim:\n                      claimName: buildpacks-source-pvc\n                  - name: cache-workspace\n                    subPath: cache\n                    persistentVolumeClaim:\n                      claimName: buildpacks-source-pvc\n                params:\n                  - name: IMAGE\n                    value: \"registry.gitlab.com/$(tt.params.gitrepository_pathonly)\" #here our GitLab's registry url must be configured\n                  - name: SOURCE_URL\n                    value: $(tt.params.gitrepositoryurl)\n                  - name: SOURCE_REVISION\n                    value: $(tt.params.gitrevision)\n```\n\nThere's not so much that changes here. The param `SOURCE_URL` will be provided the git repo url via `$(tt.params.gitrepositoryurl)` - this is done using the `tt.params` notation [as the docs state](https://tekton.dev/docs/triggers/triggertemplates/#specifying-parameters).\n\nThe same is used for `SOURCE_REVISION`. Both parameters must be defined in the `template:spec:params` section also. \n\nAnd they must all be defined in the TriggerBinding inside the `bindings` section. Here we read the values from the parsed json request from GitLab using the `$(body.key1)` notation - see [the docs on TriggerBindings json payload access](https://tekton.dev/docs/triggers/triggerbindings/#accessing-data-in-http-json-payloads).\n\nFinally we also read another value in the `bindings` section: the `gitrepository_pathonly` value will be obtained from `$(body.project.path_with_namespace)` - which represents our GitLab repo's group and repo names. In this example this is `jonashackt/microservice-api-spring-boot`.\n\nBut what do we need this value for? You'll see it inside the `IMAGE` parameter of the `PipelineRun` definition inside the TriggerTemplate. \n\nWith `value: \"registry.gitlab.com/$(tt.params.gitrepository_pathonly)\"` we use the `gitrepository_pathonly` to craft the correct GitLab Container Registry URL with the predefined GitLab CR domain name and the appended group and repo name.\n\n\n#### Test GitLab Webhook with Ingress URL\n\nGo to your project (in my example here this is https://gitlab.com/jonashackt/microservice-api-spring-boot) and head over to __Settings/Webhooks__.\n\nNow insert our Tekton Triggers EventListener URL http://gitlab-listener.tekton-argocd.de into the already created Webhook's __URL__ field ().\n\nYou need to scroll down to __Project Hooks__ and __edit__ your existing Webhook.\n\nFinally Test-drive the Webhook again choosing __Test__ and __Push event__ from the drop down.\n\nSwitch to the Tekton Dashboard in your Browser and you should see the PipelineRun triggered:\n\n![tekton-triggers-pipelinerun-triggered-through-gitlab-webhook](screenshots/tekton-triggers-pipelinerun-triggered-through-gitlab-webhook.png)\n\n\n\n\n\n# Report Tekton Pipeline Status back to GitLab \n\nThe last step in our journey of integrating GitLab with Tekton is to report the status of our Tekton Pipelines back to GitLab.\n\nThere are multiple options. Let's first start simple using the Tekton Hub task https://hub.tekton.dev/tekton/task/gitlab-set-status\n\n\n### Install gitlab-set-status Task\n\nInside our [provision.yml](.github/workflows/provision.yml) workflow we need to install the gitlab-set-status Task:\n\n```shell\nkubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/main/task/gitlab-set-status/0.1/gitlab-set-status.yaml\n```\n\n\n### Create Access Token\n\nTo access the GitLab commit API (see https://docs.gitlab.com/ee/api/commits.html#post-the-build-status-to-a-commit) using the `gitlab-set-status` task we need to create an access token as stated in https://docs.gitlab.com/ee/api/index.html#authentication\n\nOn self-managed GitLab instances you can create project access tokens for example.\n\nUsing gitlab.com we cannot use project access tokens, but can create personal access tokens instead: https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token\n\nTherefore head over to __Edit profile__ and choose __Access Tokens__ on the left. Then create a token called `gitlab-api-token` for example:\n\n![gitlab-personal-access-token](screenshots/gitlab-personal-access-token.png)\n\nNow since we're using GitHub Actions to provision our EKS cluster and install Tekton, we need to enhance our [provision.yml](.github/workflows/provision.yml) workflow.\n\nFirst create a new GitHub repository secret `GITLAB_API_TOKEN` containing the personal access token which we just created:\n\n![github-repository-secret-for-gitlab-api](screenshots/github-repository-secrets-for-gitlab-api.png)\n\nNow we can use these GitHub repo secrets to create the actual Kubernetes secret in our [provision.yml](.github/workflows/provision.yml) workflow:\n\n```yaml\n          kubectl create secret generic gitlab-api-secret \\\n          --from-literal=token=${{ secrets.GITLAB_API_TOKEN }} \\\n          --namespace default \\\n          --save-config --dry-run=client -o yaml | kubectl apply -f -\n```\n\n\n### Create task leveraging gitlab-set-status\n\nUsing the Tekton Hub task https://hub.tekton.dev/tekton/task/gitlab-set-status we can create a new step inside our [Tekton Pipeline](tekton/pipelines/pipeline.yml). But first ne need to create some new parameters for our Pipeline:\n\n```yaml\n  params:\n    ...\n    - name: REPO_PATH_ONLY\n      type: string\n      description: GitLab group \u0026 repo name only (e.g. jonashackt/microservice-api-spring-boot)\n    ...\n    - name: GITLAB_HOST\n      type: string\n      description: Your GitLabs host only (e.g. gitlab.com)\n    - name: TEKTON_DASHBOARD_HOST\n      type: string\n      description: The Tekton dashboard host name only\n```\n\nNow we can implement the task:\n\n```yaml\n    - name: report-pipeline-end-to-gitlab\n      taskRef:\n        name: \"gitlab-set-status\"\n      runAfter:\n        - buildpacks\n      params:\n        - name: \"STATE\"\n          value: \"success\"\n        - name: \"GITLAB_HOST_URL\"\n          value: \"$(params.GITLAB_HOST)\"\n        - name: \"REPO_FULL_NAME\"\n          value: \"$(params.REPO_PATH_ONLY)\"\n        - name: \"GITLAB_TOKEN_SECRET_NAME\"\n          value: \"gitlab-api-secret\"\n        - name: \"GITLAB_TOKEN_SECRET_KEY\"\n          value: \"token\"\n        - name: \"SHA\"\n          value: \"$(params.SOURCE_REVISION)\"\n        - name: \"TARGET_URL\"\n          value: \"$(params.TEKTON_DASHBOARD_HOST)/#/namespaces/default/pipelineruns/$(context.pipelineRun.name)\"\n        - name: \"CONTEXT\"\n          value: \"tekton-pipeline\"\n        - name: \"DESCRIPTION\"\n          value: \"Finished building your commit in Tekton\"\n```\n\nFirst we should make sure this task runs only after the `buildpacks` task using `runAfter`.\n\nThen we need to provide the `GITLAB_HOST_URL` and `REPO_FULL_NAME`.\n\nAlso the `GITLAB_TOKEN_SECRET_NAME` needs to refer to the Kubernetes secret `gitlab-api-secret` we created leveraging a Personal Access Token. The `GITLAB_TOKEN_SECRET_KEY` must reference to the `key` name inside the `kubectl create secret` command, where we used `--from-literal=token=${{ secrets.GITLAB_API_TOKEN }}`. So the `GITLAB_TOKEN_SECRET_KEY` is `token` here (which is also the default).\n\nThe `TARGET_URL` is a bit more tricky and needs to be crafted with care:\n\n```yaml\n        - name: \"TARGET_URL\"\n          value: \"$(params.TEKTON_DASHBOARD_HOST)/#/namespaces/default/pipelineruns/$(context.pipelineRun.name)\"\n```\n\nIt consists of the Tekton dashboard host `$(params.TEKTON_DASHBOARD_HOST)` and the Pipeline details prefix `/#/namespaces/default/pipelineruns/`. The `$(context.pipelineRun.name)` finally gives us the current PipelineRun's name we need to be able to reference the actual PipelineRun from the GitLab UI.\n\nFinally the `CONTEXT` and `DESCRIPTION` should contain useful information to be displayed in the GitLab UI later:\n\n![gitlab-set-status-detail-finished](screenshots/gitlab-set-status-detail-finished.png)\n\n\n### Add new gitlab-set-status parameters to PipelineRun and EventListener\n\nOur [pipeline-run.yml](tekton/pipelines/pipeline-run.yml) (for manual triggering):\n\n```yaml\n...\n  params:\n    - name: IMAGE\n      value: registry.gitlab.com/jonashackt/microservice-api-spring-boot # This defines the name of output image\n    - name: SOURCE_URL\n      value: https://gitlab.com/jonashackt/microservice-api-spring-boot\n    - name: REPO_PATH_ONLY\n      value: jonashackt/microservice-api-spring-boot\n    - name: SOURCE_REVISION\n      value: main\n    - name: GITLAB_HOST\n      value: gitlab.com\n    - name: TEKTON_DASHBOARD_HOST\n      value: http://abd1c6f235c9642bf9d4cdf632962298-1232135946.eu-central-1.elb.amazonaws.com\n```\n\nand the [EventListener](tekton/triggers/gitlab-push-listener.yml) (for automatic triggering by our gitlab.com projects) need to pass some new parameters in order to get the `gitlab-set-status` task working:\n\n```yaml\n                params:\n                  - name: IMAGE\n                    value: \"registry.gitlab.com/$(tt.params.gitrepository_pathonly)\" #here our GitLab's registry url must be configured\n                  - name: SOURCE_URL\n                    value: $(tt.params.gitrepositoryurl)\n                  - name: REPO_PATH_ONLY\n                    value: $(tt.params.gitrepository_pathonly)\n                  - name: SOURCE_REVISION\n                    value: $(tt.params.gitrevision)\n                  - name: GITLAB_HOST\n                    value: gitlab.com\n                  - name: TEKTON_DASHBOARD_HOST\n                    value: {{TEKTON_DASHBOARD_HOST}}\n```\n\nAdding the `REPO_PATH_ONLY` is no problem, since we alread used `$(tt.params.gitrepository_pathonly)` inside the `IMAGE` parameter. As with the GitLab registry url we can also \"hard code\" the `GITLAB_HOST` here for now.\n\nThe `TEKTON_DASHBOARD_HOST` is the trickiest part, since we need to substitute this value from outside of the Tekton Trigger process, which doesn't know about the Tekton dashboard url.\n\nBut luckily inside our GitHub Actions [provision.yml](.github/workflows/provision.yml) workflow we can use https://stackoverflow.com/questions/48296082/how-to-set-dynamic-values-with-kubernetes-yaml-file/70152914#70152914:\n\n```yaml\n          echo \"--- Insert Tekton dashboard url into EventListener config and apply it (see https://stackoverflow.com/a/70152914/4964553)\"\n          TEKTON_DASHBOARD_HOST=\"${{ steps.dashboard-expose.outputs.dashboard_host }}\"\n          sed \"s#{{TEKTON_DASHBOARD_HOST}}#$TEKTON_DASHBOARD_HOST#g\" tekton/triggers/gitlab-push-listener.yml | kubectl apply -f -\n```\n\nUsing sed we simply replace `{{TEKTON_DASHBOARD_HOST}}` with the already defined GitHub Actions variable `${{ steps.dashboard-expose.outputs.dashboard_host }}`.\n\nTesting our full workflow is simple pushing a change to our repo using a branch without GitLab CI: https://gitlab.com/jonashackt/microservice-api-spring-boot/-/commits/trigger-tekton-via-webhook\n\nNow our GitLab Pipelines view gets filled with a Tekton Pipelines status (`success` only for now):\n\n![gitlab-set-status-finished](screenshots/gitlab-set-status-finished.png)\n\n\n### Reporting `running` status to GitLab\n\nNow that we generally know how to use the `gitlab-set-status` Task, we could also use another Task definition to report the starting of a Tekton Pipeline run to GitLab UI.\n\nTherefore we enhance our [Tekton Pipeline](tekton/pipelines/pipeline.yml) with a new Task starting the whole Pipeline called `report-pipeline-start-to-gitlab`:\n\n```yaml\n  tasks:\n    - name: report-pipeline-start-to-gitlab\n      taskRef:\n        name: gitlab-set-status\n      params:\n        - name: \"STATE\"\n          value: \"running\"\n        - name: \"GITLAB_HOST_URL\"\n          value: \"$(params.GITLAB_HOST)\"\n        - name: \"REPO_FULL_NAME\"\n          value: \"$(params.REPO_PATH_ONLY)\"\n        - name: \"GITLAB_TOKEN_SECRET_NAME\"\n          value: \"gitlab-api-secret\"\n        - name: \"GITLAB_TOKEN_SECRET_KEY\"\n          value: \"token\"\n        - name: \"SHA\"\n          value: \"$(params.SOURCE_REVISION)\"\n        - name: \"TARGET_URL\"\n          value: \"$(params.TEKTON_DASHBOARD_HOST)/#/namespaces/default/pipelineruns/$(context.pipelineRun.name)\"\n        - name: \"CONTEXT\"\n          value: \"tekton-pipeline\"\n        - name: \"DESCRIPTION\"\n          value: \"Building your commit in Tekton\"\n    - name: fetch-repository # This task fetches a repository from github, using the `git-clone` task you installed\n      taskRef:\n        name: git-clone\n      runAfter:\n        - report-pipeline-start-to-gitlab\n```\n\nAnd the `fetch-repository` only starts after the status is reported to GitLab. With this new Task in place a push to our repository https://gitlab.com/jonashackt/microservice-api-spring-boot __directly__ presents a `running` Pipeline inside the GitLab UI: \n\n![gitlab-set-status-running](screenshots/gitlab-set-status-running.png)\n\nAnd in the details view we can directly access our running Tekton Pipeline via a correct Tekton dashboard link:\n\n![gitlab-set-status-detail-running](screenshots/gitlab-set-status-detail-running.png)\n\n\n### Reporting `failed` status to GitLab\n\nHow do we catch all status from our Tekton pipeline and then report based on that to GitLab?\n\n[In v0.14 Tekton introduced finally Tasks](https://github.com/tektoncd/pipeline/releases/tag/v0.14.0), which run at the end of every PipelineRun - regardless which Task failed or succeeded. [As the docs state](https://tekton.dev/docs/pipelines/pipelines/#adding-finally-to-the-pipeline):\n\n\u003e finally tasks are guaranteed to be executed in parallel after all PipelineTasks under tasks have completed regardless of success or error.\n\nFinally tasks look like this:\n\n```yaml\nspec:\n  tasks:\n    - name: tests\n      taskRef:\n        name: integration-test\n  finally:\n    - name: cleanup-test\n      taskRef:\n        name: cleanup\n```\n\nWith [Guard[ing] finally Task execution using when expressions](https://tekton.dev/docs/pipelines/pipelines/#guard-finally-task-execution-using-when-expressions) we can enhance this even further.\n\nBecause using `when` expressions we can run Tasks based on the overall Pipeline status (or Aggregate Pipeline status) - see https://tekton.dev/docs/pipelines/pipelines/#when-expressions-using-aggregate-execution-status-of-tasks-in-finally-tasks\n\n```yaml\nfinally:\n  - name: notify-any-failure # executed only when one or more tasks fail\n    when:\n      - input: $(tasks.status)\n        operator: in\n        values: [\"Failed\"]\n    taskRef:\n      name: notify-failure\n```\n\nThe [Aggregate Execution Status](https://tekton.dev/docs/pipelines/pipelines/#using-aggregate-execution-status-of-all-tasks) we can grap using `$(tasks.status)` is stated to have those 4 possible status:\n\n`Succeeded` (\"all tasks have succeeded\") or `Completed` (\"all tasks completed successfully including one or more skipped tasks\"), which could be translated into the `gitlab-set-status` Tasks `STATE` value `success`.\n\nAnd `Failed` (\"one ore more tasks failed\") or `None` (\"no aggregate execution status available (i.e. none of the above), one or more tasks could be pending/running/cancelled/timedout\"), which could both be translated into the `gitlab-set-status` Tasks `STATE` value `failed`. For `None` this is only valid, since we're in a `finally task`, since `pending/running` could otherwise also mean that a Pipeline is in a good state. \n\nLuckily the `when` expressions\n\n\u003e [values is an array of string values.](https://tekton.dev/docs/pipelines/pipelines/#guard-task-execution-using-when-expressions)\n\nSo we're able to do \n\n```yaml\n  when:\n    - input: $(tasks.status)\n      operator: in\n      values: [ \"Failed\", \"None\" ]\n```\n\nand\n\n```yaml\n  when:\n    - input: $(tasks.status)\n      operator: in\n      values: [ \"Succeeded\", \"Completed\" ]\n```\n\n\nIn the end this results in our [Tekton Pipeline's](tekton/pipelines/pipeline.yml) `finally` block locking like this:\n\n```yaml\n...\n  finally:\n    - name: report-pipeline-failed-to-gitlab\n      when:\n        - input: $(tasks.status)\n          operator: in\n          values: [ \"Failed\", \"None\" ] # see aggregated status https://tekton.dev/docs/pipelines/pipelines/#using-aggregate-execution-status-of-all-tasks\n      taskRef:\n        name: \"gitlab-set-status\"\n      params:\n        - name: \"STATE\"\n          value: \"failed\"\n        - name: \"GITLAB_HOST_URL\"\n          value: \"$(params.GITLAB_HOST)\"\n        - name: \"REPO_FULL_NAME\"\n          value: \"$(params.REPO_PATH_ONLY)\"\n        - name: \"GITLAB_TOKEN_SECRET_NAME\"\n          value: \"gitlab-api-secret\"\n        - name: \"GITLAB_TOKEN_SECRET_KEY\"\n          value: \"token\"\n        - name: \"SHA\"\n          value: \"$(params.SOURCE_REVISION)\"\n        - name: \"TARGET_URL\"\n          value: \"$(params.TEKTON_DASHBOARD_HOST)/#/namespaces/default/pipelineruns/$(context.pipelineRun.name)\"\n        - name: \"CONTEXT\"\n          value: \"tekton-pipeline\"\n        - name: \"DESCRIPTION\"\n          value: \"An error occurred building your commit in Tekton\"\n    - name: report-pipeline-success-to-gitlab\n      when:\n          - input: $(tasks.status)\n            operator: in\n            values: [ \"Succeeded\", \"Completed\" ] # see aggregated status https://tekton.dev/docs/pipelines/pipelines/#using-aggregate-execution-status-of-all-tasks\n      taskRef:\n        name: \"gitlab-set-status\"\n      params:\n        - name: \"STATE\"\n          value: \"success\"\n        - name: \"GITLAB_HOST_URL\"\n          value: \"$(params.GITLAB_HOST)\"\n        - name: \"REPO_FULL_NAME\"\n          value: \"$(params.REPO_PATH_ONLY)\"\n        - name: \"GITLAB_TOKEN_SECRET_NAME\"\n          value: \"gitlab-api-secret\"\n        - name: \"GITLAB_TOKEN_SECRET_KEY\"\n          value: \"token\"\n        - name: \"SHA\"\n          value: \"$(params.SOURCE_REVISION)\"\n        - name: \"TARGET_URL\"\n          value: \"$(params.TEKTON_DASHBOARD_HOST)/#/namespaces/default/pipelineruns/$(context.pipelineRun.name)\"\n        - name: \"CONTEXT\"\n          value: \"tekton-pipeline\"\n        - name: \"DESCRIPTION\"\n          value: \"Finished building your commit in Tekton\"\n```\n\nExecuting our Tekton Pipeline should now be reported correctly to our GitLab. Failures look like this:\n\n![gitlab-set-status-failed](screenshots/gitlab-set-status-failed.png)\n\nand in the detail view:\n\n![gitlab-set-status-detail-failed](screenshots/gitlab-set-status-detail-failed.png)\n\nThe solution is based on https://stackoverflow.com/questions/70156006/report-tekton-pipeline-status-to-gitlab-regardless-if-pipeline-failed-or-succeed/70156007#70156007.\n\n\n### Refactoring usage of gitlab-set-status usage\n\nRight now we have a huge pipeline with only 2-3 relevant Tekton tasks, the other 3 Tasks are solely used to communicate the pipeline's status to GitLab.\n\nSo is there a way we could refactor these to a more generic form? I digged into `PipelineResources` as I thought they could be a nice option to handle this. But sadly they didn't make it into the Tekton beta: https://tekton.dev/docs/pipelines/migrating-v1alpha1-to-v1beta1/#replacing-pipelineresources-with-tasks\n\nThat's why we need another way to accomplish our refactoring. So what about simply using Tasks that reference other Tasks? Sadly that doesn't seem to be possible. Since Tasks only specify `steps` not `tasks` with `taskRef`.\n\n\n#### Using Pipelines-in-Pipelines\n\nBut how about using Pipelines using other Pipelines? Is this possible? Yes, but currently only as experimental: https://tekton.dev/docs/pipelines/pipelines/#compose-using-pipelines-in-pipelines \u0026 https://github.com/tektoncd/experimental/blob/main/pipelines-in-pipelines/examples/pipelinerun-with-pipeline-in-pipeline.yaml\n\nAs stated we can create a normal Tekton Pipeline as we're already used to - and then use this Pipeline in another Pipeline simply by using `taskRef` with `kind: Pipeline` and `apiVersion: tekton.dev/v1beta1` accompanying the referenced pipeline name:\n\n```yaml\n      - name: reference-other-pipeline\n        taskRef:\n          apiVersion: tekton.dev/v1beta1\n          kind: Pipeline\n          name: other-pipeline-name\n```\n\nIn order to be able to use this feature, [we need to install the `Pipelines-In-Pipelines Controller`](https://github.com/tektoncd/experimental/tree/main/pipelines-in-pipelines#install) with (but be aware of https://github.com/tektoncd/experimental/issues/817 which is fixed by https://github.com/tektoncd/experimental/pull/818)\n\n```shell\nkubectl apply --filename https://storage.googleapis.com/tekton-releases-nightly/pipelines-in-pipelines/latest/release.yaml\n```\n\n\n#### Enabling Tekton alpha features\n\nNow running our pipelines would result in the following error:\n\n```shell\nPipeline default/buildpacks-test-pipeline can't be Run; it contains Tasks that don't exist: Couldn't retrieve Task \"generic-gitlab-set-status\": tasks.tekton.dev \"generic-gitlab-set-status\" not found\n```\n\nThat's because the Pipeline-in-Pipelines feature is an alpha feature - see this issue https://github.com/tektoncd/experimental/issues/785\n\nIn order to activate [Tekton alpha features](https://tekton.dev/docs/pipelines/install/#alpha-features) we need to [Customize the Pipeline Controllers behavior](https://tekton.dev/docs/pipelines/install/#customizing-the-pipelines-controller-behavior).\n\nAs stated in https://stackoverflow.com/a/70336211/4964553 this could be done on-the-fly with `curl` and `sed` piped into `kubectl apply` like this:\n\n```shell\ncurl https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml | sed \"s#stable#alpha#g\" | kubectl apply -f -\n```\n\n\n#### Create a generic gitlab-set-status pipeline for later re-use\n\nLet's try to create a generic Tekton Pipeline for the `gitlab-set-status` as [generic-gitlab-set-status.yml](tekton/pipelines/generic-gitlab-set-status.yml):\n\n```yaml\napiVersion: tekton.dev/v1beta1\nkind: Pipeline\nmetadata:\n  name: generic-gitlab-set-status\nspec:\n  params:\n    - name: STATE\n      type: string\n      description: The gitlab-set-status Tasks state to set. Can be one of the following pending, running, success, failed, or canceled.\n    - name: PIPELINE_NAME\n      type: string\n      description: The calling pipelines name.\n    - name: REPO_PATH_ONLY\n      type: string\n      description: GitLab group \u0026 repo name only (e.g. jonashackt/microservice-api-spring-boot)\n    - name: SOURCE_REVISION\n      description: The branch, tag or SHA to checkout.\n      default: \"\"\n    - name: GITLAB_TOOLTIP\n      type: string\n      description: The tooltip to be shown in the GitLab Pipelines details view.\n\n  tasks:\n    - name: report-pipeline-start-to-gitlab\n      taskRef:\n        name: gitlab-set-status\n      params:\n        - name: \"STATE\"\n          value: \"$(params.STATE)\"\n        - name: \"GITLAB_HOST_URL\"\n          value: \"gitlab.com\"\n        - name: \"REPO_FULL_NAME\"\n          value: \"$(params.REPO_PATH_ONLY)\"\n        - name: \"GITLAB_TOKEN_SECRET_NAME\"\n          value: \"gitlab-api-secret\"\n        - name: \"GITLAB_TOKEN_SECRET_KEY\"\n          value: \"token\"\n        - name: \"SHA\"\n          value: \"$(params.SOURCE_REVISION)\"\n        - name: \"TARGET_URL\"\n          value: \"{{TEKTON_DASHBOARD_HOST}}/#/namespaces/default/pipelineruns/$(params.PIPELINE_NAME)\"\n        - name: \"CONTEXT\"\n          value: \"tekton-pipeline\"\n        - name: \"DESCRIPTION\"\n          value: \"$(params.GITLAB_TOOLTIP)\"\n```\n\nAs you can see we define the `TEKTON_DASHBOARD_HOST` using brackets so we can later use `sed` to dynamically set the actual Tekton Dashboard URL in our GitHub Actions pipeline (using `sed` for this is also described in https://stackoverflow.com/a/70152914/4964553)\n\n```shell\nTEKTON_DASHBOARD_HOST=\"${{ steps.dashboard-expose.outputs.dashboard_host }}\"\nsed \"s#{{TEKTON_DASHBOARD_HOST}}#$TEKTON_DASHBOARD_HOST#g\" tekton/pipelines/generic-gitlab-set-status.yml | kubectl apply -f -\n```\n\n\n#### Use the generic gitlab-set-status pipeline in our actual pipeline\n\nIn our [pipeline.yml](tekton/pipelines/pipeline.yml) we can now reduce many lines that we don't need to pass to the generic gitlab-set-status pipeline any more. So our pipeline becomes much more readable and only the things remain that are naturally defined inside a pipeline. See the usage of our generic gitlab-set-status pipeline here using the Pipelines-in-Pipelines feature: \n\n```yaml\n    - name: report-pipeline-start-to-gitlab\n      taskRef:\n        apiVersion: tekton.dev/v1beta1\n        kind: Pipeline\n        name: generic-gitlab-set-status\n      params:\n        - name: \"STATE\"\n          value: \"running\"\n        - name: \"REPO_PATH_ONLY\"\n          value: \"$(params.REPO_PATH_ONLY)\"\n        - name: \"SHA\"\n          value: \"$(params.SOURCE_REVISION)\"\n        - name: \"GITLAB_TOOLTIP\"\n          value: \"Building your commit in Tekton\"\n        - name: \"PIPELINE_NAME\"\n          value: \"$(context.pipelineRun.name)\"\n```\n\nAs the Pipeline-in-Pipeline feature is in alpha state, this is really cool to see it working. In a future version there might be the option to also remove the `PIPELINE_NAME` parameter (but currently I see no option for that https://github.com/tektoncd/experimental/tree/main/pipelines-in-pipelines).  \n\nThe code needed to invoke the gitlab-set-status task is reduced all the way:\n\n![pip-in-pip-codereduction-params](screenshots/pip-in-pip-codereduction-params.png)\n\n![pip-in-pip-codereduction-state-running](screenshots/pip-in-pip-codereduction-state-running.png)\n\n![pip-in-pip-codereduction-finally](screenshots/pip-in-pip-codereduction-finally.png)\n\nFinally we can also __remove code__ from our [pipeline-run.yml](tekton/pipelines/pipeline-run.yml) and [gitlab-push-listener.yml](tekton/triggers/gitlab-push-listener.yml):\n\n```yaml\n    - name: GITLAB_HOST\n      value: gitlab.com\n    - name: TEKTON_DASHBOARD_HOST\n      value: {{TEKTON_DASHBOARD_HOST}}\n```\n\nbecause this is now centrally configured in our generic pipeline :)\n\nThere's maybe one thing that could be considered as downside: One logic pipeline now triggers 3 PipelineRuns - those are also shown in the Tekton Dashboard:\n\n![pip-in-pip-producing-3-pipelineruns](screenshots/pip-in-pip-producing-3-pipelineruns.png)\n\n\n\n\n# GitOps with ArgoCD\n\nWe want to implement [the \"Pull-based\" deployment approach in GitOps](https://www.gitops.tech/) using ArgoCD.\n\n\n## Install Argo CLI\n\n```shell\nbrew install argocd\n```\n\nYou can [install ArgoCD as described in the getting started guide](https://argo-cd.readthedocs.io/en/stable/getting_started/) - but that will lead you to some problems together with Traefik: \n\n## Access The Argo CD API Server \u0026 Dashboard\n\nYou can expose the ArgoCD API Server via Loadbalancer, Ingress or port forwarding to localhost: https://argo-cd.readthedocs.io/en/stable/getting_started/#3-access-the-argo-cd-api-server\n\nhttps://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/#ingress-configuration\n\n\u003e Argo CD runs both a gRPC server (used by the CLI), as well as a HTTP/HTTPS server (used by the UI). Both protocols are exposed by the argocd-server service object on the following ports:\n\n\u003e 443 - gRPC/HTTPS \u0026 80 - HTTP (redirects to HTTPS)\n\n\u003e There are several ways how Ingress can be configured\n\n\nSo let's use Ingress with our Traefik and the nice Route53 domain \u0026 wildcard record to route from argocd.tekton-argocd.de. Simply create an Traefik `IngressRoute` as described in  [traefik/argocd-dashboard.yml](traefik/argocd-dashboard.yml):\n\n\u003e As of writing the exact `IngressRoute` from the docs produces an error:\n\n```shell\n$ kubectl apply -f traefik/argocd-dashboard.yml\nerror: error validating \"traefik/argocd-dashboard.yml\": error validating data: ValidationError(IngressRoute.spec.tls.options): missing required field \"name\" in us.containo.traefik.v1alpha1.IngressRoute.spec.tls.options; if you choose to ignore these errors, turn validation off with --validate=false\n```\n\nSee https://github.com/argoproj/argo-cd/pull/8951\n\n```yaml\napiVersion: traefik.containo.us/v1alpha1\nkind: IngressRoute\nmetadata:\n  name: argocd-server\n  namespace: argocd\nspec:\n  entryPoints:\n    - websecure\n  routes:\n    - kind: Rule\n      match: Host(`argocd.tekton-argocd.de`)\n      priority: 10\n      services:\n        - name: argocd-server\n          port: 80\n    - kind: Rule\n      match: Host(`argocd.tekton-argocd.de`) \u0026\u0026 Headers(`Content-Type`, `application/grpc`)\n      priority: 11\n      services:\n        - name: argocd-server\n          port: 80\n          scheme: h2c\n  tls:\n    certResolver: default\n\n```\n\nWith this approach we also don't need to `kubectl patch svc argocd-server -n argocd -p '{\"spec\": {\"type\": \"LoadBalancer\"}}'` to use a `LoadBalancer`, which provisions an AWS ELB (incl. additional costs).\n\nApply it with `kubectl apply -f traefik/argocd-dashboard.yml`. Now ArgoCD should be accessible via http://argocd.tekton-argocd.de\n\n\n## Why Kustomize is a great way to manage the ArgoCD installation \u0026 custom configuration\n\nIf [we installed ArgoCD as described in the getting started guide](https://argo-cd.readthedocs.io/en/stable/getting_started/) by using `kubectl apply -f` we will run into `HTTP 307` redirects! What's the problem here?\n\n![traefik-argocd-http307-redirects](screenshots/traefik-argocd-http307-redirects.png)\n\nSee https://github.com/argoproj/argo-cd/issues/2953#issuecomment-602898868\n\n\u003e The problem is that by default Argo-CD handles TLS termination itself and always redirects HTTP requests to HTTPS. Combine that with an ingress controller that also handles TLS termination and always communicates with the backend service with HTTP and you get Argo-CD's server always responding with a redirects to HTTPS.\n\nAnd the ArgoCD docs for Traefik Ingress configuration at https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/#traefik-v22 tell us to \n\n\u003e The API server should be run with TLS disabled. Edit the argocd-server deployment to add the --insecure flag to the argocd-server command.\n\nBut [How to configure argocd-server Deployment to run with TLS disabled (where to put --insecure flag)](https://stackoverflow.com/questions/71692891/argocd-traefik-2-x-how-to-configure-argocd-server-deployment-to-run-with-tls)?\n\nAs stated [in this answer](https://stackoverflow.com/a/71692892/4964553) there's great way to manage custom configuration for Kubernetes deployments like ArgoCD by using Kustomize!\n\nA great way is to use a declarative approach, which should be the default Kubernetes-style. Skimming the ArgoCD docs there's a [additional configuration section](https://argo-cd.readthedocs.io/en/stable/operator-manual/server-commands/additional-configuration-method/#synopsis) where the possible flags of the ConfigMap `argocd-cmd-params-cm` can be found. The flags are described in [argocd-cmd-params-cm.yaml](https://argo-cd.readthedocs.io/en/stable/operator-manual/argocd-cmd-params-cm.yaml). One of them is the flag `server.insecure`\n\n```yaml\n    ## Server properties\n    # Run server without TLS\n    server.insecure: \"false\"\n```\n\nThe `argocd-server` deployment which ships with https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml will use this parameter, if it is defined in the `argocd-cmd-params-cm` ConfigMap.\n\nIn order to declaratively configure the ArgoCD configuration, [the ArgoCD docs have a great section](https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#manage-argo-cd-using-argo-cd) on how to do that with [Kustomize](https://kustomize.io/). In fact the ArgoCD team itself uses this approach to deploy their own ArgoCD instances - a live deployment is available here https://cd.apps.argoproj.io/ and the configuration used [can be found on GitHub](https://github.com/argoproj/argoproj-deployments/tree/master/argocd).\n\nAdopting this to our use case, we need to switch our ArgoCD installation from simply using `kubectl apply -f` to a Kustomize-based installation. The ArgoCD docs also have [a section on how to do this](https://argo-cd.readthedocs.io/en/stable/operator-manual/installation/#kustomize). Here are the brief steps:\n\n\n#### Create a `argocd/install` directory with a new file `kustomization.yaml`\n\nWe slightly enhance the `kustomization.yaml` proposed in the docs and create it inside [argocd/install/kustomization.yaml](argocd/install/kustomization.yaml):\n\n```\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\n\nresources:\n  - https://raw.githubusercontent.com/argoproj/argo-cd/v2.3.3/manifests/install.yaml\n\n## changes to config maps\npatchesStrategicMerge:\n  - argocd-cmd-params-cm-patch.yml\n\nnamespace: argocd\n```\n\nSince the docs state\n\n\u003e It is recommended to include the manifest as a remote resource and\n\u003e apply additional customizations using Kustomize patches.\n\nwe use the `patchesStrategicMerge` configuration key, which contains another new file we need to create called `argocd-cmd-params-cm-patch.yml`.\n\n\n#### Create a new file `argocd-cmd-params-cm-patch.yml`**\n\nThis new [argocd/install/argocd-cmd-params-cm-patch.yml](argocd/install/argocd-cmd-params-cm-patch.yml) only contains the configuration we want to change inside the ConfigMap `argocd-cmd-params-cm`:\n\n```yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: argocd-cmd-params-cm\ndata:\n  server.insecure: \"true\"\n```\n\n#### Install ArgoCD using the Kustomization files \u0026 `kubectl apply -k`\n\nThere's a separate `kustomize` CLI one can install e.g. via `brew install kustomize`. But as Kustomize is build into `kubectl` we only have to use `kubectl apply -k` and point that to our newly created `argocd/installation` directory like this:\n\n```shell\nkubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f -    \nkubectl apply -k argocd/install\n```\n\nAs you can see we also need to make sure the namespace `argocd` is present before Kustomize can apply all the ArgoCD resources.\n\nThis will install ArgoCD and configure the `argocd-server` deployment to use the `--insecure` flag as needed to stop Argo from handling the TLS termination itself and giving that responsibility to Traefik.\n\nNow accessing https://argocd.tekton-argocd.de should open the ArgoCD dashboard as expected:\n\n![traefik-argocd-working-dashboard-access](screenshots/traefik-argocd-working-dashboard-access.png)\n\n\n## Get ArgoCD admin password, login to argocd-server and change password\n\nObtain ArgoCD admin account's initial password\n\n```shell\nkubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=\"{.data.password}\" | base64 -d; echo\n```\n\nLogin to `argocd-server` hostname, which was exposed as IngressRoute through Traefik - using username `admin` and the initial password obtained above:\n\n```shell\nargocd login argocd.tekton-argocd.de\n```\n\nThis will result in a hopefully successful login like this:\n\n```shell\nargocd login yourservername.eu-central-1.elb.amazonaws.com\nWARNING: server certificate had error: x509: certificate is valid for localhost, argocd-server, argocd-server.argocd, argocd-server.argocd.svc, argocd-server.argocd.svc.cluster.local, not argocd.tekton-argocd.de. Proceed insecurely (y/n)? y\nUsername: admin\nPassword:\n'admin:login' logged in successfully\nContext 'argocd.tekton-argocd.de' updated\n```\n\nFinally change the initial password via:\n\n```shell\nargocd account update-password\n```\n\n#### Access ArgoCD UI\n\nUsing your Browser open `argocd.tekton-argocd.de` and accept the certificate warnings. Then sign in using the `admin` user credentials from above:\n\n![argocd-ui-first-login](screenshots/argocd-ui-first-login.png)\n\n\nIf you don't want [to deploy to a different Kubernetes cluster than the current one where Argo was installed](https://argo-cd.readthedocs.io/en/stable/getting_started/#5-register-a-cluster-to-deploy-apps-to-optional), then everything should be prepared to deploy our first application.\n\n\n\n\n## ArgoCD installation \u0026 configuration within GitHub Actions\n\n#### ArgoCD installation in GH Actions\n\nLet's just create a new GitHub Actions job for this purpose, which also needs the `kubeconfig` from the first Pulumi task which bootstraps our EKS cluster:\n\n```yaml\n  install-and-run-argocd-on-eks:\n    runs-on: ubuntu-latest\n    needs: provision-eks-with-pulumi\n    environment:\n      name: argocd-dashboard\n      url: ${{ steps.dashboard-expose.outputs.dashboard_host }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@master\n      # We must use single quotes (!!!) here to access the kubeconfig like this:\n      # echo '${{ needs.provision-eks-with-pulumi.outputs.kubeconfig }}' \u003e ~/.kube/config\n      # Otherwise we'll run into errors like (see https://stackoverflow.com/a/15930393/4964553):\n      # \"error: error loading config file \"/home/runner/.kube/config\": yaml: did not find expected ',' or '}'\"\n      - name: Configure kubeconfig to use with kubectl from provisioning job\n        run: |\n          mkdir ~/.kube\n          echo '${{ needs.provision-eks-with-pulumi.outputs.kubeconfig }}' \u003e ~/.kube/config\n          echo \"--- Checking connectivity to cluster\"\n          kubectl get nodes\n\n      - name: Install ArgoCD\n        run: |\n          echo \"--- Create argo namespace and install it\"\n          kubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f -\n          echo \"--- Install \u0026 configure ArgoCD via Kustomize - see https://stackoverflow.com/a/71692892/4964553\"\n          kubectl apply -k argocd/installation\n```\n\nAs you can see we must `apply` the `argocd` namespace here instead of `create`ing it - otherwise the workflow will only run once.\n\n\n#### Expose the ArgoCD dashboard as GitHub Actions environment\n\nWe should also configure a GitHub Actions environment for our ArgoCD dashboard (as we already did with the Tekton dashboard):\n\n```yaml\n      - name: Expose ArgoCD Dashboard as GitHub environment\n        id: dashboard-expose\n        run: |\n          echo \"--- Expose ArgoCD Dashboard via K8s Service\"\n          kubectl apply -f traefik/argocd-dashboard.yml\n\n          echo \"--- Create GitHub environment var\"\n          DASHBOARD_HOST=\"https://argocd.$ROUTE53_DOMAIN_NAME\"\n          echo \"The ArgoCD dashboard is accessible at $DASHBOARD_HOST - creating GitHub Environment\"\n          echo \"::set-output name=dashboard_host::$DASHBOARD_HOST\"\n```\n\n![argo-dashboard-as-github-environment](screenshots/argo-dashboard-as-github-environment.png)\n\n\n# ArgoCD application deployment\n\nEven with ArgoCD there are two ways on how to deploy our application: push-based and pull-based.\n\n## ArgoCD application deployment (push)\n\nWe need a example project here - so what about https://github.com/jonashackt/restexamples and it's GitHub Container Registry image https://github.com/jonashackt/restexamples/pkgs/container/restexamples ?!\n\nTo access the GHCR we need to also create a secret inside our EKS cluster, so let's do that in our Actions workflow too:\n\n```yaml\n      - name: Create GitHub Container Registry Secret to be able to pull from ghcr.io\n        run: |\n          echo \"--- Create Secret to access GitHub Container Registry\"\n          kubectl create secret docker-registry github-container-registry \\\n              --docker-server=ghcr.io \\\n              --docker-username=${{ secrets.GHCR_USER }} \\\n              --docker-password=${{ secrets.GHCR_PASSWORD }} \\\n              --namespace default \\\n              --save-config --dry-run=client -o yaml | kubectl apply -f -\n```\n\nDon't forget to create the needed repository secrets `GHCR_USER` and `GHCR_PASSWORD` containing a GitHub PAT (since we can't simply use the `GITHUB_TOKEN` as we want to access another repositorie's images):\n\n![github-container-registry-access-pat-secrets](screenshots/github-container-registry-access-pat-secrets.png)\n\n\n#### Deploy via ArgoCD UI\n\nSee https://argo-cd.readthedocs.io/en/stable/getting_started/#creating-apps-via-ui\n\n![argo-dashboard-deploy-app-manually](screenshots/argo-dashboard-deploy-app-manually.png)\n\nIn order to be able to deploy an application with ArgoCD UI, we need to fill in the source url and names etc. But we also need to provide a `path` and this must contain some form of Kubernetes `Deployment` and `Service` configuration like this: \n\nrestexamples-deployment.yml\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: restexamples\nspec:\n  replicas: 1\n  revisionHistoryLimit: 3\n  selector:\n    matchLabels:\n      app: restexamples\n  template:\n    metadata:\n      labels:\n        app: restexamples\n    spec:\n      containers:\n        - image: ghcr.io/jonashackt/restexamples:latest\n          name: restexamples\n          ports:\n            - containerPort: 8090\n      imagePullSecrets:\n        - name: github-container-registry\n```\n\nrestexamples-service.yml\n\n```yaml\napiVersion: v1\nkind: Service\nmetadata:\n  name: restexamples\nspec:\n  ports:\n    - port: 80\n      targetPort: 8090\n  selector:\n    app: restexamples\n\n```\n\nSee both in the repo also https://github.com/jonashackt/restexamples/tree/argocd/deployment\n\nIf you filled out everything, click on `create` and then manually on `sync`. Now your app should be deployed to the EKS cluster:\n\n![argo-cd-first-deployment-synced](screenshots/argo-cd-first-deployment-synced.png)\n\nNow have a look into `k9s` - you should see your app beeing deployed as a `Service` and as a `Deployment` in","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonashackt%2Ftekton-argocd-eks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonashackt%2Ftekton-argocd-eks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonashackt%2Ftekton-argocd-eks/lists"}