Manage PAC Component

For Administrators Only

This guide is for cluster administrators only. It covers PAC component deployment, configuration, and maintenance tasks that require cluster administrator permissions.

Regular users should refer to:

  • Guides - Set up Git provider integration

This guide explains how to deploy, update, and uninstall the Pipelines-as-Code (PAC) component on Kubernetes platforms.

Prerequisites

Before managing PAC, ensure you have:

  • Kubernetes cluster (version 1.24 or higher)
  • Tekton Operator installed and running
  • Cluster administrator permissions
  • kubectl installed and configured to access your cluster

Deploy PAC Component

PAC is deployed by the Tekton Operator through the cluster-scoped TektonConfig CR named config. On the default spec.profile: all the operator derives an OpenShiftPipelinesAsCode CR from it and keeps that CR in sync, so TektonConfig is the only resource you edit. This guide assumes that profile; on basic or lite the operator does not reconcile PAC at all, and any OpenShiftPipelinesAsCode CR on the cluster is yours to manage directly — see How the Operator Manages PAC. The examples use the default PAC namespace tekton-pipelines; replace it in the commands and manifests if your TektonConfig.spec.targetNamespace is different.

Enable PAC and set its configuration under spec.platforms.kubernetes.pipelinesAsCode:

apiVersion: operator.tekton.dev/v1alpha1
kind: TektonConfig
metadata:
  name: config
spec:
  profile: all
  targetNamespace: tekton-pipelines  # Default namespace, you can customize this
  platforms:
    kubernetes:
      pipelinesAsCode:
        enable: true
        settings:
          application-name: Pipelines as Code CI
          hub-url: http://artifacthub-shim-api.artifacthub-shim-system.svc.cluster.local
          hub-catalog-type: artifacthub
          hub-catalog-name: catalog
          remote-tasks: "true"
          secret-auto-create: "true"

Apply the change:

kubectl edit tektonconfig config
Use

platforms.kubernetes, not platforms.openshift

The operator rejects anything under spec.platforms.openshiftpipelinesAsCode and scc alike — with a validation error. If an older configuration still carries that subtree, remove it before applying.

Check the derived OpenShiftPipelinesAsCode CR status:

kubectl get openshiftpipelinesascodes.operator.tekton.dev

The output should show READY=True; VERSION varies by release and REASON is usually empty.

NAME                  VERSION     READY   REASON
pipelines-as-code    <version>   True

Verify the PAC pods are running:

kubectl get pods -n tekton-pipelines | grep pipelines-as-code

Example output (the controller, watcher, and webhook pods must be Running):

NAME                                      READY   STATUS    RESTARTS   AGE
pipelines-as-code-controller-xxxxx        1/1     Running   0          5m
pipelines-as-code-watcher-xxxxx           1/1     Running   0          5m
pipelines-as-code-webhook-xxxxx           1/1     Running   0          5m

Three pods (controller, watcher, webhook) must be Running.

How the Operator Manages PAC

Three consequences follow from TektonConfig owning the PAC deployment. They explain most of the surprises in the rest of this guide:

  • spec.profile must be all. The TektonConfig the operator creates for you uses all, so this normally holds. On profile: basic — the value applied when a hand-written TektonConfig leaves the field empty — or on profile: lite, the reconciler returns before it reaches the PAC section. Nothing under pipelinesAsCode is acted on: PAC is neither installed nor removed, and an OpenShiftPipelinesAsCode CR that already exists is left untouched.
  • The OpenShiftPipelinesAsCode CR is derived, not authored. On profile: all with PAC enabled the operator creates that CR, sets an owner reference to TektonConfig, and on every reconcile copies targetNamespace, config, settings, options and additionalPACControllers back over from TektonConfig. Editing any of those fields on the derived CR is not an error, but the next reconcile overwrites the change. Read the CR for troubleshooting; write to TektonConfig.
  • Deleting the derived CR does not uninstall PAC. As long as profile is all and PAC is still enabled, the next reconcile recreates it. Setting enable: false is what makes the operator remove it and keep it removed — see Uninstall PAC Component.

Enable Repository UI Feature

If users need to create or manage PAC Repository resources from the UI, enable the Repository UI feature flag in the tektoncd-enhancement-feature-flags ConfigMap. PAC can still run without this flag, but the UI hides the Repository workflow when enable-pac-repository is not set to "true".

The ConfigMap is managed by TektonConfig through spec.pipeline.options.configMaps. Apply the following configuration, and replace tekton-pipelines if your TektonConfig.spec.targetNamespace uses a different namespace:

