├── .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 | [](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 |
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 |
--------------------------------------------------------------------------------