├── .dockerignore ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build.yml │ ├── codecov.yml │ └── pr.yml ├── .gitignore ├── .snyk ├── .sonarcloud.properties ├── .tekton ├── application-service-pull-request.yaml └── application-service-push.yaml ├── .vscode ├── launch.json └── tasks.json ├── Dockerfile ├── LICENSE ├── Makefile ├── OWNERS ├── PROJECT ├── README.md ├── appdata.gitconfig ├── bundle.Dockerfile ├── bundle ├── manifests │ ├── application-service-application-editor-role_rbac.authorization.k8s.io_v1_clusterrole.yaml │ ├── application-service-application-viewer-role_rbac.authorization.k8s.io_v1_clusterrole.yaml │ ├── application-service-component-editor-role_rbac.authorization.k8s.io_v1_clusterrole.yaml │ ├── application-service-component-viewer-role_rbac.authorization.k8s.io_v1_clusterrole.yaml │ ├── application-service-componentdetectionquery-editor-role_rbac.authorization.k8s.io_v1_clusterrole.yaml │ ├── application-service-componentdetectionquery-viewer-role_rbac.authorization.k8s.io_v1_clusterrole.yaml │ ├── application-service-controller-manager-metrics-service_v1_service.yaml │ ├── application-service-devfile-registry-config_v1_configmap.yaml │ ├── application-service-github-config_v1_configmap.yaml │ ├── application-service-has-config_v1_configmap.yaml │ ├── application-service-manager-config_v1_configmap.yaml │ ├── application-service-manager-rolebinding-appsnapshot_rbac.authorization.k8s.io_v1_clusterrolebinding.yaml │ ├── application-service-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml │ ├── application-service-webhook-service_v1_service.yaml │ ├── application-service.clusterserviceversion.yaml │ ├── appstudio.redhat.com_applications.yaml │ ├── appstudio.redhat.com_componentdetectionqueries.yaml │ └── appstudio.redhat.com_components.yaml ├── metadata │ └── annotations.yaml └── tests │ └── scorecard │ └── config.yaml ├── check-manager-kustomize.sh ├── codecov.yml ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_config_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── devfile_registry.properties │ ├── feature_flag.properties │ ├── github.properties │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ ├── bases │ │ └── application-service.clusterserviceversion.yaml │ └── kustomization.yaml ├── monitoring │ ├── grafana-dashboards │ │ ├── has-gitops-repo-metrics.json │ │ └── has-rate-limiting-metrics.json │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── application_editor_role.yaml │ ├── application_viewer_role.yaml │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── component_editor_role.yaml │ ├── component_viewer_role.yaml │ ├── componentdetectionquery_editor_role.yaml │ ├── componentdetectionquery_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── role_build.yaml │ ├── role_spi.yaml │ └── service_account.yaml ├── samples │ ├── application │ │ ├── app-existing-repos.yaml │ │ └── app.yaml │ ├── appstudio_v1alpha1_application.yaml │ ├── appstudio_v1alpha1_component.yaml │ ├── appstudio_v1alpha1_componentdetectionquery.yaml │ ├── component │ │ └── component-basic.yaml │ ├── componentdetectionquery │ │ └── componentdetectionquery-basic.yaml │ ├── kustomization.yaml │ └── snapshot │ │ └── snapshot.yaml ├── scorecard │ ├── bases │ │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── docs ├── CONTRIBUTING.md ├── DCO ├── build-test-and-deploy.md ├── installing-pact-tools.md ├── pact-tests.md ├── private-git-repos.md ├── release.md ├── serviceability.md └── testing.md ├── entrypoint.sh ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── replace_placeholders_and_deploy.sh └── routecrd │ └── route.yaml ├── license_header.txt ├── main.go ├── pkg └── util │ ├── util.go │ └── util_test.go └── webhooks ├── application_webhook.go ├── application_webhook_test.go ├── application_webhook_unit_test.go ├── component_webhook.go ├── component_webhook_test.go ├── component_webhook_unit_test.go ├── webhook_suite_test.go └── webhooks.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What does this PR do?: 2 | 3 | 4 | ### Which issue(s)/story(ies) does this PR fixes: 5 | 6 | 7 | ### PR acceptance criteria: 8 | 17 | 18 | - [ ] Unit/Functional tests 19 | 20 | 21 | 22 | - [ ] Documentation 23 | 24 | 25 | 26 | - [ ] Client Impact 27 | 28 | 29 | 30 | 31 | ### How to test changes / Special notes to the reviewer: 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Container build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build-image: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - name: Checkout application-service source code 12 | uses: actions/checkout@v2 13 | - name: Docker meta 14 | id: meta 15 | uses: docker/metadata-action@v4 16 | with: 17 | images: | 18 | quay.io/redhat-appstudio/application-service 19 | tags: | 20 | next 21 | type=sha 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v2 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v2 26 | - name: Login to DockerHub 27 | uses: docker/login-action@v2 28 | with: 29 | username: ${{ secrets.QUAY_USERNAME }} 30 | password: ${{ secrets.QUAY_PASSWORD }} 31 | registry: quay.io 32 | repository: redhat-appstudio/application-service 33 | - name: Docker Build & Push - application-service Operator Image 34 | uses: docker/build-push-action@v3 35 | with: 36 | dockerfile: Dockerfile 37 | platforms: linux/amd64,linux/ppc64le 38 | push: true 39 | tags: ${{ steps.meta.outputs.tags }} 40 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: Code Coverage Report 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | with: 13 | persist-credentials: false 14 | - name: Set up Go 1.x 15 | uses: actions/setup-go@v4 16 | with: 17 | go-version: 1.19 18 | - name: Run Go Tests 19 | run: | 20 | # Temporarily adding a pact-go installation. 21 | # It should be gone once https://issues.redhat.com/browse/HAC-4879 is solved 22 | go get github.com/pact-foundation/pact-go/v2@2.x.x 23 | go install github.com/pact-foundation/pact-go/v2@2.x.x 24 | sudo /home/runner/go/bin/pact-go -l DEBUG install 25 | 26 | go mod download 27 | make test 28 | - name: Codecov 29 | uses: codecov/codecov-action@v3 -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Validate PRs 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | go: 9 | name: Check sources 10 | runs-on: ubuntu-20.04 11 | env: 12 | OPERATOR_SDK_VERSION: v1.14.0 13 | PR_CHECK: true 14 | steps: 15 | - name: Set up Go 1.x 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: 1.21 19 | - name: Check out code into the Go module directory 20 | uses: actions/checkout@v2 21 | with: 22 | fetch-depth: 0 23 | - name: Cache Operator SDK ${{ env.OPERATOR_SDK_VERSION }} 24 | uses: actions/cache@v2 25 | id: cache-operator-sdk 26 | with: 27 | path: ~/cache 28 | key: operator-sdk-${{ env.OPERATOR_SDK_VERSION }} 29 | - name: Download Operator SDK ${{ env.OPERATOR_SDK_VERSION }} 30 | if: steps.cache-operator-sdk.outputs.cache-hit != 'true' 31 | run: | 32 | mkdir -p ~/cache 33 | wget https://github.com/operator-framework/operator-sdk/releases/download/${OPERATOR_SDK_VERSION}/operator-sdk_linux_amd64 -O ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} > /dev/null -O ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} > /dev/null 34 | chmod +x ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} 35 | - name: Install Operator SDK ${{ env.OPERATOR_SDK_VERSION }} 36 | run: | 37 | mkdir -p ~/bin 38 | cp ~/cache/operator-sdk-${OPERATOR_SDK_VERSION} ~/bin/operator-sdk 39 | echo "$HOME/bin" >> $GITHUB_PATH 40 | - name: Cache go modules 41 | id: cache-mod 42 | uses: actions/cache@v2 43 | with: 44 | path: ~/go/pkg/mod 45 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 46 | restore-keys: | 47 | ${{ runner.os }}-go- 48 | - name: Download dependencies 49 | run: go mod download 50 | if: steps.cache-mod.outputs.cache-hit != 'true' 51 | - name: Check go mod status 52 | run: | 53 | go mod tidy 54 | if [[ ! -z $(git status -s) ]] 55 | then 56 | echo "Go mod state is not clean:" 57 | git --no-pager diff 58 | exit 1 59 | fi 60 | - name: Check format 61 | run: | 62 | go install github.com/google/addlicense@latest 63 | go install golang.org/x/tools/cmd/goimports@v0.22.0 64 | git reset HEAD --hard 65 | make check_fmt 66 | if [[ $? != 0 ]] 67 | then 68 | echo "not well formatted sources are found:" 69 | git --no-pager diff 70 | exit 1 71 | fi 72 | - uses: dominikh/staticcheck-action@v1.3.0 73 | with: 74 | #version: "2022.1.3" 75 | version: "latest" 76 | install-go: false 77 | - name: Check manifests 78 | run: | 79 | # Note: fmt is necessary after generate since generated sources will 80 | # fail format check by default. 81 | make generate fmt manifests 82 | if [[ ! -z $(git status -s) ]] 83 | then 84 | echo "generated sources are not up to date:" 85 | git --no-pager diff 86 | exit 1 87 | fi 88 | - name: Run Go Tests 89 | run: | 90 | # Temporarily adding a pact-go installation. 91 | # It should be gone once https://issues.redhat.com/browse/HAC-4879 is solved 92 | go get github.com/pact-foundation/pact-go/v2@2.x.x 93 | go install github.com/pact-foundation/pact-go/v2@2.x.x 94 | sudo /home/runner/go/bin/pact-go -l DEBUG install 95 | 96 | make test 97 | - name: Check if Manager Kustomize has the right image 98 | run: | 99 | ./check-manager-kustomize.sh 100 | exit $? 101 | - name: Upload coverage to Codecov 102 | uses: codecov/codecov-action@v2.1.0 103 | - name: Run Gosec Security Scanner 104 | run: | 105 | go install github.com/securego/gosec/v2/cmd/gosec@v2.19.0 106 | make gosec 107 | if [[ $? != 0 ]] 108 | then 109 | echo "gosec scanner failed to run " 110 | exit 1 111 | fi 112 | 113 | - name: Upload SARIF file 114 | uses: github/codeql-action/upload-sarif@v2 115 | with: 116 | # Path to SARIF file relative to the root of the repository 117 | sarif_file: gosec.sarif 118 | 119 | - name: Upload coverage to Codecov 120 | uses: codecov/codecov-action@v2.1.0 121 | docker: 122 | name: Check docker build 123 | runs-on: ubuntu-latest 124 | steps: 125 | - name: Check out code into the Go module directory 126 | uses: actions/checkout@v2 127 | with: 128 | fetch-depth: 0 129 | - name: Check if dockerimage build is working 130 | run: docker build -f ./Dockerfile . 131 | kube-linter: 132 | runs-on: ubuntu-latest 133 | steps: 134 | - uses: actions/checkout@v3 135 | - name: Create ./.kube-linter/ for deployment files 136 | shell: bash 137 | run: mkdir -p ./.kube-linter/ && touch .kube-linter/manifests.yaml 138 | - name: Generate manifests for scan 139 | shell: bash 140 | run: kustomize build config/default > ./.kube-linter/manifests.yaml 141 | - name: Scan yaml files with kube-linter 142 | uses: stackrox/kube-linter-action@v1 143 | id: kube-linter-action-scan 144 | with: 145 | # Adjust this directory to the location where your kubernetes resources and helm charts are located. 146 | directory: ./.kube-linter/ 147 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | application-service 2 | controller-gen 3 | setup-envtest 4 | bin/* 5 | cover.out 6 | *__debug_bin* 7 | tmp 8 | 9 | .idea/ 10 | .vscode/ 11 | .tmp/ 12 | output.log 13 | syncer.yaml 14 | .DS_Store 15 | pact.log 16 | *resource.file* 17 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | exclude: 2 | global: 3 | - ./cdq-analysis/ -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | # Path to sources 2 | sonar.sources=. 3 | sonar.exclusions=**/*test*, **/vendor/**, **/zz_generated.*, 4 | # Source encoding 5 | sonar.sourceEncoding=UTF-8 6 | -------------------------------------------------------------------------------- /.tekton/application-service-pull-request.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1 2 | kind: PipelineRun 3 | metadata: 4 | annotations: 5 | build.appstudio.openshift.io/repo: https://github.com/redhat-appstudio/application-service?rev={{revision}} 6 | build.appstudio.redhat.com/commit_sha: '{{revision}}' 7 | build.appstudio.redhat.com/pull_request_number: '{{pull_request_number}}' 8 | build.appstudio.redhat.com/target_branch: '{{target_branch}}' 9 | pipelinesascode.tekton.dev/max-keep-runs: "3" 10 | pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch 11 | == "main" 12 | creationTimestamp: null 13 | labels: 14 | appstudio.openshift.io/application: application-service 15 | appstudio.openshift.io/component: application-service 16 | pipelines.appstudio.openshift.io/type: build 17 | name: application-service-on-pull-request 18 | namespace: rhtap-has-tenant 19 | spec: 20 | params: 21 | - name: dockerfile 22 | value: Dockerfile 23 | - name: git-url 24 | value: '{{repo_url}}' 25 | - name: image-expires-after 26 | value: 5d 27 | - name: output-image 28 | value: quay.io/redhat-user-workloads/rhtap-has-tenant/application-service/application-service:on-pr-{{revision}} 29 | - name: path-context 30 | value: . 31 | - name: revision 32 | value: '{{revision}}' 33 | - name: build-source-image 34 | value: 'true' 35 | pipelineSpec: 36 | finally: 37 | - name: show-sbom 38 | params: 39 | - name: IMAGE_URL 40 | value: $(tasks.build-container.results.IMAGE_URL) 41 | taskRef: 42 | params: 43 | - name: name 44 | value: show-sbom 45 | - name: bundle 46 | value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:52f8b96b96ce4203d4b74d850a85f963125bf8eef0683ea5acdd80818d335a28 47 | - name: kind 48 | value: task 49 | resolver: bundles 50 | - name: show-summary 51 | params: 52 | - name: pipelinerun-name 53 | value: $(context.pipelineRun.name) 54 | - name: git-url 55 | value: $(tasks.clone-repository.results.url)?rev=$(tasks.clone-repository.results.commit) 56 | - name: image-url 57 | value: $(params.output-image) 58 | - name: build-task-status 59 | value: $(tasks.build-container.status) 60 | taskRef: 61 | params: 62 | - name: name 63 | value: summary 64 | - name: bundle 65 | value: quay.io/konflux-ci/tekton-catalog/task-summary:0.2@sha256:d97c04ab42f277b1103eb6f3a053b247849f4f5b3237ea302a8ecada3b24e15b 66 | - name: kind 67 | value: task 68 | resolver: bundles 69 | params: 70 | - description: Source Repository URL 71 | name: git-url 72 | type: string 73 | - default: "" 74 | description: Revision of the Source Repository 75 | name: revision 76 | type: string 77 | - description: Fully Qualified Output Image 78 | name: output-image 79 | type: string 80 | - default: . 81 | description: Path to the source code of an application's component from where 82 | to build image. 83 | name: path-context 84 | type: string 85 | - default: Dockerfile 86 | description: Path to the Dockerfile inside the context specified by parameter 87 | path-context 88 | name: dockerfile 89 | type: string 90 | - default: "false" 91 | description: Force rebuild image 92 | name: rebuild 93 | type: string 94 | - default: "false" 95 | description: Skip checks against built image 96 | name: skip-checks 97 | type: string 98 | - default: "false" 99 | description: Execute the build with network isolation 100 | name: hermetic 101 | type: string 102 | - default: "" 103 | description: Build dependencies to be prefetched by Cachi2 104 | name: prefetch-input 105 | type: string 106 | - default: "false" 107 | description: Java build 108 | name: java 109 | type: string 110 | - default: "" 111 | description: Image tag expiration time, time values could be something like 112 | 1h, 2d, 3w for hours, days, and weeks, respectively. 113 | name: image-expires-after 114 | - default: "false" 115 | description: Build a source image. 116 | name: build-source-image 117 | type: string 118 | results: 119 | - description: "" 120 | name: IMAGE_URL 121 | value: $(tasks.build-container.results.IMAGE_URL) 122 | - description: "" 123 | name: IMAGE_DIGEST 124 | value: $(tasks.build-container.results.IMAGE_DIGEST) 125 | - description: "" 126 | name: CHAINS-GIT_URL 127 | value: $(tasks.clone-repository.results.url) 128 | - description: "" 129 | name: CHAINS-GIT_COMMIT 130 | value: $(tasks.clone-repository.results.commit) 131 | - description: "" 132 | name: JAVA_COMMUNITY_DEPENDENCIES 133 | value: $(tasks.build-container.results.JAVA_COMMUNITY_DEPENDENCIES) 134 | tasks: 135 | - name: init 136 | params: 137 | - name: image-url 138 | value: $(params.output-image) 139 | - name: rebuild 140 | value: $(params.rebuild) 141 | - name: skip-checks 142 | value: $(params.skip-checks) 143 | taskRef: 144 | params: 145 | - name: name 146 | value: init 147 | - name: bundle 148 | value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:f239f38bba3a8351c8cb0980fde8e2ee477ded7200178b0f45175e4006ff1dca 149 | - name: kind 150 | value: task 151 | resolver: bundles 152 | - name: clone-repository 153 | params: 154 | - name: url 155 | value: $(params.git-url) 156 | - name: revision 157 | value: $(params.revision) 158 | runAfter: 159 | - init 160 | taskRef: 161 | params: 162 | - name: name 163 | value: git-clone 164 | - name: bundle 165 | value: quay.io/konflux-ci/tekton-catalog/task-git-clone:0.1@sha256:2cccdf8729ad4d5adf65e8b66464f8efa1e1c87ba16d343b4a6c621a2a40f7e1 166 | - name: kind 167 | value: task 168 | resolver: bundles 169 | when: 170 | - input: $(tasks.init.results.build) 171 | operator: in 172 | values: 173 | - "true" 174 | workspaces: 175 | - name: output 176 | workspace: workspace 177 | - name: basic-auth 178 | workspace: git-auth 179 | - name: prefetch-dependencies 180 | params: 181 | - name: input 182 | value: $(params.prefetch-input) 183 | runAfter: 184 | - clone-repository 185 | taskRef: 186 | params: 187 | - name: name 188 | value: prefetch-dependencies 189 | - name: bundle 190 | value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.1@sha256:f53fe5482599b39ae2d1004cf09a2026fd9dd3822ab6ef46b51b4a398b0a3232 191 | - name: kind 192 | value: task 193 | resolver: bundles 194 | when: 195 | - input: $(params.hermetic) 196 | operator: in 197 | values: 198 | - "true" 199 | workspaces: 200 | - name: source 201 | workspace: workspace 202 | - name: build-container 203 | params: 204 | - name: IMAGE 205 | value: $(params.output-image) 206 | - name: DOCKERFILE 207 | value: $(params.dockerfile) 208 | - name: CONTEXT 209 | value: $(params.path-context) 210 | - name: HERMETIC 211 | value: $(params.hermetic) 212 | - name: PREFETCH_INPUT 213 | value: $(params.prefetch-input) 214 | - name: IMAGE_EXPIRES_AFTER 215 | value: $(params.image-expires-after) 216 | - name: COMMIT_SHA 217 | value: $(tasks.clone-repository.results.commit) 218 | runAfter: 219 | - prefetch-dependencies 220 | taskRef: 221 | params: 222 | - name: name 223 | value: buildah 224 | - name: bundle 225 | value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.2@sha256:11b7f08ddaa281fcf40494a2a2f79e0aebcaa3e7da93790fecad4d46983648d2 226 | - name: kind 227 | value: task 228 | resolver: bundles 229 | when: 230 | - input: $(tasks.init.results.build) 231 | operator: in 232 | values: 233 | - "true" 234 | workspaces: 235 | - name: source 236 | workspace: workspace 237 | - name: build-source-image 238 | params: 239 | - name: BINARY_IMAGE 240 | value: $(params.output-image) 241 | runAfter: 242 | - build-container 243 | taskRef: 244 | params: 245 | - name: name 246 | value: source-build 247 | - name: bundle 248 | value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.1@sha256:53a41b0838b61cbacc7ecd4ffd87cf3f41b28a4aa9e095fe95779982c688dc85 249 | - name: kind 250 | value: task 251 | resolver: bundles 252 | when: 253 | - input: $(tasks.init.results.build) 254 | operator: in 255 | values: 256 | - "true" 257 | - input: $(params.build-source-image) 258 | operator: in 259 | values: 260 | - "true" 261 | workspaces: 262 | - name: workspace 263 | workspace: workspace 264 | - name: deprecated-base-image-check 265 | params: 266 | - name: IMAGE_URL 267 | value: $(tasks.build-container.results.IMAGE_URL) 268 | - name: IMAGE_DIGEST 269 | value: $(tasks.build-container.results.IMAGE_DIGEST) 270 | runAfter: 271 | - build-container 272 | taskRef: 273 | params: 274 | - name: name 275 | value: deprecated-image-check 276 | - name: bundle 277 | value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.4@sha256:443ffa897ee35e416a0bfd39721c68cbf88cfa5c74c843c5183218d0cd586e82 278 | - name: kind 279 | value: task 280 | resolver: bundles 281 | when: 282 | - input: $(params.skip-checks) 283 | operator: in 284 | values: 285 | - "false" 286 | - name: clair-scan 287 | params: 288 | - name: image-digest 289 | value: $(tasks.build-container.results.IMAGE_DIGEST) 290 | - name: image-url 291 | value: $(tasks.build-container.results.IMAGE_URL) 292 | runAfter: 293 | - build-container 294 | taskRef: 295 | params: 296 | - name: name 297 | value: clair-scan 298 | - name: bundle 299 | value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:90e371fe7ec2288259a906bc1fd49c53b8b97a0b0b02da0893fb65e3be2a5801 300 | - name: kind 301 | value: task 302 | resolver: bundles 303 | when: 304 | - input: $(params.skip-checks) 305 | operator: in 306 | values: 307 | - "false" 308 | - name: sast-snyk-check 309 | runAfter: 310 | - clone-repository 311 | taskRef: 312 | params: 313 | - name: name 314 | value: sast-snyk-check 315 | - name: bundle 316 | value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.2@sha256:eb7c643130f226c345b3602dca280e6f8cd6f90f948503918d5a2677bf0610f7 317 | - name: kind 318 | value: task 319 | resolver: bundles 320 | when: 321 | - input: $(params.skip-checks) 322 | operator: in 323 | values: 324 | - "false" 325 | workspaces: 326 | - name: workspace 327 | workspace: workspace 328 | - name: clamav-scan 329 | params: 330 | - name: image-digest 331 | value: $(tasks.build-container.results.IMAGE_DIGEST) 332 | - name: image-url 333 | value: $(tasks.build-container.results.IMAGE_URL) 334 | runAfter: 335 | - build-container 336 | taskRef: 337 | params: 338 | - name: name 339 | value: clamav-scan 340 | - name: bundle 341 | value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.1@sha256:21c7d037df3b430fc5c21b932e2062d0b82b046f39a2dc965aba7dff7a9cfc57 342 | - name: kind 343 | value: task 344 | resolver: bundles 345 | when: 346 | - input: $(params.skip-checks) 347 | operator: in 348 | values: 349 | - "false" 350 | - name: rpms-signature-scan 351 | params: 352 | - name: image-url 353 | value: $(tasks.build-container.results.IMAGE_URL) 354 | - name: image-digest 355 | value: $(tasks.build-container.results.IMAGE_DIGEST) 356 | runAfter: 357 | - build-container 358 | taskRef: 359 | params: 360 | - name: name 361 | value: rpms-signature-scan 362 | - name: bundle 363 | value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:0c9667fba291af05997397a32e5e938ccaa46e93a2e14bad228e64a6427c5545 364 | - name: kind 365 | value: task 366 | resolver: bundles 367 | when: 368 | - input: $(params.skip-checks) 369 | operator: in 370 | values: 371 | - "false" 372 | workspaces: 373 | - name: workspace 374 | - name: git-auth 375 | optional: true 376 | taskRunTemplate: {} 377 | workspaces: 378 | - name: workspace 379 | volumeClaimTemplate: 380 | metadata: 381 | creationTimestamp: null 382 | spec: 383 | accessModes: 384 | - ReadWriteOnce 385 | resources: 386 | requests: 387 | storage: 1Gi 388 | status: {} 389 | - name: git-auth 390 | secret: 391 | secretName: '{{ git_auth_secret }}' 392 | status: {} 393 | -------------------------------------------------------------------------------- /.tekton/application-service-push.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tekton.dev/v1 2 | kind: PipelineRun 3 | metadata: 4 | annotations: 5 | build.appstudio.openshift.io/repo: https://github.com/redhat-appstudio/application-service?rev={{revision}} 6 | build.appstudio.redhat.com/commit_sha: '{{revision}}' 7 | build.appstudio.redhat.com/target_branch: '{{target_branch}}' 8 | pipelinesascode.tekton.dev/max-keep-runs: "3" 9 | pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch 10 | == "main" 11 | creationTimestamp: null 12 | labels: 13 | appstudio.openshift.io/application: application-service 14 | appstudio.openshift.io/component: application-service 15 | pipelines.appstudio.openshift.io/type: build 16 | name: application-service-on-push 17 | namespace: rhtap-has-tenant 18 | spec: 19 | params: 20 | - name: dockerfile 21 | value: Dockerfile 22 | - name: git-url 23 | value: '{{repo_url}}' 24 | - name: output-image 25 | value: quay.io/redhat-user-workloads/rhtap-has-tenant/application-service/application-service:{{revision}} 26 | - name: path-context 27 | value: . 28 | - name: revision 29 | value: '{{revision}}' 30 | - name: build-source-image 31 | value: 'true' 32 | pipelineSpec: 33 | finally: 34 | - name: show-sbom 35 | params: 36 | - name: IMAGE_URL 37 | value: $(tasks.build-container.results.IMAGE_URL) 38 | taskRef: 39 | params: 40 | - name: name 41 | value: show-sbom 42 | - name: bundle 43 | value: quay.io/konflux-ci/tekton-catalog/task-show-sbom:0.1@sha256:52f8b96b96ce4203d4b74d850a85f963125bf8eef0683ea5acdd80818d335a28 44 | - name: kind 45 | value: task 46 | resolver: bundles 47 | - name: show-summary 48 | params: 49 | - name: pipelinerun-name 50 | value: $(context.pipelineRun.name) 51 | - name: git-url 52 | value: $(tasks.clone-repository.results.url)?rev=$(tasks.clone-repository.results.commit) 53 | - name: image-url 54 | value: $(params.output-image) 55 | - name: build-task-status 56 | value: $(tasks.build-container.status) 57 | taskRef: 58 | params: 59 | - name: name 60 | value: summary 61 | - name: bundle 62 | value: quay.io/konflux-ci/tekton-catalog/task-summary:0.2@sha256:d97c04ab42f277b1103eb6f3a053b247849f4f5b3237ea302a8ecada3b24e15b 63 | - name: kind 64 | value: task 65 | resolver: bundles 66 | params: 67 | - description: Source Repository URL 68 | name: git-url 69 | type: string 70 | - default: "" 71 | description: Revision of the Source Repository 72 | name: revision 73 | type: string 74 | - description: Fully Qualified Output Image 75 | name: output-image 76 | type: string 77 | - default: . 78 | description: Path to the source code of an application's component from where 79 | to build image. 80 | name: path-context 81 | type: string 82 | - default: Dockerfile 83 | description: Path to the Dockerfile inside the context specified by parameter 84 | path-context 85 | name: dockerfile 86 | type: string 87 | - default: "false" 88 | description: Force rebuild image 89 | name: rebuild 90 | type: string 91 | - default: "false" 92 | description: Skip checks against built image 93 | name: skip-checks 94 | type: string 95 | - default: "false" 96 | description: Execute the build with network isolation 97 | name: hermetic 98 | type: string 99 | - default: "" 100 | description: Build dependencies to be prefetched by Cachi2 101 | name: prefetch-input 102 | type: string 103 | - default: "false" 104 | description: Java build 105 | name: java 106 | type: string 107 | - default: "" 108 | description: Image tag expiration time, time values could be something like 109 | 1h, 2d, 3w for hours, days, and weeks, respectively. 110 | name: image-expires-after 111 | - default: "false" 112 | description: Build a source image. 113 | name: build-source-image 114 | type: string 115 | results: 116 | - description: "" 117 | name: IMAGE_URL 118 | value: $(tasks.build-container.results.IMAGE_URL) 119 | - description: "" 120 | name: IMAGE_DIGEST 121 | value: $(tasks.build-container.results.IMAGE_DIGEST) 122 | - description: "" 123 | name: CHAINS-GIT_URL 124 | value: $(tasks.clone-repository.results.url) 125 | - description: "" 126 | name: CHAINS-GIT_COMMIT 127 | value: $(tasks.clone-repository.results.commit) 128 | - description: "" 129 | name: JAVA_COMMUNITY_DEPENDENCIES 130 | value: $(tasks.build-container.results.JAVA_COMMUNITY_DEPENDENCIES) 131 | tasks: 132 | - name: init 133 | params: 134 | - name: image-url 135 | value: $(params.output-image) 136 | - name: rebuild 137 | value: $(params.rebuild) 138 | - name: skip-checks 139 | value: $(params.skip-checks) 140 | taskRef: 141 | params: 142 | - name: name 143 | value: init 144 | - name: bundle 145 | value: quay.io/konflux-ci/tekton-catalog/task-init:0.2@sha256:f239f38bba3a8351c8cb0980fde8e2ee477ded7200178b0f45175e4006ff1dca 146 | - name: kind 147 | value: task 148 | resolver: bundles 149 | - name: clone-repository 150 | params: 151 | - name: url 152 | value: $(params.git-url) 153 | - name: revision 154 | value: $(params.revision) 155 | runAfter: 156 | - init 157 | taskRef: 158 | params: 159 | - name: name 160 | value: git-clone 161 | - name: bundle 162 | value: quay.io/konflux-ci/tekton-catalog/task-git-clone:0.1@sha256:2cccdf8729ad4d5adf65e8b66464f8efa1e1c87ba16d343b4a6c621a2a40f7e1 163 | - name: kind 164 | value: task 165 | resolver: bundles 166 | when: 167 | - input: $(tasks.init.results.build) 168 | operator: in 169 | values: 170 | - "true" 171 | workspaces: 172 | - name: output 173 | workspace: workspace 174 | - name: basic-auth 175 | workspace: git-auth 176 | - name: prefetch-dependencies 177 | params: 178 | - name: input 179 | value: $(params.prefetch-input) 180 | runAfter: 181 | - clone-repository 182 | taskRef: 183 | params: 184 | - name: name 185 | value: prefetch-dependencies 186 | - name: bundle 187 | value: quay.io/konflux-ci/tekton-catalog/task-prefetch-dependencies:0.1@sha256:f53fe5482599b39ae2d1004cf09a2026fd9dd3822ab6ef46b51b4a398b0a3232 188 | - name: kind 189 | value: task 190 | resolver: bundles 191 | when: 192 | - input: $(params.hermetic) 193 | operator: in 194 | values: 195 | - "true" 196 | workspaces: 197 | - name: source 198 | workspace: workspace 199 | - name: build-container 200 | params: 201 | - name: IMAGE 202 | value: $(params.output-image) 203 | - name: DOCKERFILE 204 | value: $(params.dockerfile) 205 | - name: CONTEXT 206 | value: $(params.path-context) 207 | - name: HERMETIC 208 | value: $(params.hermetic) 209 | - name: PREFETCH_INPUT 210 | value: $(params.prefetch-input) 211 | - name: IMAGE_EXPIRES_AFTER 212 | value: $(params.image-expires-after) 213 | - name: COMMIT_SHA 214 | value: $(tasks.clone-repository.results.commit) 215 | runAfter: 216 | - prefetch-dependencies 217 | taskRef: 218 | params: 219 | - name: name 220 | value: buildah 221 | - name: bundle 222 | value: quay.io/konflux-ci/tekton-catalog/task-buildah:0.2@sha256:11b7f08ddaa281fcf40494a2a2f79e0aebcaa3e7da93790fecad4d46983648d2 223 | - name: kind 224 | value: task 225 | resolver: bundles 226 | when: 227 | - input: $(tasks.init.results.build) 228 | operator: in 229 | values: 230 | - "true" 231 | workspaces: 232 | - name: source 233 | workspace: workspace 234 | - name: build-source-image 235 | params: 236 | - name: BINARY_IMAGE 237 | value: $(params.output-image) 238 | runAfter: 239 | - build-container 240 | taskRef: 241 | params: 242 | - name: name 243 | value: source-build 244 | - name: bundle 245 | value: quay.io/konflux-ci/tekton-catalog/task-source-build:0.1@sha256:53a41b0838b61cbacc7ecd4ffd87cf3f41b28a4aa9e095fe95779982c688dc85 246 | - name: kind 247 | value: task 248 | resolver: bundles 249 | when: 250 | - input: $(tasks.init.results.build) 251 | operator: in 252 | values: 253 | - "true" 254 | - input: $(params.build-source-image) 255 | operator: in 256 | values: 257 | - "true" 258 | workspaces: 259 | - name: workspace 260 | workspace: workspace 261 | - name: deprecated-base-image-check 262 | params: 263 | - name: IMAGE_URL 264 | value: $(tasks.build-container.results.IMAGE_URL) 265 | - name: IMAGE_DIGEST 266 | value: $(tasks.build-container.results.IMAGE_DIGEST) 267 | runAfter: 268 | - build-container 269 | taskRef: 270 | params: 271 | - name: name 272 | value: deprecated-image-check 273 | - name: bundle 274 | value: quay.io/konflux-ci/tekton-catalog/task-deprecated-image-check:0.4@sha256:443ffa897ee35e416a0bfd39721c68cbf88cfa5c74c843c5183218d0cd586e82 275 | - name: kind 276 | value: task 277 | resolver: bundles 278 | when: 279 | - input: $(params.skip-checks) 280 | operator: in 281 | values: 282 | - "false" 283 | - name: clair-scan 284 | params: 285 | - name: image-digest 286 | value: $(tasks.build-container.results.IMAGE_DIGEST) 287 | - name: image-url 288 | value: $(tasks.build-container.results.IMAGE_URL) 289 | runAfter: 290 | - build-container 291 | taskRef: 292 | params: 293 | - name: name 294 | value: clair-scan 295 | - name: bundle 296 | value: quay.io/konflux-ci/tekton-catalog/task-clair-scan:0.2@sha256:90e371fe7ec2288259a906bc1fd49c53b8b97a0b0b02da0893fb65e3be2a5801 297 | - name: kind 298 | value: task 299 | resolver: bundles 300 | when: 301 | - input: $(params.skip-checks) 302 | operator: in 303 | values: 304 | - "false" 305 | - name: sast-snyk-check 306 | runAfter: 307 | - clone-repository 308 | taskRef: 309 | params: 310 | - name: name 311 | value: sast-snyk-check 312 | - name: bundle 313 | value: quay.io/konflux-ci/tekton-catalog/task-sast-snyk-check:0.2@sha256:eb7c643130f226c345b3602dca280e6f8cd6f90f948503918d5a2677bf0610f7 314 | - name: kind 315 | value: task 316 | resolver: bundles 317 | when: 318 | - input: $(params.skip-checks) 319 | operator: in 320 | values: 321 | - "false" 322 | workspaces: 323 | - name: workspace 324 | workspace: workspace 325 | - name: clamav-scan 326 | params: 327 | - name: image-digest 328 | value: $(tasks.build-container.results.IMAGE_DIGEST) 329 | - name: image-url 330 | value: $(tasks.build-container.results.IMAGE_URL) 331 | runAfter: 332 | - build-container 333 | taskRef: 334 | params: 335 | - name: name 336 | value: clamav-scan 337 | - name: bundle 338 | value: quay.io/konflux-ci/tekton-catalog/task-clamav-scan:0.1@sha256:21c7d037df3b430fc5c21b932e2062d0b82b046f39a2dc965aba7dff7a9cfc57 339 | - name: kind 340 | value: task 341 | resolver: bundles 342 | when: 343 | - input: $(params.skip-checks) 344 | operator: in 345 | values: 346 | - "false" 347 | - name: rpms-signature-scan 348 | params: 349 | - name: image-url 350 | value: $(tasks.build-container.results.IMAGE_URL) 351 | - name: image-digest 352 | value: $(tasks.build-container.results.IMAGE_DIGEST) 353 | runAfter: 354 | - build-container 355 | taskRef: 356 | params: 357 | - name: name 358 | value: rpms-signature-scan 359 | - name: bundle 360 | value: quay.io/konflux-ci/tekton-catalog/task-rpms-signature-scan:0.2@sha256:0c9667fba291af05997397a32e5e938ccaa46e93a2e14bad228e64a6427c5545 361 | - name: kind 362 | value: task 363 | resolver: bundles 364 | when: 365 | - input: $(params.skip-checks) 366 | operator: in 367 | values: 368 | - "false" 369 | workspaces: 370 | - name: workspace 371 | - name: git-auth 372 | optional: true 373 | taskRunTemplate: {} 374 | workspaces: 375 | - name: workspace 376 | volumeClaimTemplate: 377 | metadata: 378 | creationTimestamp: null 379 | spec: 380 | accessModes: 381 | - ReadWriteOnce 382 | resources: 383 | requests: 384 | storage: 1Gi 385 | status: {} 386 | - name: git-auth 387 | secret: 388 | secretName: '{{ git_auth_secret }}' 389 | status: {} 390 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "useApiV1": false, 6 | "name": "Debug operator", 7 | "type": "go", 8 | "request": "launch", 9 | "mode": "auto", 10 | "program": "${workspaceFolder}/main.go", 11 | "cwd": "${workspaceFolder}", 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Format operator code", 6 | "command": "make fmt", 7 | "options": { 8 | "env": { 9 | "ARCH": "amd64" 10 | } 11 | }, 12 | "type": "shell", 13 | "args": [], 14 | "problemMatcher": [ 15 | "$go" 16 | ], 17 | "presentation": { 18 | "reveal": "always" 19 | }, 20 | "group": "build" 21 | }, 22 | { 23 | "label": "Run tests", 24 | "command": "make test", 25 | "options": { 26 | "env": { 27 | "ARCH": "amd64" 28 | } 29 | }, 30 | "type": "shell", 31 | "args": [], 32 | "problemMatcher": [ 33 | "$go" 34 | ], 35 | "presentation": { 36 | "reveal": "always" 37 | }, 38 | "group": "build" 39 | }, 40 | { 41 | "label": "Compile operator binary", 42 | "command": "make build", 43 | "options": { 44 | "env": { 45 | "ARCH": "amd64" 46 | } 47 | }, 48 | "type": "shell", 49 | "args": [], 50 | "problemMatcher": [ 51 | "$go" 52 | ], 53 | "presentation": { 54 | "reveal": "always" 55 | }, 56 | "group": "build" 57 | }, 58 | { 59 | "label": "Build and push operator image", 60 | "command": "make docker-build docker-push IMG=${IMAGE_REGISTRY_HOST}/${IMAGE_REGISTRY_USER_NAME}/application-service", 61 | "options": { 62 | "env": { 63 | "ARCH": "amd64" 64 | } 65 | }, 66 | "type": "shell", 67 | "args": [], 68 | "problemMatcher": [ 69 | "$go" 70 | ], 71 | "presentation": { 72 | "reveal": "always" 73 | }, 74 | "group": "build" 75 | }, 76 | { 77 | "label": "Deploy operator", 78 | "command": "IMG=${IMAGE_REGISTRY_HOST}/${IMAGE_REGISTRY_USER_NAME}/application-service; make deploy IMG=\"${IMG}\"", 79 | "options": { 80 | "env": { 81 | "ARCH": "amd64" 82 | } 83 | }, 84 | "type": "shell", 85 | "args": [], 86 | "problemMatcher": [ 87 | "$go" 88 | ], 89 | "presentation": { 90 | "reveal": "always" 91 | }, 92 | "group": "build" 93 | }, 94 | { 95 | "label": "UnDeploy operator", 96 | "command": "make undeploy", 97 | "options": { 98 | "env": { 99 | "ARCH": "amd64" 100 | } 101 | }, 102 | "type": "shell", 103 | "args": [], 104 | "problemMatcher": [ 105 | "$go" 106 | ], 107 | "presentation": { 108 | "reveal": "always" 109 | }, 110 | "group": "build" 111 | }, 112 | { 113 | "label": "Launch operator debug session", 114 | "command": "make debug -s", 115 | "options": { 116 | "env": { 117 | "ARCH": "amd64" 118 | } 119 | }, 120 | "type": "shell", 121 | "args": [], 122 | "problemMatcher": [ 123 | "$go" 124 | ], 125 | "presentation": { 126 | "reveal": "always" 127 | }, 128 | "group": "build" 129 | }, 130 | { 131 | "label": "Stop operator debug session", 132 | "command": "make debug-stop -s", 133 | "options": { 134 | "env": { 135 | "ARCH": "amd64" 136 | } 137 | }, 138 | "type": "shell", 139 | "args": [], 140 | "problemMatcher": [ 141 | "$go" 142 | ], 143 | "presentation": { 144 | "reveal": "always" 145 | }, 146 | "group": "build" 147 | }, 148 | ] 149 | } 150 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM registry.access.redhat.com/ubi9/go-toolset:1.20.10 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY main.go main.go 14 | # ToDo: Uncomment once API added 15 | COPY webhooks/ webhooks/ 16 | COPY pkg pkg/ 17 | 18 | # Build 19 | RUN CGO_ENABLED=0 GOOS=linux go build -a -o manager main.go 20 | 21 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 22 | RUN microdnf update --setopt=install_weak_deps=0 -y && microdnf install git 23 | COPY entrypoint.sh /usr/local/bin/entrypoint.sh 24 | RUN chmod +x /usr/local/bin/entrypoint.sh 25 | 26 | ARG ENABLE_WEBHOOKS=true 27 | ENV ENABLE_WEBHOOKS=${ENABLE_WEBHOOKS} 28 | 29 | # disable http/2 on the webhook server by default 30 | ARG ENABLE_WEBHOOK_HTTP2=false 31 | ENV ENABLE_WEBHOOK_HTTP2=${ENABLE_WEBHOOK_HTTP2} 32 | 33 | # Set the Git config for the AppData bot 34 | WORKDIR / 35 | COPY --from=builder /workspace/manager . 36 | 37 | COPY appdata.gitconfig /.gitconfig 38 | RUN chgrp -R 0 /.gitconfig && chmod -R g=u /.gitconfig 39 | 40 | WORKDIR / 41 | 42 | USER 1001 43 | 44 | LABEL description="RHTAP Hybrid Application Service operator" 45 | LABEL com.redhat.component="Hybrid Application Service" 46 | LABEL name="Hybrid Application Service" 47 | LABEL io.k8s.description="RHTAP Hybrid Application Service operator" 48 | LABEL io.k8s.display-name="application-service" 49 | LABEL io.openshift.tags="rhtap" 50 | LABEL summary="RHTAP Hybrid Application Service" 51 | 52 | ENTRYPOINT ["entrypoint.sh"] 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs: https://go.k8s.io/owners 2 | 3 | approvers: 4 | - elsony 5 | - johnmcollier 6 | - maysunfaisal 7 | - michael-valdron 8 | - thepetk 9 | - yangcao77 10 | 11 | reviewers: 12 | - elsony 13 | - johnmcollier 14 | - maysunfaisal 15 | - michael-valdron 16 | - thepetk 17 | - yangcao77 18 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: redhat.com 2 | layout: 3 | - go.kubebuilder.io/v3 4 | plugins: 5 | manifests.sdk.operatorframework.io/v2: {} 6 | scorecard.sdk.operatorframework.io/v2: {} 7 | projectName: application-service 8 | repo: github.com/redhat-appstudio/application-service 9 | version: "3" 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hybrid Application Service (HAS) 2 | 3 | [![codecov](https://codecov.io/gh/redhat-appstudio/application-service/branch/main/graph/badge.svg)](https://codecov.io/gh/redhat-appstudio/application-service) 4 | 5 | ## Overview 6 | 7 | A Kubernetes operator to create, manage and control the lifecycle of applications and components. 8 | 9 | This repository is closely associated with the [application-api](https://github.com/konflux-ci/application-api/) repository, which contains the Kubernetes CRD definitions for the application-service specific resources - `Application`, `Component` and `ComponentDetectionQuery`. 10 | 11 | ## Documentation 12 | 13 | ### ⚡ Project Info 14 | * [HAS/application-service project information page](https://docs.google.com/document/d/1axzNOhRBSkly3M2Y32Pxr1MBpBif2ljb-ufj0_aEt74/edit?usp=sharing) - document detailing the project 15 | * [Google Drive](https://drive.google.com/drive/u/0/folders/1pqESr0oc2ldtfj9RDx65vD_KdkgY_G9h) - information for people new to the project 16 | 17 | ### 🔥 Developers 18 | 19 | * [Build, Test and Deploy](./docs/build-test-and-deploy.md) - build, test and deploy the application-service 20 | * [Serviceability](./docs/serviceability.md) - serviceability information like accessing and understanding logs, debugging, common problems and FAQs 21 | 22 | ### ⭐ Other Info 23 | 24 | * [Pact tests](./docs/pact-tests.md) - contract tests using a Pact framework (part of the unit tests) 25 | * [OpenShift CI job artifacts](https://docs.ci.openshift.org/docs/how-tos/artifacts/) - Prow job executed by the CI system generates an artifacts directory, this document describes the contents of this directory and how they can be used to investigate the job steps 26 | 27 | ## Release 28 | 29 | For more information on the application-service release policy, please read the release [guideline](./docs/release.md). 30 | 31 | ## Contributions 32 | 33 | If you would like to contribute to application-service, please be so kind to read our [CONTRIBUTING](./docs/CONTRIBUTING.md) guide for more information. 34 | -------------------------------------------------------------------------------- /appdata.gitconfig: -------------------------------------------------------------------------------- 1 | [user] 2 | name = AppData Robot 3 | email = 95716864+appstudio-appdata-bot@users.noreply.github.com 4 | -------------------------------------------------------------------------------- /bundle.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | # Core bundle labels. 4 | LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 5 | LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ 6 | LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ 7 | LABEL operators.operatorframework.io.bundle.package.v1=application-service 8 | LABEL operators.operatorframework.io.bundle.channels.v1=alpha 9 | LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.13.0+git 10 | LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 11 | LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3 12 | 13 | # Labels for testing. 14 | LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 15 | LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ 16 | 17 | # Copy files to locations specified by labels. 18 | COPY bundle/manifests /manifests/ 19 | COPY bundle/metadata /metadata/ 20 | COPY bundle/tests/scorecard /tests/scorecard/ 21 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-application-editor-role_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 7 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 8 | name: application-service-application-editor-role 9 | rules: 10 | - apiGroups: 11 | - appstudio.redhat.com 12 | resources: 13 | - applications 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - appstudio.redhat.com 24 | resources: 25 | - applications/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-application-viewer-role_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | rbac.authorization.k8s.io/aggregate-to-view: "true" 7 | name: application-service-application-viewer-role 8 | rules: 9 | - apiGroups: 10 | - appstudio.redhat.com 11 | resources: 12 | - applications 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - apiGroups: 18 | - appstudio.redhat.com 19 | resources: 20 | - applications/status 21 | verbs: 22 | - get 23 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-component-editor-role_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 7 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 8 | name: application-service-component-editor-role 9 | rules: 10 | - apiGroups: 11 | - appstudio.redhat.com 12 | resources: 13 | - components 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - appstudio.redhat.com 24 | resources: 25 | - components/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-component-viewer-role_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | rbac.authorization.k8s.io/aggregate-to-view: "true" 7 | name: application-service-component-viewer-role 8 | rules: 9 | - apiGroups: 10 | - appstudio.redhat.com 11 | resources: 12 | - components 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - apiGroups: 18 | - appstudio.redhat.com 19 | resources: 20 | - components/status 21 | verbs: 22 | - get 23 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-componentdetectionquery-editor-role_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 7 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 8 | name: application-service-componentdetectionquery-editor-role 9 | rules: 10 | - apiGroups: 11 | - appstudio.redhat.com 12 | resources: 13 | - componentdetectionqueries 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - appstudio.redhat.com 24 | resources: 25 | - componentdetectionqueries/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-componentdetectionquery-viewer-role_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | rbac.authorization.k8s.io/aggregate-to-view: "true" 7 | name: application-service-componentdetectionquery-viewer-role 8 | rules: 9 | - apiGroups: 10 | - appstudio.redhat.com 11 | resources: 12 | - componentdetectionqueries 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - apiGroups: 18 | - appstudio.redhat.com 19 | resources: 20 | - componentdetectionqueries/status 21 | verbs: 22 | - get 23 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-controller-manager-metrics-service_v1_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | control-plane: controller-manager 7 | name: application-service-controller-manager-metrics-service 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | status: 17 | loadBalancer: {} 18 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-devfile-registry-config_v1_configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | DEVFILE_REGISTRY_URL: "" 4 | kind: ConfigMap 5 | metadata: 6 | name: application-service-devfile-registry-config 7 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-github-config_v1_configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | GITHUB_ORG: "" 4 | kind: ConfigMap 5 | metadata: 6 | name: application-service-github-config 7 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-has-config_v1_configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | DEVFILE_REGISTRY_URL: "" 4 | GITHUB_ORG: "" 5 | kind: ConfigMap 6 | metadata: 7 | name: application-service-has-config 8 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-manager-config_v1_configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | controller_manager_config.yaml: | 4 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 5 | kind: ControllerManagerConfig 6 | health: 7 | healthProbeBindAddress: :8081 8 | metrics: 9 | bindAddress: 127.0.0.1:8080 10 | webhook: 11 | port: 9443 12 | leaderElection: 13 | leaderElect: true 14 | resourceName: f50829e1.redhat.com 15 | kind: ConfigMap 16 | metadata: 17 | name: application-service-manager-config 18 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-manager-rolebinding-appsnapshot_rbac.authorization.k8s.io_v1_clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | creationTimestamp: null 5 | name: application-service-manager-rolebinding-appsnapshot 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: ClusterRole 9 | name: manager-role-appsnapshot 10 | subjects: 11 | - kind: ServiceAccount 12 | name: application-service-controller-manager 13 | namespace: application-service-system 14 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | creationTimestamp: null 5 | name: application-service-metrics-reader 6 | rules: 7 | - nonResourceURLs: 8 | - /metrics 9 | verbs: 10 | - get 11 | -------------------------------------------------------------------------------- /bundle/manifests/application-service-webhook-service_v1_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | service.beta.openshift.io/serving-cert-secret-name: webhook-server-cert 6 | creationTimestamp: null 7 | name: application-service-webhook-service 8 | spec: 9 | ports: 10 | - port: 443 11 | protocol: TCP 12 | targetPort: 9443 13 | selector: 14 | control-plane: controller-manager 15 | status: 16 | loadBalancer: {} 17 | -------------------------------------------------------------------------------- /bundle/manifests/appstudio.redhat.com_applications.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | annotations: 5 | controller-gen.kubebuilder.io/version: v0.6.1 6 | creationTimestamp: null 7 | name: applications.appstudio.redhat.com 8 | spec: 9 | conversion: 10 | strategy: Webhook 11 | webhook: 12 | clientConfig: 13 | service: 14 | name: application-service-webhook-service 15 | namespace: application-service-system 16 | path: /convert 17 | conversionReviewVersions: 18 | - v1 19 | group: appstudio.redhat.com 20 | names: 21 | kind: Application 22 | listKind: ApplicationList 23 | plural: applications 24 | shortNames: 25 | - hasapp 26 | - ha 27 | - app 28 | singular: application 29 | scope: Namespaced 30 | versions: 31 | - name: v1alpha1 32 | schema: 33 | openAPIV3Schema: 34 | description: Application is the Schema for the applications API 35 | properties: 36 | apiVersion: 37 | description: 'APIVersion defines the versioned schema of this representation 38 | of an object. Servers should convert recognized schemas to the latest 39 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 40 | type: string 41 | kind: 42 | description: 'Kind is a string value representing the REST resource this 43 | object represents. Servers may infer this from the endpoint the client 44 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 45 | type: string 46 | metadata: 47 | type: object 48 | spec: 49 | description: ApplicationSpec defines the desired state of Application 50 | properties: 51 | appModelRepository: 52 | description: AppModelRepository refers to the git repository that 53 | will store the application model (a devfile) Can be the same as 54 | GitOps repository. A repository will be generated if this field 55 | is left blank. 56 | properties: 57 | branch: 58 | description: Branch corresponds to the branch in the repository 59 | that should be used 60 | type: string 61 | context: 62 | description: Context corresponds to the context within the repository 63 | that should be used 64 | type: string 65 | url: 66 | description: URL refers to the repository URL that should be used. 67 | type: string 68 | required: 69 | - url 70 | type: object 71 | description: 72 | description: Description refers to a brief description of the application. 73 | type: string 74 | displayName: 75 | description: DisplayName refers to the name that an application will 76 | be deployed with in App Studio. 77 | type: string 78 | gitOpsRepository: 79 | description: GitOpsRepository refers to the git repository that will 80 | store the gitops resources. Can be the same as App Model Repository. 81 | A repository will be generated if this field is left blank. 82 | properties: 83 | branch: 84 | description: Branch corresponds to the branch in the repository 85 | that should be used 86 | type: string 87 | context: 88 | description: Context corresponds to the context within the repository 89 | that should be used 90 | type: string 91 | url: 92 | description: URL refers to the repository URL that should be used. 93 | type: string 94 | required: 95 | - url 96 | type: object 97 | required: 98 | - displayName 99 | type: object 100 | status: 101 | description: ApplicationStatus defines the observed state of Application 102 | properties: 103 | conditions: 104 | description: 'INSERT ADDITIONAL STATUS FIELD - define observed state 105 | of cluster Important: Run "make" to regenerate code after modifying 106 | this file' 107 | items: 108 | description: "Condition contains details for one aspect of the current 109 | state of this API Resource. --- This struct is intended for direct 110 | use as an array at the field path .status.conditions. For example, 111 | type FooStatus struct{ // Represents the observations of a 112 | foo's current state. // Known .status.conditions.type are: 113 | \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type 114 | \ // +patchStrategy=merge // +listType=map // +listMapKey=type 115 | \ Conditions []metav1.Condition `json:\"conditions,omitempty\" 116 | patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` 117 | \n // other fields }" 118 | properties: 119 | lastTransitionTime: 120 | description: lastTransitionTime is the last time the condition 121 | transitioned from one status to another. This should be when 122 | the underlying condition changed. If that is not known, then 123 | using the time when the API field changed is acceptable. 124 | format: date-time 125 | type: string 126 | message: 127 | description: message is a human readable message indicating 128 | details about the transition. This may be an empty string. 129 | maxLength: 32768 130 | type: string 131 | observedGeneration: 132 | description: observedGeneration represents the .metadata.generation 133 | that the condition was set based upon. For instance, if .metadata.generation 134 | is currently 12, but the .status.conditions[x].observedGeneration 135 | is 9, the condition is out of date with respect to the current 136 | state of the instance. 137 | format: int64 138 | minimum: 0 139 | type: integer 140 | reason: 141 | description: reason contains a programmatic identifier indicating 142 | the reason for the condition's last transition. Producers 143 | of specific condition types may define expected values and 144 | meanings for this field, and whether the values are considered 145 | a guaranteed API. The value should be a CamelCase string. 146 | This field may not be empty. 147 | maxLength: 1024 148 | minLength: 1 149 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ 150 | type: string 151 | status: 152 | description: status of the condition, one of True, False, Unknown. 153 | enum: 154 | - "True" 155 | - "False" 156 | - Unknown 157 | type: string 158 | type: 159 | description: type of condition in CamelCase or in foo.example.com/CamelCase. 160 | --- Many .condition.type values are consistent across resources 161 | like Available, but because arbitrary conditions can be useful 162 | (see .node.status.conditions), the ability to deconflict is 163 | important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) 164 | maxLength: 316 165 | pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ 166 | type: string 167 | required: 168 | - lastTransitionTime 169 | - message 170 | - reason 171 | - status 172 | - type 173 | type: object 174 | type: array 175 | devfile: 176 | description: Devfile corresponds to the devfile representation of 177 | the Application resource 178 | type: string 179 | required: 180 | - conditions 181 | type: object 182 | type: object 183 | served: true 184 | storage: true 185 | subresources: 186 | status: {} 187 | status: 188 | acceptedNames: 189 | kind: "" 190 | plural: "" 191 | conditions: [] 192 | storedVersions: [] 193 | -------------------------------------------------------------------------------- /bundle/metadata/annotations.yaml: -------------------------------------------------------------------------------- 1 | annotations: 2 | # Core bundle annotations. 3 | operators.operatorframework.io.bundle.mediatype.v1: registry+v1 4 | operators.operatorframework.io.bundle.manifests.v1: manifests/ 5 | operators.operatorframework.io.bundle.metadata.v1: metadata/ 6 | operators.operatorframework.io.bundle.package.v1: application-service 7 | operators.operatorframework.io.bundle.channels.v1: alpha 8 | operators.operatorframework.io.metrics.builder: operator-sdk-v1.13.0+git 9 | operators.operatorframework.io.metrics.mediatype.v1: metrics+v1 10 | operators.operatorframework.io.metrics.project_layout: go.kubebuilder.io/v3 11 | 12 | # Annotations for testing. 13 | operators.operatorframework.io.test.mediatype.v1: scorecard+v1 14 | operators.operatorframework.io.test.config.v1: tests/scorecard/ 15 | -------------------------------------------------------------------------------- /bundle/tests/scorecard/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: 8 | - entrypoint: 9 | - scorecard-test 10 | - basic-check-spec 11 | image: quay.io/operator-framework/scorecard-test:v1.13.1 12 | labels: 13 | suite: basic 14 | test: basic-check-spec-test 15 | storage: 16 | spec: 17 | mountPath: {} 18 | - entrypoint: 19 | - scorecard-test 20 | - olm-bundle-validation 21 | image: quay.io/operator-framework/scorecard-test:v1.13.1 22 | labels: 23 | suite: olm 24 | test: olm-bundle-validation-test 25 | storage: 26 | spec: 27 | mountPath: {} 28 | - entrypoint: 29 | - scorecard-test 30 | - olm-crds-have-validation 31 | image: quay.io/operator-framework/scorecard-test:v1.13.1 32 | labels: 33 | suite: olm 34 | test: olm-crds-have-validation-test 35 | storage: 36 | spec: 37 | mountPath: {} 38 | - entrypoint: 39 | - scorecard-test 40 | - olm-crds-have-resources 41 | image: quay.io/operator-framework/scorecard-test:v1.13.1 42 | labels: 43 | suite: olm 44 | test: olm-crds-have-resources-test 45 | storage: 46 | spec: 47 | mountPath: {} 48 | - entrypoint: 49 | - scorecard-test 50 | - olm-spec-descriptors 51 | image: quay.io/operator-framework/scorecard-test:v1.13.1 52 | labels: 53 | suite: olm 54 | test: olm-spec-descriptors-test 55 | storage: 56 | spec: 57 | mountPath: {} 58 | - entrypoint: 59 | - scorecard-test 60 | - olm-status-descriptors 61 | image: quay.io/operator-framework/scorecard-test:v1.13.1 62 | labels: 63 | suite: olm 64 | test: olm-status-descriptors-test 65 | storage: 66 | spec: 67 | mountPath: {} 68 | storage: 69 | spec: 70 | mountPath: {} 71 | -------------------------------------------------------------------------------- /check-manager-kustomize.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | THIS_DIR="$(dirname "$(realpath "$0")")" 4 | MANAGER_KUSTOMIZATION="$( realpath ${THIS_DIR}/config/manager/kustomization.yaml)" 5 | 6 | cat ${MANAGER_KUSTOMIZATION} | grep "newName: quay.io/redhat-appstudio/application-service" 7 | 8 | exit $? -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "api/v1alpha1/zz_generated.deepcopy.go" # generated file, does not need to be included in the coverage 3 | - "pkg/spi/spi_mock.go" # mock file for SPI testing 4 | - "gitops/generate_mock.go" # mock file for testing 5 | - "controllers/start_test_env.go" # setup of a test environment for unit and Pact tests 6 | - "controllers/application_pact_test_state_handlers.go" # state handlers for the Pact tests 7 | - "controllers/application_pact_test_utils.go" # utils file for the Pact tests 8 | - "cdq-analysis/pkg/detect_mock.go" # mock file for testing CDQ detection logic 9 | - "cdq-analysis/pkg/mock.go" # mock file for CDQUtil interface methods 10 | - "cdq-analysis/main.go" # entry point for cdq-analysis docker image 11 | - "pkg/github/mock.go" # mock file for testing 12 | - "pkg/github/token_mock.go" # mock file for testing 13 | - "contracts/" # contract testing files 14 | coverage: 15 | status: 16 | # Allows coverage to drop by a 2% when compared against the base commit. 17 | project: 18 | default: 19 | target: auto 20 | threshold: 2% 21 | patch: 22 | default: 23 | informational: true 24 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. 4 | apiVersion: cert-manager.io/v1 5 | kind: Issuer 6 | metadata: 7 | name: selfsigned-issuer 8 | namespace: system 9 | spec: 10 | selfSigned: {} 11 | --- 12 | apiVersion: cert-manager.io/v1 13 | kind: Certificate 14 | metadata: 15 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 16 | namespace: system 17 | spec: 18 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 19 | dnsNames: 20 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 22 | issuerRef: 23 | kind: Issuer 24 | name: selfsigned-issuer 25 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 26 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: application-service-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: application-service- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../rbac 17 | - ../manager 18 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 19 | # crd/kustomization.yaml 20 | - ../webhook 21 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 22 | #- ../certmanager 23 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 24 | #- ../prometheus 25 | 26 | patchesStrategicMerge: 27 | # Protect the /metrics endpoint by putting it behind auth. 28 | # If you want your controller-manager to expose the /metrics 29 | # endpoint w/o any authn/z, please comment the following line. 30 | - manager_auth_proxy_patch.yaml 31 | 32 | # Mount the controller config file for loading manager configurations 33 | # through a ComponentConfig type 34 | #- manager_config_patch.yaml 35 | 36 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 37 | # crd/kustomization.yaml 38 | - manager_webhook_patch.yaml 39 | 40 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 41 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 42 | # 'CERTMANAGER' needs to be enabled to use ca injection 43 | - webhookcainjection_patch.yaml 44 | 45 | # the following config is for teaching kustomize how to do var substitution 46 | #vars: 47 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 48 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 49 | # objref: 50 | # kind: Certificate 51 | # group: cert-manager.io 52 | # version: v1 53 | # name: serving-cert # this name should match the one in certificate.yaml 54 | # fieldref: 55 | # fieldpath: metadata.namespace 56 | #- name: CERTIFICATE_NAME 57 | # objref: 58 | # kind: Certificate 59 | # group: cert-manager.io 60 | # version: v1 61 | # name: serving-cert # this name should match the one in certificate.yaml 62 | #- name: SERVICE_NAMESPACE # namespace of the service 63 | # objref: 64 | # kind: Service 65 | # version: v1 66 | # name: webhook-service 67 | # fieldref: 68 | # fieldpath: metadata.namespace 69 | #- name: SERVICE_NAME 70 | # objref: 71 | # kind: Service 72 | # version: v1 73 | # name: webhook-service 74 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: registry.redhat.io/openshift4/ose-kube-rbac-proxy-rhel9:v4.18 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | - "--http2-disable=true" 20 | ports: 21 | - containerPort: 8443 22 | protocol: TCP 23 | name: https 24 | securityContext: 25 | allowPrivilegeEscalation: false 26 | readOnlyRootFilesystem: true 27 | resources: 28 | limits: 29 | cpu: 500m 30 | memory: 400Mi 31 | requests: 32 | cpu: 100m 33 | memory: 20Mi 34 | - name: manager 35 | args: 36 | - "--health-probe-bind-address=:8081" 37 | - "--metrics-bind-address=127.0.0.1:8080" 38 | - "--leader-elect" 39 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: mutating-webhook-configuration 7 | annotations: 8 | service.beta.openshift.io/inject-cabundle: "true" 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | service.beta.openshift.io/inject-cabundle: "true" 16 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: f50829e1.redhat.com 12 | -------------------------------------------------------------------------------- /config/manager/devfile_registry.properties: -------------------------------------------------------------------------------- 1 | DEVFILE_REGISTRY_URL 2 | -------------------------------------------------------------------------------- /config/manager/feature_flag.properties: -------------------------------------------------------------------------------- 1 | ENVIRONMENT 2 | -------------------------------------------------------------------------------- /config/manager/github.properties: -------------------------------------------------------------------------------- 1 | GITHUB_ORG 2 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: application-service-system 3 | 4 | resources: 5 | - manager.yaml 6 | 7 | generatorOptions: 8 | disableNameSuffixHash: true 9 | 10 | configMapGenerator: 11 | - files: 12 | - controller_manager_config.yaml 13 | name: manager-config 14 | - envs: 15 | - github.properties 16 | name: github-config 17 | - envs: 18 | - devfile_registry.properties 19 | name: devfile-registry-config 20 | - envs: 21 | - feature_flag.properties 22 | name: feature-flag-config 23 | 24 | apiVersion: kustomize.config.k8s.io/v1beta1 25 | kind: Kustomization 26 | images: 27 | - name: controller 28 | newName: quay.io/redhat-appstudio/application-service 29 | newTag: next 30 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | control-plane: controller-manager 24 | spec: 25 | volumes: 26 | - name: tmp-storage 27 | emptyDir: {} 28 | securityContext: 29 | runAsNonRoot: true 30 | containers: 31 | - command: 32 | - entrypoint.sh 33 | args: 34 | - --leader-elect 35 | image: controller:latest 36 | imagePullPolicy: Always 37 | name: manager 38 | securityContext: 39 | allowPrivilegeEscalation: false 40 | readOnlyRootFilesystem: true 41 | ports: 42 | - containerPort: 8081 43 | name: health 44 | protocol: TCP 45 | livenessProbe: 46 | httpGet: 47 | path: /healthz 48 | port: 8081 49 | initialDelaySeconds: 15 50 | periodSeconds: 20 51 | readinessProbe: 52 | httpGet: 53 | path: /readyz 54 | port: 8081 55 | initialDelaySeconds: 5 56 | periodSeconds: 10 57 | resources: 58 | limits: 59 | cpu: 500m 60 | memory: 400Mi 61 | requests: 62 | cpu: 100m 63 | memory: 20Mi 64 | env: 65 | - name: GITHUB_ORG 66 | valueFrom: 67 | configMapKeyRef: 68 | name: github-config 69 | key: GITHUB_ORG 70 | optional: true 71 | - name: GITHUB_AUTH_TOKEN 72 | valueFrom: 73 | secretKeyRef: 74 | name: has-github-token 75 | key: token 76 | optional: true 77 | - name: GITHUB_TOKEN_LIST 78 | valueFrom: 79 | secretKeyRef: 80 | name: has-github-token 81 | key: tokens 82 | optional: true 83 | - name: CDQ_GITHUB_TOKEN 84 | valueFrom: 85 | secretKeyRef: 86 | name: has-github-token 87 | key: cdq-token 88 | optional: true 89 | - name: DEVFILE_REGISTRY_URL 90 | valueFrom: 91 | configMapKeyRef: 92 | name: devfile-registry-config 93 | key: DEVFILE_REGISTRY_URL 94 | optional: true 95 | - name: ENVIRONMENT 96 | valueFrom: 97 | configMapKeyRef: 98 | name: feature-flag-config 99 | key: ENVIRONMENT 100 | optional: true 101 | volumeMounts: 102 | - name: tmp-storage 103 | mountPath: /tmp 104 | readOnly: false 105 | serviceAccountName: controller-manager 106 | terminationGracePeriodSeconds: 10 -------------------------------------------------------------------------------- /config/manifests/bases/application-service.clusterserviceversion.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.coreos.com/v1alpha1 2 | kind: ClusterServiceVersion 3 | metadata: 4 | annotations: 5 | alm-examples: '[]' 6 | capabilities: Basic Install 7 | name: application-service.v0.0.0 8 | namespace: placeholder 9 | spec: 10 | apiservicedefinitions: {} 11 | customresourcedefinitions: 12 | owned: 13 | - description: Application is the Schema for the applications API 14 | displayName: Application 15 | kind: Application 16 | name: applications.appstudio.redhat.com 17 | version: v1alpha1 18 | - description: ComponentDetectionQuery is the Schema for the componentdetectionqueries 19 | API 20 | displayName: Component Detection Query 21 | kind: ComponentDetectionQuery 22 | name: componentdetectionqueries.appstudio.redhat.com 23 | version: v1alpha1 24 | - description: Component is the Schema for the components API 25 | displayName: Component 26 | kind: Component 27 | name: components.appstudio.redhat.com 28 | version: v1alpha1 29 | description: Hybrid Application Service operator 30 | displayName: Hybrid Application Service 31 | icon: 32 | - base64data: PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTIgMTQ1Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2UwMDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlJlZEhhdC1Mb2dvLUhhdC1Db2xvcjwvdGl0bGU+PHBhdGggZD0iTTE1Ny43Nyw2Mi42MWExNCwxNCwwLDAsMSwuMzEsMy40MmMwLDE0Ljg4LTE4LjEsMTcuNDYtMzAuNjEsMTcuNDZDNzguODMsODMuNDksNDIuNTMsNTMuMjYsNDIuNTMsNDRhNi40Myw2LjQzLDAsMCwxLC4yMi0xLjk0bC0zLjY2LDkuMDZhMTguNDUsMTguNDUsMCwwLDAtMS41MSw3LjMzYzAsMTguMTEsNDEsNDUuNDgsODcuNzQsNDUuNDgsMjAuNjksMCwzNi40My03Ljc2LDM2LjQzLTIxLjc3LDAtMS4wOCwwLTEuOTQtMS43My0xMC4xM1oiLz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xMjcuNDcsODMuNDljMTIuNTEsMCwzMC42MS0yLjU4LDMwLjYxLTE3LjQ2YTE0LDE0LDAsMCwwLS4zMS0zLjQybC03LjQ1LTMyLjM2Yy0xLjcyLTcuMTItMy4yMy0xMC4zNS0xNS43My0xNi42QzEyNC44OSw4LjY5LDEwMy43Ni41LDk3LjUxLjUsOTEuNjkuNSw5MCw4LDgzLjA2LDhjLTYuNjgsMC0xMS42NC01LjYtMTcuODktNS42LTYsMC05LjkxLDQuMDktMTIuOTMsMTIuNSwwLDAtOC40MSwyMy43Mi05LjQ5LDI3LjE2QTYuNDMsNi40MywwLDAsMCw0Mi41Myw0NGMwLDkuMjIsMzYuMywzOS40NSw4NC45NCwzOS40NU0xNjAsNzIuMDdjMS43Myw4LjE5LDEuNzMsOS4wNSwxLjczLDEwLjEzLDAsMTQtMTUuNzQsMjEuNzctMzYuNDMsMjEuNzdDNzguNTQsMTA0LDM3LjU4LDc2LjYsMzcuNTgsNTguNDlhMTguNDUsMTguNDUsMCwwLDEsMS41MS03LjMzQzIyLjI3LDUyLC41LDU1LC41LDc0LjIyYzAsMzEuNDgsNzQuNTksNzAuMjgsMTMzLjY1LDcwLjI4LDQ1LjI4LDAsNTYuNy0yMC40OCw1Ni43LTM2LjY1LDAtMTIuNzItMTEtMjcuMTYtMzAuODMtMzUuNzgiLz48L3N2Zz4= 33 | mediatype: image/svg+xml 34 | install: 35 | spec: 36 | deployments: null 37 | strategy: "" 38 | installModes: 39 | - supported: false 40 | type: OwnNamespace 41 | - supported: false 42 | type: SingleNamespace 43 | - supported: false 44 | type: MultiNamespace 45 | - supported: true 46 | type: AllNamespaces 47 | keywords: 48 | - has 49 | - appstudio 50 | - redhat 51 | - kubernetes 52 | - application-service 53 | links: 54 | - name: Application Service 55 | url: https://application-service.domain 56 | maintainers: 57 | - email: jcollier@redhat.com 58 | name: John Collier 59 | - email: mfaisal@redhat.com 60 | name: Maysun Faisal 61 | - email: eyuen@redhat.com 62 | name: Elson Yuen 63 | maturity: alpha 64 | provider: 65 | name: Red Hat 66 | version: 0.0.0 67 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/application-service.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | 9 | # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. 10 | # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. 11 | # These patches remove the unnecessary "cert" volume and its manager container volumeMount. 12 | #patchesJson6902: 13 | #- target: 14 | # group: apps 15 | # version: v1 16 | # kind: Deployment 17 | # name: controller-manager 18 | # namespace: system 19 | # patch: |- 20 | # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. 21 | # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. 22 | # - op: remove 23 | # path: /spec/template/spec/containers/1/volumeMounts/0 24 | # # Remove the "cert" volume, since OLM will create and mount a set of certs. 25 | # # Update the indices in this path if adding or removing volumes in the manager's Deployment. 26 | # - op: remove 27 | # path: /spec/template/spec/volumes/0 28 | -------------------------------------------------------------------------------- /config/monitoring/grafana-dashboards/has-gitops-repo-metrics.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": { 7 | "type": "grafana", 8 | "uid": "-- Grafana --" 9 | }, 10 | "enable": true, 11 | "hide": true, 12 | "iconColor": "rgba(0, 211, 255, 1)", 13 | "name": "Annotations & Alerts", 14 | "target": { 15 | "limit": 100, 16 | "matchAny": false, 17 | "tags": [], 18 | "type": "dashboard" 19 | }, 20 | "type": "dashboard" 21 | } 22 | ] 23 | }, 24 | "description": "", 25 | "editable": true, 26 | "fiscalYearStartMonth": 0, 27 | "graphTooltip": 0, 28 | "id": 13, 29 | "links": [], 30 | "liveNow": false, 31 | "panels": [ 32 | { 33 | "description": "The rate of gitops repo creation requests", 34 | "fieldConfig": { 35 | "defaults": { 36 | "color": { 37 | "mode": "continuous-GrYlRd" 38 | }, 39 | "custom": { 40 | "axisCenteredZero": false, 41 | "axisColorMode": "text", 42 | "axisLabel": "", 43 | "axisPlacement": "auto", 44 | "barAlignment": 0, 45 | "drawStyle": "line", 46 | "fillOpacity": 20, 47 | "gradientMode": "scheme", 48 | "hideFrom": { 49 | "legend": false, 50 | "tooltip": false, 51 | "viz": false 52 | }, 53 | "lineInterpolation": "smooth", 54 | "lineWidth": 3, 55 | "pointSize": 5, 56 | "scaleDistribution": { 57 | "type": "linear" 58 | }, 59 | "showPoints": "auto", 60 | "spanNulls": false, 61 | "stacking": { 62 | "group": "A", 63 | "mode": "none" 64 | }, 65 | "thresholdsStyle": { 66 | "mode": "off" 67 | } 68 | }, 69 | "mappings": [], 70 | "thresholds": { 71 | "mode": "absolute", 72 | "steps": [ 73 | { 74 | "color": "green", 75 | "value": null 76 | }, 77 | { 78 | "color": "red", 79 | "value": 80 80 | } 81 | ] 82 | }, 83 | "unit": "reqps" 84 | }, 85 | "overrides": [] 86 | }, 87 | "gridPos": { 88 | "h": 11, 89 | "w": 24, 90 | "x": 0, 91 | "y": 0 92 | }, 93 | "id": 2, 94 | "options": { 95 | "legend": { 96 | "calcs": [ 97 | "lastNotNull" 98 | ], 99 | "displayMode": "list", 100 | "placement": "bottom", 101 | "showLegend": true 102 | }, 103 | "tooltip": { 104 | "mode": "single", 105 | "sort": "none" 106 | } 107 | }, 108 | "targets": [ 109 | { 110 | "editorMode": "code", 111 | "expr": "sum(rate(has_gitops_repo_creation_total[5m]))", 112 | "legendFormat": "__auto", 113 | "range": true, 114 | "refId": "A" 115 | } 116 | ], 117 | "title": "Gitops Repo Creation Requests", 118 | "type": "timeseries" 119 | }, 120 | { 121 | "description": "Percentage of successful gitops repo creation requests", 122 | "fieldConfig": { 123 | "defaults": { 124 | "mappings": [], 125 | "max": 1, 126 | "thresholds": { 127 | "mode": "absolute", 128 | "steps": [ 129 | { 130 | "color": "green", 131 | "value": null 132 | }, 133 | { 134 | "color": "orange", 135 | "value": 70 136 | }, 137 | { 138 | "color": "red", 139 | "value": 85 140 | } 141 | ] 142 | }, 143 | "unit": "percentunit" 144 | }, 145 | "overrides": [] 146 | }, 147 | "gridPos": { 148 | "h": 8, 149 | "w": 12, 150 | "x": 0, 151 | "y": 11 152 | }, 153 | "id": 4, 154 | "options": { 155 | "orientation": "auto", 156 | "reduceOptions": { 157 | "calcs": [ 158 | "mean" 159 | ], 160 | "fields": "", 161 | "values": false 162 | }, 163 | "showThresholdLabels": false, 164 | "showThresholdMarkers": true 165 | }, 166 | "pluginVersion": "9.1.6", 167 | "targets": [ 168 | { 169 | "editorMode": "code", 170 | "expr": "1-sum(increase(has_gitops_failed_repo_creation_total[$__range]))/sum(increase(has_gitops_repo_creation_total[$__range]))", 171 | "legendFormat": "__auto", 172 | "range": true, 173 | "refId": "A" 174 | } 175 | ], 176 | "title": "Successful Gitops Repo Creation", 177 | "type": "gauge" 178 | }, 179 | { 180 | "description": "Percentage of failed gitops repo creation requests", 181 | "fieldConfig": { 182 | "defaults": { 183 | "mappings": [], 184 | "thresholds": { 185 | "mode": "absolute", 186 | "steps": [ 187 | { 188 | "color": "green", 189 | "value": null 190 | }, 191 | { 192 | "color": "orange", 193 | "value": 70 194 | }, 195 | { 196 | "color": "red", 197 | "value": 85 198 | } 199 | ] 200 | }, 201 | "unit": "percentunit" 202 | }, 203 | "overrides": [] 204 | }, 205 | "gridPos": { 206 | "h": 8, 207 | "w": 12, 208 | "x": 12, 209 | "y": 11 210 | }, 211 | "id": 6, 212 | "options": { 213 | "orientation": "auto", 214 | "reduceOptions": { 215 | "calcs": [ 216 | "mean" 217 | ], 218 | "fields": "", 219 | "values": false 220 | }, 221 | "showThresholdLabels": false, 222 | "showThresholdMarkers": true 223 | }, 224 | "pluginVersion": "9.1.6", 225 | "targets": [ 226 | { 227 | "editorMode": "code", 228 | "expr": "sum(increase(has_gitops_failed_repo_creation_total[$__range]))/sum(increase(has_gitops_repo_creation_total[$__range]))", 229 | "legendFormat": "__auto", 230 | "range": true, 231 | "refId": "A" 232 | } 233 | ], 234 | "title": "Failed Gitops Repo Creation", 235 | "type": "gauge" 236 | } 237 | ], 238 | "schemaVersion": 37, 239 | "style": "dark", 240 | "tags": [], 241 | "templating": {}, 242 | "time": { 243 | "from": "now-24h", 244 | "to": "now" 245 | }, 246 | "timepicker": {}, 247 | "timezone": "", 248 | "title": "Gitops Repo Creation Metrics", 249 | "uid": "5jB4nloVk", 250 | "version": 11, 251 | "weekStart": "" 252 | } -------------------------------------------------------------------------------- /config/monitoring/kustomization.yaml: -------------------------------------------------------------------------------- 1 | kind: Kustomization 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | 4 | namespace: grafana-operator-system 5 | 6 | configMapGenerator: 7 | - name: grafana-dashboard-has-gitops-repo-metrics 8 | files: 9 | - grafana-dashboards/has-gitops-repo-metrics.json 10 | - name: grafana-dashboard-has-rate-limiting-metrics 11 | files: 12 | - grafana-dashboards/has-rate-limiting-metrics.json 13 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: metrics-reader 5 | namespace: application-service # deployment namespace from https://github.com/redhat-appstudio/infra-deployments/blob/main/components/has/base/kustomization.yaml#L20 6 | --- 7 | apiVersion: v1 8 | kind: Secret 9 | metadata: 10 | name: metrics-reader 11 | namespace: application-service 12 | annotations: 13 | kubernetes.io/service-account.name: metrics-reader 14 | type: kubernetes.io/service-account-token 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1 17 | kind: ClusterRoleBinding 18 | metadata: 19 | name: prometheus-application-service-metrics-reader 20 | roleRef: 21 | apiGroup: rbac.authorization.k8s.io 22 | kind: ClusterRole 23 | name: metrics-reader 24 | subjects: 25 | - kind: ServiceAccount 26 | name: metrics-reader 27 | namespace: application-service 28 | --- 29 | apiVersion: monitoring.coreos.com/v1 30 | kind: ServiceMonitor 31 | metadata: 32 | name: service-monitor 33 | namespace: application-service 34 | spec: 35 | endpoints: 36 | - path: /metrics 37 | port: https 38 | scheme: https 39 | bearerTokenSecret: 40 | name: "metrics-reader" 41 | key: token 42 | tlsConfig: 43 | insecureSkipVerify: true 44 | selector: 45 | matchLabels: 46 | control-plane: controller-manager -------------------------------------------------------------------------------- /config/rbac/application_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit applications. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: application-editor-role 6 | labels: 7 | rbac.authorization.k8s.io/aggregate-to-edit: 'true' 8 | rbac.authorization.k8s.io/aggregate-to-admin: 'true' 9 | rules: 10 | - apiGroups: 11 | - appstudio.redhat.com 12 | resources: 13 | - applications 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - appstudio.redhat.com 24 | resources: 25 | - applications/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/application_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view applications. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: application-viewer-role 6 | labels: 7 | rbac.authorization.k8s.io/aggregate-to-view: 'true' 8 | rules: 9 | - apiGroups: 10 | - appstudio.redhat.com 11 | resources: 12 | - applications 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - apiGroups: 18 | - appstudio.redhat.com 19 | resources: 20 | - applications/status 21 | verbs: 22 | - get 23 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /config/rbac/component_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit components. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: component-editor-role 6 | labels: 7 | rbac.authorization.k8s.io/aggregate-to-edit: 'true' 8 | rbac.authorization.k8s.io/aggregate-to-admin: 'true' 9 | rules: 10 | - apiGroups: 11 | - appstudio.redhat.com 12 | resources: 13 | - components 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - appstudio.redhat.com 24 | resources: 25 | - components/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/component_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view components. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: component-viewer-role 6 | labels: 7 | rbac.authorization.k8s.io/aggregate-to-view: 'true' 8 | rules: 9 | - apiGroups: 10 | - appstudio.redhat.com 11 | resources: 12 | - components 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - apiGroups: 18 | - appstudio.redhat.com 19 | resources: 20 | - components/status 21 | verbs: 22 | - get 23 | -------------------------------------------------------------------------------- /config/rbac/componentdetectionquery_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit componentdetectionqueries. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: componentdetectionquery-editor-role 6 | labels: 7 | rbac.authorization.k8s.io/aggregate-to-edit: 'true' 8 | rbac.authorization.k8s.io/aggregate-to-admin: 'true' 9 | rules: 10 | - apiGroups: 11 | - appstudio.redhat.com 12 | resources: 13 | - componentdetectionqueries 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - appstudio.redhat.com 24 | resources: 25 | - componentdetectionqueries/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/componentdetectionquery_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view componentdetectionqueries. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: componentdetectionquery-viewer-role 6 | labels: 7 | rbac.authorization.k8s.io/aggregate-to-view: 'true' 8 | rules: 9 | - apiGroups: 10 | - appstudio.redhat.com 11 | resources: 12 | - componentdetectionqueries 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - apiGroups: 18 | - appstudio.redhat.com 19 | resources: 20 | - componentdetectionqueries/status 21 | verbs: 22 | - get 23 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | 13 | # Aggegating roles which are otherwise done by OLM 14 | - application_editor_role.yaml 15 | - application_viewer_role.yaml 16 | - component_editor_role.yaml 17 | - component_viewer_role.yaml 18 | - componentdetectionquery_editor_role.yaml 19 | - componentdetectionquery_viewer_role.yaml 20 | 21 | # Comment the following 4 lines if you want to disable 22 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 23 | # which protects your /metrics endpoint. 24 | - auth_proxy_service.yaml 25 | - auth_proxy_role.yaml 26 | - auth_proxy_role_binding.yaml 27 | - auth_proxy_client_clusterrole.yaml 28 | 29 | ## Tekton Builds 30 | - role_build.yaml 31 | 32 | ## SPI Roles 33 | - role_spi.yaml 34 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | creationTimestamp: null 6 | name: manager-role 7 | rules: 8 | - apiGroups: 9 | - appstudio.redhat.com 10 | resources: 11 | - applications 12 | verbs: 13 | - create 14 | - delete 15 | - get 16 | - list 17 | - patch 18 | - update 19 | - watch 20 | - apiGroups: 21 | - appstudio.redhat.com 22 | resources: 23 | - applications/finalizers 24 | verbs: 25 | - update 26 | - apiGroups: 27 | - appstudio.redhat.com 28 | resources: 29 | - applications/status 30 | verbs: 31 | - get 32 | - patch 33 | - update 34 | - apiGroups: 35 | - appstudio.redhat.com 36 | resources: 37 | - componentdetectionqueries 38 | verbs: 39 | - create 40 | - delete 41 | - get 42 | - list 43 | - patch 44 | - update 45 | - watch 46 | - apiGroups: 47 | - appstudio.redhat.com 48 | resources: 49 | - componentdetectionqueries/finalizers 50 | verbs: 51 | - update 52 | - apiGroups: 53 | - appstudio.redhat.com 54 | resources: 55 | - componentdetectionqueries/status 56 | verbs: 57 | - get 58 | - patch 59 | - update 60 | - apiGroups: 61 | - appstudio.redhat.com 62 | resources: 63 | - components 64 | verbs: 65 | - create 66 | - delete 67 | - get 68 | - list 69 | - patch 70 | - update 71 | - watch 72 | - apiGroups: 73 | - appstudio.redhat.com 74 | resources: 75 | - components/finalizers 76 | verbs: 77 | - update 78 | - apiGroups: 79 | - appstudio.redhat.com 80 | resources: 81 | - components/status 82 | verbs: 83 | - get 84 | - patch 85 | - update 86 | - apiGroups: 87 | - appstudio.redhat.com 88 | resources: 89 | - spifilecontentrequests 90 | verbs: 91 | - create 92 | - get 93 | - list 94 | - apiGroups: 95 | - appstudio.redhat.com 96 | resources: 97 | - spifilecontentrequests/finalizers 98 | verbs: 99 | - update 100 | - apiGroups: 101 | - appstudio.redhat.com 102 | resources: 103 | - spifilecontentrequests/status 104 | verbs: 105 | - get 106 | - apiGroups: 107 | - batch 108 | resources: 109 | - jobs 110 | verbs: 111 | - create 112 | - delete 113 | - get 114 | - list 115 | - update 116 | - watch 117 | - apiGroups: 118 | - "" 119 | resources: 120 | - configmaps 121 | verbs: 122 | - create 123 | - delete 124 | - get 125 | - list 126 | - update 127 | - watch 128 | - apiGroups: 129 | - "" 130 | resources: 131 | - secrets 132 | verbs: 133 | - get 134 | - list 135 | - watch 136 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | --- 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | kind: ClusterRoleBinding 16 | metadata: 17 | name: manager-rolebinding-build 18 | roleRef: 19 | apiGroup: rbac.authorization.k8s.io 20 | kind: ClusterRole 21 | name: manager-role-build 22 | subjects: 23 | - kind: ServiceAccount 24 | name: controller-manager 25 | namespace: system 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | name: manager-rolebinding-spi 31 | roleRef: 32 | apiGroup: rbac.authorization.k8s.io 33 | kind: ClusterRole 34 | name: manager-role-spi 35 | subjects: 36 | - kind: ServiceAccount 37 | name: controller-manager 38 | namespace: system 39 | -------------------------------------------------------------------------------- /config/rbac/role_build.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | name: manager-role-build 7 | rules: 8 | - verbs: 9 | - create 10 | - delete 11 | - deletecollection 12 | - get 13 | - list 14 | - patch 15 | - update 16 | - watch 17 | apiGroups: 18 | - tekton.dev 19 | resources: 20 | - pipelineruns 21 | - apiGroups: 22 | - triggers.tekton.dev 23 | resources: 24 | - eventlisteners 25 | - triggers 26 | - triggertemplates 27 | verbs: 28 | - create 29 | - update 30 | - patch 31 | - delete 32 | - watch 33 | - list 34 | - verbs: 35 | - get 36 | - list 37 | - create 38 | - watch 39 | - update 40 | resources: 41 | - persistentvolumeclaims 42 | - persistentvolumeclaims/status 43 | - secrets 44 | - serviceaccounts 45 | apiGroups: 46 | - "" 47 | - verbs: 48 | - get 49 | - list 50 | - create 51 | - watch 52 | resources: 53 | - routes 54 | apiGroups: 55 | - route.openshift.io 56 | 57 | -------------------------------------------------------------------------------- /config/rbac/role_spi.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | name: manager-role-spi 7 | rules: 8 | - verbs: 9 | - '*' 10 | apiGroups: 11 | - appstudio.redhat.com 12 | resources: 13 | - spiaccesstokenbindings 14 | - spiaccesstokens 15 | - verbs: 16 | - create # allows addition of credentials only. 17 | - delete 18 | - list 19 | - get 20 | - watch 21 | apiGroups: 22 | - '' 23 | resources: 24 | - secrets -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/samples/application/app-existing-repos.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appstudio.redhat.com/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: application-sample 5 | spec: 6 | # Add fields here 7 | displayName: "petclinic" 8 | description: "application definition for petclinic-app" 9 | gitOpsRepository: 10 | url: "https://github.com/devfile-resources/petclinic-gitops" 11 | appModelRepository: 12 | url: "https://github.com/devfile-resources/petclinic-app" -------------------------------------------------------------------------------- /config/samples/application/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appstudio.redhat.com/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: application-sample 5 | spec: 6 | displayName: "petclinic" 7 | description: "application definition for petclinic-app" -------------------------------------------------------------------------------- /config/samples/appstudio_v1alpha1_application.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appstudio.redhat.com/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: application-sample 5 | spec: 6 | # Add fields here 7 | displayName: "petclinic" 8 | description: "application definition for petclinic-app" 9 | -------------------------------------------------------------------------------- /config/samples/appstudio_v1alpha1_component.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appstudio.redhat.com/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: component-sample 5 | spec: 6 | componentName: backend 7 | application: application-sample 8 | source: 9 | git: 10 | url: https://github.com/devfile-samples/devfile-sample-java-springboot-basic 11 | -------------------------------------------------------------------------------- /config/samples/appstudio_v1alpha1_componentdetectionquery.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appstudio.redhat.com/v1alpha1 2 | kind: ComponentDetectionQuery 3 | metadata: 4 | name: componentdetectionquery-sample 5 | spec: 6 | # Add fields here 7 | foo: bar 8 | -------------------------------------------------------------------------------- /config/samples/component/component-basic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appstudio.redhat.com/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: component-sample 5 | spec: 6 | componentName: backend 7 | application: application-sample 8 | replicas: 1 9 | targetPort: 1111 10 | route: route111 11 | resources: 12 | limits: 13 | memory: "500Mi" 14 | cpu: "2" 15 | storage: "400Mi" 16 | requests: 17 | memory: "400Mi" 18 | cpu: "700m" 19 | storage: "200Mi" 20 | env: 21 | - name: FOO 22 | value: "foo1" 23 | - name: BAR 24 | value: "bar1" 25 | source: 26 | git: 27 | url: https://github.com/devfile-samples/devfile-sample-java-springboot-basic -------------------------------------------------------------------------------- /config/samples/componentdetectionquery/componentdetectionquery-basic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: appstudio.redhat.com/v1alpha1 2 | kind: ComponentDetectionQuery 3 | metadata: 4 | name: componentdetectionquery-sample 5 | spec: 6 | git: 7 | url: https://github.com/devfile-samples/devfile-sample-java-springboot-basic 8 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - appstudio_v1alpha1_application.yaml 4 | - appstudio_v1alpha1_component.yaml 5 | - appstudio_v1alpha1_componentdetectionquery.yaml 6 | #+kubebuilder:scaffold:manifestskustomizesamples 7 | -------------------------------------------------------------------------------- /config/samples/snapshot/snapshot.yaml: -------------------------------------------------------------------------------- 1 | # API type definition is in https://github.com/konflux-ci/application-api/tree/main/api/v1alpha1 2 | apiVersion: appstudio.redhat.com/v1alpha1 3 | kind: Snapshot 4 | metadata: 5 | name: snapshot-sample 6 | spec: 7 | application: application-sample 8 | displayName: my-snapshot 9 | displayDescription: my first snapshot 10 | components: 11 | - name: component-sample 12 | containerImage: quay.io/redhat-appstudio/user-workload:application-service-system-component-sample 13 | - name: component-sample2 14 | containerImage: quay.io/redhat-appstudio/user-workload:application-service-system-component-sample2 15 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.13.1 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.13.1 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.13.1 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.13.1 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.13.1 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.13.1 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | creationTimestamp: null 6 | name: mutating-webhook-configuration 7 | webhooks: 8 | - admissionReviewVersions: 9 | - v1 10 | clientConfig: 11 | service: 12 | name: webhook-service 13 | namespace: system 14 | path: /mutate-appstudio-redhat-com-v1alpha1-application 15 | failurePolicy: Fail 16 | name: mapplication.kb.io 17 | rules: 18 | - apiGroups: 19 | - appstudio.redhat.com 20 | apiVersions: 21 | - v1alpha1 22 | operations: 23 | - CREATE 24 | - UPDATE 25 | resources: 26 | - applications 27 | sideEffects: None 28 | - admissionReviewVersions: 29 | - v1 30 | clientConfig: 31 | service: 32 | name: webhook-service 33 | namespace: system 34 | path: /mutate-appstudio-redhat-com-v1alpha1-component 35 | failurePolicy: Fail 36 | name: mcomponent.kb.io 37 | rules: 38 | - apiGroups: 39 | - appstudio.redhat.com 40 | apiVersions: 41 | - v1alpha1 42 | operations: 43 | - CREATE 44 | - UPDATE 45 | resources: 46 | - components 47 | - components/status 48 | sideEffects: None 49 | --- 50 | apiVersion: admissionregistration.k8s.io/v1 51 | kind: ValidatingWebhookConfiguration 52 | metadata: 53 | creationTimestamp: null 54 | name: validating-webhook-configuration 55 | webhooks: 56 | - admissionReviewVersions: 57 | - v1 58 | clientConfig: 59 | service: 60 | name: webhook-service 61 | namespace: system 62 | path: /validate-appstudio-redhat-com-v1alpha1-application 63 | failurePolicy: Fail 64 | name: vapplication.kb.io 65 | rules: 66 | - apiGroups: 67 | - appstudio.redhat.com 68 | apiVersions: 69 | - v1alpha1 70 | operations: 71 | - CREATE 72 | - UPDATE 73 | resources: 74 | - applications 75 | sideEffects: None 76 | - admissionReviewVersions: 77 | - v1 78 | clientConfig: 79 | service: 80 | name: webhook-service 81 | namespace: system 82 | path: /validate-appstudio-redhat-com-v1alpha1-component 83 | failurePolicy: Fail 84 | name: vcomponent.kb.io 85 | rules: 86 | - apiGroups: 87 | - appstudio.redhat.com 88 | apiVersions: 89 | - v1alpha1 90 | operations: 91 | - CREATE 92 | - UPDATE 93 | resources: 94 | - components 95 | sideEffects: None 96 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | annotations: 8 | service.beta.openshift.io/serving-cert-secret-name: webhook-server-cert 9 | spec: 10 | ports: 11 | - port: 443 12 | protocol: TCP 13 | targetPort: 9443 14 | selector: 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We welcome contributions from the community. Here are a few ways you can help us improve. 4 | 5 | ## Submit a Pull Request 6 | 7 | If you find something you'd like to fix that's obviously broken, create a branch, commit your code, and submit a pull request. 8 | Here's how: 9 | 10 | 1. Fork the repo on GitHub, and then clone it locally. 11 | 2. Create a branch named appropriately for the change you are going to make. 12 | 3. Make your code change. 13 | 4. If you are creating a function, please add a tests for it if possible. 14 | 5. Push your code change up to your forked repo. 15 | 6. Open a Pull Request to merge your changes to this repo. The comment box will be filled in automatically via a template. 16 | 7. All Pull Requests will be subject to Linting checks. Please make sure that your code complies and fix any warnings that arise. These are Checks that appear at the bottom of your Pull Request. 17 | 8. All Pull requests are subject to Testing. 18 | 19 | See [Using Pull Requests](https://help.github.com/articles/using-pull-requests/) got more information on how to use GitHub PRs. 20 | 21 | For an in depth guide on how to contribute see [this article](https://opensource.com/article/19/7/create-pull-request-github) 22 | 23 | ## Certificate of Origin 24 | 25 | By contributing to this project you agree to the Developer Certificate of 26 | Origin (DCO). This document was created by the Linux Kernel community and is a 27 | simple statement that you, as a contributor, have the legal right to make the 28 | contribution. See the [DCO](./DCO) file for details. -------------------------------------------------------------------------------- /docs/DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 1 Letterman Drive 6 | Suite D4700 7 | San Francisco, CA, 94129 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this 10 | license document, but changing it is not allowed. 11 | 12 | 13 | Developer's Certificate of Origin 1.1 14 | 15 | By making a contribution to this project, I certify that: 16 | 17 | (a) The contribution was created in whole or in part by me and I 18 | have the right to submit it under the open source license 19 | indicated in the file; or 20 | 21 | (b) The contribution is based upon previous work that, to the best 22 | of my knowledge, is covered under an appropriate open source 23 | license and I have the right under that license to submit that 24 | work with modifications, whether created in whole or in part 25 | by me, under the same open source license (unless I am 26 | permitted to submit under a different license), as indicated 27 | in the file; or 28 | 29 | (c) The contribution was provided directly to me by some other 30 | person who certified (a), (b) or (c) and I have not modified 31 | it. 32 | 33 | (d) I understand and agree that this project and the contribution 34 | are public and that a record of the contribution (including all 35 | personal information I submit with it, including my sign-off) is 36 | maintained indefinitely and may be redistributed consistent with 37 | this project or the open source license(s) involved. 38 | -------------------------------------------------------------------------------- /docs/build-test-and-deploy.md: -------------------------------------------------------------------------------- 1 | # Build, Test and Deploy 2 | 3 | ## Build 4 | This operator provides a `Makefile` to run all the usual development tasks. If you simply run `make` without any arguments, you'll get a list of available "targets". 5 | 6 | To build the operator binary run: 7 | 8 | ``` 9 | make build 10 | ``` 11 | 12 | To build the docker image of the operator one can run: 13 | 14 | ``` 15 | make docker-build 16 | ``` 17 | 18 | This will make a docker image called `controller:latest` which might or might not be what you want. To override the name of the image build, specify it in the `IMG` environment variable, e.g.: 19 | 20 | ``` 21 | IMG=quay.io/user/hasoperator:next make docker-build 22 | ``` 23 | 24 | To push the image to an image repository one can use: 25 | 26 | ``` 27 | make docker-push 28 | ``` 29 | 30 | The image being pushed can again be modified using the environment variable: 31 | ``` 32 | IMG=quay.io/user/hasoperator:next make docker-push 33 | ``` 34 | 35 | ## Test 36 | 37 | To test the code: 38 | 39 | ``` 40 | make test 41 | ``` 42 | 43 | **Note:** In order for the controller tests to run, follow the instructions for [installing the Pact tools](./installing-pact-tools.md) 44 | 45 | ## Deploy 46 | 47 | The following section outlines the steps to deploy application-service on a physical Kubernetes cluster. 48 | 49 | ### Deploying on a Local Cluster 50 | 51 | #### Creating a GitHub Secret for application-service 52 | 53 | Before deploying the operator, you must ensure that a secret `has-github-token`, exists in the namespace where application-service will be deployed. This secret must contain a key `tokens`, whose value points to a comma separated list without spaces of key-value pairs of token names and tokens, delimited by a colon. 54 | 55 | For example, on OpenShift: 56 | 57 | Screenshot 2023-03-22 at 3 53 11 PM 58 | 59 | Or via command-line: 60 | 61 | ```bash 62 | application-service % kubectl create secret generic has-github-token --from-literal=tokens=token1:ghp_faketoken,token2:ghp_anothertoken,token3:ghp_thirdtoken 63 | ``` 64 | 65 | Any token that is used here must have the following permissions set: 66 | - `repo` 67 | - `delete_repo` 68 | 69 | In addition to this, each GitHub token must be associated with an account that has write access to the GitHub organization you plan on using with application-service. 70 | 71 | #### Using Private Git Repos 72 | 73 | The application-service component requires SPI to be set up in order to work with private git repositories. 74 | 75 | Please refer to the [instructions](./private-git-repos.md) for information on setting up application-service and SPI for use with private git repositories. 76 | 77 | #### Deploy application-service 78 | 79 | 80 | Once a secret has been created, simply run the following commands to deploy application-service: 81 | ``` 82 | make install 83 | make deploy 84 | ``` 85 | 86 | The application-service deployment can be further configured. Please refer to the sections below for your needs. 87 | 88 | #### Specifying Alternate GitHub org 89 | 90 | By default, application-service will use the `redhat-appstudio-appdata` org for the creation of GitOps repositories. If you wish to use your own account, or a different GitHub org, setting `GITHUB_ORG=` before deploying will ensure that an alternate location is used. 91 | 92 | For example: 93 | 94 | `GITHUB_ORG=fake-organization make deploy` would deploy application-service configured to use github.com/fake-organization. 95 | 96 | #### Specifying Alternate Devfile Registry URL 97 | 98 | By default, the production devfile registry URL will be used for `ComponentDetectionQuery`. If you wish to use a different devfile registry, setting `DEVFILE_REGISTRY_URL=` before deploying will ensure that an alternate devfile registry is used. 99 | 100 | For example: 101 | 102 | `DEVFILE_REGISTRY_URL=https://myregistry make deploy` would deploy application-service configured to use https://myregistry. 103 | 104 | #### Enabling HTTP/2 on the Webhook Server 105 | 106 | By default, http/2 on the webhook server is disabled due to CVE-2023-44487. 107 | 108 | If you want to enable http/2 for the webhook server, build with `ENABLE_WEBHOOK_HTTP2=true make docker-build` 109 | 110 | ### Deploying Locally 111 | 112 | #### Disabling Webhooks for Local Development 113 | 114 | Webhooks require self-signed certificates to validate the Kubernetes resources. To disable webhooks during local development and testing, export `ENABLE_WEBHOOKS=false` 115 | 116 | #### Setting the GitHub Token Environment variable 117 | 118 | Either of the Environment variable `GITHUB_AUTH_TOKEN` or `GITHUB_TOKEN_LIST` needs to be set. 119 | 120 | The `GITHUB_AUTH_TOKEN` variable is the legacy format and requires one token, example `GITHUB_AUTH_TOKEN=ghp_faketoken`. The `GITHUB_TOKEN_LIST` can take a list of tokens, example `GITHUB_TOKEN_LIST=token1:ghp_faketoken,token2:ghp_anothertoken`. 121 | 122 | #### Executing the application-service binary 123 | 124 | The application-service controller manager can be run locally on your development environment (example, laptop). For example, to build and run the executable manager: 125 | 126 | ``` 127 | make install 128 | make build 129 | ./bin/manager 130 | ``` 131 | -------------------------------------------------------------------------------- /docs/installing-pact-tools.md: -------------------------------------------------------------------------------- 1 | # Installing Pact Tools 2 | 3 | The Pact tests in the controller package require pact tooling to be installed and on your path. Follow these instructions to do so: 4 | 5 | 1. Change directory to an appropriate folder (e.g. `/usr/local`) 6 | 2. Run `curl -fsSL https://raw.githubusercontent.com/pact-foundation/pact-ruby-standalone/master/install.sh | bash` 7 | 3. Add the pact tools' bin folder (e.g. `/usr/local/pact/bin`) to your path to your shell PATH. Ensure all binary files within the `bin/` folder has executable permissions 8 | 4. Run `go install github.com/pact-foundation/pact-go@v1` to install the `pact-go` tool 9 | 5. Run `pact-go install` to validate that all of the necessary Pact tools are installed 10 | -------------------------------------------------------------------------------- /docs/pact-tests.md: -------------------------------------------------------------------------------- 1 | # HAS contract testing using Pact 2 | 3 | HAS is a participant in contract testing within RHTAP using [Pact](https://pact.io/) framework. It is a provider of an API that is used by HAC-dev. This documentation is about the specifics of provider testing on HAS side. If you want to know more about contract testing using Pact, follow the [official documentation](https://docs.pact.io/). For more information about the RHTAP contract testing, follow the documentation in the [HAC-dev repo](https://github.com/openshift/hac-dev/blob/main/pactTests.md). 4 | 5 | ## Table of content 6 | - [When does test run](#when-does-test-run) 7 | - [Implementation details](#implementation-details) 8 | - [Adding a new verification](#adding-a-new-verification) 9 | - [Failing test](#failing-test) 10 | 11 | ## When does test run 12 | Pact tests are triggered during different life phases of a product. See the table below for the detail. 13 | 14 | | Event | What is checked | Pushed to Pact broker | Implemented | 15 | |-------------|-----------------|-----------------------|-------------| 16 | | Locally as part of unit tests
`make test` | Runs verification against
consumer "main" branch | No | Yes | 17 | | PR update | Runs verification against consumer
"main" branch and all environments | No* | Yes** [link](https://github.com/redhat-appstudio/application-service/blob/main/.github/workflows/pr.yml#L124) | 18 | | PR merge | ? | Yes
commit SHA is a version
tagged by branch "main" | No | 19 | 20 | \* The idea was to push also those tags, but for now, nothing is pushed as we don't have access to the secrets from this GH action. 21 | 22 | \*\* Currently, we don't have any environment specified. There should be a "staging" and "production" environment in the future. 23 | 24 | For more information, follow the [HAC-dev documentation](https://github.com/openshift/hac-dev/blob/main/pactTests.md) also with the [Gating](https://github.com/openshift/hac-dev/blob/main/pactTests.md#gating) chapter. 25 | 26 | 27 | ## Implementation details 28 | Pact tests live in a `controllers` folder. The main test file is an `application_pact_tests.go`. The Pact setup is done there, including the way to obtain the contracts. By default, contracts are downloaded from the Pact broker. If you are developing the new tests and want to specify the contract file locally, there is a section commented out that helps you with the setup. For more information, follow the [officital documentation](https://docs.pact.io/implementation_guides/go/readme#provider-verification). 29 | 30 | The definition of StateHandlers is one of the most important parts of the test setup. They define the state of the provider before the request from the contract is executed. The string defining the state have to be the same as the `providerState` field of the contract. Methods implementing a state are extracted to the `application_pact_test_state_handlers.go` file. 31 | 32 | The place where the tests are executed is the line 33 | ``` 34 | // Run pact tests 35 | _, err = pact.VerifyProvider(t, verifyRequest) 36 | ``` 37 | Pact itself is taking care of downloading the contracts, executing the tests, generating results, and (if configured) pushing them to the Pact broker. 38 | 39 | ## Adding a new verification 40 | When a new contract is created, it would probably include a new state that is not implemented yet on the provider side. Although the Pact tests would probably fail because of the undefined state, an error message may be misleading. Instead of telling you that the state is not defined, the pact just skips the state and executes the request which is probably going to fail. You can see this message in the log: 41 | ``` 42 | [WARN] state handler not found for state: 43 | [DEBUG] skipping state handler for request 44 | 45 | ``` 46 | 47 | To implement the state, add the string from the contract to the `setup state handlers` block. 48 | ``` 49 | verifyRequest.StateHandlers = pactTypes.StateHandlers{ 50 | "No app with the name myapp in the default namespace exists.": func() error { return nil }, 51 | "App myapp exists and has component gh-component and quay-component": <-createAppAndComponents(HASAppNamespace), 52 | } 53 | ``` 54 | Add implementation of this state to the `application_pact_test_state_handlers.go`. Consider adding some logic to the `AfterEach` block if needed. That's it! 55 | 56 | ## Failing test 57 | Pact tests are running locally as part of the unit tests, so you may find them failing once the breaking change is made. If you're not sure why the test is failing, feel free to ping kfoniok. 58 | The best way to deal with the failing contract test is to fix the code change to make it compatible with the contract again. If it is not possible, then you should ping someone from the HAC-dev team to make them aware that the breaking change is coming. If you decide to push the code to the PR with the failing contract tests, it should fail the Pact PR check job there too. 59 | Verification results are not pushed to the Pact broker until the PR is merged. (TBD) PR should be merged only when Pact tests are passing. -------------------------------------------------------------------------------- /docs/private-git-repos.md: -------------------------------------------------------------------------------- 1 | # Using Private Git Repositories with HAS 2 | 3 | Please follow the following instructions to install SPI and use with application-service. 4 | 5 | Note: SPI _cannot_ be used on a Red Hat OpenShift Local (formerly, CRC) cluster. 6 | ## Configuring SPI 7 | 8 | In order to use HAS resources (e.g. `Application`, `Component`, `ComponentDetectionQuery`) with private git repositories, SPI must be installed on the same cluster as HAS (TODO review HAS install instructions): 9 | 10 | 1) Clone the [SPI operator repo](https://github.com/redhat-appstudio/service-provider-integration-operator) and run the [make command](https://github.com/redhat-appstudio/service-provider-integration-operator/blob/main/docs/DEVELOP.md#running-in-cluster) corresponding to your target cluster type e.g. `make deploy_openshift` 11 | 12 | 13 | 2) Set up SPI 14 | 1) Get SPI oauth route URL from `spi-system` namespace `oc get routes -n spi-system` 15 | 2) Create oauth app in GitHub (`Settings` -> `Developer Settings` -> `OAuth Apps`) 16 | - Use the SPI oauth url as the Application Callback URL. 17 | - Homepage URL does not matter 18 | - Record the Client ID and Client Secret values 19 | 3) To set up a Github Oauth app with SPI, modify the overlay in your cloned SPI repo that corresponds with the cluster type e.g. in config/overlays/openshift_vault/config.yaml, replace the `clientId` and `clientSecret` with the values from the oauth app you created in step 2. Run ` kustomize build config/overlays/openshift_vault | kubectl apply -f -` to update the `shared-configuration-file` secret 20 | 21 | 22 | 23 | ## Creating a Token 24 | 25 | 1) In Github, generate a new classic token with User and Repo scope and note down the token value. 26 | 2) To create a token secret to use with HAS, draft a `SPIAccessTokenBinding` resource with the following contents: 27 | ```yaml 28 | apiVersion: appstudio.redhat.com/v1beta1 29 | kind: SPIAccessTokenBinding 30 | metadata: 31 | name: test-access-token-binding 32 | spec: 33 | permissions: 34 | required: 35 | - type: rw 36 | area: repository 37 | repoUrl: https://github.com/johnmcollier/private-devfile-repo 38 | secret: 39 | name: token-secret 40 | type: kubernetes.io/basic-auth 41 | ``` 42 | 43 | 3) Create the resource in the namespace you will be creating HAS resources in. Upon successful creation, the CR will be in `AwaitingTokenData` phase status and a corresponding `SPIAccessToken` CR will be created in the same namespace. 44 | 45 | 3) Upload the token: 46 | 1) Set the TARGET_NAMESPACE to where your CRs instances are. Run `UPLOAD_URL=$(kubectl get spiaccesstokenbinding/test-access-token-binding -n $TARGET_NAMESPACE -o json | jq -r .status.uploadUrl)` 47 | 2) Inject the token with the curl command, where TOKEN is the console admin token and GITHUB_TOKEN is the token created in main step 1 above 48 | 49 | `curl -v -H 'Content-Type: application/json' -H "Authorization: bearer "$TOKEN -d "{ \"access_token\": \"$GITHUB_TOKEN\" }" $UPLOAD_URL` 50 | 3) The state of the `SPIAccessTokenBinding` CR should change to `Injected` and the state of the `SPIAccessToken` should be `Ready` 51 | 4) This will also create a K8s secret corresponding to the name of the secret that was specified in the `SPIAccessTokenBinding` created in main step 2 above, for example `token-secret`. Use the secret in HAS CRs for private repositories. 52 | 53 | 54 | ## Using Private Git Repositories 55 | 56 | Now, with the token secret created for the git repository, when creating HAS resources (`Components`, `ComponentDetectionQueries`) that need to access that private Git repository, just pass in the token secret to the resource: 57 | 58 | **Component** 59 | 60 | ```yaml 61 | apiVersion: appstudio.redhat.com/v1alpha1 62 | kind: Component 63 | metadata: 64 | name: component-sample 65 | spec: 66 | componentName: backend 67 | application: application-sample 68 | replicas: 1 69 | source: 70 | git: 71 | url: https://github.com/devfile-resources/devfile-private.git 72 | secret: token-secret 73 | ``` 74 | 75 | **ComponentDetectionQuery** 76 | 77 | ```yaml 78 | apiVersion: appstudio.redhat.com/v1alpha1 79 | kind: ComponentDetectionQuery 80 | metadata: 81 | name: componentdetectionquery-sample 82 | spec: 83 | git: 84 | url: https://github.com/devfile-resources/multi-component-private.git 85 | secret: token-secret 86 | ``` -------------------------------------------------------------------------------- /docs/release.md: -------------------------------------------------------------------------------- 1 | # Release 2 | 3 | The application-service repository does not do GitHub releases at the moment. Consumers of the application-service Go module are expected to update to the latest commit on a frequent basis. 4 | 5 | Any depreceation or breaking changes are documented as part of the code change in the PR or the function description. 6 | 7 | Since application-service is part of the AppStudio project, code commits to the application-service repository are automatically added to the [infra-deployments](https://github.com/redhat-appstudio/infra-deployments/tree/main/components/has) repository. 8 | -------------------------------------------------------------------------------- /docs/serviceability.md: -------------------------------------------------------------------------------- 1 | # Serviceability 2 | 3 | The serviceability document aims to help the local application-service developer and the Site Reliability Engineer (SRE) to access and service the application-service component. This document will help you understand how to access and understand the application-service logs, how to debug an application-service problem and provides a quick summary on the various questions that you might have regarding application-service. 4 | 5 | ## Accessing the Logs 6 | 7 | ### Deployed Locally 8 | View the application-service controller logs in the terminal window where the executable manager is running. Example, `./bin/manager` will output the controller logs in the terminal. 9 | 10 | ### Deployed on a Local Cluster 11 | View the application-service controller logs by tailing the manager container log of the controller manager pod. The pod resides in the application-service namespace. Example, 12 | 13 | ``` 14 | oc logs -f application-service-application-service-controller-manager -c manager -n application-service 15 | ``` 16 | 17 | ### Deployed on a Managed Cluster 18 | 19 | To access the application-service controller logs on either the AppStudio Staging or Production clusters, you would need access to CloudWatch service. To learn more about how to request access to CloudWatch and view the application-service logs, refer to the HAS Access Control [documentation](https://docs.google.com/document/d/1cK4XGKpXBEYOKfIqSiHuuCfsfHjElxhG9lrlEozzgVE/edit#heading=h.yxk6h5uvh57d). 20 | 21 | ## Understanding the Logs 22 | Each application-service controller logs their reconcile logic to the manager. The log message format is generally of format 23 | 24 | ``` 25 | {"level":"info","ts":"2023-08-31T19:59:21.144Z","msg":"Finished reconcile loop for user-tenant/devfile-sample-go-basic-development-binding-hr9nm","controller":"snapshotenvironmentbinding","controllerGroup":"appstudio.redhat.com","controllerKind":"SnapshotEnvironmentBinding","SnapshotEnvironmentBinding":{"name":"devfile-sample-go-basic-development-binding-hr9nm","namespace":"user-tenant"},"namespace":"user-tenant","name":"devfile-sample-go-basic-development-binding-hr9nm","reconcileID":"d5c8545b-957b-4f1a-b177-84a5d5f0d26c"} 26 | ``` 27 | 28 | To understand the AppStudio controller logging convention, refer to the Appstudio [ADR](https://github.com/redhat-appstudio/book/blob/main/ADR/0006-log-conventions.md) 29 | 30 | ## Debugging 31 | 32 | - Insert break points at the controller functions to debug unit tests or to debug a local controller deployment, refer to the next section on how to set up a debugger 33 | - When debugging unit and integration tests, remember that the mock clients used are hosting dummy data and mocking the API call and returns 34 | - For debugging controller logs either from a local cluster or a managed cluster, gather the log and search for: 35 | - the namespace the user belongs to. For example, if the user is on the project/namespace, search for `user-tenant` 36 | - the resource name that the controller is reconciling. For example, if the `SnapshotEnvironmentBinding` resource is being reconciled, then search for `"name":"devfile-sample-go-basic-development-binding-hr9nm"` belonging to `"controllerKind":"SnapshotEnvironmentBinding"` 37 | - the log message. For example, `"msg":"Finished reconcile loop for user-tenant/devfile-sample-go-basic-development-binding-hr9nm"`. You may search for the string `Finished reconcile loop for` in the application-service repository to track down the code logic that is emitting the log. Remember to look out for resource name concatenation and/or error wrapping that may not turn up in your code search. It is advised to exclude such strings from the code search for debugging purposes 38 | 39 | ### How to debug on RHTAP or How to set up a debugger on VS Code 40 | 41 | For more information, on how to debug on RHTAP Staging or how to set up a debugger on VS Code for local deployment of the application-service controller, please refer to the [Debugging](https://docs.google.com/document/d/1dneldJepfnJ6LnESSYMIhKqmFgjMtf_om_Eud5NMDtU/edit#heading=h.lz54tm3le87l) section of the Education Module document. 42 | 43 | ## Common Problems 44 | - When deploying HAS locally or on a local cluster, a Github Personal Access Token is required as the application-service controller requires the token for pushing the resources to the GitOps repository. Please refer to the [instructions](../docs/build-test-and-deploy.md#setting-the-github-token-environment-variable) in the deploy section for more information 45 | - When creating a `Component` from the `ComponentDetectionQuery`, remember to replace the generic application name `insert-application-name`, if the information is being used from a `ComponentDetectionQuery` status 46 | 47 | ## FAQs 48 | Q. Where can I view the application-service API types? 49 | 50 | A. The application-service API types and their corresponding webhooks are defined in the [konflux-ci/application-api](https://github.com/konflux-ci/application-api) repository. You can also find the API reference information in the [Book of AppStudio](https://redhat-appstudio.github.io/architecture/ref/index.html) website. 51 | 52 | Q. Where are the application-service controller logic located? 53 | 54 | A. Most of the application-service business logic like the reconcile functions are located in the [controllers](https://github.com/redhat-appstudio/application-service/tree/main/controllers) pkg. 55 | 56 | Q. What is the application-service release process? 57 | 58 | A. Since application-service is part of the AppStudio project, code commits to the application-service repository are automatically added to the [infra-deployments](https://github.com/redhat-appstudio/infra-deployments/tree/main/components/has) repository. 59 | 60 | Q. Where can I learn more about the devfile project? 61 | 62 | A. The devfile project is hosted on the [devfile/api](https://github.com/devfile/api) repository and more information about the project as well as the spec can be found on [devfile.io](https://devfile.io/). 63 | 64 | Q. How does `ComponentDetectionQuery` detect the component framework and runtime? 65 | 66 | A. `ComponentDetectionQuery` uses the go module [devfile/alizer](https://github.com/devfile/alizer) for component detection. For more information about the Alizer project, please head over to the Alizer repository. 67 | 68 | Q. How do I debug zombie processes? 69 | 70 | A. For more information on how zombie processes affect application-service controllers, please refer to the Troubleshooting [guide](https://docs.google.com/document/d/1yCFkFslhbdd8M_RarRhZcgx6gm9nr2JwDObxNtl4H-U/edit#heading=h.4brqv3sh6lq9). 71 | 72 | Q. How do I debug Rate Limiting? 73 | 74 | A. The GitHub Personal Access Tokens used by application-service controllers may be rate limited. For more information on how the GitHub PAT rate limiting affects application-service controllers, please refer to the Troubleshooting [guide](https://docs.google.com/document/d/1yCFkFslhbdd8M_RarRhZcgx6gm9nr2JwDObxNtl4H-U/edit#heading=h.3xnfno3qm3if). 75 | -------------------------------------------------------------------------------- /docs/testing.md: -------------------------------------------------------------------------------- 1 | # Testing HAS 2 | 3 | ## Unit Tests 4 | 5 | There are unit tests written for the operator, using the Go `testing` library. They cover any packages under the `pkg/` folder. To run these tests, just run 6 | 7 | ``` 8 | make test 9 | ``` 10 | 11 | ## Controller Tests 12 | 13 | There are tests written for each controller (e.g. Application) in the operator, using the `ginkgo` BDD testing library. To run these tests (along with any unit tests), just run 14 | 15 | ``` 16 | make test 17 | ``` 18 | 19 | ### Running Controller Tests in VS Code 20 | 21 | Running the controller's tests (tests under the `controller/` directory) in VS Code require a bit of leg work before working. 22 | 23 | First, make sure you have run `make test` on your system at least once, as this ensures certain binaries like `kube-apiserver` and `etcd` are downloaded on to your system. On Linux, this is `$XDG_DATA_HOME/io.kubebuilder.envtest`; on Windows, `%LocalAppData\io.kubebuilder.envtest`; and on OSX, `~/Library/Application Support/io.kubebuilder.envtest` 24 | 25 | cd to that folder 26 | Then, add the following environment variable to your shell configuration: 27 | 28 | ``` 29 | export KUBEBUILDER_ASSETS=/io.kubebuilder.envtest/k8s/ 30 | ``` 31 | 32 | e.g.: 33 | ``` 34 | export KUBEBUILDER_ASSETS=/Users/john/Library/Application Support/io.kubebuilder.envtest/k8s/1.22.1-darwin-amd64 35 | ``` 36 | 37 | From here, you should be good to run the controller's unit tests in VS Code. Verify this by opening `application_controller_test.go` in VS Code and running `run package tests`. 38 | 39 | ## Integration Tests 40 | 41 | TBD -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -eux 3 | 4 | exec /manager $* 5 | 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/redhat-appstudio/application-service 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/go-logr/logr v1.4.1 7 | github.com/konflux-ci/application-api v0.0.0-20240812090716-e7eb2ecfb409 8 | github.com/konflux-ci/operator-toolkit v0.0.0-20240402130556-ef6dcbeca69d 9 | github.com/onsi/ginkgo v1.16.5 10 | github.com/onsi/gomega v1.27.10 11 | github.com/openshift/api v0.0.0-20220912161038-458ad9ca9ca5 12 | github.com/stretchr/testify v1.8.4 13 | go.uber.org/zap v1.27.0 14 | k8s.io/api v0.26.10 15 | k8s.io/apimachinery v0.27.7 16 | k8s.io/client-go v0.26.10 17 | sigs.k8s.io/controller-runtime v0.14.7 18 | ) 19 | 20 | require ( 21 | github.com/beorn7/perks v1.0.1 // indirect 22 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 24 | github.com/emicklei/go-restful/v3 v3.10.1 // indirect 25 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 26 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 27 | github.com/fsnotify/fsnotify v1.7.0 // indirect 28 | github.com/go-logr/zapr v1.2.4 // indirect 29 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 30 | github.com/go-openapi/jsonreference v0.20.1 // indirect 31 | github.com/go-openapi/swag v0.22.3 // indirect 32 | github.com/gogo/protobuf v1.3.2 // indirect 33 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 34 | github.com/golang/protobuf v1.5.4 // indirect 35 | github.com/google/gnostic v0.5.7-v3refs // indirect 36 | github.com/google/go-cmp v0.6.0 // indirect 37 | github.com/google/gofuzz v1.2.0 // indirect 38 | github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect 39 | github.com/google/uuid v1.6.0 // indirect 40 | github.com/imdario/mergo v0.3.16 // indirect 41 | github.com/josharian/intern v1.0.0 // indirect 42 | github.com/json-iterator/go v1.1.12 // indirect 43 | github.com/mailru/easyjson v0.7.7 // indirect 44 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 46 | github.com/modern-go/reflect2 v1.0.2 // indirect 47 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 48 | github.com/nxadm/tail v1.4.8 // indirect 49 | github.com/pkg/errors v0.9.1 // indirect 50 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 51 | github.com/prometheus/client_golang v1.17.0 // indirect 52 | github.com/prometheus/client_model v0.5.0 // indirect 53 | github.com/prometheus/common v0.44.0 // indirect 54 | github.com/prometheus/procfs v0.12.0 // indirect 55 | github.com/rogpeppe/go-internal v1.11.0 // indirect 56 | github.com/spf13/pflag v1.0.5 // indirect 57 | go.uber.org/multierr v1.11.0 // indirect 58 | golang.org/x/net v0.22.0 // indirect 59 | golang.org/x/oauth2 v0.16.0 // indirect 60 | golang.org/x/sys v0.18.0 // indirect 61 | golang.org/x/term v0.18.0 // indirect 62 | golang.org/x/text v0.14.0 // indirect 63 | golang.org/x/time v0.5.0 // indirect 64 | golang.org/x/tools v0.19.0 // indirect 65 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 66 | google.golang.org/appengine v1.6.8 // indirect 67 | google.golang.org/protobuf v1.33.0 // indirect 68 | gopkg.in/inf.v0 v0.9.1 // indirect 69 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 70 | gopkg.in/yaml.v2 v2.4.0 // indirect 71 | gopkg.in/yaml.v3 v3.0.1 // indirect 72 | k8s.io/apiextensions-apiserver v0.26.10 // indirect 73 | k8s.io/component-base v0.26.10 // indirect 74 | k8s.io/klog/v2 v2.100.1 // indirect 75 | k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect 76 | k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect 77 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 78 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 79 | sigs.k8s.io/yaml v1.3.0 // indirect 80 | ) 81 | 82 | replace github.com/antlr/antlr4 => github.com/antlr/antlr4 v0.0.0-20211106181442-e4c1a74c66bd 83 | 84 | replace github.com/go-git/go-git/v5 => github.com/go-git/go-git/v5 v5.11.0 85 | 86 | replace github.com/Microsoft/hcsshim => github.com/Microsoft/hcsshim v0.12.2 87 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021-2022 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /hack/replace_placeholders_and_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script copies the config directory containing the kustomize templates into a subdirectory under .tmp. 4 | 5 | set -eux 6 | 7 | # the path to the kustomize executable 8 | KUSTOMIZE=$1 9 | 10 | echo "********" 11 | echo $KUSTOMIZE 12 | # the name of the deployment - only used as a part of the directory the templates are copied into 13 | DEPL_NAME=$2 14 | 15 | # The name of the kustomize overlay directory to apply 16 | OVERLAY=$3 17 | 18 | # Whether or not to kubectl apply or delete the resources 19 | # can either be 'apply' or 'delete' 20 | COMMAND=$4 21 | 22 | THIS_DIR="$(dirname "$(realpath "$0")")" 23 | TEMP_DIR="${THIS_DIR}/../.tmp/deployment_${DEPL_NAME}" 24 | 25 | OVERLAY_DIR="${TEMP_DIR}/${OVERLAY}" 26 | 27 | # we need this to keep kustomize patches intact 28 | export patch="\$patch" 29 | 30 | mkdir -p "${TEMP_DIR}" 31 | cp -r "${THIS_DIR}/../config/"* "${TEMP_DIR}" 32 | find "${TEMP_DIR}" -name '*.yaml' | while read -r f; do 33 | tmp=$(mktemp) 34 | envsubst > "$tmp" < "$f" 35 | mv "$tmp" "$f" 36 | done 37 | 38 | CURDIR=$(pwd) 39 | 40 | cd "${OVERLAY_DIR}" || exit 41 | 42 | GITHUB_ORG=${GITHUB_ORG} DEVFILE_REGISTRY_URL=${DEVFILE_REGISTRY_URL} ${KUSTOMIZE} build . | kubectl ${COMMAND} -f - 43 | 44 | cd "${CURDIR}" || exit 45 | -------------------------------------------------------------------------------- /license_header.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2023 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021-2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "crypto/tls" 21 | "flag" 22 | "log" 23 | "net/http" 24 | "os" 25 | 26 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 27 | // to ensure that exec-entrypoint and run can make use of them. 28 | "go.uber.org/zap/zapcore" 29 | _ "k8s.io/client-go/plugin/pkg/client/auth" 30 | 31 | "github.com/konflux-ci/operator-toolkit/webhook" 32 | "k8s.io/apimachinery/pkg/runtime" 33 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 34 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 35 | ctrl "sigs.k8s.io/controller-runtime" 36 | "sigs.k8s.io/controller-runtime/pkg/healthz" 37 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 38 | 39 | routev1 "github.com/openshift/api/route/v1" 40 | 41 | appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" 42 | "github.com/redhat-appstudio/application-service/webhooks" 43 | 44 | // Enable pprof for profiling 45 | /* #nosec G108 -- debug code */ 46 | _ "net/http/pprof" 47 | //+kubebuilder:scaffold:imports 48 | ) 49 | 50 | var ( 51 | scheme = runtime.NewScheme() 52 | setupLog = ctrl.Log.WithName("setup") 53 | ) 54 | 55 | func init() { 56 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 57 | 58 | utilruntime.Must(appstudiov1alpha1.AddToScheme(scheme)) 59 | 60 | //+kubebuilder:scaffold:scheme 61 | } 62 | 63 | func main() { 64 | var metricsAddr string 65 | var enableLeaderElection bool 66 | var probeAddr string 67 | var apiExportName string 68 | flag.StringVar(&apiExportName, "api-export-name", "", "The name of the APIExport.") 69 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 70 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 71 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 72 | "Enable leader election for controller manager. "+ 73 | "Enabling this will ensure there is only one active controller manager.") 74 | opts := zap.Options{ 75 | TimeEncoder: zapcore.ISO8601TimeEncoder, 76 | } 77 | opts.BindFlags(flag.CommandLine) 78 | flag.Parse() 79 | 80 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 81 | 82 | ctx := ctrl.SetupSignalHandler() 83 | 84 | restConfig := ctrl.GetConfigOrDie() 85 | setupLog = setupLog.WithValues("controllerKind", apiExportName) 86 | 87 | // Set up pprof if needed 88 | if os.Getenv("ENABLE_PPROF") == "true" { 89 | go func() { 90 | /* #nosec G114 -- debug code */ 91 | log.Println(http.ListenAndServe("localhost:6060", nil)) 92 | }() 93 | } 94 | var mgr ctrl.Manager 95 | var err error 96 | options := ctrl.Options{ 97 | Scheme: scheme, 98 | MetricsBindAddress: metricsAddr, 99 | Port: 9443, 100 | HealthProbeBindAddress: probeAddr, 101 | LeaderElection: enableLeaderElection, 102 | LeaderElectionID: "f50829e1.redhat.com", 103 | LeaderElectionConfig: restConfig, 104 | } 105 | mgr, err = ctrl.NewManager(restConfig, options) 106 | if err != nil { 107 | setupLog.Error(err, "unable to start manager") 108 | os.Exit(1) 109 | } 110 | 111 | if err := routev1.AddToScheme(mgr.GetScheme()); err != nil { 112 | setupLog.Error(err, "unable to add triggers api to the scheme") 113 | os.Exit(1) 114 | } 115 | 116 | if os.Getenv("ENABLE_WEBHOOKS") != "false" { 117 | setupLog.Info("setting up webhooks") 118 | setUpWebhooks(mgr) 119 | } 120 | 121 | //+kubebuilder:scaffold:builder 122 | 123 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 124 | setupLog.Error(err, "unable to set up health check") 125 | os.Exit(1) 126 | } 127 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 128 | setupLog.Error(err, "unable to set up ready check") 129 | os.Exit(1) 130 | } 131 | 132 | setupLog.Info("starting manager") 133 | if err := mgr.Start(ctx); err != nil { 134 | setupLog.Error(err, "problem running manager") 135 | os.Exit(1) 136 | } 137 | } 138 | 139 | // setUpWebhooks sets up webhooks. 140 | func setUpWebhooks(mgr ctrl.Manager) { 141 | err := webhook.SetupWebhooks(mgr, webhooks.EnabledWebhooks...) 142 | if err != nil { 143 | setupLog.Error(err, "unable to setup webhooks") 144 | os.Exit(1) 145 | } 146 | 147 | // Retrieve the option to enable HTTP2 on the Webhook server 148 | enableWebhookHTTP2 := os.Getenv("ENABLE_WEBHOOK_HTTP2") 149 | if enableWebhookHTTP2 == "" { 150 | enableWebhookHTTP2 = "false" 151 | } 152 | 153 | if enableWebhookHTTP2 == "false" { 154 | setupLog.Info("disabling http/2 on the webhook server") 155 | server := mgr.GetWebhookServer() 156 | server.TLSOpts = append(server.TLSOpts, 157 | func(c *tls.Config) { 158 | c.NextProtos = []string{"http/1.1"} 159 | }, 160 | ) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021-2023 Red Hat, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package util 17 | 18 | // StrInList returns true if the given string is present in strList 19 | func StrInList(str string, strList []string) bool { 20 | for _, val := range strList { 21 | if str == val { 22 | return true 23 | } 24 | } 25 | return false 26 | } 27 | 28 | // RemoveStrFromList removes the first occurence of str from the slice strList 29 | func RemoveStrFromList(str string, strList []string) []string { 30 | for i, v := range strList { 31 | if v == str { 32 | return append(strList[:i], strList[i+1:]...) 33 | } 34 | } 35 | return strList 36 | } 37 | -------------------------------------------------------------------------------- /pkg/util/util_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2021-2023 Red Hat, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package util 17 | 18 | import ( 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestStrInList(t *testing.T) { 25 | tests := []struct { 26 | name string 27 | str string 28 | list []string 29 | want bool 30 | }{ 31 | { 32 | name: "str not in list", 33 | str: "test", 34 | list: []string{"some", "words"}, 35 | want: false, 36 | }, 37 | { 38 | name: "str in list", 39 | str: "test", 40 | list: []string{"some", "test", "words"}, 41 | want: true, 42 | }, 43 | } 44 | 45 | for _, tt := range tests { 46 | val := StrInList(tt.str, tt.list) 47 | assert.True(t, val == tt.want, "Expected bool value %v got %v", tt.want, val) 48 | } 49 | } 50 | 51 | func TestRemoveStrFromList(t *testing.T) { 52 | tests := []struct { 53 | name string 54 | str string 55 | list []string 56 | want []string 57 | }{ 58 | { 59 | name: "single string in list", 60 | str: "test", 61 | list: []string{"some", "test", "words"}, 62 | want: []string{"some", "words"}, 63 | }, 64 | { 65 | name: "string not in list", 66 | str: "test", 67 | list: []string{"some", "words"}, 68 | want: []string{"some", "words"}, 69 | }, 70 | { 71 | name: "multiple occurence of string in list", 72 | str: "test", 73 | list: []string{"some", "test", "words", "test", "again"}, 74 | want: []string{"some", "words", "test", "again"}, 75 | }, 76 | } 77 | 78 | for _, tt := range tests { 79 | strList := RemoveStrFromList(tt.str, tt.list) 80 | if len(strList) != len(tt.want) { 81 | t.Errorf("TestRemoveStrFromList(): unexpected error. expected string list %v, got %v", tt.want, strList) 82 | } 83 | for i := range strList { 84 | if strList[i] != tt.want[i] { 85 | t.Errorf("TestRemoveStrFromList(): unexpected error. expected string %v at index %v, got %v", tt.want[i], i, strList[i]) 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /webhooks/application_webhook.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package webhooks 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "github.com/go-logr/logr" 24 | appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "k8s.io/apimachinery/pkg/util/validation" 27 | ctrl "sigs.k8s.io/controller-runtime" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | ) 30 | 31 | // Webhook describes the data structure for the release webhook 32 | type ApplicationWebhook struct { 33 | client client.Client 34 | log logr.Logger 35 | } 36 | 37 | //+kubebuilder:webhook:path=/mutate-appstudio-redhat-com-v1alpha1-application,mutating=true,failurePolicy=fail,sideEffects=None,groups=appstudio.redhat.com,resources=applications,verbs=create;update,versions=v1alpha1,name=mapplication.kb.io,admissionReviewVersions=v1 38 | 39 | func (w *ApplicationWebhook) Register(mgr ctrl.Manager, log *logr.Logger) error { 40 | w.client = mgr.GetClient() 41 | 42 | return ctrl.NewWebhookManagedBy(mgr). 43 | For(&appstudiov1alpha1.Application{}). 44 | WithDefaulter(w). 45 | WithValidator(w). 46 | Complete() 47 | } 48 | 49 | // +kubebuilder:webhook:path=/validate-appstudio-redhat-com-v1alpha1-application,mutating=false,failurePolicy=fail,sideEffects=None,groups=appstudio.redhat.com,resources=applications,verbs=create;update,versions=v1alpha1,name=vapplication.kb.io,admissionReviewVersions=v1 50 | 51 | // Default implements webhook.Defaulter so a webhook will be registered for the type 52 | func (r *ApplicationWebhook) Default(ctx context.Context, obj runtime.Object) error { 53 | 54 | // TODO(user): fill in your defaulting logic. 55 | return nil 56 | } 57 | 58 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type 59 | func (r *ApplicationWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) error { 60 | app := obj.(*appstudiov1alpha1.Application) 61 | 62 | applicationlog := r.log.WithValues("controllerKind", "Application").WithValues("name", app.Name).WithValues("namespace", app.Namespace) 63 | applicationlog.Info("validating the create request") 64 | // We use the DNS-1035 format for application names, so ensure it conforms to that specification 65 | if len(validation.IsDNS1035Label(app.Name)) != 0 { 66 | return fmt.Errorf("invalid application name: %q: an application resource name must start with a lower case alphabetical character, be under 63 characters, and can only consist of lower case alphanumeric characters or ‘-’,", app.Name) 67 | } 68 | if app.Spec.DisplayName == "" { 69 | return fmt.Errorf("display name must be provided when creating an Application") 70 | } 71 | return nil 72 | } 73 | 74 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type 75 | func (r *ApplicationWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error { 76 | newApp := newObj.(*appstudiov1alpha1.Application) 77 | applicationlog := r.log.WithValues("controllerKind", "Application").WithValues("name", newApp.Name).WithValues("namespace", newApp.Namespace) 78 | applicationlog.Info("validating the update request") 79 | 80 | return nil 81 | } 82 | 83 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type 84 | func (r *ApplicationWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) error { 85 | 86 | // TODO(user): fill in your validation logic upon object deletion. 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /webhooks/application_webhook_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Red Hat, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package webhooks 17 | 18 | import ( 19 | "context" 20 | "time" 21 | 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | 25 | appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" 26 | 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | //+kubebuilder:scaffold:imports 29 | ) 30 | 31 | const ( 32 | timeout = time.Second * 10 33 | duration = time.Second * 10 34 | interval = time.Millisecond * 250 35 | ) 36 | 37 | var _ = Describe("Application validation webhook", func() { 38 | 39 | // Define utility constants for object names and testing timeouts/durations and intervals. 40 | const ( 41 | HASAppName = "test-application" 42 | HASAppNamespace = "default" 43 | DisplayName = "petclinic" 44 | Description = "Simple petclinic app" 45 | ) 46 | 47 | Context("Create Application CR with missing displayName", func() { 48 | It("Should fail with error saying displayName is required field", func() { 49 | ctx := context.Background() 50 | 51 | hasApp := &appstudiov1alpha1.Application{ 52 | TypeMeta: metav1.TypeMeta{ 53 | APIVersion: "appstudio.redhat.com/v1alpha1", 54 | Kind: "Application", 55 | }, 56 | ObjectMeta: metav1.ObjectMeta{ 57 | Name: HASAppName, 58 | Namespace: HASAppNamespace, 59 | }, 60 | Spec: appstudiov1alpha1.ApplicationSpec{}, 61 | } 62 | 63 | Expect(k8sClient.Create(ctx, hasApp)).Should(Not(Succeed())) 64 | }) 65 | }) 66 | 67 | Context("Create Application CR with invalid metadata.name", func() { 68 | It("Should fail with error saying name does not conform to spec", func() { 69 | ctx := context.Background() 70 | 71 | hasApp := &appstudiov1alpha1.Application{ 72 | TypeMeta: metav1.TypeMeta{ 73 | APIVersion: "appstudio.redhat.com/v1alpha1", 74 | Kind: "Application", 75 | }, 76 | ObjectMeta: metav1.ObjectMeta{ 77 | Name: "1-invalid-application-name", 78 | Namespace: HASAppNamespace, 79 | }, 80 | Spec: appstudiov1alpha1.ApplicationSpec{}, 81 | } 82 | 83 | err := k8sClient.Create(ctx, hasApp) 84 | Expect(err).Should(Not(Succeed())) 85 | Expect(err.Error()).Should(ContainSubstring("an application resource name must start with a lower case alphabetical character, be under 63 characters, and can only consist of lower case alphanumeric characters or ‘-’")) 86 | }) 87 | }) 88 | 89 | }) 90 | -------------------------------------------------------------------------------- /webhooks/application_webhook_unit_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Red Hat, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package webhooks 17 | 18 | import ( 19 | "context" 20 | "testing" 21 | 22 | appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" 23 | "go.uber.org/zap/zapcore" 24 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestApplicationValidatingWebhook(t *testing.T) { 30 | 31 | originalApplication := appstudiov1alpha1.Application{ 32 | Spec: appstudiov1alpha1.ApplicationSpec{ 33 | DisplayName: "My App", 34 | AppModelRepository: appstudiov1alpha1.ApplicationGitRepository{ 35 | URL: "http://appmodelrepo", 36 | }, 37 | GitOpsRepository: appstudiov1alpha1.ApplicationGitRepository{ 38 | URL: "http://gitopsrepo", 39 | }, 40 | }, 41 | } 42 | 43 | tests := []struct { 44 | name string 45 | updateApp appstudiov1alpha1.Application 46 | err string 47 | }{ 48 | { 49 | name: "display name can be changed", 50 | updateApp: appstudiov1alpha1.Application{ 51 | Spec: appstudiov1alpha1.ApplicationSpec{ 52 | DisplayName: "My App 2", 53 | AppModelRepository: appstudiov1alpha1.ApplicationGitRepository{ 54 | URL: "http://appmodelrepo", 55 | }, 56 | GitOpsRepository: appstudiov1alpha1.ApplicationGitRepository{ 57 | URL: "http://gitopsrepo", 58 | }, 59 | }, 60 | }, 61 | }, 62 | } 63 | for _, test := range tests { 64 | t.Run(test.name, func(t *testing.T) { 65 | var err error 66 | 67 | appWebhook := ApplicationWebhook{ 68 | log: zap.New(zap.UseFlagOptions(&zap.Options{ 69 | Development: true, 70 | TimeEncoder: zapcore.ISO8601TimeEncoder, 71 | })), 72 | } 73 | 74 | err = appWebhook.ValidateUpdate(context.Background(), &originalApplication, &test.updateApp) 75 | 76 | if test.err == "" { 77 | assert.Nil(t, err) 78 | } else { 79 | assert.Contains(t, err.Error(), test.err) 80 | } 81 | }) 82 | } 83 | } 84 | 85 | func TestApplicationDeleteValidatingWebhook(t *testing.T) { 86 | 87 | tests := []struct { 88 | name string 89 | app appstudiov1alpha1.Application 90 | err string 91 | }{ 92 | { 93 | name: "ValidateDelete should return nil, it's unimplimented", 94 | err: "", 95 | app: appstudiov1alpha1.Application{}, 96 | }, 97 | } 98 | for _, test := range tests { 99 | t.Run(test.name, func(t *testing.T) { 100 | 101 | appWebhook := ApplicationWebhook{ 102 | log: zap.New(zap.UseFlagOptions(&zap.Options{ 103 | Development: true, 104 | TimeEncoder: zapcore.ISO8601TimeEncoder, 105 | })), 106 | } 107 | 108 | err := appWebhook.ValidateDelete(context.Background(), &test.app) 109 | 110 | if test.err == "" { 111 | assert.Nil(t, err) 112 | } else { 113 | assert.Contains(t, err.Error(), test.err) 114 | } 115 | }) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /webhooks/component_webhook.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package webhooks 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "fmt" 23 | "net/url" 24 | 25 | appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" 26 | "github.com/redhat-appstudio/application-service/pkg/util" 27 | 28 | "github.com/go-logr/logr" 29 | k8sErrors "k8s.io/apimachinery/pkg/api/errors" 30 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 | "k8s.io/apimachinery/pkg/runtime" 32 | "k8s.io/apimachinery/pkg/types" 33 | "k8s.io/apimachinery/pkg/util/validation" 34 | "k8s.io/client-go/util/retry" 35 | ctrl "sigs.k8s.io/controller-runtime" 36 | "sigs.k8s.io/controller-runtime/pkg/client" 37 | ) 38 | 39 | // log is for logging in this package. 40 | // Webhook describes the data structure for the release webhook 41 | type ComponentWebhook struct { 42 | client client.Client 43 | log logr.Logger 44 | } 45 | 46 | func (w *ComponentWebhook) Register(mgr ctrl.Manager, log *logr.Logger) error { 47 | w.client = mgr.GetClient() 48 | 49 | return ctrl.NewWebhookManagedBy(mgr). 50 | For(&appstudiov1alpha1.Component{}). 51 | WithDefaulter(w). 52 | WithValidator(w). 53 | Complete() 54 | } 55 | 56 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 57 | 58 | // +kubebuilder:webhook:path=/mutate-appstudio-redhat-com-v1alpha1-component,mutating=true,failurePolicy=fail,sideEffects=None,groups=appstudio.redhat.com,resources=components;components/status,verbs=create;update,versions=v1alpha1,name=mcomponent.kb.io,admissionReviewVersions=v1 59 | 60 | // Default implements webhook.Defaulter so a webhook will be registered for the type 61 | func (r *ComponentWebhook) Default(ctx context.Context, obj runtime.Object) error { 62 | component := obj.(*appstudiov1alpha1.Component) 63 | compName := component.Name 64 | componentlog := r.log.WithValues("controllerKind", "Component").WithValues("name", compName).WithValues("namespace", component.Namespace) 65 | 66 | if len(component.OwnerReferences) == 0 && component.DeletionTimestamp.IsZero() { 67 | // Get the Application CR 68 | // Use the background context to ensure the operator's kubeconfig is used 69 | hasApplication := appstudiov1alpha1.Application{} 70 | err := r.client.Get(context.Background(), types.NamespacedName{Name: component.Spec.Application, Namespace: component.Namespace}, &hasApplication) 71 | if err != nil { 72 | // Don't block if the Application doesn't exist yet - this will retrigger whenever the resource is modified 73 | err = fmt.Errorf("unable to get the Application %s for Component %s, ignoring for now", component.Spec.Application, compName) 74 | componentlog.Error(err, "skip setting owner reference on component") 75 | } else { 76 | // Update the Component's owner ref's - retry on conflict 77 | err = retry.RetryOnConflict(retry.DefaultRetry, func() error { 78 | var curComp appstudiov1alpha1.Component 79 | // Get the Component to update using the operator's kubeconfig so that there aren't any permissions issues setting the owner reference 80 | // Use the background context to ensure the operator's kubeconfig is used 81 | err := r.client.Get(context.Background(), types.NamespacedName{Name: compName, Namespace: component.Namespace}, &curComp) 82 | if err != nil { 83 | componentlog.Error(err, "unable to get current component, so skip setting owner reference") 84 | return nil 85 | } 86 | 87 | ownerReference := metav1.OwnerReference{ 88 | APIVersion: hasApplication.APIVersion, 89 | Kind: hasApplication.Kind, 90 | Name: hasApplication.Name, 91 | UID: hasApplication.UID, 92 | } 93 | curComp.SetOwnerReferences(append(curComp.GetOwnerReferences(), ownerReference)) 94 | err = r.client.Update(ctx, &curComp) 95 | return err 96 | }) 97 | if err != nil { 98 | componentlog.Error(err, "error setting owner-references") 99 | } 100 | 101 | } 102 | } 103 | 104 | return nil 105 | } 106 | 107 | // UpdateNudgedComponentStatus retrieves the list of components that the Component nudges and updates their statuses to list 108 | // the component as a nudging component (status.BuildNudgedBy) 109 | func (r *ComponentWebhook) UpdateNudgedComponentStatus(ctx context.Context, obj runtime.Object) error { 110 | comp := obj.(*appstudiov1alpha1.Component) 111 | compName := comp.Name 112 | componentlog := r.log.WithValues("controllerKind", "Component").WithValues("name", compName).WithValues("namespace", comp.Namespace) 113 | 114 | // For each component that the Component nudges, retrieve its resource and update its status accordingly 115 | for _, nudgedCompName := range comp.Spec.BuildNudgesRef { 116 | // Retrieved the nudged component 117 | nudgedComp := &appstudiov1alpha1.Component{} 118 | err := r.client.Get(ctx, types.NamespacedName{Namespace: comp.Namespace, Name: nudgedCompName}, nudgedComp) 119 | if err != nil { 120 | // Return an error if an error was encountered retrieving the resource. 121 | // If the resource wasn't found yet - leave it however 122 | if !k8sErrors.IsNotFound(err) { 123 | return err 124 | } else { 125 | componentlog.Error(err, "nudged component not found, skip setting the status for now") 126 | continue 127 | } 128 | } 129 | 130 | // Add the component to the status if it's not already present 131 | if !util.StrInList(compName, nudgedComp.Status.BuildNudgedBy) { 132 | 133 | // Update the Component's status - retry on conflict 134 | err := retry.RetryOnConflict(retry.DefaultRetry, func() error { 135 | currentNudgedComp := &appstudiov1alpha1.Component{} 136 | err := r.client.Get(ctx, types.NamespacedName{Namespace: comp.Namespace, Name: nudgedCompName}, currentNudgedComp) 137 | if err != nil { 138 | return err 139 | } 140 | currentNudgedComp.Status.BuildNudgedBy = append(currentNudgedComp.Status.BuildNudgedBy, compName) 141 | err = r.client.Status().Update(ctx, currentNudgedComp) 142 | return err 143 | }) 144 | if err != nil { 145 | componentlog.Error(err, "error setting build-nudged-by in status") 146 | } 147 | 148 | } 149 | 150 | } 151 | return nil 152 | } 153 | 154 | // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. 155 | // +kubebuilder:webhook:path=/validate-appstudio-redhat-com-v1alpha1-component,mutating=false,failurePolicy=fail,sideEffects=None,groups=appstudio.redhat.com,resources=components,verbs=create;update,versions=v1alpha1,name=vcomponent.kb.io,admissionReviewVersions=v1 156 | 157 | // ValidateCreate implements webhook.Validator so a webhook will be registered for the type 158 | func (r *ComponentWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) error { 159 | comp := obj.(*appstudiov1alpha1.Component) 160 | componentlog := r.log.WithValues("controllerKind", "Component").WithValues("name", comp.Name).WithValues("namespace", comp.Namespace) 161 | componentlog.Info("validating the create request") 162 | 163 | // We use the DNS-1035 format for component names, so ensure it conforms to that specification 164 | if len(validation.IsDNS1035Label(comp.Name)) != 0 { 165 | return fmt.Errorf(appstudiov1alpha1.InvalidDNS1035Name, comp.Name) 166 | } 167 | sourceSpecified := false 168 | 169 | if comp.Spec.Source.GitSource != nil && comp.Spec.Source.GitSource.URL != "" { 170 | if _, err := url.ParseRequestURI(comp.Spec.Source.GitSource.URL); err != nil { 171 | return fmt.Errorf(err.Error() + appstudiov1alpha1.InvalidSchemeGitSourceURL) 172 | } 173 | sourceSpecified = true 174 | } else if comp.Spec.ContainerImage != "" { 175 | sourceSpecified = true 176 | } 177 | 178 | if !sourceSpecified { 179 | return errors.New(appstudiov1alpha1.MissingGitOrImageSource) 180 | } 181 | 182 | if len(comp.Spec.BuildNudgesRef) != 0 { 183 | err := r.validateBuildNudgesRefGraph(ctx, comp.Spec.BuildNudgesRef, comp.Namespace, comp.Name) 184 | if err != nil { 185 | return err 186 | } 187 | err = r.UpdateNudgedComponentStatus(ctx, comp) 188 | if err != nil { 189 | return err 190 | } 191 | } 192 | 193 | return nil 194 | } 195 | 196 | // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type 197 | func (r *ComponentWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error { 198 | oldComp := oldObj.(*appstudiov1alpha1.Component) 199 | newComp := newObj.(*appstudiov1alpha1.Component) 200 | 201 | componentlog := r.log.WithValues("controllerKind", "Component").WithValues("name", newComp.Name).WithValues("namespace", newComp.Namespace) 202 | componentlog.Info("validating the update request") 203 | 204 | if newComp.Spec.ComponentName != oldComp.Spec.ComponentName { 205 | return fmt.Errorf(appstudiov1alpha1.ComponentNameUpdateError, newComp.Spec.ComponentName) 206 | } 207 | 208 | if newComp.Spec.Application != oldComp.Spec.Application { 209 | return fmt.Errorf(appstudiov1alpha1.ApplicationNameUpdateError, newComp.Spec.Application) 210 | } 211 | 212 | if newComp.Spec.Source.GitSource != nil && oldComp.Spec.Source.GitSource != nil && (newComp.Spec.Source.GitSource.URL != oldComp.Spec.Source.GitSource.URL) { 213 | return fmt.Errorf(appstudiov1alpha1.GitSourceUpdateError, *(newComp.Spec.Source.GitSource)) 214 | } 215 | if len(newComp.Spec.BuildNudgesRef) != 0 { 216 | err := r.validateBuildNudgesRefGraph(ctx, newComp.Spec.BuildNudgesRef, newComp.Namespace, newComp.Name) 217 | if err != nil { 218 | return err 219 | } 220 | 221 | // If the dependency graph was successfully validated, update the statuses of the Components 222 | err = r.UpdateNudgedComponentStatus(ctx, newComp) 223 | if err != nil { 224 | return err 225 | } 226 | } 227 | 228 | return nil 229 | } 230 | 231 | // ValidateDelete implements webhook.Validator so a webhook will be registered for the type 232 | func (r *ComponentWebhook) ValidateDelete(ctx context.Context, obj runtime.Object) error { 233 | comp := obj.(*appstudiov1alpha1.Component) 234 | compName := comp.Name 235 | componentNamespace := comp.Namespace 236 | componentlog := r.log.WithValues("controllerKind", "Component").WithValues("name", compName).WithValues("namespace", comp.Namespace) 237 | 238 | // Check which Components this component nudges. Update their statuses to remove the component 239 | for _, nudgedComponentName := range comp.Spec.BuildNudgesRef { 240 | err := retry.RetryOnConflict(retry.DefaultRetry, func() error { 241 | nudgedComponent := &appstudiov1alpha1.Component{} 242 | err := r.client.Get(ctx, types.NamespacedName{Namespace: componentNamespace, Name: nudgedComponentName}, nudgedComponent) 243 | if err != nil { 244 | return err 245 | } 246 | nudgedComponent.Status.BuildNudgedBy = util.RemoveStrFromList(compName, nudgedComponent.Status.BuildNudgedBy) 247 | err = r.client.Status().Update(ctx, nudgedComponent) 248 | return err 249 | }) 250 | 251 | if err != nil { 252 | // Don't block component deletion if this fails, but log and continue 253 | componentlog.Error(err, "error deleting component name from build-nudges-ref") 254 | } 255 | 256 | } 257 | 258 | // Next, loop through the Component's list of nudging components, and update their specs 259 | for _, nudgedComponentName := range comp.Status.BuildNudgedBy { 260 | err := retry.RetryOnConflict(retry.DefaultRetry, func() error { 261 | nudgingComponent := &appstudiov1alpha1.Component{} 262 | err := r.client.Get(ctx, types.NamespacedName{Namespace: componentNamespace, Name: nudgedComponentName}, nudgingComponent) 263 | if err != nil { 264 | return err 265 | } 266 | nudgingComponent.Spec.BuildNudgesRef = util.RemoveStrFromList(compName, nudgingComponent.Spec.BuildNudgesRef) 267 | err = r.client.Update(ctx, nudgingComponent) 268 | return err 269 | }) 270 | if err != nil { 271 | // Don't block component deletion if this fails, but log and continue 272 | componentlog.Error(err, "error deleting component name from build-nudges-ref") 273 | } 274 | } 275 | return nil 276 | } 277 | 278 | // validateBuildNudgesRefGraph returns an error if a cycle was found in the 'build-nudges-ref' dependency graph 279 | // If no cycle is found, it returns nil 280 | func (r *ComponentWebhook) validateBuildNudgesRefGraph(ctx context.Context, nudgedComponentNames []string, componentNamespace string, componentName string) error { 281 | for _, nudgedComponentName := range nudgedComponentNames { 282 | if nudgedComponentName == componentName { 283 | return fmt.Errorf("cycle detected: component %s cannot reference itself, directly or indirectly, via build-nudges-ref", nudgedComponentName) 284 | } 285 | 286 | nudgedComponent := &appstudiov1alpha1.Component{} 287 | err := r.client.Get(ctx, types.NamespacedName{Namespace: componentNamespace, Name: nudgedComponentName}, nudgedComponent) 288 | if err != nil { 289 | // Return an error if an error was encountered retrieving the resource 290 | if !k8sErrors.IsNotFound(err) { 291 | return err 292 | } 293 | } 294 | 295 | err = r.validateBuildNudgesRefGraph(ctx, nudgedComponent.Spec.BuildNudgesRef, componentNamespace, componentName) 296 | if err != nil { 297 | return err 298 | } 299 | } 300 | 301 | return nil 302 | } 303 | -------------------------------------------------------------------------------- /webhooks/component_webhook_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022-2023 Red Hat, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package webhooks 17 | 18 | import ( 19 | "context" 20 | "errors" 21 | "fmt" 22 | "reflect" 23 | 24 | . "github.com/onsi/ginkgo" 25 | . "github.com/onsi/gomega" 26 | 27 | appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" 28 | 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | "k8s.io/apimachinery/pkg/types" 31 | //+kubebuilder:scaffold:imports 32 | ) 33 | 34 | var _ = Describe("Application validation webhook", func() { 35 | 36 | // Define utility constants for object names and testing timeouts/durations and intervals. 37 | const ( 38 | HASAppName = "test-application-123" 39 | HASCompName = "test-component-123" 40 | HASAppNamespace = "default" 41 | ComponentName = "backend" 42 | SampleRepoLink = "https://github.com/devfile-samples/devfile-sample-java-springboot-basic" 43 | ) 44 | 45 | Context("Create Application CR with bad fields", func() { 46 | It("Should reject until all the fields are valid", func() { 47 | ctx := context.Background() 48 | 49 | uniqueHASCompName := HASCompName + "1" 50 | 51 | badHASCompName := "1-sdsfsdfsdf-bad-name" 52 | 53 | // Bad Component Name, Bad Application Name and no Src 54 | hasComp := &appstudiov1alpha1.Component{ 55 | TypeMeta: metav1.TypeMeta{ 56 | APIVersion: "appstudio.redhat.com/v1alpha1", 57 | Kind: "Component", 58 | }, 59 | ObjectMeta: metav1.ObjectMeta{ 60 | Namespace: HASAppNamespace, 61 | }, 62 | Spec: appstudiov1alpha1.ComponentSpec{ 63 | ComponentName: "@#", 64 | Application: "@#", 65 | Source: appstudiov1alpha1.ComponentSource{ 66 | ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ 67 | GitSource: &appstudiov1alpha1.GitSource{}, 68 | }, 69 | }, 70 | }, 71 | } 72 | 73 | err := k8sClient.Create(ctx, hasComp) 74 | Expect(err).Should(HaveOccurred()) 75 | Expect(err.Error()).Should(ContainSubstring("spec.componentName in body should match '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'")) 76 | Expect(err.Error()).Should(ContainSubstring("spec.application in body should match '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'")) 77 | 78 | hasComp.Spec.ComponentName = ComponentName 79 | hasComp.Spec.Application = HASAppName 80 | 81 | hasComp.Name = badHASCompName 82 | err = k8sClient.Create(ctx, hasComp) 83 | Expect(err).Should(HaveOccurred()) 84 | Expect(err.Error()).Should(ContainSubstring(fmt.Errorf(appstudiov1alpha1.InvalidDNS1035Name, hasComp.Name).Error())) 85 | hasComp.Name = uniqueHASCompName 86 | 87 | err = k8sClient.Create(ctx, hasComp) 88 | Expect(err).Should(HaveOccurred()) 89 | Expect(err.Error()).Should(ContainSubstring(appstudiov1alpha1.MissingGitOrImageSource)) 90 | 91 | // Bad URL 92 | hasComp.Spec.Source.GitSource.URL = "badurl" 93 | err = k8sClient.Create(ctx, hasComp) 94 | Expect(err).Should(HaveOccurred()) 95 | Expect(err.Error()).Should(ContainSubstring(errors.New("invalid URI for request" + appstudiov1alpha1.InvalidSchemeGitSourceURL).Error())) 96 | 97 | // Good URL 98 | hasComp.Spec.Source.GitSource.URL = SampleRepoLink 99 | err = k8sClient.Create(ctx, hasComp) 100 | Expect(err).Should(BeNil()) 101 | 102 | // Look up the has app resource that was created. 103 | hasCompLookupKey := types.NamespacedName{Name: uniqueHASCompName, Namespace: HASAppNamespace} 104 | createdHasComp := &appstudiov1alpha1.Component{} 105 | Eventually(func() bool { 106 | k8sClient.Get(ctx, hasCompLookupKey, createdHasComp) 107 | return !reflect.DeepEqual(createdHasComp, &appstudiov1alpha1.Component{}) 108 | }, timeout, interval).Should(BeTrue()) 109 | 110 | // Delete the specified HASComp resource 111 | deleteHASCompCR(hasCompLookupKey) 112 | }) 113 | }) 114 | 115 | Context("Update Application CR fields", func() { 116 | It("Should update non immutable fields successfully and err out on immutable fields", func() { 117 | ctx := context.Background() 118 | 119 | uniqueHASCompName := HASCompName + "2" 120 | 121 | hasComp := &appstudiov1alpha1.Component{ 122 | TypeMeta: metav1.TypeMeta{ 123 | APIVersion: "appstudio.redhat.com/v1alpha1", 124 | Kind: "Component", 125 | }, 126 | ObjectMeta: metav1.ObjectMeta{ 127 | Name: uniqueHASCompName, 128 | Namespace: HASAppNamespace, 129 | }, 130 | Spec: appstudiov1alpha1.ComponentSpec{ 131 | ComponentName: ComponentName, 132 | Application: HASAppName, 133 | Source: appstudiov1alpha1.ComponentSource{ 134 | ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ 135 | GitSource: &appstudiov1alpha1.GitSource{ 136 | URL: SampleRepoLink, 137 | }, 138 | }, 139 | }, 140 | }, 141 | } 142 | Expect(k8sClient.Create(ctx, hasComp)).Should(Succeed()) 143 | 144 | // Look up the has app resource that was created. 145 | hasCompLookupKey := types.NamespacedName{Name: uniqueHASCompName, Namespace: HASAppNamespace} 146 | createdHasComp := &appstudiov1alpha1.Component{} 147 | Eventually(func() bool { 148 | k8sClient.Get(ctx, hasCompLookupKey, createdHasComp) 149 | return !reflect.DeepEqual(createdHasComp, &appstudiov1alpha1.Component{}) 150 | }, timeout, interval).Should(BeTrue()) 151 | 152 | // Update the Comp application 153 | createdHasComp.Spec.Application = "newapp" 154 | err := k8sClient.Update(ctx, createdHasComp) 155 | Expect(err).Should(HaveOccurred()) 156 | Expect(err.Error()).Should(ContainSubstring(fmt.Errorf(appstudiov1alpha1.ApplicationNameUpdateError, createdHasComp.Spec.Application).Error())) 157 | 158 | // Update the Comp component name 159 | createdHasComp.Spec.Application = hasComp.Spec.Application 160 | createdHasComp.Spec.ComponentName = "newcomp" 161 | err = k8sClient.Update(ctx, createdHasComp) 162 | Expect(err).Should(HaveOccurred()) 163 | Expect(err.Error()).Should(ContainSubstring(fmt.Errorf(appstudiov1alpha1.ComponentNameUpdateError, createdHasComp.Spec.ComponentName).Error())) 164 | 165 | // Update the Comp git src 166 | createdHasComp.Spec.ComponentName = hasComp.Spec.ComponentName 167 | createdHasComp.Spec.Source.GitSource.Context = hasComp.Spec.Source.GitSource.Context 168 | createdHasComp.Spec.Source.GitSource = &appstudiov1alpha1.GitSource{ 169 | URL: "newlink", 170 | } 171 | err = k8sClient.Update(ctx, createdHasComp) 172 | Expect(err).Should(HaveOccurred()) 173 | Expect(err.Error()).Should(ContainSubstring(fmt.Errorf(appstudiov1alpha1.GitSourceUpdateError, *createdHasComp.Spec.Source.GitSource).Error())) 174 | 175 | // Delete the specified HASComp resource 176 | deleteHASCompCR(hasCompLookupKey) 177 | }) 178 | }) 179 | 180 | Context("Create Application CR with invalid build-nudges-ref", func() { 181 | It("Should reject until it's resolved", func() { 182 | ctx := context.Background() 183 | 184 | uniqueHASCompName := HASCompName + "3" 185 | 186 | nudgedComp := &appstudiov1alpha1.Component{ 187 | TypeMeta: metav1.TypeMeta{ 188 | APIVersion: "appstudio.redhat.com/v1alpha1", 189 | Kind: "Component", 190 | }, 191 | ObjectMeta: metav1.ObjectMeta{ 192 | Namespace: HASAppNamespace, 193 | Name: uniqueHASCompName + "-nudge", 194 | }, 195 | Spec: appstudiov1alpha1.ComponentSpec{ 196 | ComponentName: uniqueHASCompName + "-nudge", 197 | Application: "test-application", 198 | Source: appstudiov1alpha1.ComponentSource{ 199 | ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ 200 | GitSource: &appstudiov1alpha1.GitSource{ 201 | URL: SampleRepoLink, 202 | }, 203 | }, 204 | }, 205 | }, 206 | } 207 | 208 | err := k8sClient.Create(ctx, nudgedComp) 209 | Expect(err).Should(Not(HaveOccurred())) 210 | 211 | // nudgingComp 212 | nudgingComp := &appstudiov1alpha1.Component{ 213 | TypeMeta: metav1.TypeMeta{ 214 | APIVersion: "appstudio.redhat.com/v1alpha1", 215 | Kind: "Component", 216 | }, 217 | ObjectMeta: metav1.ObjectMeta{ 218 | Namespace: HASAppNamespace, 219 | Name: uniqueHASCompName, 220 | }, 221 | Spec: appstudiov1alpha1.ComponentSpec{ 222 | ComponentName: uniqueHASCompName, 223 | Application: "test-application", 224 | BuildNudgesRef: []string{uniqueHASCompName}, 225 | Source: appstudiov1alpha1.ComponentSource{ 226 | ComponentSourceUnion: appstudiov1alpha1.ComponentSourceUnion{ 227 | GitSource: &appstudiov1alpha1.GitSource{ 228 | URL: SampleRepoLink, 229 | }, 230 | }, 231 | }, 232 | }, 233 | } 234 | 235 | err = k8sClient.Create(ctx, nudgingComp) 236 | Expect(err).Should((HaveOccurred())) 237 | Expect(err.Error()).Should(ContainSubstring("cycle detected")) 238 | 239 | // After changing to a valid build nudges ref, create should succeed 240 | nudgingComp.Spec.BuildNudgesRef = []string{uniqueHASCompName + "-nudge"} 241 | err = k8sClient.Create(ctx, nudgingComp) 242 | Expect(err).Should(BeNil()) 243 | 244 | // Look up the has app resource that was created. 245 | hasCompLookupKey := types.NamespacedName{Name: uniqueHASCompName, Namespace: HASAppNamespace} 246 | createdHasComp := &appstudiov1alpha1.Component{} 247 | Eventually(func() bool { 248 | k8sClient.Get(ctx, hasCompLookupKey, createdHasComp) 249 | return !reflect.DeepEqual(createdHasComp, &appstudiov1alpha1.Component{}) 250 | }, timeout, interval).Should(BeTrue()) 251 | 252 | // Look up the nudged component and verify that the status was appropriately set 253 | nudgedCompLookupKey := types.NamespacedName{Name: uniqueHASCompName + "-nudge", Namespace: HASAppNamespace} 254 | nudgedComp = &appstudiov1alpha1.Component{} 255 | Eventually(func() bool { 256 | k8sClient.Get(ctx, nudgedCompLookupKey, nudgedComp) 257 | return len(nudgedComp.Status.BuildNudgedBy) != 0 258 | }, timeout, interval).Should(BeTrue()) 259 | buildNudgedByList := nudgedComp.Status.BuildNudgedBy 260 | Expect(len(buildNudgedByList)).To(Equal(1)) 261 | Expect(buildNudgedByList[0] == uniqueHASCompName) 262 | 263 | // Delete the specified HASComp resource 264 | deleteHASCompCR(hasCompLookupKey) 265 | }) 266 | }) 267 | 268 | }) 269 | 270 | // deleteHASCompCR deletes the specified hasComp resource and verifies it was properly deleted 271 | func deleteHASCompCR(hasCompLookupKey types.NamespacedName) { 272 | // Delete 273 | Eventually(func() error { 274 | f := &appstudiov1alpha1.Component{} 275 | k8sClient.Get(context.Background(), hasCompLookupKey, f) 276 | return k8sClient.Delete(context.Background(), f) 277 | }, timeout, interval).Should(Succeed()) 278 | 279 | // Wait for delete to finish 280 | Eventually(func() error { 281 | f := &appstudiov1alpha1.Component{} 282 | return k8sClient.Get(context.Background(), hasCompLookupKey, f) 283 | }, timeout, interval).ShouldNot(Succeed()) 284 | } 285 | -------------------------------------------------------------------------------- /webhooks/webhook_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Red Hat, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package webhooks 18 | 19 | import ( 20 | "context" 21 | "crypto/tls" 22 | "fmt" 23 | "go/build" 24 | "net" 25 | "path/filepath" 26 | "testing" 27 | "time" 28 | 29 | . "github.com/onsi/ginkgo" 30 | . "github.com/onsi/gomega" 31 | 32 | appstudiov1alpha1 "github.com/konflux-ci/application-api/api/v1alpha1" 33 | toolkit "github.com/konflux-ci/operator-toolkit/webhook" 34 | admissionv1beta1 "k8s.io/api/admission/v1beta1" 35 | 36 | //+kubebuilder:scaffold:imports 37 | "k8s.io/apimachinery/pkg/runtime" 38 | ctrl "sigs.k8s.io/controller-runtime" 39 | "sigs.k8s.io/controller-runtime/pkg/client" 40 | "sigs.k8s.io/controller-runtime/pkg/envtest" 41 | logf "sigs.k8s.io/controller-runtime/pkg/log" 42 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 43 | ) 44 | 45 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 46 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 47 | 48 | var k8sClient client.Client 49 | var testEnv *envtest.Environment 50 | var ctx context.Context 51 | var cancel context.CancelFunc 52 | 53 | func TestAPIs(t *testing.T) { 54 | RegisterFailHandler(Fail) 55 | 56 | RunSpecs(t, "Webhook Suite") 57 | } 58 | 59 | var _ = BeforeSuite(func() { 60 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 61 | 62 | ctx, cancel = context.WithCancel(context.TODO()) 63 | 64 | By("bootstrapping test environment") 65 | applicationAPIDepVersion := "v0.0.0-20240812090716-e7eb2ecfb409" 66 | testEnv = &envtest.Environment{ 67 | CRDDirectoryPaths: []string{filepath.Join(build.Default.GOPATH, "pkg", "mod", "github.com", "konflux-ci", "application-api@"+applicationAPIDepVersion, "manifests")}, 68 | ErrorIfCRDPathMissing: true, 69 | WebhookInstallOptions: envtest.WebhookInstallOptions{ 70 | Paths: []string{filepath.Join("..", "config", "webhook")}, 71 | }, 72 | } 73 | 74 | cfg, err := testEnv.Start() 75 | Expect(err).NotTo(HaveOccurred()) 76 | Expect(cfg).NotTo(BeNil()) 77 | 78 | //hasApplication := false 79 | //for _, crd := range testEnv.CRDs { 80 | // if crd.Spec.Names.Singular == "Component" { 81 | // hasApplication = true 82 | // } 83 | //} 84 | //Expect(hasApplication).To(BeTrue()) 85 | 86 | scheme := runtime.NewScheme() 87 | err = appstudiov1alpha1.AddToScheme(scheme) 88 | Expect(err).NotTo(HaveOccurred()) 89 | 90 | err = admissionv1beta1.AddToScheme(scheme) 91 | Expect(err).NotTo(HaveOccurred()) 92 | 93 | err = admissionv1beta1.AddToScheme(scheme) 94 | Expect(err).NotTo(HaveOccurred()) 95 | 96 | err = admissionv1beta1.AddToScheme(scheme) 97 | Expect(err).NotTo(HaveOccurred()) 98 | 99 | //+kubebuilder:scaffold:scheme 100 | 101 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) 102 | Expect(err).NotTo(HaveOccurred()) 103 | Expect(k8sClient).NotTo(BeNil()) 104 | 105 | // start webhook server using Manager 106 | webhookInstallOptions := &testEnv.WebhookInstallOptions 107 | mgr, err := ctrl.NewManager(cfg, ctrl.Options{ 108 | Scheme: scheme, 109 | Host: webhookInstallOptions.LocalServingHost, 110 | Port: webhookInstallOptions.LocalServingPort, 111 | CertDir: webhookInstallOptions.LocalServingCertDir, 112 | LeaderElection: false, 113 | MetricsBindAddress: "0", 114 | }) 115 | Expect(err).NotTo(HaveOccurred()) 116 | 117 | err = toolkit.SetupWebhooks(mgr, &ApplicationWebhook{}, &ComponentWebhook{}) 118 | Expect(err).NotTo(HaveOccurred()) 119 | 120 | //+kubebuilder:scaffold:webhook 121 | 122 | go func() { 123 | defer GinkgoRecover() 124 | err = mgr.Start(ctx) 125 | Expect(err).NotTo(HaveOccurred()) 126 | }() 127 | 128 | // wait for the webhook server to get ready 129 | dialer := &net.Dialer{Timeout: time.Second} 130 | addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) 131 | Eventually(func() error { 132 | conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) 133 | if err != nil { 134 | return err 135 | } 136 | _ = conn.Close() 137 | return nil 138 | }).Should(Succeed()) 139 | 140 | }, 60) 141 | 142 | var _ = AfterSuite(func() { 143 | cancel() 144 | By("tearing down the test environment") 145 | err := testEnv.Stop() 146 | Expect(err).NotTo(HaveOccurred()) 147 | }) 148 | -------------------------------------------------------------------------------- /webhooks/webhooks.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2023 Red Hat, Inc. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package webhooks 17 | 18 | import ( 19 | "github.com/konflux-ci/operator-toolkit/webhook" 20 | ) 21 | 22 | // EnabledWebhooks is a slice containing references to all the webhooks that have to be registered 23 | var EnabledWebhooks = []webhook.Webhook{ 24 | &ApplicationWebhook{}, 25 | &ComponentWebhook{}, 26 | } 27 | --------------------------------------------------------------------------------