apiVersion: operator.tekton.dev/v1alpha1
kind: TektonConfig
metadata:
  name: config
spec:
  targetNamespace: tekton-pipelines
  pipeline:
    options:
      disabled: false
      configMaps:
        tektoncd-enhancement-feature-flags:
          data:
            enable-pac-repository: "true"

Verify that the operator reconciled the ConfigMap:

kubectl get configmap tektoncd-enhancement-feature-flags \
  -n tekton-pipelines \
  -o jsonpath='{.data.enable-pac-repository}'

Expected output:

true

Configure Access

Important

The PAC controller must be reachable from the Git providers that will send webhook events to it. Expose it through one of the methods below before configuring any repository.

Using Gateway API

Use this method to expose the PAC controller with a domain name through ACP Gateway API.

Git provider -> PAC domain -> Envoy Gateway Service -> Gateway/HTTPRoute -> pipelines-as-code-controller

This example uses:

  • Default PAC namespace: tekton-pipelines
  • GatewayClass: envoy-gateway-operator-cpaas-default
  • Domain: pac.example.com

Step 1: Prepare Envoy Gateway. Install Alauda build of Envoy Gateway and ensure the default GatewayClass is accepted. Reference: Envoy Gateway Operator.

kubectl get gatewayclass envoy-gateway-operator-cpaas-default
NAME                                   ACCEPTED
envoy-gateway-operator-cpaas-default   True

Step 2: Prepare LoadBalancer addresses. The Envoy Gateway Service will be created as type: LoadBalancer, so LoadBalancer Services must be able to get an external IP. On ACP bare-metal clusters, install and configure Alauda Container Platform Load Balancer for MetalLB. Reference: Configure MetalLB.

kubectl get ipaddresspool,l2advertisement -A

Expected result:

NAMESPACE        NAME                                    ADDRESSES
metallb-system   ipaddresspool.metallb.io/default-pool   ["192.168.1.100-192.168.1.110"]

Step 3: Create Gateway API resources. Create gateway-api.yaml. Replace tekton-pipelines, envoy-gateway-operator-cpaas-default, and pac.example.com as needed. References: Configure GatewayAPI Gateway and Configure GatewayAPI Route.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: pipelines-as-code
  namespace: tekton-pipelines
spec:
  infrastructure:
    parametersRef:
      group: gateway.envoyproxy.io
      kind: EnvoyProxy
      name: pipelines-as-code
  gatewayClassName: envoy-gateway-operator-cpaas-default
  listeners:
    - name: http
      port: 80
      hostname: pac.example.com
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: Same
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
  name: pipelines-as-code
  namespace: tekton-pipelines
spec:
  provider:
    kubernetes:
      envoyService:
        type: LoadBalancer
      envoyDeployment:
        replicas: 1
        container:
          imageRepository: registry.alauda.cn:60080/acp/envoyproxy/envoy
          resources:
            limits:
              cpu: "1"
              memory: 1Gi
            requests:
              cpu: 100m
              memory: 256Mi
    type: Kubernetes
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: pipelines-as-code
  namespace: tekton-pipelines
spec:
  hostnames:
    - pac.example.com
  parentRefs:
    - name: pipelines-as-code
      sectionName: http
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: pipelines-as-code-controller
          port: 8080

Apply the file:

kubectl apply -f gateway-api.yaml

Expected result:

gateway.gateway.networking.k8s.io/pipelines-as-code created
envoyproxy.gateway.envoyproxy.io/pipelines-as-code created
httproute.gateway.networking.k8s.io/pipelines-as-code created

Step 4: Get the external address. Check the generated Envoy Service:

kubectl get svc -A \
  -l gateway.envoyproxy.io/owning-gateway-name=pipelines-as-code,gateway.envoyproxy.io/owning-gateway-namespace=tekton-pipelines

Expected result:

NAMESPACE                  NAME                                                 TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)
envoy-gateway-operator    envoy-tekton-pipelines-pipelines-as-code-3f8c88ad    LoadBalancer   10.96.10.20     192.168.1.100   80:32176/TCP

Step 5: Verify and get the webhook URL. Make sure the Git provider can resolve and access the PAC domain. A common way is to create a DNS A record. For example, if the Service EXTERNAL-IP is 192.168.1.100, create:

Type: A
Name: pac.example.com
Value: 192.168.1.100

If DNS is not ready yet, or you only want to test the route from your current machine, use curl --resolve:

