From 6326d80615728153677df0d1feff3545f4dc53f9 Mon Sep 17 00:00:00 2001 From: mbpavan Date: Wed, 8 Apr 2026 11:27:04 +0530 Subject: [PATCH] Add Pipelines-as-Code on Kubernetes and resolve openshift-pipelines/pipelines-as-code to tektoncd repo --- Makefile | 12 +- .../tekton-operator/templates/deployment.yaml | 2 +- .../templates/kubernetes-crds.yaml | 41 +++++ .../templates/openshift-crds.yaml | 2 +- charts/tekton-operator/values.yaml | 2 +- .../pipelines-as-code-templates/rbac.yaml | 15 +- .../pipelines-as-code-templates/rbac.yaml | 35 ++++ components.nightly.yaml | 2 +- components.yaml | 2 +- ...v1alpha1_openshiftpipelinesascode_crd.yaml | 54 ++++++ config/base/kustomization.yaml | 1 + config/crs/kubernetes/kustomization.yaml | 1 + .../operator_v1alpha1_pipelinesascode_cr.yaml | 20 +++ config/kubernetes/base/kustomization.yaml | 1 + config/kubernetes/base/operator.yaml | 2 +- config/kubernetes/base/pipelinesascode.yaml | 32 ++++ .../base/300-operator_v1alpha1_addon_crd.yaml | 2 +- config/openshift/base/kustomization.yaml | 1 - docs/TektonConfig.md | 9 +- go.mod | 1 + go.sum | 4 +- hack/fetch-releases.sh | 24 ++- .../operator.tekton.dev_tektonaddons.yaml | 2 +- .../operator/v1alpha1/kubernetes_platform.go | 23 +++ .../operator/v1alpha1/openshift_platform.go | 9 - pkg/apis/operator/v1alpha1/pipelinesascode.go | 28 +++ .../v1alpha1/tektonconfig_default_test.go | 167 ++++++++++++++---- .../v1alpha1/tektonconfig_defaults.go | 54 ++++-- .../operator/v1alpha1/tektonconfig_types.go | 13 ++ .../v1alpha1/tektonconfig_validation.go | 30 +++- .../v1alpha1/tektonconfig_validation_test.go | 46 +++++ .../v1alpha1/zz_generated.deepcopy.go | 22 +++ pkg/reconciler/common/common.go | 5 + pkg/reconciler/common/initcontroller.go | 2 +- pkg/reconciler/common/releases.go | 2 + pkg/reconciler/common/releases_test.go | 19 ++ .../kodata/pipelines-as-code/0.1.0/dummy.yaml | 4 + .../kodata/pipelines-as-code/0.2.0/dummy.yaml | 4 + .../kubernetes/kubernetesplatform/config.go | 5 + .../kubernetes/pipelinesascode/controller.go | 31 ++++ .../pipelinesascode/kubernetes_extension.go | 103 +++++++++++ .../kubernetes/tektonconfig/extension.go | 21 +++ .../openshiftpipelinesascode/extension.go | 10 +- .../pipelinerun_templates.go | 5 +- .../test-expected-additional-pac-dep.yaml | 2 +- .../testdata/test-filter-manifest.yaml | 4 +- .../openshiftpipelinesascode/transform.go | 22 ++- .../transform_test.go | 11 +- .../openshift/openshiftplatform/config.go | 9 +- .../openshift/tektonconfig/extension.go | 14 +- pkg/reconciler/platform/const.go | 7 +- .../pipelinesascode}/pipelinesascode.go | 56 +++--- .../pipelinesascode}/pipelinesascode_test.go | 13 +- .../tektonconfig/upgrade/pre_upgrade.go | 11 +- .../common/00_tektonconfigdeployment_test.go | 27 ++- test/e2e/kubernetes/pipelinesascode_test.go | 116 ++++++++++++ test/resources/openshiftpipelinesascode.go | 25 ++- vendor/modules.txt | 3 +- 58 files changed, 1009 insertions(+), 181 deletions(-) rename cmd/{openshift/operator/kodata/tekton-addon => kubernetes/operator/kodata}/pipelines-as-code-templates/rbac.yaml (52%) create mode 100644 cmd/openshift/operator/kodata/pipelines-as-code-templates/rbac.yaml create mode 100644 config/base/300-operator_v1alpha1_openshiftpipelinesascode_crd.yaml create mode 100644 config/crs/kubernetes/pipelinesascode/operator_v1alpha1_pipelinesascode_cr.yaml create mode 100644 config/kubernetes/base/pipelinesascode.yaml create mode 100644 pkg/apis/operator/v1alpha1/kubernetes_platform.go create mode 100644 pkg/apis/operator/v1alpha1/pipelinesascode.go create mode 100644 pkg/reconciler/common/testdata/kodata/pipelines-as-code/0.1.0/dummy.yaml create mode 100644 pkg/reconciler/common/testdata/kodata/pipelines-as-code/0.2.0/dummy.yaml create mode 100644 pkg/reconciler/kubernetes/pipelinesascode/controller.go create mode 100644 pkg/reconciler/kubernetes/pipelinesascode/kubernetes_extension.go rename pkg/reconciler/{openshift/tektonconfig/extension => shared/tektonconfig/pipelinesascode}/pipelinesascode.go (74%) rename pkg/reconciler/{openshift/tektonconfig/extension => shared/tektonconfig/pipelinesascode}/pipelinesascode_test.go (82%) create mode 100644 test/e2e/kubernetes/pipelinesascode_test.go diff --git a/Makefile b/Makefile index 1862fef8e9..dea6ce5ace 100644 --- a/Makefile +++ b/Makefile @@ -69,14 +69,14 @@ ifeq ($(TARGET), openshift) rm -rf ./cmd/$(TARGET)/operator/kodata/manual-approval-gate rm -rf ./cmd/$(TARGET)/operator/kodata/tekton-pruner rm -rf ./cmd/$(TARGET)/operator/kodata/pruner - rm -rf ./cmd/$(TARGET)/operator/kodata/tekton-addon/pipelines-as-code + rm -rf ./cmd/$(TARGET)/operator/kodata/pipelines-as-code find ./cmd/$(TARGET)/operator/kodata/tekton-addon/addons/06-ecosystem/tasks -type f ! -name "role.yaml" ! -name "rolebinding.yaml" -delete find ./cmd/$(TARGET)/operator/kodata/tekton-addon/addons/06-ecosystem/stepactions -type f ! -name "role.yaml" ! -name "rolebinding.yaml" -delete - rm -rf ./cmd/$(TARGET)/operator/kodata/tekton-addon/pipelines-as-code-templates/go.yaml - rm -rf ./cmd/$(TARGET)/operator/kodata/tekton-addon/pipelines-as-code-templates/java.yaml - rm -rf ./cmd/$(TARGET)/operator/kodata/tekton-addon/pipelines-as-code-templates/nodejs.yaml - rm -rf ./cmd/$(TARGET)/operator/kodata/tekton-addon/pipelines-as-code-templates/python.yaml - rm -rf ./cmd/$(TARGET)/operator/kodata/tekton-addon/pipelines-as-code-templates/generic.yaml + rm -f ./cmd/$(TARGET)/operator/kodata/pipelines-as-code-templates/go.yaml + rm -f ./cmd/$(TARGET)/operator/kodata/pipelines-as-code-templates/java.yaml + rm -f ./cmd/$(TARGET)/operator/kodata/pipelines-as-code-templates/nodejs.yaml + rm -f ./cmd/$(TARGET)/operator/kodata/pipelines-as-code-templates/python.yaml + rm -f ./cmd/$(TARGET)/operator/kodata/pipelines-as-code-templates/generic.yaml else rm -rf ./cmd/$(TARGET)/operator/kodata/tekton* rm -rf ./cmd/$(TARGET)/operator/kodata/pruner diff --git a/charts/tekton-operator/templates/deployment.yaml b/charts/tekton-operator/templates/deployment.yaml index 37d12baf6a..8a01373fad 100644 --- a/charts/tekton-operator/templates/deployment.yaml +++ b/charts/tekton-operator/templates/deployment.yaml @@ -76,7 +76,7 @@ spec: {{- end }} args: - "-controllers" - - {{ .Values.controllers | default "tektonconfig,tektonpipeline,tektontrigger,tektonhub,tektonchain,tektonresult,tektondashboard,manualapprovalgate,tektonpruner" | quote }} + - {{ .Values.controllers | default "tektonconfig,tektonpipeline,tektontrigger,tektonhub,tektonchain,tektonresult,tektondashboard,manualapprovalgate,tektonpruner,openshiftpipelinesascode" | quote }} - "-unique-process-name" - "tekton-operator-lifecycle" image: {{ include "tekton-operator.operator-image" . }} diff --git a/charts/tekton-operator/templates/kubernetes-crds.yaml b/charts/tekton-operator/templates/kubernetes-crds.yaml index 8ad6bdfc09..e6542e065e 100644 --- a/charts/tekton-operator/templates/kubernetes-crds.yaml +++ b/charts/tekton-operator/templates/kubernetes-crds.yaml @@ -42,6 +42,47 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + labels: + operator.tekton.dev/release: "devel" + version: "devel" + name: openshiftpipelinesascodes.operator.tekton.dev +spec: + group: operator.tekton.dev + names: + kind: OpenShiftPipelinesAsCode + listKind: OpenShiftPipelinesAsCodeList + plural: openshiftpipelinesascodes + singular: openshiftpipelinesascode + shortNames: + - opac + - pac + preserveUnknownFields: false + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.version + name: Version + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].message + name: Reason + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Schema for the OpenShiftPipelinesAsCode API + type: object + x-kubernetes-preserve-unknown-fields: true + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: labels: operator.tekton.dev/release: "devel" diff --git a/charts/tekton-operator/templates/openshift-crds.yaml b/charts/tekton-operator/templates/openshift-crds.yaml index 75d669c54a..b84be8861a 100644 --- a/charts/tekton-operator/templates/openshift-crds.yaml +++ b/charts/tekton-operator/templates/openshift-crds.yaml @@ -111,7 +111,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Schema for the tektonaddons API + description: Schema for the tektonaddons API. Supported on OpenShift only. type: object x-kubernetes-preserve-unknown-fields: true served: true diff --git a/charts/tekton-operator/values.yaml b/charts/tekton-operator/values.yaml index a4cb4d8d23..cba164aa05 100644 --- a/charts/tekton-operator/values.yaml +++ b/charts/tekton-operator/values.yaml @@ -14,7 +14,7 @@ openshift: installCRDs: false ## Controllers to install -controllers: "tektonconfig,tektonpipeline,tektontrigger,tektonhub,tektonchain,tektonresult,tektondashboard,manualapprovalgate,tektonpruner" +controllers: "tektonconfig,tektonpipeline,tektontrigger,tektonhub,tektonchain,tektonresult,tektondashboard,manualapprovalgate,tektonpruner,openshiftpipelinesascode" ## Control the creation of RBAC resources (Serviceaccount, Role, ClusterRole, ClusterRoleBinding) rbac: diff --git a/cmd/openshift/operator/kodata/tekton-addon/pipelines-as-code-templates/rbac.yaml b/cmd/kubernetes/operator/kodata/pipelines-as-code-templates/rbac.yaml similarity index 52% rename from cmd/openshift/operator/kodata/tekton-addon/pipelines-as-code-templates/rbac.yaml rename to cmd/kubernetes/operator/kodata/pipelines-as-code-templates/rbac.yaml index a1afaee0ec..f35b1907c5 100644 --- a/cmd/openshift/operator/kodata/tekton-addon/pipelines-as-code-templates/rbac.yaml +++ b/cmd/kubernetes/operator/kodata/pipelines-as-code-templates/rbac.yaml @@ -1,3 +1,7 @@ +# Role and RoleBinding so authenticated users can read PAC PipelineRun template +# ConfigMaps in this namespace (same namespace where PostSet installs template CMs). +# Loaded with runtime templates from pipelines-as-code-templates/; see +# pkg/reconciler/openshift/openshiftpipelinesascode/pipelinerun_templates.go apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: @@ -5,14 +9,15 @@ metadata: labels: app.kubernetes.io/part-of: pipelines-as-code rules: - # All system:authenticated users needs to have access - # of the pipelines-as-code-templates ConfigMap even if they don't - # have access to the other resources present in the - # installed namespace. - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "list"] - resourceNames: ["pipelines-as-code-go-template", "pipelines-as-code-java-template", "pipelines-as-code-nodejs-template", "pipelines-as-code-python-template"] + resourceNames: + - pipelines-as-code-pipelinerun-go + - pipelines-as-code-pipelinerun-java + - pipelines-as-code-pipelinerun-nodejs + - pipelines-as-code-pipelinerun-python + - pipelines-as-code-pipelinerun-generic --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/cmd/openshift/operator/kodata/pipelines-as-code-templates/rbac.yaml b/cmd/openshift/operator/kodata/pipelines-as-code-templates/rbac.yaml new file mode 100644 index 0000000000..f35b1907c5 --- /dev/null +++ b/cmd/openshift/operator/kodata/pipelines-as-code-templates/rbac.yaml @@ -0,0 +1,35 @@ +# Role and RoleBinding so authenticated users can read PAC PipelineRun template +# ConfigMaps in this namespace (same namespace where PostSet installs template CMs). +# Loaded with runtime templates from pipelines-as-code-templates/; see +# pkg/reconciler/openshift/openshiftpipelinesascode/pipelinerun_templates.go +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: pipelines-as-code-templates + labels: + app.kubernetes.io/part-of: pipelines-as-code +rules: + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list"] + resourceNames: + - pipelines-as-code-pipelinerun-go + - pipelines-as-code-pipelinerun-java + - pipelines-as-code-pipelinerun-nodejs + - pipelines-as-code-pipelinerun-python + - pipelines-as-code-pipelinerun-generic +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: pipelines-as-code-templates + labels: + app.kubernetes.io/part-of: pipelines-as-code +subjects: + - kind: Group + name: system:authenticated + apiGroup: rbac.authorization.k8s.io +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: pipelines-as-code-templates diff --git a/components.nightly.yaml b/components.nightly.yaml index 2e9b5b309f..3517176154 100644 --- a/components.nightly.yaml +++ b/components.nightly.yaml @@ -25,7 +25,7 @@ hub: github: openshift-pipelines/hub version: v1.18.0 pipelines-as-code: - github: openshift-pipelines/pipelines-as-code + github: tektoncd/pipelines-as-code version: nightly pruner: github: tektoncd/pruner diff --git a/components.yaml b/components.yaml index 1be785b87b..9600cb2c87 100644 --- a/components.yaml +++ b/components.yaml @@ -17,7 +17,7 @@ pipeline: github: tektoncd/pipeline version: v1.11.0 pipelines-as-code: - github: openshift-pipelines/pipelines-as-code + github: tektoncd/pipelines-as-code version: v0.45.0 pruner: github: tektoncd/pruner diff --git a/config/base/300-operator_v1alpha1_openshiftpipelinesascode_crd.yaml b/config/base/300-operator_v1alpha1_openshiftpipelinesascode_crd.yaml new file mode 100644 index 0000000000..4be4d89098 --- /dev/null +++ b/config/base/300-operator_v1alpha1_openshiftpipelinesascode_crd.yaml @@ -0,0 +1,54 @@ +# Copyright 2022 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: openshiftpipelinesascodes.operator.tekton.dev + labels: + version: "devel" + operator.tekton.dev/release: "devel" +spec: + group: operator.tekton.dev + names: + kind: OpenShiftPipelinesAsCode + listKind: OpenShiftPipelinesAsCodeList + plural: openshiftpipelinesascodes + singular: openshiftpipelinesascode + shortNames: + - opac + - pac + preserveUnknownFields: false + scope: Cluster + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.version + name: Version + type: string + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + - jsonPath: ".status.conditions[?(@.type==\"Ready\")].message" + name: Reason + type: string + schema: + openAPIV3Schema: + type: object + description: Schema for the OpenShiftPipelinesAsCode API + x-kubernetes-preserve-unknown-fields: true diff --git a/config/base/kustomization.yaml b/config/base/kustomization.yaml index 51063b70c4..10a0e4cbf1 100644 --- a/config/base/kustomization.yaml +++ b/config/base/kustomization.yaml @@ -27,6 +27,7 @@ resources: - 300-operator_v1alpha1_scheduler_crd.yaml - 300-operator_v1alpha1_multiclusterproxyaae_crd.yaml - 300-operator_v1alpha1_syncerservice_crd.yaml +- 300-operator_v1alpha1_openshiftpipelinesascode_crd.yaml - config-logging.yaml - config-observability.yaml - tekton-config-defaults.yaml diff --git a/config/crs/kubernetes/kustomization.yaml b/config/crs/kubernetes/kustomization.yaml index 552c3ee0bc..d3e28ceebe 100644 --- a/config/crs/kubernetes/kustomization.yaml +++ b/config/crs/kubernetes/kustomization.yaml @@ -13,3 +13,4 @@ resources: - pruner/operator_v1alpha1_pruner_cr.yaml - scheduler/operator_v1alpha1_scheduler_cr.yaml - multicluster-proxy-aae/operator_v1alpha1_multiclusterproxyaae_cr.yaml +- pipelinesascode/operator_v1alpha1_pipelinesascode_cr.yaml diff --git a/config/crs/kubernetes/pipelinesascode/operator_v1alpha1_pipelinesascode_cr.yaml b/config/crs/kubernetes/pipelinesascode/operator_v1alpha1_pipelinesascode_cr.yaml new file mode 100644 index 0000000000..58599ff58c --- /dev/null +++ b/config/crs/kubernetes/pipelinesascode/operator_v1alpha1_pipelinesascode_cr.yaml @@ -0,0 +1,20 @@ +# Copyright 2025 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: operator.tekton.dev/v1alpha1 +kind: OpenShiftPipelinesAsCode +metadata: + name: pipelines-as-code +spec: + targetNamespace: tekton-pipelines diff --git a/config/kubernetes/base/kustomization.yaml b/config/kubernetes/base/kustomization.yaml index 5362b66878..a3ca40e60d 100644 --- a/config/kubernetes/base/kustomization.yaml +++ b/config/kubernetes/base/kustomization.yaml @@ -33,3 +33,4 @@ resources: - ../../webhooks/ - 300-operator_v1alpha1_dashboard_crd.yaml - operator_service.yaml +- pipelinesascode.yaml diff --git a/config/kubernetes/base/operator.yaml b/config/kubernetes/base/operator.yaml index 8bc8c3a646..1668219f4f 100644 --- a/config/kubernetes/base/operator.yaml +++ b/config/kubernetes/base/operator.yaml @@ -33,7 +33,7 @@ spec: image: ko://github.com/tektoncd/operator/cmd/kubernetes/operator args: - "-controllers" - - "tektonconfig,tektonpipeline,tektontrigger,tektonhub,tektonchain,tektonresult,tektondashboard,manualapprovalgate,tektonpruner,tektonscheduler,tektonmulticlusterproxyaae" + - "tektonconfig,tektonpipeline,tektontrigger,tektonhub,tektonchain,tektonresult,tektondashboard,manualapprovalgate,tektonpruner,tektonscheduler,tektonmulticlusterproxyaae,openshiftpipelinesascode" - "-unique-process-name" - "tekton-operator-lifecycle" imagePullPolicy: IfNotPresent diff --git a/config/kubernetes/base/pipelinesascode.yaml b/config/kubernetes/base/pipelinesascode.yaml new file mode 100644 index 0000000000..f4462dec62 --- /dev/null +++ b/config/kubernetes/base/pipelinesascode.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: tekton-pac-controller-role +rules: + - apiGroups: + - pipelinesascode.tekton.dev + resources: + - repositories + - webhooks + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - deletecollection +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: tekton-pac-controller-role-binding +subjects: + - kind: ServiceAccount + name: tekton-operator + namespace: tekton-operator +roleRef: + kind: ClusterRole + name: tekton-pac-controller-role + apiGroup: rbac.authorization.k8s.io diff --git a/config/openshift/base/300-operator_v1alpha1_addon_crd.yaml b/config/openshift/base/300-operator_v1alpha1_addon_crd.yaml index b3bcbc7ae1..c2bc5a3788 100644 --- a/config/openshift/base/300-operator_v1alpha1_addon_crd.yaml +++ b/config/openshift/base/300-operator_v1alpha1_addon_crd.yaml @@ -47,5 +47,5 @@ spec: schema: openAPIV3Schema: type: object - description: Schema for the tektonaddons API + description: Schema for the tektonaddons API. Supported on OpenShift only. x-kubernetes-preserve-unknown-fields: true diff --git a/config/openshift/base/kustomization.yaml b/config/openshift/base/kustomization.yaml index 51782e5b88..0cc333b0a7 100644 --- a/config/openshift/base/kustomization.yaml +++ b/config/openshift/base/kustomization.yaml @@ -44,7 +44,6 @@ resources: - ../../base/ - ../../webhooks - 300-operator_v1alpha1_addon_crd.yaml -- 300-operator_v1alpha1_openshiftpipelinesascode_crd.yaml - operator_service.yaml - operator_servicemonitor.yaml apiVersion: kustomize.config.k8s.io/v1beta1 diff --git a/docs/TektonConfig.md b/docs/TektonConfig.md index 2f108b805f..6b191ff93e 100644 --- a/docs/TektonConfig.md +++ b/docs/TektonConfig.md @@ -21,6 +21,7 @@ Other than the above components depending on the platform operator also provides - [TektonResult](./TektonResult.md) - On Kubernetes - [TektonDashboard](./TektonDashboard.md) + - [OpenShiftPipelinesAsCode](./OpenShiftPipelinesAsCode.md) (installed via `spec.platforms.kubernetes.pipelinesAsCode`; same CRD/kind as on OpenShift) - On OpenShift - [TektonAddon](./TektonAddon.md) - [OpenShiftPipelinesAsCode](./OpenShiftPipelinesAsCode.md) @@ -130,7 +131,7 @@ spec: trigger: disabled: false platforms: - openshift: + openshift: # or `kubernetes:` on Kubernetes clusters pipelinesAsCode: additionalPACControllers: : @@ -576,7 +577,7 @@ In the deployment the environment name will be converted as follows, ### OpenShiftPipelinesAsCode -The PipelinesAsCode section allows you to customize the Pipelines as Code features. When you change the TektonConfig CR, the Operator automatically applies the settings to custom resources and configmaps in your installation. +The PipelinesAsCode section allows you to customize the Pipelines as Code features on both Kubernetes and OpenShift. When you change the TektonConfig CR, the Operator automatically applies the settings to custom resources and configmaps in your installation. On Kubernetes, configure `spec.platforms.kubernetes.pipelinesAsCode` (the managed CR remains `OpenShiftPipelinesAsCode` for API compatibility). Some of the fields have default values, so operator will add them if the user hasn't passed in CR. Other fields which don't have default values unless the user specifies them. User can find those [here](https://pipelinesascode.com/docs/install/settings/#pipelines-as-code-configuration-settings). @@ -585,7 +586,7 @@ Example: ```yaml platforms: - openshift: + openshift: # or `kubernetes:` pipelinesAsCode: additionalPACControllers: controllername: @@ -680,7 +681,7 @@ pipelinesascode.tekton.dev/task: "artifact://buildah" For more details, see the [Pipelines-as-Code Remote Hub Catalogs documentation](https://pipelinesascode.com/docs/install/settings/#remote-hub-catalogs). -**NOTE**: OpenShiftPipelinesAsCode is currently available for the OpenShift Platform only. +**NOTE**: On Kubernetes clusters, use `spec.platforms.kubernetes.pipelinesAsCode`. The custom resource kind remains `OpenShiftPipelinesAsCode`. ### Event based pruner diff --git a/go.mod b/go.mod index b0930a3f3c..ae2033e8d6 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( replace ( github.com/alibabacloud-go/cr-20160607 => github.com/vdemeester/cr-20160607 v1.0.1 github.com/go-jose/go-jose/v4 => github.com/go-jose/go-jose/v4 v4.1.4 + github.com/openshift-pipelines/pipelines-as-code => github.com/tektoncd/pipelines-as-code v0.42.0 k8s.io/api => k8s.io/api v0.32.4 k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.32.9 k8s.io/apimachinery => k8s.io/apimachinery v0.32.4 diff --git a/go.sum b/go.sum index c9891a1208..8c9c6ad90d 100644 --- a/go.sum +++ b/go.sum @@ -2464,8 +2464,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/openshift-pipelines/pipelines-as-code v0.42.0 h1:2VmvFso+tA+301Uxv0guoYTDv4KJ/FedY2dwSvdJFBU= -github.com/openshift-pipelines/pipelines-as-code v0.42.0/go.mod h1:brABe5XhsEEkVhMoM0tFhDZmqum4nD6N91+brKPSQbQ= github.com/openshift/api v0.0.0-20240521185306-0314f31e7774 h1:SsoLnIil/D0FcjUbQ9Z8h95B7rxFvrso2X6OQjR8jPw= github.com/openshift/api v0.0.0-20240521185306-0314f31e7774/go.mod h1:7Hm1kLJGxWT6eysOpD2zUztdn+w91eiERn6KtI5o9aw= github.com/openshift/apiserver-library-go v0.0.0-20230816171015-6bfafa975bfb h1:UMgJny13BBcHpY+JQ9Eg1Dm9+J7nWO3eqPvV1Zpd49A= @@ -2716,6 +2714,8 @@ github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhg github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/tektoncd/pipeline v1.9.1 h1:Js6NQleJLoo5vrS6ebg+WoHntMDY6xMS9zDvbGR2RjQ= github.com/tektoncd/pipeline v1.9.1/go.mod h1:PTlIZ4Mhr8HZDx404O7spJtafiynetTMedCsXStjtHk= +github.com/tektoncd/pipelines-as-code v0.42.0 h1:KZqRXQk4v6blEhjNu6n9ZpPgyIeQR4Dc3ZqnK3s1pm8= +github.com/tektoncd/pipelines-as-code v0.42.0/go.mod h1:brABe5XhsEEkVhMoM0tFhDZmqum4nD6N91+brKPSQbQ= github.com/tektoncd/plumbing v0.0.0-20250805154627-25448098dea2 h1:v4UPEbe6MEto5i4ELtiXWBxUAUIAWL5U1DznfPhi4WE= github.com/tektoncd/plumbing v0.0.0-20250805154627-25448098dea2/go.mod h1:BC6F3DlZc+wpUT9YcwG9MoSfb4tUiH2olB9xYoIsB4I= github.com/tektoncd/pruner v0.3.5 h1:eQTgnQ56Par9d/6BICsXfTkR0Ybx8c/0QLZeNoJnaYQ= diff --git a/hack/fetch-releases.sh b/hack/fetch-releases.sh index 40e60ca07a..c2dd4250a2 100755 --- a/hack/fetch-releases.sh +++ b/hack/fetch-releases.sh @@ -206,13 +206,18 @@ release_yaml_pac() { local version=$3 ko_data=${SCRIPT_DIR}/cmd/${TARGET}/operator/kodata - comp_dir=${ko_data}/tekton-addon/pipelines-as-code + comp_dir=${ko_data}/pipelines-as-code dirPath=${comp_dir}/${version} + local pac_release_yaml=release.yaml + if [[ ${TARGET} != "openshift" ]]; then + pac_release_yaml=release.k8s.yaml + fi + if [[ ${version} == "stable" || ${version} == "nightly" ]]; then - url="https://raw.githubusercontent.com/openshift-pipelines/pipelines-as-code/${version}/release.yaml" + url="https://raw.githubusercontent.com/tektoncd/pipelines-as-code/${version}/${pac_release_yaml}" else - url="https://raw.githubusercontent.com/openshift-pipelines/pipelines-as-code/release-${version}/release.yaml" + url="https://raw.githubusercontent.com/tektoncd/pipelines-as-code/release-${version}/${pac_release_yaml}" fi dest=${dirPath}/${fileName}.yaml @@ -247,8 +252,8 @@ release_yaml_pac() { do echo "fetching PipelineRun template for runtime: $run" - source="https://raw.githubusercontent.com/openshift-pipelines/pipelines-as-code/${version}/pkg/cmd/tknpac/generate/templates/${run}.yaml" - dest_dir="${ko_data}/tekton-addon/pipelines-as-code-templates" + source="https://raw.githubusercontent.com/tektoncd/pipelines-as-code/${version}/pkg/cmd/tknpac/generate/templates/${run}.yaml" + dest_dir="${ko_data}/pipelines-as-code-templates" mkdir -p ${dest_dir} || true destination="${dest_dir}/${run}.yaml" @@ -411,9 +416,12 @@ main() { # get release YAML for Dashboard release_yaml dashboard release-full 00-dashboard ${d_version} release_yaml dashboard release 00-dashboard ${d_version} - else - pac_version=$(go run ./cmd/tool component-version ${CONFIG} pipelines-as-code) - release_yaml_pac pipelinesascode release ${pac_version} + fi + + pac_version=$(go run ./cmd/tool component-version ${CONFIG} pipelines-as-code) + release_yaml_pac pipelinesascode release ${pac_version} + + if [[ ${TARGET} == "openshift" ]]; then fetch_openshift_addon_tasks fi diff --git a/operatorhub/openshift/release-artifacts/bundle/manifests/operator.tekton.dev_tektonaddons.yaml b/operatorhub/openshift/release-artifacts/bundle/manifests/operator.tekton.dev_tektonaddons.yaml index 5ecb21711b..a6f06d92ee 100644 --- a/operatorhub/openshift/release-artifacts/bundle/manifests/operator.tekton.dev_tektonaddons.yaml +++ b/operatorhub/openshift/release-artifacts/bundle/manifests/operator.tekton.dev_tektonaddons.yaml @@ -28,7 +28,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Schema for the tektonaddons API + description: Schema for the tektonaddons API. Supported on OpenShift only. type: object x-kubernetes-preserve-unknown-fields: true served: true diff --git a/pkg/apis/operator/v1alpha1/kubernetes_platform.go b/pkg/apis/operator/v1alpha1/kubernetes_platform.go new file mode 100644 index 0000000000..1ca9cadef6 --- /dev/null +++ b/pkg/apis/operator/v1alpha1/kubernetes_platform.go @@ -0,0 +1,23 @@ +/* +Copyright 2025 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +type Kubernetes struct { + // PipelinesAsCode allows configuring PipelinesAsCode configurations + // +optional + PipelinesAsCode *PipelinesAsCode `json:"pipelinesAsCode,omitempty"` +} diff --git a/pkg/apis/operator/v1alpha1/openshift_platform.go b/pkg/apis/operator/v1alpha1/openshift_platform.go index 620efdf754..19493b5a74 100644 --- a/pkg/apis/operator/v1alpha1/openshift_platform.go +++ b/pkg/apis/operator/v1alpha1/openshift_platform.go @@ -35,15 +35,6 @@ type OpenShift struct { EnableCentralTLSConfig bool `json:"enableCentralTLSConfig,omitempty"` } -type PipelinesAsCode struct { - // Enable or disable pipelines as code by changing this bool - // +optional - Enable *bool `json:"enable,omitempty"` - // PACSettings allows user to configure PAC configurations - // +optional - PACSettings `json:",inline"` -} - type SCC struct { // Default contains the default SCC that will be attached to the service // account used for workloads (`pipeline` SA by default) and defined in diff --git a/pkg/apis/operator/v1alpha1/pipelinesascode.go b/pkg/apis/operator/v1alpha1/pipelinesascode.go new file mode 100644 index 0000000000..2dff93a581 --- /dev/null +++ b/pkg/apis/operator/v1alpha1/pipelinesascode.go @@ -0,0 +1,28 @@ +/* +Copyright 2025 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +// PipelinesAsCode holds common settings for both Kubernetes and OpenShift. +// +k8s:openapi-gen=true +type PipelinesAsCode struct { + // Enable or disable pipelines as code by changing this bool + // +optional + Enable *bool `json:"enable,omitempty"` + // PACSettings allows user to configure PAC configurations + // +optional + PACSettings `json:",inline"` +} diff --git a/pkg/apis/operator/v1alpha1/tektonconfig_default_test.go b/pkg/apis/operator/v1alpha1/tektonconfig_default_test.go index d007f894b6..e93723fea3 100644 --- a/pkg/apis/operator/v1alpha1/tektonconfig_default_test.go +++ b/pkg/apis/operator/v1alpha1/tektonconfig_default_test.go @@ -28,6 +28,31 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +func Test_SetDefaults_OpenShift_MigratesKubernetesPipelinesAsCode(t *testing.T) { + t.Setenv("PLATFORM", "openshift") + kpac := &PipelinesAsCode{ + Enable: ptr.Bool(true), + PACSettings: PACSettings{ + Settings: map[string]string{"application-name": "test"}, + }, + } + tc := &TektonConfig{ + Spec: TektonConfigSpec{ + CommonSpec: CommonSpec{TargetNamespace: "ns"}, + Platforms: Platforms{ + Kubernetes: Kubernetes{PipelinesAsCode: kpac}, + }, + }, + } + tc.SetDefaults(context.TODO()) + if tc.Spec.Platforms.Kubernetes.PipelinesAsCode != nil { + t.Fatalf("expected kubernetes.pipelinesAsCode cleared after migration, got %+v", tc.Spec.Platforms.Kubernetes.PipelinesAsCode) + } + if tc.Spec.Platforms.OpenShift.PipelinesAsCode == nil || tc.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.Settings["application-name"] != "test" { + t.Fatalf("expected PAC migrated to openshift, got %+v", tc.Spec.Platforms.OpenShift.PipelinesAsCode) + } +} + func Test_SetDefaults_Profile(t *testing.T) { tc := &TektonConfig{ @@ -129,56 +154,122 @@ func Test_SetDefaults_Triggers_Properties(t *testing.T) { } func Test_SetDefaults_PipelineAsCode(t *testing.T) { - t.Setenv("PLATFORM", "openshift") - - // PAC disabled through addon - tc := &TektonConfig{ - Spec: TektonConfigSpec{ - Addon: Addon{ - EnablePAC: ptr.Bool(false), + platforms := []struct { + name string + getEnable func(cfg *TektonConfig) *bool + }{ + { + name: "openshift", + getEnable: func(cfg *TektonConfig) *bool { + return cfg.Spec.Platforms.OpenShift.PipelinesAsCode.Enable }, }, - } - tc.SetDefaults(context.TODO()) - assert.Equal(t, *tc.Spec.Platforms.OpenShift.PipelinesAsCode.Enable, false) - assert.Assert(t, tc.Spec.Addon.EnablePAC == nil) - - // PAC enabled through addon, moving to openshiftPipelinesAsCode - tc = &TektonConfig{ - Spec: TektonConfigSpec{ - Addon: Addon{ - EnablePAC: ptr.Bool(true), + { + name: "kubernetes", + getEnable: func(cfg *TektonConfig) *bool { + return cfg.Spec.Platforms.Kubernetes.PipelinesAsCode.Enable }, }, } - tc.SetDefaults(context.TODO()) - assert.Equal(t, *tc.Spec.Platforms.OpenShift.PipelinesAsCode.Enable, true) - assert.Assert(t, tc.Spec.Addon.EnablePAC == nil) - - // New installation - tc = &TektonConfig{} - tc.SetDefaults(context.TODO()) - assert.Equal(t, *tc.Spec.Platforms.OpenShift.PipelinesAsCode.Enable, true) - assert.Assert(t, tc.Spec.Addon.EnablePAC == nil) - // if PAC is enabled already then ignore addon pac field - tc = &TektonConfig{ - Spec: TektonConfigSpec{ - Addon: Addon{ - EnablePAC: ptr.Bool(false), + cases := []struct { + desc string + initialConfig *TektonConfig + wantEnable bool + wantPACFieldNil bool + skipUnlessPlatform string + }{ + { + desc: "new install: nil PipelinesAsCode, no addon override", + initialConfig: &TektonConfig{}, + wantEnable: true, + wantPACFieldNil: true, + }, + { + desc: "disabled via addon => pipelinesAsCode.Enable=false", + initialConfig: &TektonConfig{ + Spec: TektonConfigSpec{ + Addon: Addon{EnablePAC: ptr.Bool(false)}, + }, }, - Platforms: Platforms{ - OpenShift: OpenShift{ - PipelinesAsCode: &PipelinesAsCode{ - Enable: ptr.Bool(true), + wantEnable: false, + wantPACFieldNil: true, + }, + { + desc: "enabled via addon => pipelinesAsCode.Enable=true", + initialConfig: &TektonConfig{ + Spec: TektonConfigSpec{ + Addon: Addon{EnablePAC: ptr.Bool(true)}, + }, + }, + wantEnable: true, + wantPACFieldNil: true, + }, + { + desc: "existing openshift PipelinesAsCode overrides addon", + initialConfig: &TektonConfig{ + Spec: TektonConfigSpec{ + Addon: Addon{EnablePAC: ptr.Bool(false)}, + Platforms: Platforms{ + OpenShift: OpenShift{ + PipelinesAsCode: &PipelinesAsCode{Enable: ptr.Bool(true)}, + }, }, }, }, + wantEnable: true, + wantPACFieldNil: true, + skipUnlessPlatform: "openshift", + }, + { + desc: "existing kubernetes PipelinesAsCode overrides addon", + initialConfig: &TektonConfig{ + Spec: TektonConfigSpec{ + Addon: Addon{EnablePAC: ptr.Bool(false)}, + Platforms: Platforms{ + Kubernetes: Kubernetes{ + PipelinesAsCode: &PipelinesAsCode{Enable: ptr.Bool(true)}, + }, + }, + }, + }, + wantEnable: true, + wantPACFieldNil: true, + skipUnlessPlatform: "kubernetes", }, } - tc.SetDefaults(context.TODO()) - assert.Equal(t, *tc.Spec.Platforms.OpenShift.PipelinesAsCode.Enable, true) - assert.Assert(t, tc.Spec.Addon.EnablePAC == nil) + + for _, p := range platforms { + t.Run(p.name, func(t *testing.T) { + t.Setenv("PLATFORM", p.name) + + for _, c := range cases { + if c.skipUnlessPlatform != "" && c.skipUnlessPlatform != p.name { + continue + } + t.Run(c.desc, func(t *testing.T) { + cfg := c.initialConfig.DeepCopy() + cfg.SetDefaults(context.TODO()) + + gotEnable := p.getEnable(cfg) + if gotEnable == nil { + t.Fatalf("PipelinesAsCode.Enable is nil for platform %q", p.name) + } + if *gotEnable != c.wantEnable { + t.Errorf("for %q @ %s, Enable = %v; want %v", + c.desc, p.name, *gotEnable, c.wantEnable) + } + + hasPAC := cfg.Spec.Addon.EnablePAC != nil + expectedHasPAC := !c.wantPACFieldNil + if hasPAC != expectedHasPAC { + t.Errorf("for %q @ %s, Addon.EnablePAC exists = %v; want exists? %v", + c.desc, p.name, hasPAC, expectedHasPAC) + } + }) + } + }) + } } func Test_SetDefaults_SCC(t *testing.T) { diff --git a/pkg/apis/operator/v1alpha1/tektonconfig_defaults.go b/pkg/apis/operator/v1alpha1/tektonconfig_defaults.go index 93e4cd7945..e16640cdbb 100644 --- a/pkg/apis/operator/v1alpha1/tektonconfig_defaults.go +++ b/pkg/apis/operator/v1alpha1/tektonconfig_defaults.go @@ -36,27 +36,39 @@ func (tc *TektonConfig) SetDefaults(ctx context.Context) { tc.Spec.Scheduler.SetDefaults() if IsOpenShiftPlatform() { - if tc.Spec.Platforms.OpenShift.PipelinesAsCode == nil { + // PAC may appear under spec.platforms.kubernetes if the mutating webhook ran without + // PLATFORM=openshift (e.g. wrong image/order) or from older releases. Move it to + // spec.platforms.openshift so the stored TektonConfig matches the OpenShift operator. + if tc.Spec.Platforms.Kubernetes.PipelinesAsCode != nil { + if tc.Spec.Platforms.OpenShift.PipelinesAsCode == nil { + p := *tc.Spec.Platforms.Kubernetes.PipelinesAsCode + tc.Spec.Platforms.OpenShift.PipelinesAsCode = &p + } + tc.Spec.Platforms.Kubernetes.PipelinesAsCode = nil + } + + if tc.Spec.Platforms.OpenShift.PipelinesAsCode != nil { + tc.Spec.Addon.EnablePAC = nil + } else { tc.Spec.Platforms.OpenShift.PipelinesAsCode = &PipelinesAsCode{ Enable: ptr.Bool(true), PACSettings: PACSettings{ Settings: map[string]string{}, }, } - } else { - tc.Spec.Addon.EnablePAC = nil } - // check if PAC is disabled through addon before enabling through OpenShiftPipelinesAsCode + // check if PAC is disabled through addon before enabling through OpenShift PipelinesAsCode if tc.Spec.Addon.EnablePAC != nil && !*tc.Spec.Addon.EnablePAC { - tc.Spec.Platforms.OpenShift.PipelinesAsCode.Enable = ptr.Bool(false) - tc.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.Settings = nil + if tc.Spec.Platforms.OpenShift.PipelinesAsCode != nil { + tc.Spec.Platforms.OpenShift.PipelinesAsCode.Enable = ptr.Bool(false) + tc.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.Settings = nil + } } - // pac defaulting - if *tc.Spec.Platforms.OpenShift.PipelinesAsCode.Enable { + if p := tc.Spec.Platforms.OpenShift.PipelinesAsCode; p != nil && p.Enable != nil && *p.Enable { logger := logging.FromContext(ctx) - tc.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.setPACDefaults(logger) + p.PACSettings.setPACDefaults(logger) } // SCC defaulting @@ -69,8 +81,28 @@ func (tc *TektonConfig) SetDefaults(ctx context.Context) { setAddonDefaults(&tc.Spec.Addon) } else { - tc.Spec.Addon = Addon{} - tc.Spec.Platforms.OpenShift = OpenShift{} + // Kubernetes Platform + if tc.Spec.Platforms.Kubernetes.PipelinesAsCode == nil { + tc.Spec.Platforms.Kubernetes.PipelinesAsCode = &PipelinesAsCode{ + Enable: ptr.Bool(true), + PACSettings: PACSettings{ + Settings: map[string]string{}, + }, + } + } else { + tc.Spec.Addon.EnablePAC = nil + } + + if tc.Spec.Addon.EnablePAC != nil && !*tc.Spec.Addon.EnablePAC { + tc.Spec.Platforms.Kubernetes.PipelinesAsCode.Enable = ptr.Bool(false) + tc.Spec.Platforms.Kubernetes.PipelinesAsCode.PACSettings.Settings = nil + } + + if *tc.Spec.Platforms.Kubernetes.PipelinesAsCode.Enable { + logger := logging.FromContext(ctx) + tc.Spec.Platforms.Kubernetes.PipelinesAsCode.PACSettings.setPACDefaults(logger) + } + setAddonDefaults(&tc.Spec.Addon) } // earlier pruner was disabled with empty schedule or empty resources diff --git a/pkg/apis/operator/v1alpha1/tektonconfig_types.go b/pkg/apis/operator/v1alpha1/tektonconfig_types.go index 9d9129cb62..6083241356 100644 --- a/pkg/apis/operator/v1alpha1/tektonconfig_types.go +++ b/pkg/apis/operator/v1alpha1/tektonconfig_types.go @@ -130,6 +130,16 @@ type TektonConfigSpec struct { TargetNamespaceMetadata *NamespaceMetadata `json:"targetNamespaceMetadata,omitempty"` } +// PipelinesAsCodeForCurrentPlatform returns the PipelinesAsCode block for the operator build +// (OpenShift vs Kubernetes, see PLATFORM env). TektonConfig.Validate rejects using the other +// platform's spec.platforms subtree. +func (s *TektonConfigSpec) PipelinesAsCodeForCurrentPlatform() *PipelinesAsCode { + if IsOpenShiftPlatform() { + return s.Platforms.OpenShift.PipelinesAsCode + } + return s.Platforms.Kubernetes.PipelinesAsCode +} + // TektonConfigStatus defines the observed state of TektonConfig type TektonConfigStatus struct { duckv1.Status `json:",inline"` @@ -192,4 +202,7 @@ type Platforms struct { // OpenShift allows configuring openshift specific components and configurations // +optional OpenShift OpenShift `json:"openshift,omitempty"` + // Kubernetes allows configuring kubernetes specific components and configurations + // +optional + Kubernetes Kubernetes `json:"kubernetes,omitempty"` } diff --git a/pkg/apis/operator/v1alpha1/tektonconfig_validation.go b/pkg/apis/operator/v1alpha1/tektonconfig_validation.go index 6788847662..d725ef5bf7 100644 --- a/pkg/apis/operator/v1alpha1/tektonconfig_validation.go +++ b/pkg/apis/operator/v1alpha1/tektonconfig_validation.go @@ -56,9 +56,29 @@ func (tc *TektonConfig) Validate(ctx context.Context) (errs *apis.FieldError) { } } + // One platform subtree per operator build (PLATFORM=openshift vs unset/other). Reject before PAC + // validation so clients get a single admission error (e.g. oc patch / oc edit), not stacked messages. + if !IsOpenShiftPlatform() && isOpenShiftPlatformsSectionSet(tc.Spec.Platforms.OpenShift) { + return errs.Also(apis.ErrGeneric( + "this cluster runs the Kubernetes Tekton Operator; configure Pipelines as Code and SCC-related settings only under spec.platforms.kubernetes. "+ + "Remove spec.platforms.openshift (including pipelinesAsCode and scc). "+ + "Do not set both spec.platforms.openshift.pipelinesAsCode and spec.platforms.kubernetes.pipelinesAsCode.", + "spec.platforms", + )) + } + if IsOpenShiftPlatform() && isKubernetesPlatformsSectionSet(tc.Spec.Platforms.Kubernetes) { + return errs.Also(apis.ErrGeneric( + "this cluster runs the OpenShift Tekton Operator; configure Pipelines as Code only under spec.platforms.openshift. "+ + "Remove spec.platforms.kubernetes.pipelinesAsCode.", + "spec.platforms", + )) + } + + logger := logging.FromContext(ctx) if IsOpenShiftPlatform() && tc.Spec.Platforms.OpenShift.PipelinesAsCode != nil { - logger := logging.FromContext(ctx) errs = errs.Also(tc.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.validate(logger, "spec.platforms.openshift.pipelinesAsCode")) + } else if !IsOpenShiftPlatform() && tc.Spec.Platforms.Kubernetes.PipelinesAsCode != nil { + errs = errs.Also(tc.Spec.Platforms.Kubernetes.PipelinesAsCode.PACSettings.validate(logger, "spec.platforms.kubernetes.pipelinesAsCode")) } // validate SCC config @@ -179,6 +199,14 @@ func isValueInArray(arr []string, key string) bool { return false } +func isOpenShiftPlatformsSectionSet(o OpenShift) bool { + return o.PipelinesAsCode != nil || o.SCC != nil +} + +func isKubernetesPlatformsSectionSet(k Kubernetes) bool { + return k.PipelinesAsCode != nil +} + func verifySCCExists(ctx context.Context, sccName string) error { securityClient := common.GetSecurityClient(ctx) _, err := securityClient.SecurityV1().SecurityContextConstraints().Get(ctx, sccName, metav1.GetOptions{}) diff --git a/pkg/apis/operator/v1alpha1/tektonconfig_validation_test.go b/pkg/apis/operator/v1alpha1/tektonconfig_validation_test.go index c7792ba614..03bcd7c702 100644 --- a/pkg/apis/operator/v1alpha1/tektonconfig_validation_test.go +++ b/pkg/apis/operator/v1alpha1/tektonconfig_validation_test.go @@ -85,6 +85,52 @@ func Test_ValidateTektonConfig_InvalidProfile(t *testing.T) { assert.Equal(t, "invalid value: test: spec.profile", err.Error()) } +func Test_ValidateTektonConfig_OpenShiftPlatformsOnKubernetes(t *testing.T) { + t.Setenv("PLATFORM", "") + tc := &TektonConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: ConfigResourceName, + }, + Spec: TektonConfigSpec{ + CommonSpec: CommonSpec{ + TargetNamespace: "namespace", + }, + Pruner: Prune{Disabled: true}, + Platforms: Platforms{ + OpenShift: OpenShift{ + PipelinesAsCode: &PipelinesAsCode{Enable: ptr.Bool(true)}, + }, + }, + }, + } + + err := tc.Validate(context.TODO()) + assert.Assert(t, err != nil) +} + +func Test_ValidateTektonConfig_KubernetesPlatformsOnOpenShift(t *testing.T) { + t.Setenv("PLATFORM", "openshift") + tc := &TektonConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: ConfigResourceName, + }, + Spec: TektonConfigSpec{ + CommonSpec: CommonSpec{ + TargetNamespace: "namespace", + }, + Pruner: Prune{Disabled: true}, + Platforms: Platforms{ + Kubernetes: Kubernetes{ + PipelinesAsCode: &PipelinesAsCode{Enable: ptr.Bool(true)}, + }, + }, + }, + } + + err := tc.Validate(context.TODO()) + assert.Assert(t, err != nil) +} + func Test_ValidateTektonConfig_InvalidPruningResource(t *testing.T) { tc := &TektonConfig{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go index b453f23dad..873d2daf4a 100644 --- a/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/operator/v1alpha1/zz_generated.deepcopy.go @@ -441,6 +441,27 @@ func (in *Hub) DeepCopy() *Hub { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Kubernetes) DeepCopyInto(out *Kubernetes) { + *out = *in + if in.PipelinesAsCode != nil { + in, out := &in.PipelinesAsCode, &out.PipelinesAsCode + *out = new(PipelinesAsCode) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Kubernetes. +func (in *Kubernetes) DeepCopy() *Kubernetes { + if in == nil { + return nil + } + out := new(Kubernetes) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LokiStackProperties) DeepCopyInto(out *LokiStackProperties) { *out = *in @@ -1098,6 +1119,7 @@ func (in *PipelinesAsCode) DeepCopy() *PipelinesAsCode { func (in *Platforms) DeepCopyInto(out *Platforms) { *out = *in in.OpenShift.DeepCopyInto(&out.OpenShift) + in.Kubernetes.DeepCopyInto(&out.Kubernetes) return } diff --git a/pkg/reconciler/common/common.go b/pkg/reconciler/common/common.go index 4ec5ede63a..a81452dc2a 100644 --- a/pkg/reconciler/common/common.go +++ b/pkg/reconciler/common/common.go @@ -27,6 +27,11 @@ import ( ) const ( + // PipelinesAsCodeManifestDir is the kodata subdirectory for PAC release manifests (not under tekton-addon). + PipelinesAsCodeManifestDir = "pipelines-as-code" + // PipelinesAsCodeTemplatesDir is the kodata subdirectory for PAC PipelineRun templates embedded as ConfigMaps. + PipelinesAsCodeTemplatesDir = "pipelines-as-code-templates" + PipelineNotReady = "tekton-pipelines not ready" PipelineNotFound = "tekton-pipelines not installed" TriggerNotReady = "tekton-triggers not ready" diff --git a/pkg/reconciler/common/initcontroller.go b/pkg/reconciler/common/initcontroller.go index a5dff82504..325c6e274a 100644 --- a/pkg/reconciler/common/initcontroller.go +++ b/pkg/reconciler/common/initcontroller.go @@ -118,7 +118,7 @@ func (ctrl Controller) fetchSourceManifests(ctx context.Context, opts PayloadOpt var results v1alpha1.TektonResult return AppendTarget(ctx, ctrl.Manifest, &results) case "pipelines-as-code": - pacLocation := filepath.Join(os.Getenv(KoEnvKey), "tekton-addon", "pipelines-as-code") + pacLocation := filepath.Join(os.Getenv(KoEnvKey), PipelinesAsCodeManifestDir) return AppendManifest(ctrl.Manifest, pacLocation) case "manual-approval-gate": var mag v1alpha1.ManualApprovalGate diff --git a/pkg/reconciler/common/releases.go b/pkg/reconciler/common/releases.go index bd2d076a13..c7b9748fdc 100644 --- a/pkg/reconciler/common/releases.go +++ b/pkg/reconciler/common/releases.go @@ -100,6 +100,8 @@ func ComponentDir(instance v1alpha1.TektonComponent) string { return filepath.Join(koDataDir, "tekton-chains") case *v1alpha1.ManualApprovalGate: return filepath.Join(koDataDir, "manual-approval-gate") + case *v1alpha1.OpenShiftPipelinesAsCode: + return filepath.Join(koDataDir, PipelinesAsCodeManifestDir) case *v1alpha1.TektonPruner: // Event-based pruner uses "pruner" directory (not "tekton-pruner") // to avoid conflicts with job-based pruner in "tekton-pruner" directory diff --git a/pkg/reconciler/common/releases_test.go b/pkg/reconciler/common/releases_test.go index d265dc7eb8..a2090fce36 100644 --- a/pkg/reconciler/common/releases_test.go +++ b/pkg/reconciler/common/releases_test.go @@ -17,6 +17,7 @@ limitations under the License. package common import ( + "path/filepath" "testing" mf "github.com/manifestival/manifestival" @@ -28,6 +29,7 @@ const ( VERSION = "0.15.2" PRUNER_VERSION = "0.3.5" CHAINS_VERSION = "0.26.2" + PAC_VERSION = "0.2.0" ) func TestGetLatestRelease(t *testing.T) { @@ -42,6 +44,9 @@ func TestGetLatestRelease(t *testing.T) { chainsVersion := latestRelease(&v1alpha1.TektonChain{}) util.AssertEqual(t, chainsVersion, CHAINS_VERSION) + + pacVersion := latestRelease(&v1alpha1.OpenShiftPipelinesAsCode{}) + util.AssertEqual(t, pacVersion, PAC_VERSION) } func TestListReleases(t *testing.T) { @@ -64,6 +69,20 @@ func TestListReleases(t *testing.T) { version, err = allReleases(&v1alpha1.TektonChain{}) util.AssertEqual(t, err, nil) util.AssertDeepEqual(t, version, expectedChainsVersions) + + // OpenShift Pipelines as Code (kodata/pipelines-as-code) + expectedPACVersions := []string{"0.2.0", "0.1.0"} + version, err = allReleases(&v1alpha1.OpenShiftPipelinesAsCode{}) + util.AssertEqual(t, err, nil) + util.AssertDeepEqual(t, version, expectedPACVersions) +} + +func TestComponentDir_OpenShiftPipelinesAsCode(t *testing.T) { + koPath := "testdata/kodata" + t.Setenv(KoEnvKey, koPath) + want := filepath.Join(koPath, PipelinesAsCodeManifestDir) + got := ComponentDir(&v1alpha1.OpenShiftPipelinesAsCode{}) + util.AssertEqual(t, got, want) } func TestAppendManifest(t *testing.T) { diff --git a/pkg/reconciler/common/testdata/kodata/pipelines-as-code/0.1.0/dummy.yaml b/pkg/reconciler/common/testdata/kodata/pipelines-as-code/0.1.0/dummy.yaml new file mode 100644 index 0000000000..9517f36448 --- /dev/null +++ b/pkg/reconciler/common/testdata/kodata/pipelines-as-code/0.1.0/dummy.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: pac-test-010 diff --git a/pkg/reconciler/common/testdata/kodata/pipelines-as-code/0.2.0/dummy.yaml b/pkg/reconciler/common/testdata/kodata/pipelines-as-code/0.2.0/dummy.yaml new file mode 100644 index 0000000000..b77e5d1610 --- /dev/null +++ b/pkg/reconciler/common/testdata/kodata/pipelines-as-code/0.2.0/dummy.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: pac-test-020 diff --git a/pkg/reconciler/kubernetes/kubernetesplatform/config.go b/pkg/reconciler/kubernetes/kubernetesplatform/config.go index bdc3da546d..d7fb4b9814 100644 --- a/pkg/reconciler/kubernetes/kubernetesplatform/config.go +++ b/pkg/reconciler/kubernetes/kubernetesplatform/config.go @@ -18,6 +18,7 @@ package kubernetesplatform import ( k8sManualApprovalGate "github.com/tektoncd/operator/pkg/reconciler/kubernetes/manualapprovalgate" + k8sPipelinesAsCode "github.com/tektoncd/operator/pkg/reconciler/kubernetes/pipelinesascode" k8sChain "github.com/tektoncd/operator/pkg/reconciler/kubernetes/tektonchain" k8sConfig "github.com/tektoncd/operator/pkg/reconciler/kubernetes/tektonconfig" k8sDashboard "github.com/tektoncd/operator/pkg/reconciler/kubernetes/tektondashboard" @@ -82,5 +83,9 @@ var ( ControllerTektonResults: injection.NamedControllerConstructor{ Name: string(ControllerTektonResults), ControllerConstructor: k8sResult.NewController}, + platform.ControllerOpenShiftPipelinesAsCode: injection.NamedControllerConstructor{ + Name: string(platform.ControllerOpenShiftPipelinesAsCode), + ControllerConstructor: k8sPipelinesAsCode.NewController, + }, } ) diff --git a/pkg/reconciler/kubernetes/pipelinesascode/controller.go b/pkg/reconciler/kubernetes/pipelinesascode/controller.go new file mode 100644 index 0000000000..591e3ab263 --- /dev/null +++ b/pkg/reconciler/kubernetes/pipelinesascode/controller.go @@ -0,0 +1,31 @@ +/* +Copyright 2025 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pipelinesascode + +import ( + "context" + + pacctrl "github.com/tektoncd/operator/pkg/reconciler/openshift/openshiftpipelinesascode" + "knative.dev/pkg/configmap" + "knative.dev/pkg/controller" +) + +// NewController initializes the controller and is called by the generated code +// Registers eventhandlers to enqueue events +func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl { + return pacctrl.NewExtendedController(NewKubernetesExtension)(ctx, cmw) +} diff --git a/pkg/reconciler/kubernetes/pipelinesascode/kubernetes_extension.go b/pkg/reconciler/kubernetes/pipelinesascode/kubernetes_extension.go new file mode 100644 index 0000000000..bf7181a8ae --- /dev/null +++ b/pkg/reconciler/kubernetes/pipelinesascode/kubernetes_extension.go @@ -0,0 +1,103 @@ +/* +Copyright 2026 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pipelinesascode + +import ( + "context" + + mf "github.com/manifestival/manifestival" + "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1" + operatorclient "github.com/tektoncd/operator/pkg/client/injection/client" + "github.com/tektoncd/operator/pkg/reconciler/common" + "github.com/tektoncd/operator/pkg/reconciler/kubernetes/tektoninstallerset/client" + pacctrl "github.com/tektoncd/operator/pkg/reconciler/openshift/openshiftpipelinesascode" + "k8s.io/client-go/kubernetes" + kubeclient "knative.dev/pkg/client/injection/kube/client" + "knative.dev/pkg/logging" +) + +// kubernetesExtension implements common.Extension for the Kubernetes operator: PipelineRun +// template PostSet and webhook owner ref. It does not load the full PAC +// manifest or sync OpenShift Routes (see openshift/openshiftpipelinesascode/extension.go). +type kubernetesExtension struct { + installerSetClient *client.InstallerSetClient + pipelineRunTemplates *mf.Manifest + kubeClientSet kubernetes.Interface +} + +func (e kubernetesExtension) Transformers(comp v1alpha1.TektonComponent) []mf.Transformer { + return []mf.Transformer{ + pacctrl.InjectNamespaceOwnerForPACWebhook(e.kubeClientSet, comp.GetSpec().GetTargetNamespace()), + } +} + +func (e kubernetesExtension) PreReconcile(context.Context, v1alpha1.TektonComponent) error { + return nil +} + +func (e kubernetesExtension) PostReconcile(ctx context.Context, comp v1alpha1.TektonComponent) error { + logger := logging.FromContext(ctx) + if err := e.installerSetClient.PostSet(ctx, comp, e.pipelineRunTemplates, kubernetesTemplateFilter()); err != nil { + logger.Error("failed post set creation: ", err) + return err + } + return nil +} + +func (e kubernetesExtension) Finalize(context.Context, v1alpha1.TektonComponent) error { + return nil +} + +func (e kubernetesExtension) GetPlatformData() string { + return "" +} + +// kubernetesTemplateFilter applies templates into the component target namespace (unlike the +// OpenShift extension, which uses the openshift namespace for templates). +func kubernetesTemplateFilter() client.FilterAndTransform { + return func(ctx context.Context, manifest *mf.Manifest, comp v1alpha1.TektonComponent) (*mf.Manifest, error) { + ns := comp.GetSpec().GetTargetNamespace() + prTemplates, err := manifest.Transform(mf.InjectNamespace(ns)) + if err != nil { + return nil, err + } + return &prTemplates, nil + } +} + +// NewKubernetesExtension builds the extension passed to pacctrl.NewExtendedController +// (shared controller wiring for the OpenShiftPipelinesAsCode CRD). +func NewKubernetesExtension(ctx context.Context) common.Extension { + logger := logging.FromContext(ctx) + + operatorVer, err := common.OperatorVersion(ctx) + if err != nil { + logger.Fatal(err) + } + + prTemplates, err := pacctrl.FetchPipelineRunTemplates() + if err != nil { + logger.Fatalf("failed to fetch pipelineRun templates: %v", err) + } + + tisClient := operatorclient.Get(ctx).OperatorV1alpha1().TektonInstallerSets() + return kubernetesExtension{ + installerSetClient: client.NewInstallerSetClient(tisClient, operatorVer, "pipelines-as-code-ext", v1alpha1.KindOpenShiftPipelinesAsCode, nil), + pipelineRunTemplates: prTemplates, + kubeClientSet: kubeclient.Get(ctx), + } +} diff --git a/pkg/reconciler/kubernetes/tektonconfig/extension.go b/pkg/reconciler/kubernetes/tektonconfig/extension.go index a677c43933..1d26194efa 100644 --- a/pkg/reconciler/kubernetes/tektonconfig/extension.go +++ b/pkg/reconciler/kubernetes/tektonconfig/extension.go @@ -26,6 +26,7 @@ import ( operatorclient "github.com/tektoncd/operator/pkg/client/injection/client" "github.com/tektoncd/operator/pkg/reconciler/common" "github.com/tektoncd/operator/pkg/reconciler/kubernetes/tektonconfig/extension" + pac "github.com/tektoncd/operator/pkg/reconciler/shared/tektonconfig/pipelinesascode" ) func KubernetesExtension(ctx context.Context) common.Extension { @@ -58,6 +59,18 @@ func (oe kubernetesExtension) PostReconcile(ctx context.Context, comp v1alpha1.T return extension.EnsureTektonDashboardCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().TektonDashboards()) } + pacSpec := configInstance.Spec.PipelinesAsCodeForCurrentPlatform() + if pacSpec != nil && pacSpec.Enable != nil && *pacSpec.Enable { + if _, err := pac.EnsureOpenShiftPipelinesAsCodeExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), configInstance, configInstance.Status.Version); err != nil { + configInstance.Status.MarkComponentNotReady(fmt.Sprintf("OpenShiftPipelinesAsCode: %s", err.Error())) + return v1alpha1.REQUEUE_EVENT_AFTER + } + } else { + if err := pac.EnsureOpenShiftPipelinesAsCodeCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes()); err != nil { + return err + } + } + return nil } func (oe kubernetesExtension) Finalize(ctx context.Context, comp v1alpha1.TektonComponent) error { @@ -65,6 +78,14 @@ func (oe kubernetesExtension) Finalize(ctx context.Context, comp v1alpha1.Tekton if configInstance.Spec.Profile == v1alpha1.ProfileAll { return extension.EnsureTektonDashboardCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().TektonDashboards()) } + + pacSpec := configInstance.Spec.PipelinesAsCodeForCurrentPlatform() + if pacSpec != nil && pacSpec.Enable != nil && *pacSpec.Enable { + if err := pac.EnsureOpenShiftPipelinesAsCodeCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes()); err != nil { + return err + } + } + return nil } diff --git a/pkg/reconciler/openshift/openshiftpipelinesascode/extension.go b/pkg/reconciler/openshift/openshiftpipelinesascode/extension.go index 975e83c66e..8875e06466 100644 --- a/pkg/reconciler/openshift/openshiftpipelinesascode/extension.go +++ b/pkg/reconciler/openshift/openshiftpipelinesascode/extension.go @@ -52,12 +52,12 @@ func OpenShiftExtension(ctx context.Context) common.Extension { logger.Fatalw("Error creating initial manifest", zap.Error(err)) } - pacLocation := filepath.Join(os.Getenv(common.KoEnvKey), "tekton-addon", "pipelines-as-code") + pacLocation := filepath.Join(os.Getenv(common.KoEnvKey), common.PipelinesAsCodeManifestDir) if err := common.AppendManifest(&pacManifest, pacLocation); err != nil { logger.Fatalf("failed to fetch PAC manifest: %v", err) } - prTemplates, err := fetchPipelineRunTemplates() + prTemplates, err := FetchPipelineRunTemplates() if err != nil { logger.Fatalf("failed to fetch pipelineRun templates: %v", err) } @@ -87,7 +87,7 @@ type openshiftExtension struct { func (oe openshiftExtension) Transformers(comp v1alpha1.TektonComponent) []mf.Transformer { return []mf.Transformer{ - injectNamespaceOwnerForPACWebhook(oe.kubeClientSet, comp.GetSpec().GetTargetNamespace()), + InjectNamespaceOwnerForPACWebhook(oe.kubeClientSet, comp.GetSpec().GetTargetNamespace()), } } func (oe openshiftExtension) PreReconcile(context.Context, v1alpha1.TektonComponent) error { @@ -125,9 +125,9 @@ func extFilterAndTransform() client.FilterAndTransform { } } -// injectNamespaceOwnerForPACWebhook adds namespace ownerReference to PAC webhook +// InjectNamespaceOwnerForPACWebhook adds namespace ownerReference to PAC webhook // to ensure proper cleanup when namespace is deleted (SRVKP-8901) -func injectNamespaceOwnerForPACWebhook(kubeClient kubernetes.Interface, targetNamespace string) mf.Transformer { +func InjectNamespaceOwnerForPACWebhook(kubeClient kubernetes.Interface, targetNamespace string) mf.Transformer { return func(u *unstructured.Unstructured) error { kind := u.GetKind() name := u.GetName() diff --git a/pkg/reconciler/openshift/openshiftpipelinesascode/pipelinerun_templates.go b/pkg/reconciler/openshift/openshiftpipelinesascode/pipelinerun_templates.go index 4eb53c1c5b..ee9a6cfa85 100644 --- a/pkg/reconciler/openshift/openshiftpipelinesascode/pipelinerun_templates.go +++ b/pkg/reconciler/openshift/openshiftpipelinesascode/pipelinerun_templates.go @@ -43,10 +43,11 @@ metadata: data: template: ""` -func fetchPipelineRunTemplates() (*mf.Manifest, error) { +// FetchPipelineRunTemplates loads kodata PipelineRun templates into a manifest (shared by OpenShift and Kubernetes extensions). +func FetchPipelineRunTemplates() (*mf.Manifest, error) { prManifests := mf.Manifest{} koDataDir := os.Getenv(common.KoEnvKey) - templateLocation := filepath.Join(koDataDir, "tekton-addon", "pipelines-as-code-templates") + templateLocation := filepath.Join(koDataDir, common.PipelinesAsCodeTemplatesDir) if err := common.AppendManifest(&prManifests, templateLocation); err != nil { return nil, err } diff --git a/pkg/reconciler/openshift/openshiftpipelinesascode/testdata/test-expected-additional-pac-dep.yaml b/pkg/reconciler/openshift/openshiftpipelinesascode/testdata/test-expected-additional-pac-dep.yaml index ecd27f15af..a1cdb29561 100644 --- a/pkg/reconciler/openshift/openshiftpipelinesascode/testdata/test-expected-additional-pac-dep.yaml +++ b/pkg/reconciler/openshift/openshiftpipelinesascode/testdata/test-expected-additional-pac-dep.yaml @@ -31,7 +31,7 @@ spec: serviceAccountName: pipelines-as-code-controller containers: - name: test-pac-controller - image: "ghcr.io/openshift-pipelines/pipelines-as-code-controller:v0.23.0" + image: "ghcr.io/openshift-pipelines/pipelines-as-code/pipelines-as-code-controller:stable" imagePullPolicy: Always ports: - name: api diff --git a/pkg/reconciler/openshift/openshiftpipelinesascode/testdata/test-filter-manifest.yaml b/pkg/reconciler/openshift/openshiftpipelinesascode/testdata/test-filter-manifest.yaml index 92ca18bd05..c66d4a0a1e 100644 --- a/pkg/reconciler/openshift/openshiftpipelinesascode/testdata/test-filter-manifest.yaml +++ b/pkg/reconciler/openshift/openshiftpipelinesascode/testdata/test-filter-manifest.yaml @@ -466,7 +466,7 @@ spec: serviceAccountName: pipelines-as-code-controller containers: - name: pac-controller - image: "ghcr.io/openshift-pipelines/pipelines-as-code-controller:v0.23.0" + image: "ghcr.io/openshift-pipelines/pipelines-as-code/pipelines-as-code-controller:stable" imagePullPolicy: Always ports: - name: api @@ -615,7 +615,7 @@ spec: serviceAccountName: pipelines-as-code-watcher containers: - name: pac-watcher - image: "ghcr.io/openshift-pipelines/pipelines-as-code-watcher:v0.23.0" + image: "ghcr.io/openshift-pipelines/pipelines-as-code/pipelines-as-code-watcher:stable" imagePullPolicy: Always env: - name: CONFIG_LOGGING_NAME diff --git a/pkg/reconciler/openshift/openshiftpipelinesascode/transform.go b/pkg/reconciler/openshift/openshiftpipelinesascode/transform.go index 1d930e894d..88afe4e72c 100644 --- a/pkg/reconciler/openshift/openshiftpipelinesascode/transform.go +++ b/pkg/reconciler/openshift/openshiftpipelinesascode/transform.go @@ -41,6 +41,8 @@ const ( additionalPACControllerNameSuffix = "-pac-controller" ) +var isOpenShiftPlatform = v1alpha1.IsOpenShiftPlatform + func filterAndTransform(extension common.Extension) client.FilterAndTransform { return func(ctx context.Context, manifest *mf.Manifest, comp v1alpha1.TektonComponent) (*mf.Manifest, error) { pac := comp.(*v1alpha1.OpenShiftPipelinesAsCode) @@ -51,15 +53,17 @@ func filterAndTransform(extension common.Extension) client.FilterAndTransform { imagesRaw := common.ToLowerCaseKeys(common.ImagesFromEnv(common.PacImagePrefix)) images := common.ImageRegistryDomainOverride(imagesRaw) - // Run transformers tfs := []mf.Transformer{ common.InjectOperandNameLabelOverwriteExisting(openshift.OperandOpenShiftPipelineAsCode), common.DeploymentImages(images), common.DeploymentEnvVarKubernetesMinVersion(), common.AddConfiguration(pac.Spec.Config), - occommon.ApplyCABundlesToDeployment, common.CopyConfigMap(pipelinesAsCodeCM, pac.Spec.Settings), - occommon.UpdateServiceMonitorTargetNamespace(pac.Spec.TargetNamespace), + } + + if isOpenShiftPlatform() { + tfs = append(tfs, occommon.ApplyCABundlesToDeployment) + tfs = append(tfs, occommon.UpdateServiceMonitorTargetNamespace(pac.Spec.TargetNamespace)) } allTfs := append(tfs, extension.Transformers(pac)...) @@ -89,15 +93,18 @@ func additionalControllerTransform(extension common.Extension, name string) clie common.InjectOperandNameLabelOverwriteExisting(openshift.OperandOpenShiftPipelineAsCode), common.DeploymentImages(images), common.AddConfiguration(pac.Spec.Config), - occommon.ApplyCABundlesToDeployment, - occommon.UpdateServiceMonitorTargetNamespace(pac.Spec.TargetNamespace), updateAdditionControllerDeployment(additionalPACControllerConfig, name), updateAdditionControllerService(name), updateAdditionControllerConfigMap(additionalPACControllerConfig), - updateAdditionControllerRoute(name), updateAdditionControllerServiceMonitor(name), } + if isOpenShiftPlatform() { + tfs = append(tfs, occommon.ApplyCABundlesToDeployment) + tfs = append(tfs, occommon.UpdateServiceMonitorTargetNamespace(pac.Spec.TargetNamespace)) + tfs = append(tfs, updateAdditionControllerRoute(name)) + } + allTfs := append(tfs, extension.Transformers(pac)...) if err := common.Transform(ctx, manifest, pac, allTfs...); err != nil { return &mf.Manifest{}, err @@ -131,7 +138,8 @@ func filterAdditionalControllerManifest(manifest mf.Manifest) mf.Manifest { serviceMonitorManifest := manifest.Filter(mf.All(mf.ByName("pipelines-as-code-controller-monitor"), mf.ByKind("ServiceMonitor"))) filteredManifest := mf.Manifest{} - filteredManifest = filteredManifest.Append(cmManifest, deploymentManifest, serviceManifest, serviceMonitorManifest, routeManifest) + filteredManifest = filteredManifest.Append(cmManifest, deploymentManifest, serviceManifest, routeManifest, serviceMonitorManifest) + return filteredManifest } diff --git a/pkg/reconciler/openshift/openshiftpipelinesascode/transform_test.go b/pkg/reconciler/openshift/openshiftpipelinesascode/transform_test.go index fc2cace06b..a02a2fe04c 100644 --- a/pkg/reconciler/openshift/openshiftpipelinesascode/transform_test.go +++ b/pkg/reconciler/openshift/openshiftpipelinesascode/transform_test.go @@ -36,11 +36,14 @@ func TestFilterAdditionalControllerManifest(t *testing.T) { manifest, err := mf.ManifestFrom(mf.Recursive(testData)) assert.NilError(t, err) - filteredManifest := filterAdditionalControllerManifest(manifest) - assert.DeepEqual(t, len(filteredManifest.Resources()), 5) + filtered := filterAdditionalControllerManifest(manifest) + assert.DeepEqual(t, len(filtered.Resources()), 5) - deployment := filteredManifest.Filter(mf.All(mf.ByKind("Deployment"))) - assert.DeepEqual(t, deployment.Resources()[0].GetName(), "pipelines-as-code-controller") + routes := filtered.Filter(mf.All(mf.ByKind("Route"))) + assert.DeepEqual(t, len(routes.Resources()), 1) + + sms := filtered.Filter(mf.All(mf.ByKind("ServiceMonitor"))) + assert.DeepEqual(t, len(sms.Resources()), 1) } func TestUpdateAdditionControllerDeployment(t *testing.T) { diff --git a/pkg/reconciler/openshift/openshiftplatform/config.go b/pkg/reconciler/openshift/openshiftplatform/config.go index f61909a2b0..b2c491930a 100644 --- a/pkg/reconciler/openshift/openshiftplatform/config.go +++ b/pkg/reconciler/openshift/openshiftplatform/config.go @@ -36,9 +36,8 @@ import ( ) const ( - ControllerTektonAddon platform.ControllerName = "tektonaddon" - ControllerOpenShiftPipelinesAsCode platform.ControllerName = "openshiftpipelinesascode" - PlatformNameOpenShift string = "openshift" + ControllerTektonAddon platform.ControllerName = "tektonaddon" + PlatformNameOpenShift string = "openshift" ) var ( @@ -77,8 +76,8 @@ var ( Name: string(ControllerTektonAddon), ControllerConstructor: openshiftAddon.NewController, }, - ControllerOpenShiftPipelinesAsCode: injection.NamedControllerConstructor{ - Name: string(ControllerOpenShiftPipelinesAsCode), + platform.ControllerOpenShiftPipelinesAsCode: injection.NamedControllerConstructor{ + Name: string(platform.ControllerOpenShiftPipelinesAsCode), ControllerConstructor: openshiftpipelinesascode.NewController, }, // there is no openshift specific extension for TektonInstallerSet Reconciler (yet 🤓) diff --git a/pkg/reconciler/openshift/tektonconfig/extension.go b/pkg/reconciler/openshift/tektonconfig/extension.go index ee0abc28f9..8d848f9fc7 100644 --- a/pkg/reconciler/openshift/tektonconfig/extension.go +++ b/pkg/reconciler/openshift/tektonconfig/extension.go @@ -33,6 +33,7 @@ import ( occommon "github.com/tektoncd/operator/pkg/reconciler/openshift/common" "github.com/tektoncd/operator/pkg/reconciler/openshift/tektonconfig/extension" "github.com/tektoncd/operator/pkg/reconciler/shared/hash" + pac "github.com/tektoncd/operator/pkg/reconciler/shared/tektonconfig/pipelinesascode" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" nsV1 "k8s.io/client-go/informers/core/v1" @@ -188,14 +189,14 @@ func (oe openshiftExtension) PostReconcile(ctx context.Context, comp v1alpha1.Te } } - pac := configInstance.Spec.Platforms.OpenShift.PipelinesAsCode - if pac != nil && *pac.Enable { - if _, err := extension.EnsureOpenShiftPipelinesAsCodeExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), configInstance, oe.operatorVersion); err != nil { + pacSpec := configInstance.Spec.PipelinesAsCodeForCurrentPlatform() + if pacSpec != nil && pacSpec.Enable != nil && *pacSpec.Enable { + if _, err := pac.EnsureOpenShiftPipelinesAsCodeExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), configInstance, oe.operatorVersion); err != nil { configInstance.Status.MarkComponentNotReady(fmt.Sprintf("OpenShiftPipelinesAsCode: %s", err.Error())) return v1alpha1.REQUEUE_EVENT_AFTER } } else { - if err := extension.EnsureOpenShiftPipelinesAsCodeCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes()); err != nil { + if err := pac.EnsureOpenShiftPipelinesAsCodeCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes()); err != nil { return err } } @@ -230,8 +231,9 @@ func (oe openshiftExtension) Finalize(ctx context.Context, comp v1alpha1.TektonC return err } } - if configInstance.Spec.Platforms.OpenShift.PipelinesAsCode != nil && *configInstance.Spec.Platforms.OpenShift.PipelinesAsCode.Enable { - if err := extension.EnsureOpenShiftPipelinesAsCodeCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes()); err != nil { + pacSpec := configInstance.Spec.PipelinesAsCodeForCurrentPlatform() + if pacSpec != nil && pacSpec.Enable != nil && *pacSpec.Enable { + if err := pac.EnsureOpenShiftPipelinesAsCodeCRNotExists(ctx, oe.operatorClientSet.OperatorV1alpha1().OpenShiftPipelinesAsCodes()); err != nil { return err } } diff --git a/pkg/reconciler/platform/const.go b/pkg/reconciler/platform/const.go index d845882d5b..4dcaeb625c 100644 --- a/pkg/reconciler/platform/const.go +++ b/pkg/reconciler/platform/const.go @@ -30,6 +30,9 @@ const ( ControllerTektonScheduler ControllerName = "tektonscheduler" ControllerMulticlusterProxyAAE ControllerName = "tektonmulticlusterproxyaae" ControllerSyncerService ControllerName = "syncerservice" - EnvControllerNames string = "CONTROLLER_NAMES" - EnvSharedMainName string = "UNIQUE_PROCESS_NAME" + // ControllerOpenShiftPipelinesAsCode is the operand reconciler for OpenShiftPipelinesAsCode; + // the same name is used on Kubernetes and OpenShift so -controllers flags stay consistent. + ControllerOpenShiftPipelinesAsCode ControllerName = "openshiftpipelinesascode" + EnvControllerNames string = "CONTROLLER_NAMES" + EnvSharedMainName string = "UNIQUE_PROCESS_NAME" ) diff --git a/pkg/reconciler/openshift/tektonconfig/extension/pipelinesascode.go b/pkg/reconciler/shared/tektonconfig/pipelinesascode/pipelinesascode.go similarity index 74% rename from pkg/reconciler/openshift/tektonconfig/extension/pipelinesascode.go rename to pkg/reconciler/shared/tektonconfig/pipelinesascode/pipelinesascode.go index 81e581386c..e088b0ff74 100644 --- a/pkg/reconciler/openshift/tektonconfig/extension/pipelinesascode.go +++ b/pkg/reconciler/shared/tektonconfig/pipelinesascode/pipelinesascode.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package extension +package pac import ( "context" @@ -29,6 +29,17 @@ import ( "knative.dev/pkg/apis" ) +func pacSettingsFromTektonPAC(p *v1alpha1.PipelinesAsCode) v1alpha1.PACSettings { + if p == nil { + return v1alpha1.PACSettings{} + } + return v1alpha1.PACSettings{ + Settings: p.Settings, + AdditionalPACControllers: p.AdditionalPACControllers, + Options: p.Options, + } +} + func EnsureOpenShiftPipelinesAsCodeExists(ctx context.Context, clients op.OpenShiftPipelinesAsCodeInterface, config *v1alpha1.TektonConfig, operatorVersion string) (*v1alpha1.OpenShiftPipelinesAsCode, error) { opacCR, err := GetPAC(ctx, clients, v1alpha1.OpenShiftPipelinesAsCodeName) if err != nil { @@ -60,6 +71,8 @@ func EnsureOpenShiftPipelinesAsCodeExists(ctx context.Context, clients op.OpenSh func createOPAC(ctx context.Context, clients op.OpenShiftPipelinesAsCodeInterface, config *v1alpha1.TektonConfig, operatorVersion string) (*v1alpha1.OpenShiftPipelinesAsCode, error) { ownerRef := *metav1.NewControllerRef(config, config.GroupVersionKind()) + pacSettings := pacSettingsFromTektonPAC(config.Spec.PipelinesAsCodeForCurrentPlatform()) + opacCR := &v1alpha1.OpenShiftPipelinesAsCode{ ObjectMeta: metav1.ObjectMeta{ Name: v1alpha1.OpenShiftPipelinesAsCodeName, @@ -72,13 +85,11 @@ func createOPAC(ctx context.Context, clients op.OpenShiftPipelinesAsCodeInterfac CommonSpec: v1alpha1.CommonSpec{ TargetNamespace: config.Spec.TargetNamespace, }, - Config: config.Spec.Config, - PACSettings: v1alpha1.PACSettings{ - Settings: config.Spec.Platforms.OpenShift.PipelinesAsCode.Settings, - AdditionalPACControllers: config.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.AdditionalPACControllers, - }, + Config: config.Spec.Config, + PACSettings: pacSettings, }, } + if _, err := clients.Create(ctx, opacCR, metav1.CreateOptions{}); err != nil { return nil, err } @@ -92,10 +103,8 @@ func GetPAC(ctx context.Context, clients op.OpenShiftPipelinesAsCodeInterface, n func updateOPAC(ctx context.Context, opacCR *v1alpha1.OpenShiftPipelinesAsCode, config *v1alpha1.TektonConfig, clients op.OpenShiftPipelinesAsCodeInterface, operatorVersion string, ) (*v1alpha1.OpenShiftPipelinesAsCode, error) { - // if the pac spec is changed then update the instance updated := false - // initialize labels(map) object if opacCR.ObjectMeta.Labels == nil { opacCR.ObjectMeta.Labels = map[string]string{} } @@ -110,19 +119,21 @@ func updateOPAC(ctx context.Context, opacCR *v1alpha1.OpenShiftPipelinesAsCode, updated = true } - if !reflect.DeepEqual(opacCR.Spec.PACSettings.Settings, config.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.Settings) { - opacCR.Spec.PACSettings.Settings = config.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.Settings - updated = true - } + if p := config.Spec.PipelinesAsCodeForCurrentPlatform(); p != nil { + if !reflect.DeepEqual(opacCR.Spec.PACSettings.Settings, p.PACSettings.Settings) { + opacCR.Spec.PACSettings.Settings = p.PACSettings.Settings + updated = true + } - if !reflect.DeepEqual(opacCR.Spec.PACSettings.Options, config.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.Options) { - opacCR.Spec.PACSettings.Options = config.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.Options - updated = true - } + if !reflect.DeepEqual(opacCR.Spec.PACSettings.Options, p.PACSettings.Options) { + opacCR.Spec.PACSettings.Options = p.PACSettings.Options + updated = true + } - if !reflect.DeepEqual(opacCR.Spec.PACSettings.AdditionalPACControllers, config.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.AdditionalPACControllers) { - opacCR.Spec.PACSettings.AdditionalPACControllers = config.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.AdditionalPACControllers - updated = true + if !reflect.DeepEqual(opacCR.Spec.PACSettings.AdditionalPACControllers, p.PACSettings.AdditionalPACControllers) { + opacCR.Spec.PACSettings.AdditionalPACControllers = p.PACSettings.AdditionalPACControllers + updated = true + } } if opacCR.ObjectMeta.OwnerReferences == nil { @@ -148,7 +159,6 @@ func updateOPAC(ctx context.Context, opacCR *v1alpha1.OpenShiftPipelinesAsCode, return opacCR, nil } -// isOPACReady will check the status conditions of the OpenShiftPipelinesAsCode and return true if the OpenShiftPipelinesAsCode is ready. func isOPACReady(s *v1alpha1.OpenShiftPipelinesAsCode, err error) (bool, error) { if s.GetStatus() != nil && s.GetStatus().GetCondition(apis.ConditionReady) != nil { if strings.Contains(s.GetStatus().GetCondition(apis.ConditionReady).Message, v1alpha1.UpgradePending) { @@ -161,21 +171,15 @@ func isOPACReady(s *v1alpha1.OpenShiftPipelinesAsCode, err error) (bool, error) func EnsureOpenShiftPipelinesAsCodeCRNotExists(ctx context.Context, clients op.OpenShiftPipelinesAsCodeInterface) error { if _, err := GetPAC(ctx, clients, v1alpha1.OpenShiftPipelinesAsCodeName); err != nil { if apierrs.IsNotFound(err) { - // OpenShiftPipelinesAsCode CR is gone, hence return nil return nil } return err } - // if the Get was successful, try deleting the CR if err := clients.Delete(ctx, v1alpha1.OpenShiftPipelinesAsCodeName, metav1.DeleteOptions{}); err != nil { if apierrs.IsNotFound(err) { - // OpenShiftPipelinesAsCode CR is gone, hence return nil return nil } return fmt.Errorf("OpenShiftPipelinesAsCode %q failed to delete: %v", v1alpha1.OpenShiftPipelinesAsCodeName, err) } - // if the Delete API call was success, - // then return requeue_event - // so that in a subsequent reconcile call the absence of the CR is verified by one of the 2 checks above return v1alpha1.RECONCILE_AGAIN_ERR } diff --git a/pkg/reconciler/openshift/tektonconfig/extension/pipelinesascode_test.go b/pkg/reconciler/shared/tektonconfig/pipelinesascode/pipelinesascode_test.go similarity index 82% rename from pkg/reconciler/openshift/tektonconfig/extension/pipelinesascode_test.go rename to pkg/reconciler/shared/tektonconfig/pipelinesascode/pipelinesascode_test.go index ca3659828f..5343056a4e 100644 --- a/pkg/reconciler/openshift/tektonconfig/extension/pipelinesascode_test.go +++ b/pkg/reconciler/shared/tektonconfig/pipelinesascode/pipelinesascode_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package extension +package pac import ( "context" @@ -38,23 +38,17 @@ func TestEnsureOpenShiftPipelinesAsCodeExists(t *testing.T) { t.Setenv("PLATFORM", "openshift") tConfig.SetDefaults(ctx) - // first invocation should create instance as it is non-existent and return RECONCILE_AGAIN_ERR _, err := EnsureOpenShiftPipelinesAsCodeExists(ctx, c.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), tConfig, "v0.70.0") util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) - // during second invocation instance exists but waiting on dependencies (pipeline, triggers) - // hence returns DEPENDENCY_UPGRADE_PENDING_ERR _, err = EnsureOpenShiftPipelinesAsCodeExists(ctx, c.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), tConfig, "v0.70.0") util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) - // mark the instance ready markOPACReady(t, ctx, c.OperatorV1alpha1().OpenShiftPipelinesAsCodes()) - // next invocation should return nil error as the instance is ready _, err = EnsureOpenShiftPipelinesAsCodeExists(ctx, c.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), tConfig, "v0.70.0") util.AssertEqual(t, err, nil) - // test update propagation from tektonConfig tConfig.Spec.TargetNamespace = "foobar" _, err = EnsureOpenShiftPipelinesAsCodeExists(ctx, c.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), tConfig, "v0.70.0") util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) @@ -69,22 +63,17 @@ func TestEnsureOpenShiftPipelinesAsCodeCRNotExists(t *testing.T) { t.Setenv("PLATFORM", "openshift") - // when no instance exists, nil error is returned immediately err := EnsureOpenShiftPipelinesAsCodeCRNotExists(ctx, c.OperatorV1alpha1().OpenShiftPipelinesAsCodes()) util.AssertEqual(t, err, nil) - // create an instance for testing other cases tConfig := pipeline.GetTektonConfig() tConfig.SetDefaults(ctx) _, err = EnsureOpenShiftPipelinesAsCodeExists(ctx, c.OperatorV1alpha1().OpenShiftPipelinesAsCodes(), tConfig, "v0.70.0") util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) - // when an instance exists the first invoacation should make the delete API call and - // return RECONCILE_AGAI_ERROR. So that the deletion can be confirmed in a subsequent invocation err = EnsureOpenShiftPipelinesAsCodeCRNotExists(ctx, c.OperatorV1alpha1().OpenShiftPipelinesAsCodes()) util.AssertEqual(t, err, v1alpha1.RECONCILE_AGAIN_ERR) - // when the instance is completely removed from a cluster, the function should return nil error err = EnsureOpenShiftPipelinesAsCodeCRNotExists(ctx, c.OperatorV1alpha1().OpenShiftPipelinesAsCodes()) util.AssertEqual(t, err, nil) } diff --git a/pkg/reconciler/shared/tektonconfig/upgrade/pre_upgrade.go b/pkg/reconciler/shared/tektonconfig/upgrade/pre_upgrade.go index 712ff347bb..8baad4b7f6 100644 --- a/pkg/reconciler/shared/tektonconfig/upgrade/pre_upgrade.go +++ b/pkg/reconciler/shared/tektonconfig/upgrade/pre_upgrade.go @@ -178,21 +178,20 @@ func preUpgradePipelinesAsCodeArtifacts(ctx context.Context, logger *zap.Sugared return err } + pacSpec := tc.Spec.Platforms.OpenShift.PipelinesAsCode // Check if Pipelines as Code is enabled - if tc.Spec.Platforms.OpenShift.PipelinesAsCode == nil || - tc.Spec.Platforms.OpenShift.PipelinesAsCode.Enable == nil || - !*tc.Spec.Platforms.OpenShift.PipelinesAsCode.Enable { + if pacSpec == nil || pacSpec.Enable == nil || !*pacSpec.Enable { logger.Infof("Pipelines as Code is not enabled, skipping artifact upgrade") return nil } // Initialize settings if nil - if tc.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.Settings == nil { - tc.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.Settings = make(map[string]string) + if pacSpec.PACSettings.Settings == nil { + pacSpec.PACSettings.Settings = make(map[string]string) } // Fetch PAC settings - settings := tc.Spec.Platforms.OpenShift.PipelinesAsCode.PACSettings.Settings + settings := pacSpec.PACSettings.Settings // Set hub-catalog-type to artifacthub if not already set or if it's set to tektonhub if catalogType, exists := settings["hub-catalog-type"]; !exists || catalogType == "tektonhub" { diff --git a/test/e2e/common/00_tektonconfigdeployment_test.go b/test/e2e/common/00_tektonconfigdeployment_test.go index 0f894b9cbb..0aefc17dbe 100644 --- a/test/e2e/common/00_tektonconfigdeployment_test.go +++ b/test/e2e/common/00_tektonconfigdeployment_test.go @@ -54,6 +54,9 @@ var ( tektonConfigProfileKubernetes = map[string]TektonProfileResource{ v1alpha1.ProfileAll: { Deployments: []string{ + "pipelines-as-code-controller", + "pipelines-as-code-watcher", + "pipelines-as-code-webhook", "tekton-dashboard", "tekton-operator-proxy-webhook", pipelineControllerDeploymentName, @@ -64,6 +67,9 @@ var ( "tekton-triggers-webhook", }, ServiceAccounts: []string{ + "pipelines-as-code-controller", + "pipelines-as-code-watcher", + "pipelines-as-code-webhook", "tekton-dashboard", "tekton-operators-proxy-webhook", "tekton-pipelines-controller", @@ -77,6 +83,9 @@ var ( }, v1alpha1.ProfileBasic: { Deployments: []string{ + "pipelines-as-code-controller", + "pipelines-as-code-watcher", + "pipelines-as-code-webhook", "tekton-operator-proxy-webhook", pipelineControllerDeploymentName, "tekton-pipelines-remote-resolvers", @@ -86,6 +95,9 @@ var ( "tekton-triggers-webhook", }, ServiceAccounts: []string{ + "pipelines-as-code-controller", + "pipelines-as-code-watcher", + "pipelines-as-code-webhook", "tekton-operators-proxy-webhook", "tekton-pipelines-controller", "tekton-pipelines-resolvers", @@ -98,12 +110,18 @@ var ( }, v1alpha1.ProfileLite: { Deployments: []string{ + "pipelines-as-code-controller", + "pipelines-as-code-watcher", + "pipelines-as-code-webhook", "tekton-operator-proxy-webhook", pipelineControllerDeploymentName, "tekton-pipelines-remote-resolvers", "tekton-pipelines-webhook", }, ServiceAccounts: []string{ + "pipelines-as-code-controller", + "pipelines-as-code-watcher", + "pipelines-as-code-webhook", "tekton-operators-proxy-webhook", "tekton-pipelines-controller", "tekton-pipelines-resolvers", @@ -923,13 +941,8 @@ func (s *TektonConfigTestSuite) verifyPAC() { "pipelines-as-code-webhook", } - // get pac enabled status from TektonConfig resource - pacEnabled := false - if config.Spec.Platforms.OpenShift.PipelinesAsCode != nil && - config.Spec.Platforms.OpenShift.PipelinesAsCode.Enable != nil && - *config.Spec.Platforms.OpenShift.PipelinesAsCode.Enable { - pacEnabled = true - } + pacSpec := config.Spec.PipelinesAsCodeForCurrentPlatform() + pacEnabled := pacSpec != nil && pacSpec.Enable != nil && *pacSpec.Enable labelSelector := fmt.Sprintf("%s=OpenShiftPipelinesAsCode", v1alpha1.CreatedByKey) installerSets, err := s.clients.Operator.TektonInstallerSets().List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector}) diff --git a/test/e2e/kubernetes/pipelinesascode_test.go b/test/e2e/kubernetes/pipelinesascode_test.go new file mode 100644 index 0000000000..0639983e8d --- /dev/null +++ b/test/e2e/kubernetes/pipelinesascode_test.go @@ -0,0 +1,116 @@ +//go:build e2e +// +build e2e + +/* +Copyright 2025 The Tekton Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubernetes + +import ( + "os" + "testing" + "time" + + "github.com/tektoncd/operator/test/client" + "github.com/tektoncd/operator/test/resources" + "github.com/tektoncd/operator/test/utils" +) + +const ( + interval = 5 * time.Second + timeout = 5 * time.Minute + deploymentName = "additional-test-pac-controller" +) + +// TestKubernetesPipelinesAsCode verifies the PipelinesAsCode CR creation, +// additional controller creation and deletion, and PipelinesAsCode deletion +// on a plain Kubernetes cluster. +func TestKubernetesPipelinesAsCode(t *testing.T) { + crNames := utils.ResourceNames{ + TektonConfig: "config", + TektonPipeline: "pipeline", + OpenShiftPipelinesAsCode: "pipelines-as-code", + Namespace: "", + TargetNamespace: "tekton-pipelines", + } + + clients := client.Setup(t, crNames.TargetNamespace) + + if os.Getenv("TARGET") == "kubernetes" { + crNames.TargetNamespace = "tekton-pipelines" + } + + utils.CleanupOnInterrupt(func() { utils.TearDownPipeline(clients, crNames.OpenShiftPipelinesAsCode) }) + utils.CleanupOnInterrupt(func() { utils.TearDownPipeline(clients, crNames.TektonPipeline) }) + utils.CleanupOnInterrupt(func() { utils.TearDownNamespace(clients, crNames.TargetNamespace) }) + + defer utils.TearDownNamespace(clients, crNames.OpenShiftPipelinesAsCode) + defer utils.TearDownPipeline(clients, crNames.TektonPipeline) + defer utils.TearDownNamespace(clients, crNames.TargetNamespace) + + resources.EnsureNoTektonConfigInstance(t, clients, crNames) + + if _, err := resources.EnsureTektonPipelineExists(clients.TektonPipeline(), crNames); err != nil { + t.Fatalf("TektonPipeline %q failed to create: %v", crNames.TektonPipeline, err) + } + t.Run("create-pipeline", func(t *testing.T) { + resources.AssertTektonPipelineCRReadyStatus(t, clients, crNames) + }) + + if _, err := resources.EnsureOpenShiftPipelinesAsCodeExists(clients.OpenShiftPipelinesAsCode(), crNames); err != nil { + t.Fatalf("PipelinesAsCode %q failed to create: %v", crNames.OpenShiftPipelinesAsCode, err) + } + t.Run("create-kubernetes-pipelines-as-code", func(t *testing.T) { + resources.AssertOpenShiftPipelinesAsCodeCRReadyStatus(t, clients, crNames) + }) + + if err := resources.CreatePACResources(clients.KubeClient, "tekton-pipelines", "additional-test-configmap", "additional-test-secret"); err != nil { + t.Fatalf("failed to create resources for additional pipelines-as-code controller in %q: %v", crNames.OpenShiftPipelinesAsCode, err) + } + if _, err := resources.CreateAdditionalPipelinesAsCodeController(clients.OpenShiftPipelinesAsCode(), crNames); err != nil { + t.Fatalf("failed to create additional pipelines-as-code controller in %q: %v", crNames.OpenShiftPipelinesAsCode, err) + } + t.Run("create-additional-pipelines-as-code-controller", func(t *testing.T) { + resources.AssertOpenShiftPipelinesAsCodeCRReadyStatus(t, clients, crNames) + }) + + if err := resources.WaitForDeploymentReady(clients.KubeClient, deploymentName, crNames.TargetNamespace, interval, timeout); err != nil { + t.Fatalf("additional PAC deployment %q not ready: %v", deploymentName, err) + } + if err := resources.WaitForDeploymentAvailable(clients.KubeClient, deploymentName, crNames.TargetNamespace, interval, timeout); err != nil { + t.Fatalf("additional PAC deployment %q not available: %v", deploymentName, err) + } + + if _, err := resources.RemoveAdditionalPipelinesAsCodeController(clients.OpenShiftPipelinesAsCode(), crNames); err != nil { + t.Fatalf("failed to remove additional pipelines-as-code controller in %q: %v", crNames.OpenShiftPipelinesAsCode, err) + } + t.Run("remove-additional-controller-pipelines-as-code", func(t *testing.T) { + resources.AssertOpenShiftPipelinesAsCodeCRReadyStatus(t, clients, crNames) + }) + if err := resources.WaitForDeploymentDeletion(clients.KubeClient, deploymentName, crNames.TargetNamespace, interval, timeout); err != nil { + t.Fatalf("additional PAC deployment %q still exists: %v", deploymentName, err) + } + + t.Run("delete-kubernetes-pipelines-as-code", func(t *testing.T) { + resources.AssertOpenShiftPipelinesAsCodeCRReadyStatus(t, clients, crNames) + resources.OpenShiftPipelinesAsCodeCRDelete(t, clients, crNames) + }) + + t.Run("delete-pipeline", func(t *testing.T) { + resources.AssertTektonPipelineCRReadyStatus(t, clients, crNames) + resources.TektonPipelineCRDelete(t, clients, crNames) + }) +} diff --git a/test/resources/openshiftpipelinesascode.go b/test/resources/openshiftpipelinesascode.go index b31f75dfe8..2d6b6b0df2 100644 --- a/test/resources/openshiftpipelinesascode.go +++ b/test/resources/openshiftpipelinesascode.go @@ -21,6 +21,9 @@ import ( "fmt" "testing" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1" typedv1alpha1 "github.com/tektoncd/operator/pkg/client/clientset/versioned/typed/operator/v1alpha1" "github.com/tektoncd/operator/test/utils" @@ -93,11 +96,12 @@ func CreateAdditionalPipelinesAsCodeController(clients typedv1alpha1.OpenShiftPi return nil, err } - // update the OpenshiftPipelines CR to add the additional Pipelines As Code Controller + enable := true opacCR.Spec.PACSettings.AdditionalPACControllers = map[string]v1alpha1.AdditionalPACControllerConfig{ "additional-test": { ConfigMapName: "additional-test-configmap", SecretName: "additional-test-secret", + Enable: &enable, }, } return clients.Update(context.TODO(), opacCR, metav1.UpdateOptions{}) @@ -132,3 +136,22 @@ func OpenShiftPipelinesAsCodeCRDelete(t *testing.T, clients *utils.Clients, crNa t.Fatal("Timed out waiting on OpenShiftPipelinesAsCode to delete", err) } } + +func CreatePACResources(client kubernetes.Interface, namespace, cmName, secretName string) error { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: cmName, Namespace: namespace}, + Data: map[string]string{"config.yaml": "repositories: []"}, + } + if _, err := client.CoreV1().ConfigMaps(namespace).Create(context.TODO(), cm, metav1.CreateOptions{}); err != nil && !apierrs.IsAlreadyExists(err) { + return err + } + + sec := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: namespace}, + StringData: map[string]string{"github-token": "dummy"}, + } + if _, err := client.CoreV1().Secrets(namespace).Create(context.TODO(), sec, metav1.CreateOptions{}); err != nil && !apierrs.IsAlreadyExists(err) { + return err + } + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4653417352..0b835ace80 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1093,7 +1093,7 @@ github.com/opencontainers/go-digest ## explicit; go 1.18 github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 -# github.com/openshift-pipelines/pipelines-as-code v0.42.0 +# github.com/openshift-pipelines/pipelines-as-code v0.42.0 => github.com/tektoncd/pipelines-as-code v0.42.0 ## explicit; go 1.25.0 github.com/openshift-pipelines/pipelines-as-code/pkg/cli github.com/openshift-pipelines/pipelines-as-code/pkg/configutil @@ -2711,6 +2711,7 @@ sigs.k8s.io/yaml sigs.k8s.io/yaml/goyaml.v2 # github.com/alibabacloud-go/cr-20160607 => github.com/vdemeester/cr-20160607 v1.0.1 # github.com/go-jose/go-jose/v4 => github.com/go-jose/go-jose/v4 v4.1.4 +# github.com/openshift-pipelines/pipelines-as-code => github.com/tektoncd/pipelines-as-code v0.42.0 # k8s.io/api => k8s.io/api v0.32.4 # k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.32.9 # k8s.io/apimachinery => k8s.io/apimachinery v0.32.4