EXTERNAL_IP=$(kubectl get svc -A \
  -l gateway.envoyproxy.io/owning-gateway-name=pipelines-as-code,gateway.envoyproxy.io/owning-gateway-namespace=tekton-pipelines \
  -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')

kubectl get gateway pipelines-as-code -n tekton-pipelines
kubectl get httproute pipelines-as-code -n tekton-pipelines -o yaml
curl -i --resolve "pac.example.com:80:${EXTERNAL_IP}" http://pac.example.com/

Expected result:

  • The Service has an EXTERNAL-IP.
  • The Gateway shows PROGRAMMED=True.
  • The HTTPRoute is accepted.
  • curl returns the PAC controller response.

After the domain is reachable from the Git provider network, print the URL:

WEBHOOK_URL=http://pac.example.com

WEBHOOK_URL is the PAC webhook URL. Record this value in pipelines-as-code-info.data.controller-url through TektonConfig; see Get the PAC Webhook URL. Use the same value when manually configuring a Git provider webhook or when tkn pac create repo prompts for a webhook URL.

If you expose PAC through ACP ALB or another Ingress Controller instead, use Using Ingress.

Notes:

  • HTTPRoute forwards to the existing pipelines-as-code-controller Service on port 8080; do not point it at the admission webhook Service named pipelines-as-code-webhook.
  • If the generated Envoy Service remains EXTERNAL-IP=<pending>, check the cluster LoadBalancer provider. For MetalLB, see Configure MetalLB.
  • For Gateway API options such as a reserved VIP, hostless routes, or HTTPS listeners, see Configure GatewayAPI Gateway and Configure GatewayAPI Route.

Using Ingress

Use this method when the cluster already has an Ingress Controller and you want to expose the PAC controller with an Ingress domain.

This example uses:

  • Default PAC namespace: tekton-pipelines
  • Domain: pac.example.com

Step 1: Prepare an Ingress Controller. Ensure an Ingress Controller is installed and ready. Reference: Configure Ingress.

Step 2: Create the Ingress resource. Create ingress.yaml. Replace tekton-pipelines and pac.example.com as needed.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: pipelines-as-code
  namespace: tekton-pipelines
spec:
  rules:
  - host: pac.example.com
    http:
      paths:
      - backend:
          service:
            name: pipelines-as-code-controller
            port:
              number: 8080
        path: /
        pathType: Prefix

Apply the file:

kubectl apply -f ingress.yaml

Expected result:

ingress.networking.k8s.io/pipelines-as-code created

Step 3: Verify the Ingress address. Check that the Ingress has an address:

kubectl get ingress pipelines-as-code -n tekton-pipelines

Expected result:

NAME                CLASS   HOSTS             ADDRESS         PORTS
pipelines-as-code   nginx   pac.example.com   192.168.1.100   80

Step 4: Get the webhook URL. Make sure the Git provider can resolve and access pac.example.com through the Ingress address. Then print the URL:

HOST=$(kubectl get ingress pipelines-as-code -n tekton-pipelines \
  -o jsonpath='{.spec.rules[0].host}')

echo "WEBHOOK_URL=http://${HOST}"

WEBHOOK_URL is the PAC webhook URL. Record this value in pipelines-as-code-info.data.controller-url through TektonConfig; see Get the PAC Webhook URL. Use the same value when manually configuring a Git provider webhook or when tkn pac create repo prompts for a webhook URL.

If you do not have a DNS name, remove the host field and use the reachable Ingress IP URL instead.

Optional: Enable HTTPS. Create a TLS Secret in tekton-pipelines and add a tls section to the same Ingress. The certificate must match pac.example.com.

kubectl create secret tls pipelines-as-code-tls \
  -n tekton-pipelines \
  --cert=tls.crt \
  --key=tls.key
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: pipelines-as-code
  namespace: tekton-pipelines
spec:
  rules:
  - host: pac.example.com
    http:
      paths:
      - backend:
          service:
            name: pipelines-as-code-controller
            port:
              number: 8080
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - pac.example.com
    secretName: pipelines-as-code-tls

When TLS is configured, use the HTTPS webhook URL:

echo "WEBHOOK_URL=https://${HOST}"

Using NodePort

Create a NodePort Service:

apiVersion: v1
kind: Service
metadata:
  name: pipelines-as-code-controller-nodeport
  namespace: tekton-pipelines
spec:
  ports:
    - name: http-listener
      port: 8080
      protocol: TCP
      targetPort: 8082  # PAC controller listens on port 8082
      nodePort: 30080  # Optional: specify a fixed NodePort
  selector:
    app.kubernetes.io/part-of: pipelines-as-code
    app.kubernetes.io/component: controller
  type: NodePort

Important:

  • The targetPort must be 8082, which is the port the PAC controller pod listens on for webhook events
  • The port (8080) is the Service port (used for internal cluster communication)
  • The nodePort (30080) is the external port accessible from outside the cluster
  • For Ingress, the Service port is 8080, which routes to the controller's port 8082 internally

Print the URL from a reachable node IP and the generated NodePort:

NODEPORT=$(kubectl get service pipelines-as-code-controller-nodeport -n tekton-pipelines \
  -o jsonpath='{.spec.ports[?(@.name=="http-listener")].nodePort}')

NODE_IP=$(kubectl get nodes \
  -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')

echo "WEBHOOK_URL=http://${NODE_IP}:${NODEPORT}"

WEBHOOK_URL is the PAC webhook URL. Record this value in pipelines-as-code-info.data.controller-url through TektonConfig; see Get the PAC Webhook URL. Use the same value when manually configuring a Git provider webhook or when tkn pac create repo prompts for a webhook URL.

Configuration Settings

PAC configuration lives in two different places under spec.platforms.kubernetes.pipelinesAsCode: settings and options.configMaps. Which one you must use depends on the key. Read Where to Put Each Setting before editing, because a key written in the wrong place is dropped without any error.

Standard Settings

These keys go under pipelinesAsCode.settings:

SettingDescriptionDefault
application-nameName displayed in Git provider UIPipelines as Code CI
hub-urlHub API URL used for PAC remote task and pipeline annotationshttp://artifacthub-shim-api.artifacthub-shim-system.svc.cluster.local
hub-catalog-typeHub provider typeartifacthub
hub-catalog-nameDefault Artifact Hub repository name for PAC remote annotationscatalog
remote-tasksEnable remote task resolutiontrue
secret-auto-createAutomatically create secretstrue
error-detection-from-container-logsDetect errors from container logs. GitHub Apps onlytrue
error-log-snippetShow error log snippetstrue
custom-console-nameDisplay name for custom console links in Git provider UI`` (empty)
custom-console-urlConsole base URL. Used verbatim; template variables are not expanded in this key`` (empty)
custom-console-url-pr-detailsURL template for the PipelineRun details page. Supports {{ namespace }}, {{ pr }}`` (empty)
custom-console-url-pr-tasklogURL template for the task log page. Supports {{ namespace }}, {{ pr }}, {{ task }}`` (empty)
custom-console-url-namespaceURL template for the namespace page. Supports {{ namespace }}`` (empty)

hub-url addresses the Hub backend the PAC controller queries when resolving remote tasks. ACP DevOps defaults this to the in-cluster artifacthub-shim Service. For Artifact Hub providers, do not append /api/v1; PAC adds that path internally.

custom-console-* settings rewrite the cluster-side links PAC posts back to the Git provider so they point at the platform console rather than at the OpenShift Console. The walkthrough is in Configure Custom Console Links.

Alauda Extension Settings

These keys are Alauda additions on top of upstream Pipelines as Code. They must go under pipelinesAsCode.options.configMaps, not under pipelinesAsCode.settings:

SettingDescriptionDefault
custom-console-url-namespace-varsExtra template variables for the custom-console-url-* templates, resolved from the labels or annotations of the namespace the run belongs to, for example {{ project }} and {{ cluster }}`` (empty)
replace-empty-template-vars-with-emptyReplace template variables that have no value (such as {{ pull_request_number }} on a push event) with an empty string instead of leaving the literal {{ ... }} in the PipelineRunfalse
enable-pull-request-number-on-push-eventsPopulate {{ pull_request_number }} on push events when the pushed commit belongs to a pull request. GitHub onlytrue

Walkthroughs: Configure Custom Console Links for custom-console-url-namespace-vars, and Configure Pull Request Number on Push Events for the other two.

Where to Put Each Setting

The two locations are handled by completely different code paths in the operator:

  • settings is not stored as you write it. The operator's defaulting webhook parses your map into the upstream Pipelines as Code settings struct and then regenerates the map from that struct. Any key that has no matching field in the struct — which is the case for every Alauda extension setting listed above — is silently discarded: TektonConfig loses the key, the derived OpenShiftPipelinesAsCode CR never sees it, the generated pipelines-as-code ConfigMap never receives it, and no error or event is reported.
  • options.configMaps is applied by the additional-options transformer, which is the last transformer to run and merges your entries into the rendered ConfigMap key by key. Nothing rewrites this map afterwards, so any key survives — including keys the operator does not know about.

Three consequences worth remembering:

  • Put upstream keys under settings and Alauda extension keys under options.configMaps. Do not move upstream keys into options without reason: settings is the documented, validated location for them.
  • If the same key is set in both places, the options.configMaps value wins, because that transformer runs after the settings-derived ConfigMap has been rendered.
  • Setting options.disabled: true turns the whole transformer off. Everything under options is then ignored, including these settings, and you are back to the silent-drop behavior. Leave it unset or false.

options.configMaps is a map keyed by ConfigMap name — it can target any ConfigMap in the component's manifest, and creates one if the name does not exist there. PAC reads its settings from the ConfigMap named pipelines-as-code, so that is the key to use here:

apiVersion: operator.tekton.dev/v1alpha1
kind: TektonConfig
metadata:
  name: config
spec:
  profile: all
  targetNamespace: tekton-pipelines
  platforms:
    kubernetes:
      pipelinesAsCode:
        enable: true
        options:
          disabled: false
          configMaps:
            pipelines-as-code:
              data:
                custom-console-url-namespace-vars: "project=label:cpaas.io/project,cluster=label:cpaas.io/cluster"

Verify that the key reached the ConfigMap. This is the only reliable check — reading it back from settings does not tell you whether PAC received it:

kubectl get configmap pipelines-as-code -n tekton-pipelines \
  -o jsonpath='{.data.custom-console-url-namespace-vars}'

Update PAC Component

Update Configuration

  1. Edit the TektonConfig CR:

    kubectl edit tektonconfig config
  2. Update the settings field as needed:

    spec:
      platforms:
        kubernetes:
          pipelinesAsCode:
            enable: true
            settings:
              application-name: "My Custom PAC"
              hub-url: http://artifacthub-shim-api.artifacthub-shim-system.svc.cluster.local
              hub-catalog-type: artifacthub
              hub-catalog-name: catalog
              remote-tasks: "true"
              error-detection-from-container-logs: "true"

    To change one of the Alauda extension settings, edit pipelinesAsCode.options.configMaps instead — those keys are removed from settings on save.

  3. Save and exit. The operator updates the derived OpenShiftPipelinesAsCode CR, then the TektonInstallerSet, and applies the changes.

Do not edit the derived OpenShiftPipelinesAsCode CR instead: on profile: all the operator copies settings and options back over from TektonConfig on the next reconcile, so the change is lost. See How the Operator Manages PAC.

Common Configuration Updates

The examples in this section update spec.platforms.kubernetes.pipelinesAsCode in the TektonConfig named config. Each example shows the field it belongs in — most use settings, and the Alauda extension settings use options.configMaps. See Where to Put Each Setting if you are unsure which applies.

kubectl edit tektonconfig config

The custom-console-* settings rewrite the cluster-side links PAC posts back to the Git provider so they point at the platform console. The example below resolves {{ project }} and {{ cluster }} from namespace labels, so the URLs do not hard-code a cluster identifier.

Note that the three templated keys live under settings, while custom-console-url-namespace-vars must live under options.configMaps — see Where to Put Each Setting:

spec:
  targetNamespace: tekton-pipelines
  platforms:
    kubernetes:
      pipelinesAsCode:
        enable: true
        settings:
          custom-console-name: "My Console"
          custom-console-url: "https://console.example.com/console-acp/"
          custom-console-url-pr-details: "https://console.example.com/console-acp/workspace/{{ project }}~{{ cluster }}~{{ namespace }}/pipeline/pipelineRuns/detail/{{ pr }}"
          custom-console-url-pr-tasklog: "https://console.example.com/console-acp/workspace/{{ project }}~{{ cluster }}~{{ namespace }}/pipeline/pipelineRuns/detail/{{ pr }}?tab=task_overview&id={{ task }}"
          custom-console-url-namespace: "https://console.example.com/console-acp/workspace/{{ project }}~{{ cluster }}~{{ namespace }}/pipeline/pipelineRuns"
        options:
          disabled: false
          configMaps:
            pipelines-as-code:
              data:
                custom-console-url-namespace-vars: "project=label:cpaas.io/project,cluster=label:cpaas.io/cluster"

Effect: Git provider status links open the platform console PipelineRun and task pages instead of the default OpenShift-style placeholder URLs.

Warning

custom-console-url is not a template

Only custom-console-url-pr-details, custom-console-url-pr-tasklog and custom-console-url-namespace go through template expansion. PAC returns custom-console-url verbatim, so writing {{ ... }} in it produces a broken link. Keep it a plain URL: it is the console entry point and also the value PAC falls back to whenever a templated URL fails to build.

PAC expands these variables when it posts status links:

VariableResolved to
{{ namespace }}Namespace where the PipelineRun runs
{{ pr }}PipelineRun name
{{ task }}Pipeline task name — the name of the entry under spec.pipelineSpec.tasks, not the generated TaskRun resource name
{{ pod }} and {{ firstFailedStep }}Pod name and the name of the first failed step. Use them only in custom-console-url-pr-tasklog — that is the only place PAC fills them in, and since they are never cleared afterwards, the other two templates get either an empty value or a stale one left over from an earlier task-log link
{{ project }} and {{ cluster }}Namespace labels declared in custom-console-url-namespace-vars

The standard event variables ({{ revision }}, {{ repo_url }} and the rest) are also available in these templates, as are the Repository spec.params entries that resolved for this event — an entry with no name, or with a CEL filter that did not match, is not passed through. The five builtin names above always win: a namespace variable or a Repository parameter that reuses one of them is ignored rather than overriding it.

custom-console-url-namespace-vars is a comma-separated list. Each item uses name=label:<key> or name=annotation:<key>, with an optional |<default> fallback used when the label or annotation is missing or empty. Without a default the variable resolves to an empty string rather than being left as a literal {{ name }}.

Two things have to hold for that to happen. PAC must be able to read the namespace — if the Namespace GET fails, for example because the PAC controller has no permission on it, no namespace-derived variable is injected for that run. And the namespace it reads is the Repository namespace while the run is being created, and the PipelineRun namespace when the final status is reported; the two differ only if a PipelineRun is routed elsewhere through the target-namespace annotation. Whenever a variable ends up unsubstituted, generateURL discards the whole URL and falls back to custom-console-url, which is exactly the "the link is right but never expands" symptom.

spec:
  platforms:
    kubernetes:
      pipelinesAsCode:
        options:
          disabled: false
          configMaps:
            pipelines-as-code:
              data:
                custom-console-url-namespace-vars: "project=label:cpaas.io/project|unknown,cluster=label:cpaas.io/cluster"
INFO

Namespace labels and annotations are cached for up to 30 minutes, so label changes may take a few minutes to appear in generated links.

For a PipelineRun named my-app-build-abc123 in namespace my-app with labels cpaas.io/project=team-a and cpaas.io/cluster=prod, PAC generates links like:

https://console.example.com/console-acp/workspace/team-a~prod~my-app/pipeline/pipelineRuns/detail/my-app-build-abc123
https://console.example.com/console-acp/workspace/team-a~prod~my-app/pipeline/pipelineRuns/detail/my-app-build-abc123?tab=task_overview&id=build-task
https://console.example.com/console-acp/workspace/team-a~prod~my-app/pipeline/pipelineRuns

These URLs appear in commit statuses, GitHub Checks panels and merge request comments.

Configure Pull Request Number on Push Events

{{ pull_request_number }} is a dynamic variable you can reference from a PipelineRun in your repository. It has an obvious value on a pull request event, but not on a push event. Two Alauda extension settings control what happens then. Both live under options.configMaps, not under settings:

spec:
  targetNamespace: tekton-pipelines
  platforms:
    kubernetes:
      pipelinesAsCode:
        enable: true
        options:
          disabled: false
          configMaps:
            pipelines-as-code:
              data:
                enable-pull-request-number-on-push-events: "true"
                replace-empty-template-vars-with-empty: "true"

enable-pull-request-number-on-push-events (default "true"): when a push event carries a commit that belongs to a pull request, PAC sets {{ pull_request_number }} to that pull request number. Set it to "false" if you want push events to never inherit a pull request number — for example when a pipeline uses the variable to decide whether it is running for a pull request.

NOTE

This setting only affects GitHub. The other providers never populate {{ pull_request_number }} from a push event, so on GitLab and the rest a push event behaves as if the setting were "false".

It also only comes into play for push events that PAC still processes. The upstream skip-push-event-for-pr-commits setting defaults to "true", and it makes PAC drop a GitHub push event outright when the pushed commit belongs to an open pull request — no PipelineRun is created at all, so no variable is expanded. Everything else still reaches this setting: tag pushes, which the skip rule never applies to; pushes whose commit is associated only with closed or merged pull requests, which is the usual case for the push that results from a merge; and every push event once skip-push-event-for-pr-commits is set to "false".

replace-empty-template-vars-with-empty (default "false"): controls what PAC does with template variables it could not resolve. By default an unresolved {{ pull_request_number }} is left in the PipelineRun as the literal string {{ pull_request_number }}, which usually shows up as a nonsensical parameter value or a validation failure. Set it to "true" to replace unresolved variables with an empty string instead. Variables whose name starts with body, headers or files are deliberately left alone, because they are resolved by a different mechanism.

For a GitHub push event that PAC does process, the two combine as follows:

enable-pull-request-number-on-push-eventsreplace-empty-template-vars-with-empty{{ pull_request_number }} resolves to
"true" (default)"false" (default)The pull request number if the commit is associated with one, otherwise the literal {{ pull_request_number }}
"true" (default)"true"The pull request number if the commit is associated with one, otherwise an empty string
"false""true"Always an empty string, even when the commit is associated with a pull request

The table assumes nothing else provides that name. A Repository spec.params entry called pull_request_number is passed through like any other parameter, and it is what the template resolves to whenever PAC did not take the value from the event itself.

Effect: with replace-empty-template-vars-with-empty: "true", pipelines that reference {{ pull_request_number }} run unchanged on both pull request and push events instead of failing on push.

NOTE

replace-empty-template-vars-with-empty applies to PipelineRun definitions resolved from the repository. It does not affect the custom-console-url-* templates, which have their own fallback behavior described in Configure Custom Console Links.

Change Application Name

Use this setting to change the display name that PAC uses in Git provider status messages:

spec:
  platforms:
    kubernetes:
      pipelinesAsCode:
        settings:
          application-name: "New Application Name"

Effect: Git provider checks, statuses, and comments show the new application name. This does not change the derived OpenShiftPipelinesAsCode resource name.

Enable Error Detection

spec:
  platforms:
    kubernetes:
      pipelinesAsCode:
        settings:
          error-detection-from-container-logs: "true"
          error-detection-max-number-of-lines: "100"

Effect: PAC scans failed task logs and adds a short error snippet to the Git provider feedback.

Update Hub URL

The default already points at the in-cluster artifacthub-shim Service, so set this only if artifacthub-shim was deployed in a different namespace or you want to use an external Hub:

spec:
  platforms:
    kubernetes:
      pipelinesAsCode:
        settings:
          # The address artifacthub-shim is installed at by default. Change only the
          # namespace if it was deployed somewhere other than artifacthub-shim-system.
          hub-url: "http://artifacthub-shim-api.artifacthub-shim-system.svc.cluster.local"
          hub-catalog-type: "artifacthub"
          hub-catalog-name: "catalog"

          # Or for a Tekton Hub instance - the catalog type has to change with it,
          # otherwise PAC queries it with the Artifact Hub API
          # hub-url: "https://api.hub.tekton.dev/v1"
          # hub-catalog-type: "tekton"

Effect: PAC resolves remote tasks from the specified Hub API instead of the default in-cluster shim Service.

To find your artifacthub-shim service:

kubectl get svc -A | grep artifacthub-shim-api

Example output:

artifacthub-shim-system   artifacthub-shim-api   ClusterIP   10.96.123.45   <none>   80/TCP   10m

Disable Remote Tasks

Use remote-tasks to control whether PAC fetches and embeds remote resources referenced by PAC annotations.

NOTE

remote-tasks: "true" is the default. PAC can fetch remote resources referenced by pipelinesascode.tekton.dev/task and pipelinesascode.tekton.dev/pipeline annotations, then embed the resolved Task or Pipeline into the generated PipelineRun.

remote-tasks: "false" disables PAC annotation-based remote resource resolution. Pipeline code must define the required Pipeline and Tasks in the repository, inline them, or rely on cluster resources.

This setting does not disable Tekton Pipelines remote resolver syntax such as taskRef.resolver or pipelineRef.resolver; those are handled by the Tekton Pipelines controller.

spec:
  platforms:
    kubernetes:
      pipelinesAsCode:
        settings:
          remote-tasks: "false"

Effect: Use "false" when you want to prevent PAC from fetching remote Tasks or Pipelines from annotation references.

Uninstall PAC Component

This section describes the profile: all cluster, where the operator owns the PAC deployment. On profile: basic or lite the operator does not reconcile PAC at all: enable: false removes nothing, and deleting the OpenShiftPipelinesAsCode CR is itself the uninstall — nothing recreates it.

Disable PAC in TektonConfig

Set enable: false. The operator deletes the derived OpenShiftPipelinesAsCode CR, which in turn tears down the TektonInstallerSet and every PAC Deployment, Service, ConfigMap and RBAC object the operator created.

spec:
  profile: all
  platforms:
    kubernetes:
      pipelinesAsCode:
        enable: false
Do not delete the derived CR by hand

On profile: all, kubectl delete openshiftpipelinesascodes.operator.tekton.dev pipelines-as-code does not uninstall PAC while TektonConfig still enables it — the next reconcile recreates the CR and PAC comes back. Disable it in TektonConfig instead.

Confirm the CR, the installer set and the pods are gone:

kubectl get openshiftpipelinesascodes.operator.tekton.dev
kubectl get tektoninstallersets | grep pipelinesascode
kubectl get pods -n tekton-pipelines | grep pipelines-as-code

The first command should print No resources found. The two grep commands should print nothing at all — an empty result is what you are looking for, not an error message. TektonInstallerSet is cluster-scoped, which is why the second command takes no namespace.

Clean up resources the operator does not own

The operator removes everything it created. Resources you added yourself stay behind and must be removed manually.

Repository CRs in user namespaces:

kubectl get repositories -A
kubectl delete repositories --all -n <namespace>

Per-repository Secrets in user namespaces. These are the Git provider access tokens (provider.token) and webhook secrets (webhook.secret) created by users when configuring repositories. The PAC-owned cluster secrets (carrying the app.kubernetes.io/part-of=pipelines-as-code label) are removed by the operator; these per-repository ones are not. Verify each Secret is not used by any other resource before deleting:

kubectl delete secret <secret-name> -n <namespace>

Gateway API resources for the PAC controller, if you created them in Configure Access:

kubectl delete httproute pipelines-as-code -n tekton-pipelines
kubectl delete gateway pipelines-as-code -n tekton-pipelines
kubectl delete envoyproxy pipelines-as-code -n tekton-pipelines

Ingress / NodePort Service for the PAC controller, if you created them in Configure Access:

kubectl delete ingress pipelines-as-code -n tekton-pipelines
kubectl delete service pipelines-as-code-controller-nodeport -n tekton-pipelines

Troubleshooting

PAC Pods Not Starting

Check pod logs:

kubectl logs -n tekton-pipelines -l app.kubernetes.io/part-of=pipelines-as-code

Example output (example log entries):

{"level":"info","ts":"2024-01-01T12:00:00Z","logger":"controller","msg":"Starting PAC controller"}
{"level":"info","ts":"2024-01-01T12:00:01Z","logger":"controller","msg":"PAC controller ready"}

OpenShiftPipelinesAsCode CR Not Ready

Check the derived CR status and events. This is a read-only check — fix the configuration in TektonConfig, not here:

kubectl describe openshiftpipelinesascodes.operator.tekton.dev pipelines-as-code

Example output (abbreviated):

Name:         pipelines-as-code
Namespace:
Status:       Ready
Version:      0.x.x
Events:
  Type    Reason   Age   From              Message
  ----    ------   ----  ----              -------
  Normal  Ready    5m    tekton-operator   PAC component deployed successfully

TektonInstallerSet Issues

When the TektonInstallerSet is not Ready, read its conditions and the operator's view of the underlying OpenShiftPipelinesAsCode CR. Both are read-only checks; never delete the installer set yourself.

kubectl get tektoninstallersets -o yaml
kubectl get openshiftpipelinesascodes.operator.tekton.dev pipelines-as-code -o yaml

TektonInstallerSet is cluster-scoped, so the first command takes no namespace and lists every installer set on the cluster; the PAC ones carry pipelinesascode in their names.

If errors persist, let the operator rebuild the CR: on profile: all, delete the derived OpenShiftPipelinesAsCode CR while TektonConfig still has enable: true, and the next reconcile recreates it together with a fresh installer set. Check the profile first — on basic or lite nothing recreates the CR, and deleting it removes PAC for good.

CR Not Deleting

A deletion that hangs is usually held by the operator finalizer. List the finalizers:

kubectl get openshiftpipelinesascodes.operator.tekton.dev pipelines-as-code -o yaml | grep finalizers

A tekton.dev/operator finalizer means the operator is still cleaning up; wait and retry. An empty output means the CR is free to delete.

Resources Not Removed

When pods or Services linger after deletion:

kubectl delete deployment -n tekton-pipelines -l app.kubernetes.io/part-of=pipelines-as-code
kubectl delete service -n tekton-pipelines -l app.kubernetes.io/part-of=pipelines-as-code

Next Steps