├── .dockerignore ├── .github └── workflows │ └── validate-primer.yaml ├── .gitignore ├── .mergify.yml ├── Dockerfile ├── Makefile ├── PROJECT ├── README.md ├── api └── v1alpha1 │ ├── export_types.go │ ├── groupversion_info.go │ └── zz_generated.deepcopy.go ├── bundle.Dockerfile ├── bundle ├── manifests │ ├── gitops-primer-controller-manager-metrics-service_v1_service.yaml │ ├── gitops-primer-manager-config_v1_configmap.yaml │ ├── gitops-primer-metrics-reader_rbac.authorization.k8s.io_v1_clusterrole.yaml │ ├── gitops-primer-webhook-service_v1_service.yaml │ ├── gitops-primer.clusterserviceversion.yaml │ └── primer.gitops.io_exports.yaml ├── metadata │ └── annotations.yaml └── tests │ └── scorecard │ └── config.yaml ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ └── primer.gitops.io_exports.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_exports.yaml │ │ └── webhook_in_exports.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_config_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ ├── bases │ │ └── gitops-primer.clusterserviceversion.yaml │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── export_editor_role.yaml │ ├── export_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── samples │ ├── kustomization.yaml │ └── primer_v1alpha1_export.yaml ├── scorecard │ ├── bases │ │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml └── webhook │ ├── deployment.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.v1beta1.yaml │ ├── manifests.yaml │ └── service.yaml ├── controllers ├── export_controller.go └── suite_test.go ├── downloader ├── Dockerfile └── Makefile ├── examples ├── download-export.yaml └── export-to-git.yaml ├── export ├── Dockerfile ├── Makefile ├── committer.sh └── plugins │ ├── build-whiteout │ ├── build-whiteout │ └── build-whiteout.go │ ├── cluster-ip-removal │ └── cluster-ip.go │ ├── default-object-whiteout │ └── default-object-whiteout.go │ ├── gitops-primer │ └── gitops-primer.go │ ├── image-whiteout │ └── image-whiteout.go │ ├── imagestream-trigger │ └── imagestream-trigger.go │ ├── metadata-remediation │ └── metadata-remediation.go │ ├── namespace-removal │ └── namespace.go │ ├── openshift │ └── openshift.go │ ├── owner-reference │ └── ownerReference.go │ ├── pvc-volume │ └── volume-patch.go │ ├── replicaset-removal │ └── replicaset-remove.go │ ├── replication-controller │ └── replicationcontroller.go │ ├── serverless-whiteout │ └── serverless-whiteout.go │ ├── status-removal │ └── status-removal.go │ ├── tekton │ └── tekton.go │ ├── whiteout-csv │ └── whiteout-csv.go │ ├── whiteout-endpoints │ └── whiteout-endpoints.go │ └── whiteout-pods │ └── whiteout-pods.go ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── router-crd.yaml └── run-in-kind.sh ├── main.go ├── test-kuttl ├── e2e │ └── export │ │ ├── 00-create-objects.sh │ │ ├── 00-create-objects.yaml │ │ ├── 05-create-user.sh │ │ ├── 05-create-user.yaml │ │ ├── 06-create-primer.sh │ │ ├── 06-create-primer.yaml │ │ ├── 10-assert.yaml │ │ ├── 15-assert.yaml │ │ ├── 16-assert.yaml │ │ ├── 17-assert.yaml │ │ ├── 20-assert.yaml │ │ ├── 22-assert.yaml │ │ ├── 25-remove-objects.sh │ │ ├── 25-remove-objects.yaml │ │ ├── 30-create-primer.sh │ │ ├── 30-create-primer.yaml │ │ ├── 35-assert.yaml │ │ └── 40-assert.yaml └── kuttl-test.yaml └── webhook ├── Dockerfile ├── Makefile ├── api ├── app.go ├── helpers.go ├── router.go ├── server.go └── user.go ├── go.mod ├── go.sum └── main.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore all files which are not go type 3 | !**/*.go 4 | !**/*.mod 5 | !**/*.sum 6 | -------------------------------------------------------------------------------- /.github/workflows/validate-primer.yaml: -------------------------------------------------------------------------------- 1 | name: Validate Primer 2 | on: 3 | push: 4 | branches: ["main", "release*"] 5 | tags: ["*"] 6 | pull_request: 7 | branches: ["main", "release*"] 8 | 9 | env: 10 | GO_VERSION: "1.17" 11 | KIND_VERSION: "0.9.0" 12 | GO111MODULE: "on" 13 | OPERATOR_IMAGE: "quay.io/migtools/gitops-primer" 14 | BUNDLE_IMAGE: "quay.io/migtools/gitops-primer-bundle" 15 | EXPORT_IMAGE: "quay.io/migtools/gitops-primer-export" 16 | DOWNLOADER_IMAGE: "quay.io/migtools/gitops-primer-downloader" 17 | WEBHOOK_IMAGE: "quay.io/migtools/gitops-primer-webhook" 18 | TAG: v0.0.11 19 | 20 | 21 | jobs: 22 | build-operator: 23 | name: Build-operator 24 | runs-on: ubuntu-20.04 25 | 26 | steps: 27 | - name: Checkout source 28 | uses: actions/checkout@v2 29 | 30 | - name: Install Go 31 | uses: actions/setup-go@v1 32 | with: 33 | go-version: ${{ env.GO_VERSION }} 34 | 35 | - name: Test 36 | run: make test 37 | 38 | - name: Build operator container 39 | run: make docker-build IMG=${OPERATOR_IMAGE} 40 | 41 | - name: Export container image 42 | run: docker save -o /tmp/operator.tar ${OPERATOR_IMAGE} 43 | 44 | - name: Login to Quay 45 | uses: docker/login-action@v1 46 | with: 47 | registry: quay.io 48 | username: ${{ secrets.REGISTRY_USERNAME}} 49 | password: ${{ secrets.REGISTRY_PASSWORD }} 50 | 51 | - name: Push operator image to quay 52 | run: docker push ${OPERATOR_IMAGE}:latest 53 | 54 | - name: Save container as artifact 55 | uses: actions/upload-artifact@v1 56 | with: 57 | name: primer-operator 58 | path: /tmp/operator.tar 59 | 60 | build-bundle: 61 | name: Build-Bundle 62 | runs-on: ubuntu-20.04 63 | 64 | steps: 65 | - name: Checkout source 66 | uses: actions/checkout@v2 67 | 68 | - name: Install Go 69 | uses: actions/setup-go@v1 70 | with: 71 | go-version: ${{ env.GO_VERSION }} 72 | 73 | - name: Install operator-sdk 74 | run: | 75 | curl -L -o operator-sdk https://github.com/operator-framework/operator-sdk/releases/download/v1.11.0/operator-sdk_linux_amd64 76 | sudo install ./operator-sdk /usr/local/bin && rm operator-sdk 77 | 78 | - name: Make bundle 79 | run: make bundle 80 | 81 | - name: Build bundle 82 | run: make bundle-build 83 | 84 | - name: Login to Quay 85 | uses: docker/login-action@v1 86 | with: 87 | registry: quay.io 88 | username: ${{ secrets.REGISTRY_USERNAME}} 89 | password: ${{ secrets.REGISTRY_PASSWORD }} 90 | 91 | - name: Push bundle image to quay 92 | run: | 93 | docker tag ${BUNDLE_IMAGE}:${TAG} ${BUNDLE_IMAGE}:latest 94 | docker push ${BUNDLE_IMAGE}:latest 95 | 96 | - name: Export container image 97 | run: docker save -o /tmp/bundle.tar ${BUNDLE_IMAGE} 98 | 99 | - name: Save container as artifact 100 | uses: actions/upload-artifact@v1 101 | with: 102 | name: primer-bundle 103 | path: /tmp/bundle.tar 104 | 105 | build-export: 106 | name: Build-export 107 | runs-on: ubuntu-20.04 108 | 109 | steps: 110 | - name: Checkout source 111 | uses: actions/checkout@v2 112 | 113 | - name: Build export container 114 | run: make -C export image 115 | 116 | - name: Export container image 117 | run: docker save -o /tmp/export.tar ${EXPORT_IMAGE} 118 | 119 | - name: Login to Quay 120 | uses: docker/login-action@v1 121 | with: 122 | registry: quay.io 123 | username: ${{ secrets.REGISTRY_USERNAME}} 124 | password: ${{ secrets.REGISTRY_PASSWORD }} 125 | 126 | - name: Push export image to quay 127 | run: docker push ${EXPORT_IMAGE}:latest 128 | 129 | - name: Save container as artifact 130 | uses: actions/upload-artifact@v1 131 | with: 132 | name: export-image 133 | path: /tmp/export.tar 134 | 135 | build-downloader: 136 | name: Build-downloader 137 | runs-on: ubuntu-20.04 138 | 139 | steps: 140 | - name: Checkout source 141 | uses: actions/checkout@v2 142 | 143 | - name: Build downloader image 144 | run: make -C downloader image 145 | 146 | - name: Export container image 147 | run: docker save -o /tmp/downloader.tar ${DOWNLOADER_IMAGE} 148 | 149 | - name: Login to Quay 150 | uses: docker/login-action@v1 151 | with: 152 | registry: quay.io 153 | username: ${{ secrets.REGISTRY_USERNAME}} 154 | password: ${{ secrets.REGISTRY_PASSWORD }} 155 | 156 | - name: Push downloader image to quay 157 | run: docker push ${DOWNLOADER_IMAGE}:latest 158 | 159 | - name: Save container as artifact 160 | uses: actions/upload-artifact@v1 161 | with: 162 | name: primer-downloader 163 | path: /tmp/downloader.tar 164 | 165 | build-webhook: 166 | name: Build-Webhook 167 | runs-on: ubuntu-20.04 168 | steps: 169 | - name: Checkout source 170 | uses: actions/checkout@v2 171 | 172 | - name: Build webhook image 173 | run: make -C webhook image 174 | 175 | - name: Export container image 176 | run: docker save -o /tmp/webhook.tar ${WEBHOOK_IMAGE} 177 | 178 | - name: Login to Quay 179 | uses: docker/login-action@v1 180 | with: 181 | registry: quay.io 182 | username: ${{ secrets.REGISTRY_USERNAME}} 183 | password: ${{ secrets.REGISTRY_PASSWORD }} 184 | 185 | - name: Push webhook image to quay 186 | run: docker push ${WEBHOOK_IMAGE}:latest 187 | 188 | - name: Save container as artifact 189 | uses: actions/upload-artifact@v1 190 | with: 191 | name: primer-webhook 192 | path: /tmp/webhook.tar 193 | 194 | push-operator: 195 | name: Push operator container to registry 196 | needs: build-export 197 | if: > 198 | (github.event_name == 'push' || github.event_name == 'schedule') && 199 | (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) 200 | runs-on: ubuntu-20.04 201 | 202 | steps: 203 | - name: Checkout source 204 | uses: actions/checkout@v2 205 | 206 | - name: Set up QEMU 207 | uses: docker/setup-qemu-action@v1 208 | 209 | - name: Set up Docker Buildx 210 | uses: docker/setup-buildx-action@v1 211 | with: 212 | buildkitd-flags: --debug 213 | 214 | - name: Login to the registry 215 | run: | 216 | [[ "${OPERATOR_IMAGE}" =~ ^([^/]+)/[^/]+/[^/]+ ]] && REGISTRY="${BASH_REMATCH[1]}" || REGISTRY="quay.io" 217 | echo "Attempting docker login to: ${REGISTRY}" 218 | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin ${REGISTRY} 219 | 220 | - name: "Build Operator Image" 221 | uses: docker/build-push-action@v2 222 | with: 223 | context: . 224 | file: "./Dockerfile" 225 | platforms: "linux/amd64,linux/arm64" 226 | push: true 227 | tags: ${{ env.OPERATOR_IMAGE }}:${{ env.TAG }} 228 | 229 | 230 | push-export: 231 | name: Push export container to registry 232 | needs: [build-webhook, build-operator, build-bundle, build-downloader, build-export] 233 | if: > 234 | (github.event_name == 'push' || github.event_name == 'schedule') && 235 | (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) 236 | runs-on: ubuntu-20.04 237 | 238 | steps: 239 | - name: Checkout source 240 | uses: actions/checkout@v2 241 | 242 | - name: Set up QEMU 243 | uses: docker/setup-qemu-action@v1 244 | 245 | - name: Set up Docker Buildx 246 | uses: docker/setup-buildx-action@v1 247 | with: 248 | buildkitd-flags: --debug 249 | 250 | - name: Login to the registry 251 | run: | 252 | [[ "${EXPORT_IMAGE}" =~ ^([^/]+)/[^/]+/[^/]+ ]] && REGISTRY="${BASH_REMATCH[1]}" || REGISTRY="quay.io" 253 | echo "Attempting docker login to: ${REGISTRY}" 254 | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin ${REGISTRY} 255 | 256 | - name: "Build Operator Image" 257 | uses: docker/build-push-action@v2 258 | with: 259 | context: "./export" 260 | file: "./export/Dockerfile" 261 | platforms: "linux/amd64,linux/arm64" 262 | push: true 263 | tags: ${{ env.EXPORT_IMAGE }}:${{ env.TAG }} 264 | 265 | push-bundle: 266 | name: Push bundle container to registry 267 | needs: [build-webhook, build-operator, build-bundle, build-downloader, build-export] 268 | if: > 269 | (github.event_name == 'push' || github.event_name == 'schedule') && 270 | (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) 271 | runs-on: ubuntu-20.04 272 | 273 | steps: 274 | - name: Checkout source 275 | uses: actions/checkout@v2 276 | 277 | - name: Set up QEMU 278 | uses: docker/setup-qemu-action@v1 279 | 280 | - name: Set up Docker Buildx 281 | uses: docker/setup-buildx-action@v1 282 | with: 283 | buildkitd-flags: --debug 284 | 285 | - name: Login to the registry 286 | run: | 287 | [[ "${BUNDLE_IMAGE}" =~ ^([^/]+)/[^/]+/[^/]+ ]] && REGISTRY="${BASH_REMATCH[1]}" || REGISTRY="quay.io" 288 | echo "Attempting docker login to: ${REGISTRY}" 289 | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin ${REGISTRY} 290 | 291 | - name: "Build Operator Image" 292 | uses: docker/build-push-action@v2 293 | with: 294 | context: . 295 | file: "./bundle.Dockerfile" 296 | platforms: "linux/amd64,linux/arm64" 297 | push: true 298 | tags: ${{ env.BUNDLE_IMAGE }}:${{ env.TAG }} 299 | 300 | push-downloader: 301 | name: Push downloader image to registry 302 | needs: [build-webhook, build-operator, build-bundle, build-downloader, build-export] 303 | if: > 304 | (github.event_name == 'push' || github.event_name == 'schedule') && 305 | (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) 306 | runs-on: ubuntu-20.04 307 | 308 | steps: 309 | - name: Checkout source 310 | uses: actions/checkout@v2 311 | 312 | - name: Set up QEMU 313 | uses: docker/setup-qemu-action@v1 314 | 315 | - name: Set up Docker Buildx 316 | uses: docker/setup-buildx-action@v1 317 | with: 318 | buildkitd-flags: --debug 319 | 320 | - name: Login to the registry 321 | run: | 322 | [[ "${DOWNLOADER_IMAGE}" =~ ^([^/]+)/[^/]+/[^/]+ ]] && REGISTRY="${BASH_REMATCH[1]}" || REGISTRY="quay.io" 323 | echo "Attempting docker login to: ${REGISTRY}" 324 | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin ${REGISTRY} 325 | 326 | - name: "Build Operator Image" 327 | uses: docker/build-push-action@v2 328 | with: 329 | context: . 330 | file: "./downloader/Dockerfile" 331 | platforms: "linux/amd64,linux/arm64" 332 | push: true 333 | tags: ${{ env.DOWNLOADER_IMAGE }}:${{ env.TAG }} 334 | 335 | push-webhook: 336 | name: Push webhook image to registry 337 | needs: [build-webhook, build-operator, build-bundle, build-downloader, build-export] 338 | if: > 339 | (github.event_name == 'push' || github.event_name == 'schedule') && 340 | (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) 341 | runs-on: ubuntu-20.04 342 | 343 | steps: 344 | - name: Checkout source 345 | uses: actions/checkout@v2 346 | 347 | - name: Set up QEMU 348 | uses: docker/setup-qemu-action@v1 349 | 350 | - name: Set up Docker Buildx 351 | uses: docker/setup-buildx-action@v1 352 | with: 353 | buildkitd-flags: --debug 354 | 355 | - name: Login to the registry 356 | run: | 357 | [[ "${WEBHOOK_IMAGE}" =~ ^([^/]+)/[^/]+/[^/]+ ]] && REGISTRY="${BASH_REMATCH[1]}" || REGISTRY="quay.io" 358 | echo "Attempting docker login to: ${REGISTRY}" 359 | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin ${REGISTRY} 360 | 361 | - name: "Build Operator Image" 362 | uses: docker/build-push-action@v2 363 | with: 364 | context: "./webhook" 365 | file: "./webhook/Dockerfile" 366 | platforms: "linux/amd64,linux/arm64" 367 | push: true 368 | tags: ${{ env.WEBHOOK_IMAGE }}:${{ env.TAG }} 369 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | testbin/* 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | 19 | !vendor/**/zz_generated.* 20 | 21 | # editor and IDE paraphernalia 22 | .idea 23 | *.swp 24 | *.swo 25 | *~ 26 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pull_request_rules: 3 | - name: Automatic merge on approval 4 | conditions: 5 | - "#changes-requested-reviews-by=0" 6 | - label!=do-not-merge 7 | - check-success=Validate Primer 8 | actions: 9 | merge: 10 | method: merge 11 | strict: true 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM registry.access.redhat.com/ubi8/go-toolset:1.15.14 as builder 3 | 4 | RUN mkdir -p $APP_ROOT/src/github.com/migtools/gitops-primer 5 | WORKDIR $APP_ROOT/src/github.com/migtools/gitops-primer 6 | # Copy the Go Modules manifests 7 | COPY go.mod go.mod 8 | COPY go.sum go.sum 9 | # cache deps before building and copying source so that we don't need to re-download as much 10 | # and so that source changes don't invalidate our downloaded layer 11 | RUN go mod download 12 | 13 | # Copy the go source 14 | COPY main.go main.go 15 | COPY api/ api/ 16 | COPY controllers/ controllers/ 17 | 18 | # Build 19 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go 20 | 21 | # Use distroless as minimal base image to package the manager binary 22 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 23 | FROM registry.access.redhat.com/ubi8/ubi-minimal 24 | WORKDIR / 25 | COPY --from=builder /opt/app-root/src/github.com/migtools/gitops-primer/manager /manager 26 | USER 65532:65532 27 | ENTRYPOINT ["/manager"] 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | KUTTL_VERSION := 0.10.0 2 | 3 | # VERSION defines the project version for the bundle. 4 | # Update this value when you upgrade the version of your project. 5 | # To re-generate a bundle for another specific version without changing the standard setup, you can: 6 | # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) 7 | # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) 8 | VERSION ?= 0.0.11 9 | 10 | # CHANNELS define the bundle channels used in the bundle. 11 | # Add a new line here if you would like to change its default config. (E.g CHANNELS = "preview,fast,stable") 12 | # To re-generate a bundle for other specific channels without changing the standard setup, you can: 13 | # - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=preview,fast,stable) 14 | # - use environment variables to overwrite this value (e.g export CHANNELS="preview,fast,stable") 15 | ifneq ($(origin CHANNELS), undefined) 16 | BUNDLE_CHANNELS := --channels=$(CHANNELS) 17 | endif 18 | 19 | # DEFAULT_CHANNEL defines the default channel used in the bundle. 20 | # Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") 21 | # To re-generate a bundle for any other default channel without changing the default setup, you can: 22 | # - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) 23 | # - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") 24 | ifneq ($(origin DEFAULT_CHANNEL), undefined) 25 | BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) 26 | endif 27 | BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) 28 | 29 | # IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. 30 | # This variable is used to construct full image tags for bundle and catalog images. 31 | # 32 | # For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both 33 | # example.com/gitops-primer-bundle:$VERSION and example.com/gitops-primer-catalog:$VERSION. 34 | IMAGE_TAG_BASE ?= quay.io/migtools/gitops-primer 35 | # IMAGE_TAG_BASE ?= quay.io/screeley44/gitops-primer 36 | 37 | # BUNDLE_IMG defines the image:tag used for the bundle. 38 | # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) 39 | BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) 40 | 41 | # Image URL to use all building/pushing image targets 42 | IMG ?= quay.io/migtools/gitops-primer:v0.0.11 43 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) 44 | CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false" 45 | 46 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 47 | ifeq (,$(shell go env GOBIN)) 48 | GOBIN=$(shell go env GOPATH)/bin 49 | else 50 | GOBIN=$(shell go env GOBIN) 51 | endif 52 | 53 | # Setting SHELL to bash allows bash commands to be executed by recipes. 54 | # This is a requirement for 'setup-envtest.sh' in the test target. 55 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 56 | SHELL = /usr/bin/env bash -o pipefail 57 | .SHELLFLAGS = -ec 58 | 59 | all: build 60 | 61 | ##@ General 62 | 63 | # The help target prints out all targets with their descriptions organized 64 | # beneath their categories. The categories are represented by '##@' and the 65 | # target descriptions by '##'. The awk commands is responsible for reading the 66 | # entire set of makefiles included in this invocation, looking for lines of the 67 | # file as xyz: ## something, and then pretty-format the target and help. Then, 68 | # if there's a line with ##@ something, that gets pretty-printed as a category. 69 | # More info on the usage of ANSI control characters for terminal formatting: 70 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 71 | # More info on the awk command: 72 | # http://linuxcommand.org/lc3_adv_awk.php 73 | 74 | help: ## Display this help. 75 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 76 | 77 | ##@ Development 78 | 79 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 80 | $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases 81 | 82 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 83 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 84 | 85 | fmt: ## Run go fmt against code. 86 | go fmt ./... 87 | 88 | vet: ## Run go vet against code. 89 | go vet ./... 90 | 91 | ENVTEST_ASSETS_DIR=$(shell pwd)/testbin 92 | test: manifests generate fmt vet ## Run tests. 93 | mkdir -p ${ENVTEST_ASSETS_DIR} 94 | test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.8.3/hack/setup-envtest.sh 95 | source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out 96 | 97 | .PHONY: test-e2e 98 | test-e2e: kuttl ## Run e2e tests. Requires cluster w/ Scribe already installed 99 | cd test-kuttl && $(KUTTL) test --namespace test 100 | rm -f test-kuttl/kubeconfig 101 | 102 | ##@ Build 103 | 104 | build: generate fmt vet ## Build manager binary. 105 | go build -o bin/manager main.go 106 | 107 | run: manifests generate fmt vet ## Run a controller from your host. 108 | go run ./main.go 109 | 110 | docker-build: test ## Build docker image with the manager. 111 | docker build -t ${IMG} . 112 | 113 | docker-push: ## Push docker image with the manager. 114 | docker push ${IMG} 115 | 116 | build-plugins: 117 | cd export/plugins && go install ./... 118 | 119 | ##@ Deployment 120 | 121 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. 122 | $(KUSTOMIZE) build config/crd | kubectl apply -f - 123 | 124 | uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. 125 | $(KUSTOMIZE) build config/crd | kubectl delete -f - 126 | 127 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. 128 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 129 | $(KUSTOMIZE) build config/default | kubectl apply -f - 130 | 131 | undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. 132 | $(KUSTOMIZE) build config/default | kubectl delete -f - 133 | 134 | 135 | CONTROLLER_GEN = $(shell pwd)/bin/controller-gen 136 | controller-gen: ## Download controller-gen locally if necessary. 137 | $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) 138 | 139 | KUSTOMIZE = $(shell pwd)/bin/kustomize 140 | kustomize: ## Download kustomize locally if necessary. 141 | $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) 142 | 143 | # go-get-tool will 'go get' any package $2 and install it to $1. 144 | PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) 145 | define go-get-tool 146 | @[ -f $(1) ] || { \ 147 | set -e ;\ 148 | TMP_DIR=$$(mktemp -d) ;\ 149 | cd $$TMP_DIR ;\ 150 | go mod init tmp ;\ 151 | echo "Downloading $(2)" ;\ 152 | GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ 153 | rm -rf $$TMP_DIR ;\ 154 | } 155 | endef 156 | 157 | .PHONY: bundle 158 | bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. 159 | operator-sdk generate kustomize manifests -q 160 | cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) 161 | $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) 162 | operator-sdk bundle validate ./bundle 163 | 164 | .PHONY: bundle-build 165 | bundle-build: ## Build the bundle image. 166 | docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . 167 | 168 | .PHONY: bundle-push 169 | bundle-push: ## Push the bundle image. 170 | $(MAKE) docker-push IMG=$(BUNDLE_IMG) 171 | 172 | .PHONY: lint 173 | lint: golangci-lint ## Lint source code 174 | $(GOLANGCILINT) run ./... 175 | 176 | .PHONY: opm 177 | OPM = ./bin/opm 178 | opm: ## Download opm locally if necessary. 179 | ifeq (,$(wildcard $(OPM))) 180 | ifeq (,$(shell which opm 2>/dev/null)) 181 | @{ \ 182 | set -e ;\ 183 | mkdir -p $(dir $(OPM)) ;\ 184 | OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ 185 | curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.15.1/$${OS}-$${ARCH}-opm ;\ 186 | chmod +x $(OPM) ;\ 187 | } 188 | else 189 | OPM = $(shell which opm) 190 | endif 191 | endif 192 | 193 | # A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). 194 | # These images MUST exist in a registry and be pull-able. 195 | BUNDLE_IMGS ?= $(BUNDLE_IMG) 196 | 197 | # The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). 198 | CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION) 199 | 200 | # Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. 201 | ifneq ($(origin CATALOG_BASE_IMG), undefined) 202 | FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) 203 | endif 204 | 205 | # Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. 206 | # This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: 207 | # https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator 208 | .PHONY: catalog-build 209 | catalog-build: opm ## Build a catalog image. 210 | $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) 211 | 212 | # Push the catalog image. 213 | .PHONY: catalog-push 214 | catalog-push: ## Push a catalog image. 215 | $(MAKE) docker-push IMG=$(CATALOG_IMG) 216 | 217 | ##@ Download utilities 218 | 219 | OS := $(shell go env GOOS) 220 | ARCH := $(shell go env GOARCH) 221 | 222 | # download-tool will curl any file $2 and install it to $1. 223 | define download-tool 224 | @[ -f $(1) ] || { \ 225 | set -e ;\ 226 | echo "Downloading $(2)" ;\ 227 | curl -sSLo "$(1)" "$(2)" ;\ 228 | chmod a+x "$(1)" ;\ 229 | } 230 | endef 231 | 232 | .PHONY: golangci-lint 233 | GOLANGCILINT := $(PROJECT_DIR)/bin/golangci-lint 234 | GOLANGCI_URL := https://install.goreleaser.com/github.com/golangci/golangci-lint.sh 235 | golangci-lint: ## Download golangci-lint 236 | ifeq (,$(wildcard $(GOLANGCILINT))) 237 | curl -sSL $(GOLANGCI_URL) | sh -s -- -b $(PROJECT_DIR)/bin $(GOLANGCI_VERSION) 238 | endif 239 | 240 | .PHONY: kuttl 241 | KUTTL := $(PROJECT_DIR)/bin/kuttl 242 | KUTTL_URL := https://github.com/kudobuilder/kuttl/releases/download/v$(KUTTL_VERSION)/kubectl-kuttl_$(KUTTL_VERSION)_linux_x86_64 243 | kuttl: ## Download kuttl 244 | $(call download-tool,$(KUTTL),$(KUTTL_URL)) 245 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: gitops.io 2 | layout: 3 | - go.kubebuilder.io/v3 4 | plugins: 5 | manifests.sdk.operatorframework.io/v2: {} 6 | scorecard.sdk.operatorframework.io/v2: {} 7 | projectName: gitops-primer 8 | repo: github.com/cooktheryan/gitops-primer 9 | resources: 10 | - api: 11 | crdVersion: v1 12 | namespaced: true 13 | controller: true 14 | domain: gitops.io 15 | group: primer 16 | kind: Export 17 | path: github.com/cooktheryan/gitops-primer/api/v1alpha1 18 | version: v1alpha1 19 | webhooks: 20 | defaulting: true 21 | webhookVersion: v1 22 | version: "3" 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Validate Primer](https://github.com/cooktheryan/gitops-primer/actions/workflows/validate-primer.yaml/badge.svg)](https://github.com/cooktheryan/gitops-primer/actions/workflows/validate-primer.yaml) 2 | 3 | # gitops-primer 4 | GitOps Primer is an operator can be deployed with a Kubernetes environment to export objects out of the cluster and store them within a Git repository. 5 | 6 | ## Developing 7 | If you would like to test or develop using GitOps Primer deploy Minikube(https://minikube.sigs.k8s.io/docs/start/) or Kind(https://kind.sigs.k8s.io/) and run the following. 8 | 9 | ``` 10 | make install 11 | make run 12 | ``` 13 | 14 | ## Deploying without OLM 15 | If you would like to run GitOps primer within your environment. 16 | ``` 17 | make deploy 18 | ``` 19 | 20 | ## Deploying with OLM 21 | If you would like to run GitOps primer within your environment that has OLM 22 | ``` 23 | oc create ns gitops-primer-system 24 | operator-sdk run bundle quay.io/migtools/gitops-primer-bundle:v0.0.1 --namespace gitops-primer-system 25 | ``` 26 | 27 | ## Running 28 | Although there are two examples that are given within the examples directory the only one that will be usable to you is *export-to-git.yaml*. This is because *download-export.yaml* requires a different pod to present the downloadable content to you. The information below will focus on the git method. 29 | 30 | WARNING: Please use a private git repository in this example! 31 | 32 | A secret containing an SSH key that is linked to the Git Repository must be created before running GitOps Primer. Follow the steps to add a new SSH key to your GitHub account(https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account). 33 | 34 | ``` 35 | oc create secret generic secret-key --from-file=id_rsa=~/.ssh/id_rsa 36 | ``` 37 | 38 | Now that the SSH key is loaded modify the file examples/export-to-git.yaml to define the git branch and private repository to use and then deploy. 39 | 40 | ``` 41 | oc create -f examples/export-to-git.yaml 42 | ``` 43 | 44 | After the job completes, items will exist within your git repository. 45 | 46 | -------------------------------------------------------------------------------- /api/v1alpha1/export_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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 v1alpha1 18 | 19 | import ( 20 | "github.com/operator-framework/operator-lib/status" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | const ( 25 | // ConditionReconciled is a status condition type that indicates whether the 26 | // CR has been successfully reconciled 27 | ConditionReconciled status.ConditionType = "Reconciled" 28 | // ReconciledReasonComplete indicates the CR was successfully reconciled 29 | ReconciledReasonComplete status.ConditionReason = "ReconcileComplete" 30 | // ReconciledReasonError indicates an error was encountered while 31 | // reconciling the CR 32 | ReconciledReasonError status.ConditionReason = "ReconcileError" 33 | ) 34 | 35 | type ExportSpec struct { 36 | // Method download or git. This defines which process 37 | // to use for exporting objects from a cluster 38 | Method string `json:"method"` 39 | // Branch within the git repository 40 | Branch string `json:"branch,omitempty"` 41 | // Git repository which will be cloned and updated 42 | Repo string `json:"repo,omitempty"` 43 | // Email used to specify the user who performed the git commit 44 | Email string `json:"email,omitempty"` 45 | // Predefined secret that contains an SSH key that will 46 | // be used for git cloning and pushing 47 | Secret string `json:"secret,omitempty"` 48 | // Set automatically by the webhook to dictate who will 49 | // run the export process 50 | User string `json:"user,omitempty"` 51 | // Set automatically by the webhook to dictate the 52 | // group 53 | Group []string `json:"group,omitempty"` 54 | // Filter Export by labels 55 | LabelSelector string `json:"labelSelector,omitempty"` 56 | } 57 | 58 | // ExportStatus defines the observed state of Export 59 | type ExportStatus struct { 60 | // Condition set by controller to signify the export completed 61 | // successfully and the route is available 62 | Completed bool `json:"completed,omitempty"` 63 | Extracted bool `json:"extracted,omitempty"` 64 | Conditions status.Conditions `json:"conditions,omitempty"` 65 | // Route that is defined by the controller to specify the 66 | // location of the zip file 67 | Route string `json:"route,omitempty"` 68 | } 69 | 70 | //+kubebuilder:object:root=true 71 | //+kubebuilder:subresource:status 72 | 73 | // Export is the Schema for the exports API 74 | type Export struct { 75 | metav1.TypeMeta `json:",inline"` 76 | metav1.ObjectMeta `json:"metadata,omitempty"` 77 | 78 | Spec ExportSpec `json:"spec,omitempty"` 79 | Status ExportStatus `json:"status,omitempty"` 80 | } 81 | 82 | //+kubebuilder:object:root=true 83 | 84 | // ExportList contains a list of Export 85 | type ExportList struct { 86 | metav1.TypeMeta `json:",inline"` 87 | metav1.ListMeta `json:"metadata,omitempty"` 88 | Items []Export `json:"items"` 89 | } 90 | 91 | func init() { 92 | SchemeBuilder.Register(&Export{}, &ExportList{}) 93 | } 94 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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 v1alpha1 contains API Schema definitions for the primer v1alpha1 API group 18 | //+kubebuilder:object:generate=true 19 | //+groupName=primer.gitops.io 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "primer.gitops.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2021. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | "github.com/operator-framework/operator-lib/status" 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *Export) DeepCopyInto(out *Export) { 30 | *out = *in 31 | out.TypeMeta = in.TypeMeta 32 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 33 | in.Spec.DeepCopyInto(&out.Spec) 34 | in.Status.DeepCopyInto(&out.Status) 35 | } 36 | 37 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Export. 38 | func (in *Export) DeepCopy() *Export { 39 | if in == nil { 40 | return nil 41 | } 42 | out := new(Export) 43 | in.DeepCopyInto(out) 44 | return out 45 | } 46 | 47 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 48 | func (in *Export) DeepCopyObject() runtime.Object { 49 | if c := in.DeepCopy(); c != nil { 50 | return c 51 | } 52 | return nil 53 | } 54 | 55 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 56 | func (in *ExportList) DeepCopyInto(out *ExportList) { 57 | *out = *in 58 | out.TypeMeta = in.TypeMeta 59 | in.ListMeta.DeepCopyInto(&out.ListMeta) 60 | if in.Items != nil { 61 | in, out := &in.Items, &out.Items 62 | *out = make([]Export, len(*in)) 63 | for i := range *in { 64 | (*in)[i].DeepCopyInto(&(*out)[i]) 65 | } 66 | } 67 | } 68 | 69 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExportList. 70 | func (in *ExportList) DeepCopy() *ExportList { 71 | if in == nil { 72 | return nil 73 | } 74 | out := new(ExportList) 75 | in.DeepCopyInto(out) 76 | return out 77 | } 78 | 79 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 80 | func (in *ExportList) DeepCopyObject() runtime.Object { 81 | if c := in.DeepCopy(); c != nil { 82 | return c 83 | } 84 | return nil 85 | } 86 | 87 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 88 | func (in *ExportSpec) DeepCopyInto(out *ExportSpec) { 89 | *out = *in 90 | if in.Group != nil { 91 | in, out := &in.Group, &out.Group 92 | *out = make([]string, len(*in)) 93 | copy(*out, *in) 94 | } 95 | } 96 | 97 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExportSpec. 98 | func (in *ExportSpec) DeepCopy() *ExportSpec { 99 | if in == nil { 100 | return nil 101 | } 102 | out := new(ExportSpec) 103 | in.DeepCopyInto(out) 104 | return out 105 | } 106 | 107 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 108 | func (in *ExportStatus) DeepCopyInto(out *ExportStatus) { 109 | *out = *in 110 | if in.Conditions != nil { 111 | in, out := &in.Conditions, &out.Conditions 112 | *out = make(status.Conditions, len(*in)) 113 | for i := range *in { 114 | (*in)[i].DeepCopyInto(&(*out)[i]) 115 | } 116 | } 117 | } 118 | 119 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExportStatus. 120 | func (in *ExportStatus) DeepCopy() *ExportStatus { 121 | if in == nil { 122 | return nil 123 | } 124 | out := new(ExportStatus) 125 | in.DeepCopyInto(out) 126 | return out 127 | } 128 | -------------------------------------------------------------------------------- /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=gitops-primer 8 | LABEL operators.operatorframework.io.bundle.channels.v1=alpha 9 | LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.18.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/gitops-primer-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: gitops-primer-controller-manager-metrics-service 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | status: 16 | loadBalancer: {} 17 | -------------------------------------------------------------------------------- /bundle/manifests/gitops-primer-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: 162233a0.gitops.io 15 | kind: ConfigMap 16 | metadata: 17 | name: gitops-primer-manager-config 18 | -------------------------------------------------------------------------------- /bundle/manifests/gitops-primer-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: gitops-primer-metrics-reader 6 | rules: 7 | - nonResourceURLs: 8 | - /metrics 9 | verbs: 10 | - get 11 | -------------------------------------------------------------------------------- /bundle/manifests/gitops-primer-webhook-service_v1_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | creationTimestamp: null 5 | name: gitops-primer-webhook-service 6 | spec: 7 | ports: 8 | - port: 443 9 | protocol: TCP 10 | targetPort: 8000 11 | selector: 12 | app: export-webhook 13 | status: 14 | loadBalancer: {} 15 | -------------------------------------------------------------------------------- /bundle/manifests/gitops-primer.clusterserviceversion.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.coreos.com/v1alpha1 2 | kind: ClusterServiceVersion 3 | metadata: 4 | annotations: 5 | alm-examples: |- 6 | [ 7 | { 8 | "apiVersion": "primer.gitops.io/v1alpha1", 9 | "kind": "Export", 10 | "metadata": { 11 | "name": "primer" 12 | }, 13 | "spec": { 14 | "method": "download" 15 | } 16 | } 17 | ] 18 | capabilities: Basic Install 19 | categories: OpenShift Optional 20 | containerImage: quay.io/migtools/gitops-primer:v0.0.11 21 | operatorframework.io/suggested-namespace: gitops-primer-system 22 | operators.operatorframework.io/builder: operator-sdk-v1.18.0+git 23 | operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 24 | name: gitops-primer.v0.0.11 25 | namespace: gitops-primer-system 26 | spec: 27 | apiservicedefinitions: {} 28 | customresourcedefinitions: 29 | owned: 30 | - description: Export is the Schema for the exports API 31 | displayName: Export 32 | kind: Export 33 | name: exports.primer.gitops.io 34 | version: v1alpha1 35 | description: Export and normalize Kubernetes YAML files for GitOps 36 | displayName: gitops-primer 37 | icon: 38 | - base64data: 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 39 | mediatype: image/png 40 | install: 41 | spec: 42 | clusterPermissions: 43 | - rules: 44 | - apiGroups: 45 | - "" 46 | resources: 47 | - groups 48 | - users 49 | verbs: 50 | - impersonate 51 | - apiGroups: 52 | - "" 53 | resourceNames: 54 | - gitops-primer-system 55 | resources: 56 | - namespaces 57 | verbs: 58 | - get 59 | - update 60 | - apiGroups: 61 | - '*' 62 | resources: 63 | - '*' 64 | verbs: 65 | - get 66 | - list 67 | - apiGroups: 68 | - apps 69 | resources: 70 | - deployments 71 | verbs: 72 | - create 73 | - delete 74 | - get 75 | - list 76 | - patch 77 | - update 78 | - watch 79 | - apiGroups: 80 | - batch 81 | resources: 82 | - jobs 83 | verbs: 84 | - create 85 | - delete 86 | - get 87 | - list 88 | - patch 89 | - update 90 | - watch 91 | - apiGroups: 92 | - "" 93 | resources: 94 | - persistentvolumeclaims 95 | verbs: 96 | - create 97 | - delete 98 | - get 99 | - list 100 | - patch 101 | - update 102 | - watch 103 | - apiGroups: 104 | - "" 105 | resources: 106 | - secrets 107 | verbs: 108 | - create 109 | - delete 110 | - get 111 | - list 112 | - patch 113 | - update 114 | - watch 115 | - apiGroups: 116 | - "" 117 | resources: 118 | - serviceaccounts 119 | verbs: 120 | - create 121 | - delete 122 | - get 123 | - list 124 | - patch 125 | - update 126 | - watch 127 | - apiGroups: 128 | - "" 129 | resources: 130 | - services 131 | verbs: 132 | - create 133 | - delete 134 | - get 135 | - list 136 | - patch 137 | - update 138 | - watch 139 | - apiGroups: 140 | - networking.k8s.io 141 | resources: 142 | - networkpolicies 143 | verbs: 144 | - create 145 | - delete 146 | - get 147 | - list 148 | - patch 149 | - update 150 | - watch 151 | - apiGroups: 152 | - primer.gitops.io 153 | resources: 154 | - exports 155 | verbs: 156 | - create 157 | - delete 158 | - get 159 | - list 160 | - patch 161 | - update 162 | - watch 163 | - apiGroups: 164 | - primer.gitops.io 165 | resources: 166 | - exports/finalizers 167 | verbs: 168 | - update 169 | - apiGroups: 170 | - primer.gitops.io 171 | resources: 172 | - exports/status 173 | verbs: 174 | - get 175 | - patch 176 | - update 177 | - apiGroups: 178 | - rbac.authorization.k8s.io 179 | resources: 180 | - clusterrolebindings 181 | verbs: 182 | - create 183 | - delete 184 | - get 185 | - list 186 | - patch 187 | - update 188 | - watch 189 | - apiGroups: 190 | - rbac.authorization.k8s.io 191 | resources: 192 | - clusterroles 193 | verbs: 194 | - create 195 | - delete 196 | - get 197 | - list 198 | - patch 199 | - update 200 | - watch 201 | - apiGroups: 202 | - route.openshift.io 203 | resources: 204 | - routes 205 | verbs: 206 | - create 207 | - delete 208 | - get 209 | - list 210 | - patch 211 | - update 212 | - watch 213 | - apiGroups: 214 | - authentication.k8s.io 215 | resources: 216 | - tokenreviews 217 | verbs: 218 | - create 219 | - apiGroups: 220 | - authorization.k8s.io 221 | resources: 222 | - subjectaccessreviews 223 | verbs: 224 | - create 225 | serviceAccountName: gitops-primer-controller-manager 226 | deployments: 227 | - name: gitops-primer-controller-manager 228 | spec: 229 | replicas: 1 230 | selector: 231 | matchLabels: 232 | control-plane: controller-manager 233 | strategy: {} 234 | template: 235 | metadata: 236 | labels: 237 | control-plane: controller-manager 238 | spec: 239 | containers: 240 | - args: 241 | - --secure-listen-address=0.0.0.0:8443 242 | - --upstream=http://127.0.0.1:8080/ 243 | - --logtostderr=true 244 | - --v=10 245 | image: registry.redhat.io/openshift4/ose-kube-rbac-proxy@sha256:6d57bfd91fac9b68eb72d27226bc297472ceb136c996628b845ecc54a48b31cb 246 | name: kube-rbac-proxy 247 | ports: 248 | - containerPort: 8443 249 | name: https 250 | resources: {} 251 | securityContext: 252 | allowPrivilegeEscalation: false 253 | capabilities: 254 | drop: 255 | - ALL 256 | runAsNonRoot: true 257 | - args: 258 | - --health-probe-bind-address=:8081 259 | - --metrics-bind-address=127.0.0.1:8080 260 | - --leader-elect 261 | command: 262 | - /manager 263 | env: 264 | - name: DownloaderImageName 265 | value: quay.io/migtools/gitops-primer-downloader:v0.0.11 266 | - name: ExportImageName 267 | value: quay.io/migtools/gitops-primer-export:v0.0.11 268 | - name: OauthImageName 269 | value: quay.io/openshift/origin-oauth-proxy:4.10 270 | image: quay.io/migtools/gitops-primer:v0.0.11 271 | imagePullPolicy: IfNotPresent 272 | livenessProbe: 273 | httpGet: 274 | path: /healthz 275 | port: 8081 276 | initialDelaySeconds: 15 277 | periodSeconds: 20 278 | name: manager 279 | readinessProbe: 280 | httpGet: 281 | path: /readyz 282 | port: 8081 283 | initialDelaySeconds: 5 284 | periodSeconds: 10 285 | resources: 286 | limits: 287 | cpu: 100m 288 | memory: 300Mi 289 | requests: 290 | cpu: 100m 291 | memory: 20Mi 292 | securityContext: 293 | allowPrivilegeEscalation: false 294 | capabilities: 295 | drop: 296 | - ALL 297 | runAsNonRoot: true 298 | securityContext: 299 | runAsNonRoot: true 300 | serviceAccountName: gitops-primer-controller-manager 301 | terminationGracePeriodSeconds: 10 302 | - name: gitops-primer-mutating-webhook-deployment 303 | spec: 304 | replicas: 1 305 | selector: 306 | matchLabels: 307 | app: export-webhook 308 | strategy: {} 309 | template: 310 | metadata: 311 | labels: 312 | app: export-webhook 313 | spec: 314 | containers: 315 | - image: quay.io/migtools/gitops-primer-webhook:v0.0.11 316 | imagePullPolicy: IfNotPresent 317 | name: export-webhook 318 | ports: 319 | - containerPort: 8000 320 | resources: 321 | limits: 322 | cpu: 500m 323 | memory: 128Mi 324 | securityContext: 325 | allowPrivilegeEscalation: false 326 | capabilities: 327 | drop: 328 | - ALL 329 | runAsNonRoot: true 330 | volumeMounts: 331 | - mountPath: /tls 332 | name: export-tls-secret 333 | readOnly: true 334 | securityContext: 335 | runAsNonRoot: true 336 | volumes: 337 | - name: export-tls-secret 338 | secret: 339 | secretName: gitops-primer-mutating-webhook-deployment-service-cert 340 | permissions: 341 | - rules: 342 | - apiGroups: 343 | - "" 344 | resources: 345 | - configmaps 346 | verbs: 347 | - get 348 | - list 349 | - watch 350 | - create 351 | - update 352 | - patch 353 | - delete 354 | - apiGroups: 355 | - coordination.k8s.io 356 | resources: 357 | - leases 358 | verbs: 359 | - get 360 | - list 361 | - watch 362 | - create 363 | - update 364 | - patch 365 | - delete 366 | - apiGroups: 367 | - "" 368 | resources: 369 | - events 370 | verbs: 371 | - create 372 | - patch 373 | serviceAccountName: gitops-primer-controller-manager 374 | strategy: deployment 375 | installModes: 376 | - supported: false 377 | type: OwnNamespace 378 | - supported: false 379 | type: SingleNamespace 380 | - supported: false 381 | type: MultiNamespace 382 | - supported: true 383 | type: AllNamespaces 384 | keywords: 385 | - gitops 386 | - operator 387 | links: 388 | - name: Gitops Primer 389 | url: https://github.com/migtools/gitops-primer 390 | maturity: alpha 391 | provider: 392 | name: Konveyor 393 | relatedImages: 394 | - image: quay.io/migtools/gitops-primer-downloader:v0.0.11 395 | name: downloader 396 | - image: quay.io/migtools/gitops-primer-export:v0.0.11 397 | name: export 398 | - image: quay.io/migtools/gitops-primer-webhook:v0.0.11 399 | name: webhook 400 | - image: quay.io/openshift/origin-oauth-proxy:4.9 401 | name: oauth-proxy 402 | - image: registry.redhat.io/openshift4/ose-kube-rbac-proxy@sha256:6d57bfd91fac9b68eb72d27226bc297472ceb136c996628b845ecc54a48b31cb 403 | name: ose-kube-rbac-proxy 404 | version: 0.0.11 405 | webhookdefinitions: 406 | - admissionReviewVersions: 407 | - v1 408 | - v1beta1 409 | containerPort: 443 410 | deploymentName: gitops-primer-mutating-webhook-deployment 411 | failurePolicy: Fail 412 | generateName: mexport.kb.io 413 | rules: 414 | - apiGroups: 415 | - primer.gitops.io 416 | apiVersions: 417 | - v1alpha1 418 | operations: 419 | - CREATE 420 | - UPDATE 421 | resources: 422 | - exports 423 | sideEffects: None 424 | targetPort: 8000 425 | type: MutatingAdmissionWebhook 426 | webhookPath: /mutate 427 | -------------------------------------------------------------------------------- /bundle/manifests/primer.gitops.io_exports.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | annotations: 5 | controller-gen.kubebuilder.io/version: v0.4.1 6 | creationTimestamp: null 7 | name: exports.primer.gitops.io 8 | spec: 9 | group: primer.gitops.io 10 | names: 11 | kind: Export 12 | listKind: ExportList 13 | plural: exports 14 | singular: export 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: Export is the Schema for the exports API 21 | properties: 22 | apiVersion: 23 | description: 'APIVersion defines the versioned schema of this representation 24 | of an object. Servers should convert recognized schemas to the latest 25 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 26 | type: string 27 | kind: 28 | description: 'Kind is a string value representing the REST resource this 29 | object represents. Servers may infer this from the endpoint the client 30 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 31 | type: string 32 | metadata: 33 | type: object 34 | spec: 35 | properties: 36 | branch: 37 | description: Branch within the git repository 38 | type: string 39 | email: 40 | description: Email used to specify the user who performed the git 41 | commit 42 | type: string 43 | group: 44 | description: Set automatically by the webhook to dictate the group 45 | items: 46 | type: string 47 | type: array 48 | labelSelector: 49 | description: Filter Export by labels 50 | type: string 51 | method: 52 | description: Method download or git. This defines which process to 53 | use for exporting objects from a cluster 54 | type: string 55 | repo: 56 | description: Git repository which will be cloned and updated 57 | type: string 58 | secret: 59 | description: Predefined secret that contains an SSH key that will 60 | be used for git cloning and pushing 61 | type: string 62 | user: 63 | description: Set automatically by the webhook to dictate who will 64 | run the export process 65 | type: string 66 | required: 67 | - method 68 | type: object 69 | status: 70 | description: ExportStatus defines the observed state of Export 71 | properties: 72 | completed: 73 | description: Condition set by controller to signify the export completed 74 | successfully and the route is available 75 | type: boolean 76 | conditions: 77 | description: Conditions is a set of Condition instances. 78 | items: 79 | description: "Condition represents an observation of an object's 80 | state. Conditions are an extension mechanism intended to be used 81 | when the details of an observation are not a priori known or would 82 | not apply to all instances of a given Kind. \n Conditions should 83 | be added to explicitly convey properties that users and components 84 | care about rather than requiring those properties to be inferred 85 | from other observations. Once defined, the meaning of a Condition 86 | can not be changed arbitrarily - it becomes part of the API, and 87 | has the same backwards- and forwards-compatibility concerns of 88 | any other part of the API." 89 | properties: 90 | lastTransitionTime: 91 | format: date-time 92 | type: string 93 | message: 94 | type: string 95 | reason: 96 | description: ConditionReason is intended to be a one-word, CamelCase 97 | representation of the category of cause of the current status. 98 | It is intended to be used in concise output, such as one-line 99 | kubectl get output, and in summarizing occurrences of causes. 100 | type: string 101 | status: 102 | type: string 103 | type: 104 | description: "ConditionType is the type of the condition and 105 | is typically a CamelCased word or short phrase. \n Condition 106 | types should indicate state in the \"abnormal-true\" polarity. 107 | For example, if the condition indicates when a policy is invalid, 108 | the \"is valid\" case is probably the norm, so the condition 109 | should be called \"Invalid\"." 110 | type: string 111 | required: 112 | - status 113 | - type 114 | type: object 115 | type: array 116 | extracted: 117 | type: boolean 118 | route: 119 | description: Route that is defined by the controller to specify the 120 | location of the zip file 121 | type: string 122 | type: object 123 | type: object 124 | served: true 125 | storage: true 126 | subresources: 127 | status: {} 128 | status: 129 | acceptedNames: 130 | kind: "" 131 | plural: "" 132 | conditions: [] 133 | storedVersions: [] 134 | -------------------------------------------------------------------------------- /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: gitops-primer 7 | operators.operatorframework.io.bundle.channels.v1: alpha 8 | operators.operatorframework.io.metrics.builder: operator-sdk-v1.18.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.7.2 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.7.2 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.7.2 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.7.2 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.7.2 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.7.2 62 | labels: 63 | suite: olm 64 | test: olm-status-descriptors-test 65 | storage: 66 | spec: 67 | mountPath: {} 68 | storage: 69 | spec: 70 | mountPath: {} 71 | -------------------------------------------------------------------------------- /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: gitops-primer-mutating-webhook-deployment-service-cert 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/crd/bases/primer.gitops.io_exports.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.4.1 8 | creationTimestamp: null 9 | name: exports.primer.gitops.io 10 | spec: 11 | group: primer.gitops.io 12 | names: 13 | kind: Export 14 | listKind: ExportList 15 | plural: exports 16 | singular: export 17 | scope: Namespaced 18 | versions: 19 | - name: v1alpha1 20 | schema: 21 | openAPIV3Schema: 22 | description: Export is the Schema for the exports API 23 | properties: 24 | apiVersion: 25 | description: 'APIVersion defines the versioned schema of this representation 26 | of an object. Servers should convert recognized schemas to the latest 27 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 28 | type: string 29 | kind: 30 | description: 'Kind is a string value representing the REST resource this 31 | object represents. Servers may infer this from the endpoint the client 32 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 33 | type: string 34 | metadata: 35 | type: object 36 | spec: 37 | properties: 38 | branch: 39 | description: Branch within the git repository 40 | type: string 41 | email: 42 | description: Email used to specify the user who performed the git 43 | commit 44 | type: string 45 | group: 46 | description: Set automatically by the webhook to dictate the group 47 | items: 48 | type: string 49 | type: array 50 | labelSelector: 51 | description: Filter Export by labels 52 | type: string 53 | method: 54 | description: Method download or git. This defines which process to 55 | use for exporting objects from a cluster 56 | type: string 57 | repo: 58 | description: Git repository which will be cloned and updated 59 | type: string 60 | secret: 61 | description: Predefined secret that contains an SSH key that will 62 | be used for git cloning and pushing 63 | type: string 64 | user: 65 | description: Set automatically by the webhook to dictate who will 66 | run the export process 67 | type: string 68 | required: 69 | - method 70 | type: object 71 | status: 72 | description: ExportStatus defines the observed state of Export 73 | properties: 74 | completed: 75 | description: Condition set by controller to signify the export completed 76 | successfully and the route is available 77 | type: boolean 78 | conditions: 79 | description: Conditions is a set of Condition instances. 80 | items: 81 | description: "Condition represents an observation of an object's 82 | state. Conditions are an extension mechanism intended to be used 83 | when the details of an observation are not a priori known or would 84 | not apply to all instances of a given Kind. \n Conditions should 85 | be added to explicitly convey properties that users and components 86 | care about rather than requiring those properties to be inferred 87 | from other observations. Once defined, the meaning of a Condition 88 | can not be changed arbitrarily - it becomes part of the API, and 89 | has the same backwards- and forwards-compatibility concerns of 90 | any other part of the API." 91 | properties: 92 | lastTransitionTime: 93 | format: date-time 94 | type: string 95 | message: 96 | type: string 97 | reason: 98 | description: ConditionReason is intended to be a one-word, CamelCase 99 | representation of the category of cause of the current status. 100 | It is intended to be used in concise output, such as one-line 101 | kubectl get output, and in summarizing occurrences of causes. 102 | type: string 103 | status: 104 | type: string 105 | type: 106 | description: "ConditionType is the type of the condition and 107 | is typically a CamelCased word or short phrase. \n Condition 108 | types should indicate state in the \"abnormal-true\" polarity. 109 | For example, if the condition indicates when a policy is invalid, 110 | the \"is valid\" case is probably the norm, so the condition 111 | should be called \"Invalid\"." 112 | type: string 113 | required: 114 | - status 115 | - type 116 | type: object 117 | type: array 118 | extracted: 119 | type: boolean 120 | route: 121 | description: Route that is defined by the controller to specify the 122 | location of the zip file 123 | type: string 124 | type: object 125 | type: object 126 | served: true 127 | storage: true 128 | subresources: 129 | status: {} 130 | status: 131 | acceptedNames: 132 | kind: "" 133 | plural: "" 134 | conditions: [] 135 | storedVersions: [] 136 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/primer.gitops.io_exports.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- patches/webhook_in_exports.yaml 12 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | #- patches/cainjection_in_exports.yaml 17 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # the following config is for teaching kustomize how to do kustomization for CRDs. 20 | configurations: 21 | - kustomizeconfig.yaml 22 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_exports.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: exports.primer.gitops.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_exports.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: exports.primer.gitops.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: gitops-primer-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: gitops-primer- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | - ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | - ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | #- manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | - webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 49 | - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 50 | objref: 51 | kind: Certificate 52 | group: cert-manager.io 53 | version: v1 54 | name: serving-cert # this name should match the one in certificate.yaml 55 | fieldref: 56 | fieldpath: metadata.namespace 57 | - name: CERTIFICATE_NAME 58 | objref: 59 | kind: Certificate 60 | group: cert-manager.io 61 | version: v1 62 | name: serving-cert # this name should match the one in certificate.yaml 63 | - name: SERVICE_NAMESPACE # namespace of the service 64 | objref: 65 | kind: Service 66 | version: v1 67 | name: webhook-service 68 | fieldref: 69 | fieldpath: metadata.namespace 70 | - name: SERVICE_NAME 71 | objref: 72 | kind: Service 73 | version: v1 74 | name: webhook-service 75 | -------------------------------------------------------------------------------- /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@sha256:6d57bfd91fac9b68eb72d27226bc297472ceb136c996628b845ecc54a48b31cb 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 | ports: 20 | - containerPort: 8443 21 | name: https 22 | securityContext: 23 | capabilities: 24 | drop: 25 | - ALL 26 | runAsNonRoot: true 27 | allowPrivilegeEscalation: false 28 | - name: manager 29 | args: 30 | - "--health-probe-bind-address=:8081" 31 | - "--metrics-bind-address=127.0.0.1:8080" 32 | - "--leader-elect" 33 | securityContext: 34 | capabilities: 35 | drop: 36 | - ALL 37 | runAsNonRoot: true 38 | allowPrivilegeEscalation: false 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/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 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | -------------------------------------------------------------------------------- /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: 162233a0.gitops.io 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - files: 9 | - controller_manager_config.yaml 10 | name: manager-config 11 | apiVersion: kustomize.config.k8s.io/v1beta1 12 | kind: Kustomization 13 | images: 14 | - name: controller 15 | newName: quay.io/migtools/gitops-primer 16 | newTag: v0.0.11 17 | -------------------------------------------------------------------------------- /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 | securityContext: 26 | runAsNonRoot: true 27 | containers: 28 | - command: 29 | - /manager 30 | args: 31 | - --leader-elect 32 | image: controller:latest 33 | imagePullPolicy: IfNotPresent 34 | env: 35 | - name: DownloaderImageName 36 | value: quay.io/migtools/gitops-primer-downloader:v0.0.11 37 | - name: ExportImageName 38 | value: quay.io/migtools/gitops-primer-export:v0.0.11 39 | - name: OauthImageName 40 | value: quay.io/openshift/origin-oauth-proxy:4.10 41 | name: manager 42 | securityContext: 43 | allowPrivilegeEscalation: false 44 | livenessProbe: 45 | httpGet: 46 | path: /healthz 47 | port: 8081 48 | initialDelaySeconds: 15 49 | periodSeconds: 20 50 | readinessProbe: 51 | httpGet: 52 | path: /readyz 53 | port: 8081 54 | initialDelaySeconds: 5 55 | periodSeconds: 10 56 | resources: 57 | limits: 58 | cpu: 100m 59 | memory: 300Mi 60 | requests: 61 | cpu: 100m 62 | memory: 20Mi 63 | serviceAccountName: controller-manager 64 | terminationGracePeriodSeconds: 10 65 | -------------------------------------------------------------------------------- /config/manifests/bases/gitops-primer.clusterserviceversion.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.coreos.com/v1alpha1 2 | kind: ClusterServiceVersion 3 | metadata: 4 | annotations: 5 | alm-examples: '[]' 6 | capabilities: Basic Install 7 | categories: OpenShift Optional 8 | containerImage: quay.io/migtools/gitops-primer:v0.0.11 9 | operatorframework.io/suggested-namespace: gitops-primer-system 10 | name: gitops-primer.v0.0.11 11 | namespace: gitops-primer-system 12 | spec: 13 | apiservicedefinitions: {} 14 | customresourcedefinitions: 15 | owned: 16 | - description: Export is the Schema for the exports API 17 | displayName: Export 18 | kind: Export 19 | name: exports.primer.gitops.io 20 | version: v1alpha1 21 | description: Export and normalize Kubernetes YAML files for GitOps 22 | displayName: gitops-primer 23 | icon: 24 | - base64data: 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 25 | mediatype: image/png 26 | install: 27 | spec: 28 | deployments: null 29 | strategy: "" 30 | installModes: 31 | - supported: false 32 | type: OwnNamespace 33 | - supported: false 34 | type: SingleNamespace 35 | - supported: false 36 | type: MultiNamespace 37 | - supported: true 38 | type: AllNamespaces 39 | keywords: 40 | - gitops 41 | - operator 42 | links: 43 | - name: Gitops Primer 44 | url: https://github.com/migtools/gitops-primer 45 | maturity: alpha 46 | provider: 47 | name: Konveyor 48 | relatedImages: 49 | - image: quay.io/migtools/gitops-primer-downloader:v0.0.11 50 | name: downloader 51 | - image: quay.io/migtools/gitops-primer-export:v0.0.11 52 | name: export 53 | - image: quay.io/migtools/gitops-primer-webhook:v0.0.11 54 | name: webhook 55 | - image: quay.io/openshift/origin-oauth-proxy:4.9 56 | name: oauth-proxy 57 | - image: registry.redhat.io/openshift4/ose-kube-rbac-proxy@sha256:6d57bfd91fac9b68eb72d27226bc297472ceb136c996628b845ecc54a48b31cb 58 | name: ose-kube-rbac-proxy 59 | version: 0.0.11 60 | -------------------------------------------------------------------------------- /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/gitops-primer.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/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | scheme: https 15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 16 | tlsConfig: 17 | insecureSkipVerify: true 18 | selector: 19 | matchLabels: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /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 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | -------------------------------------------------------------------------------- /config/rbac/export_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit exports. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: export-editor-role 6 | labels: 7 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 8 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 9 | rules: 10 | - apiGroups: 11 | - primer.gitops.io 12 | resources: 13 | - exports 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - primer.gitops.io 24 | resources: 25 | - exports/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/export_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view exports. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: export-viewer-role 6 | rules: 7 | - apiGroups: 8 | - primer.gitops.io 9 | resources: 10 | - exports 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - primer.gitops.io 17 | resources: 18 | - exports/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /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 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /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 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: manager-role 8 | rules: 9 | - apiGroups: 10 | - "" 11 | resources: 12 | - groups 13 | - users 14 | verbs: 15 | - impersonate 16 | - apiGroups: 17 | - "" 18 | resourceNames: 19 | - gitops-primer-system 20 | resources: 21 | - namespaces 22 | verbs: 23 | - get 24 | - update 25 | - apiGroups: 26 | - '*' 27 | resources: 28 | - '*' 29 | verbs: 30 | - get 31 | - list 32 | - apiGroups: 33 | - apps 34 | resources: 35 | - deployments 36 | verbs: 37 | - create 38 | - delete 39 | - get 40 | - list 41 | - patch 42 | - update 43 | - watch 44 | - apiGroups: 45 | - batch 46 | resources: 47 | - jobs 48 | verbs: 49 | - create 50 | - delete 51 | - get 52 | - list 53 | - patch 54 | - update 55 | - watch 56 | - apiGroups: 57 | - "" 58 | resources: 59 | - persistentvolumeclaims 60 | verbs: 61 | - create 62 | - delete 63 | - get 64 | - list 65 | - patch 66 | - update 67 | - watch 68 | - apiGroups: 69 | - "" 70 | resources: 71 | - secrets 72 | verbs: 73 | - create 74 | - delete 75 | - get 76 | - list 77 | - patch 78 | - update 79 | - watch 80 | - apiGroups: 81 | - "" 82 | resources: 83 | - serviceaccounts 84 | verbs: 85 | - create 86 | - delete 87 | - get 88 | - list 89 | - patch 90 | - update 91 | - watch 92 | - apiGroups: 93 | - "" 94 | resources: 95 | - services 96 | verbs: 97 | - create 98 | - delete 99 | - get 100 | - list 101 | - patch 102 | - update 103 | - watch 104 | - apiGroups: 105 | - networking.k8s.io 106 | resources: 107 | - networkpolicies 108 | verbs: 109 | - create 110 | - delete 111 | - get 112 | - list 113 | - patch 114 | - update 115 | - watch 116 | - apiGroups: 117 | - primer.gitops.io 118 | resources: 119 | - exports 120 | verbs: 121 | - create 122 | - delete 123 | - get 124 | - list 125 | - patch 126 | - update 127 | - watch 128 | - apiGroups: 129 | - primer.gitops.io 130 | resources: 131 | - exports/finalizers 132 | verbs: 133 | - update 134 | - apiGroups: 135 | - primer.gitops.io 136 | resources: 137 | - exports/status 138 | verbs: 139 | - get 140 | - patch 141 | - update 142 | - apiGroups: 143 | - rbac.authorization.k8s.io 144 | resources: 145 | - clusterrolebindings 146 | verbs: 147 | - create 148 | - delete 149 | - get 150 | - list 151 | - patch 152 | - update 153 | - watch 154 | - apiGroups: 155 | - rbac.authorization.k8s.io 156 | resources: 157 | - clusterroles 158 | verbs: 159 | - create 160 | - delete 161 | - get 162 | - list 163 | - patch 164 | - update 165 | - watch 166 | - apiGroups: 167 | - route.openshift.io 168 | resources: 169 | - routes 170 | verbs: 171 | - create 172 | - delete 173 | - get 174 | - list 175 | - patch 176 | - update 177 | - watch 178 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - primer_v1alpha1_export.yaml 4 | #+kubebuilder:scaffold:manifestskustomizesamples 5 | -------------------------------------------------------------------------------- /config/samples/primer_v1alpha1_export.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: primer.gitops.io/v1alpha1 2 | kind: Export 3 | metadata: 4 | name: primer 5 | spec: 6 | method: download 7 | -------------------------------------------------------------------------------- /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.7.2 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.7.2 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.7.2 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.7.2 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.7.2 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.7.2 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /config/webhook/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mutating-webhook-deployment 5 | namespace: system 6 | labels: 7 | app: export-webhook 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: export-webhook 13 | template: 14 | metadata: 15 | labels: 16 | app: export-webhook 17 | spec: 18 | securityContext: 19 | runAsNonRoot: true 20 | containers: 21 | - name: export-webhook 22 | # TODO: env var/kustomize this 23 | # NOTE: update this if using different image 24 | imagePullPolicy: IfNotPresent 25 | image: quay.io/migtools/gitops-primer-webhook:v0.0.11 26 | ports: 27 | - containerPort: 8000 28 | volumeMounts: 29 | - name: export-tls-secret 30 | mountPath: "/tls" 31 | readOnly: true 32 | resources: 33 | limits: 34 | memory: "128Mi" 35 | cpu: "500m" 36 | securityContext: 37 | runAsNonRoot: true 38 | allowPrivilegeEscalation: false 39 | capabilities: 40 | drop: 41 | - ALL 42 | volumes: 43 | - name: export-tls-secret 44 | secret: 45 | secretName: gitops-primer-mutating-webhook-deployment-service-cert 46 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | - deployment.yaml 5 | 6 | configurations: 7 | - kustomizeconfig.yaml 8 | -------------------------------------------------------------------------------- /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.v1beta1.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: admissionregistration.k8s.io/v1beta1 4 | kind: ValidatingWebhookConfiguration 5 | metadata: 6 | creationTimestamp: null 7 | name: validating-webhook-configuration 8 | webhooks: 9 | - admissionReviewVersions: 10 | - v1 11 | - v1beta1 12 | clientConfig: 13 | service: 14 | name: webhook-service 15 | namespace: system 16 | path: /validate-primer-gitops-io-v1alpha1-export 17 | failurePolicy: Fail 18 | name: vexport.kb.io 19 | rules: 20 | - apiGroups: 21 | - primer.gitops.io 22 | apiVersions: 23 | - v1alpha1 24 | operations: 25 | - CREATE 26 | - UPDATE 27 | resources: 28 | - exports 29 | sideEffects: None 30 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: admissionregistration.k8s.io/v1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | creationTimestamp: null 7 | name: mutating-webhook-configuration 8 | webhooks: 9 | - admissionReviewVersions: 10 | - v1 11 | - v1beta1 12 | clientConfig: 13 | service: 14 | name: webhook-service 15 | namespace: system 16 | path: /mutate 17 | failurePolicy: Fail 18 | name: mexport.kb.io 19 | rules: 20 | - apiGroups: 21 | - primer.gitops.io 22 | apiVersions: 23 | - v1alpha1 24 | operations: 25 | - CREATE 26 | - UPDATE 27 | resources: 28 | - exports 29 | sideEffects: None 30 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | protocol: TCP 11 | targetPort: 8000 12 | selector: 13 | app: export-webhook 14 | -------------------------------------------------------------------------------- /controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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 controllers 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | "k8s.io/client-go/kubernetes/scheme" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | "sigs.k8s.io/controller-runtime/pkg/envtest" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 29 | logf "sigs.k8s.io/controller-runtime/pkg/log" 30 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 31 | 32 | primerv1alpha1 "github.com/cooktheryan/gitops-primer/api/v1alpha1" 33 | //+kubebuilder:scaffold:imports 34 | ) 35 | 36 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 37 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 38 | 39 | var k8sClient client.Client 40 | var testEnv *envtest.Environment 41 | 42 | func TestAPIs(t *testing.T) { 43 | RegisterFailHandler(Fail) 44 | 45 | RunSpecsWithDefaultAndCustomReporters(t, 46 | "Controller Suite", 47 | []Reporter{printer.NewlineReporter{}}) 48 | } 49 | 50 | var _ = BeforeSuite(func() { 51 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 52 | 53 | By("bootstrapping test environment") 54 | testEnv = &envtest.Environment{ 55 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 56 | ErrorIfCRDPathMissing: true, 57 | } 58 | 59 | cfg, err := testEnv.Start() 60 | Expect(err).NotTo(HaveOccurred()) 61 | Expect(cfg).NotTo(BeNil()) 62 | 63 | err = primerv1alpha1.AddToScheme(scheme.Scheme) 64 | Expect(err).NotTo(HaveOccurred()) 65 | 66 | //+kubebuilder:scaffold:scheme 67 | 68 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 69 | Expect(err).NotTo(HaveOccurred()) 70 | Expect(k8sClient).NotTo(BeNil()) 71 | 72 | }, 60) 73 | 74 | var _ = AfterSuite(func() { 75 | By("tearing down the test environment") 76 | err := testEnv.Stop() 77 | Expect(err).NotTo(HaveOccurred()) 78 | }) 79 | -------------------------------------------------------------------------------- /downloader/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/ubi 2 | 3 | RUN yum update -y \ 4 | && yum --disableplugin=subscription-manager -y install \ 5 | httpd \ 6 | && yum --disableplugin=subscription-manager clean all 7 | 8 | RUN sed -i 's/Listen 80/Listen 8080/' /etc/httpd/conf/httpd.conf \ 9 | && chgrp -R 0 /var/log/httpd /var/run/httpd \ 10 | && chmod -R g=u /var/log/httpd /var/run/httpd 11 | 12 | EXPOSE 8080 13 | 14 | USER 1001 15 | 16 | CMD httpd -D FOREGROUND -------------------------------------------------------------------------------- /downloader/Makefile: -------------------------------------------------------------------------------- 1 | IMAGE := quay.io/migtools/gitops-primer-downloader 2 | 3 | BUILDDATE := $(shell date -u '+%Y-%m-%dT%H:%M:%S.%NZ') 4 | VERSION := $(shell git describe --match 'v[0-9]*' --tags --dirty 2> /dev/null || git describe --always --dirty) 5 | 6 | .PHONY: all 7 | all: image 8 | 9 | .PHONY: image 10 | image: 11 | docker build \ 12 | --build-arg "builddate_arg=$(BUILDDATE)" \ 13 | --build-arg "version_arg=$(VERSION)" \ 14 | -t $(IMAGE) \ 15 | -f Dockerfile . 16 | -------------------------------------------------------------------------------- /examples/download-export.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: primer.gitops.io/v1alpha1 2 | kind: Export 3 | metadata: 4 | name: primer 5 | spec: 6 | method: download 7 | -------------------------------------------------------------------------------- /examples/export-to-git.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: primer.gitops.io/v1alpha1 2 | kind: Export 3 | metadata: 4 | name: primer 5 | spec: 6 | method: git 7 | repo: git@github.com:cooktheryan/primer-poc.git 8 | branch: main 9 | email: nobody@everybody.com 10 | secret: secret-key 11 | 12 | -------------------------------------------------------------------------------- /export/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.access.redhat.com/ubi8/go-toolset:1.17.10-4 AS crane-builder 2 | RUN git clone https://github.com/migtools/crane $APP_ROOT/src/github.com/migtools/crane 3 | WORKDIR $APP_ROOT/src/github.com/migtools/crane 4 | RUN git checkout tags/v0.0.5 5 | RUN go build -o crane main.go 6 | 7 | FROM registry.access.redhat.com/ubi8/go-toolset:1.17.10-4 AS plugin-builder 8 | RUN mkdir -p $APP_ROOT/src/github.com/migtools 9 | RUN curl -L -o go.mod https://raw.githubusercontent.com/migtools/gitops-primer/main/go.mod 10 | ADD plugins $APP_ROOT/src/github.com/migtools/gitops-primer/export/plugins 11 | WORKDIR $APP_ROOT/src/github.com/migtools/gitops-primer/export/plugins 12 | ENV GOPATH=$APP_ROOT 13 | RUN go get -d ./... 14 | RUN go install ./... 15 | 16 | FROM registry.access.redhat.com/ubi8/ubi-minimal 17 | 18 | RUN microdnf update -y && \ 19 | microdnf install -y \ 20 | git \ 21 | gcc \ 22 | zip \ 23 | openssh-clients && \ 24 | microdnf clean all && \ 25 | rm -rf /var/cache/yum 26 | 27 | ADD committer.sh / 28 | 29 | COPY --from=plugin-builder /opt/app-root/bin /opt/transform-plugins 30 | 31 | COPY --from=crane-builder /opt/app-root/src/github.com/migtools/crane/crane /usr/local/bin 32 | 33 | RUN mkdir -p /usr/local/app-root/src && useradd -u 1001 -r -g 0 -d /usr/local/app-root/src -s /sbin/nologin -c "Default Application User" default && chmod g+rw /usr/local/app-root/src && chmod +x /opt/* 34 | 35 | USER 1001 36 | 37 | ENV HOME /usr/local/app-root/src 38 | 39 | ENTRYPOINT [ "/bin/bash" ] 40 | -------------------------------------------------------------------------------- /export/Makefile: -------------------------------------------------------------------------------- 1 | IMAGE := quay.io/migtools/gitops-primer-export 2 | 3 | BUILDDATE := $(shell date -u '+%Y-%m-%dT%H:%M:%S.%NZ') 4 | VERSION := $(shell git describe --match 'v[0-9]*' --tags --dirty 2> /dev/null || git describe --always --dirty) 5 | 6 | .PHONY: all 7 | all: image 8 | 9 | .PHONY: image 10 | image: 11 | docker build \ 12 | --build-arg "builddate_arg=$(BUILDDATE)" \ 13 | --build-arg "version_arg=$(VERSION)" \ 14 | -t $(IMAGE) \ 15 | -f Dockerfile . 16 | -------------------------------------------------------------------------------- /export/committer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ ${METHOD} == "git" ]; then 5 | # Setup SSH 6 | mkdir -p ~/.ssh/controlmasters 7 | chmod 711 ~/.ssh 8 | cp /keys/id_rsa ~/.ssh/id_rsa 9 | chmod 0600 ~/.ssh/id_rsa 10 | ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts 11 | 12 | cat - < ~/.ssh/config 13 | Host * 14 | # Wait max 30s to establish connection 15 | ConnectTimeout 30 16 | # Control persist to speed 2nd ssh connection 17 | ControlMaster auto 18 | ControlPath ~/.ssh/controlmasters/%C 19 | ControlPersist 5 20 | # Disables warning when IP is added to known_hosts 21 | CheckHostIP no 22 | # Use the identity provided via attached Secret 23 | IdentityFile /keys/id_rsa 24 | # Enable protocol-level keepalive to detect connection failure 25 | ServerAliveCountMax 4 26 | ServerAliveInterval 30 27 | # Using protocol-level, so we don't need TCP-level 28 | TCPKeepAlive no 29 | SSHCONFIG 30 | 31 | # Setup the repository 32 | git clone ${REPO} /output/repo -q 33 | cd /output/repo 34 | git fetch -q 35 | existed_in_remote=$(git ls-remote --heads origin ${BRANCH}) 36 | if [[ -z ${existed_in_remote} ]]; then 37 | git checkout -b ${BRANCH} 38 | else 39 | git checkout ${BRANCH} 40 | fi 41 | git config --global user.email "${EMAIL}" 42 | fi 43 | 44 | TOKEN=`cat /var/run/secrets/kubernetes.io/serviceaccount/token` 45 | CA=`cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt |base64 -w0` 46 | 47 | # Generate KUBECONFIG 48 | echo " 49 | apiVersion: v1 50 | kind: Config 51 | clusters: 52 | - name: mycluster 53 | cluster: 54 | certificate-authority-data: ${CA} 55 | server: https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT} 56 | contexts: 57 | - name: primer-export-primer@mycluster 58 | context: 59 | cluster: mycluster 60 | namespace: ${NAMESPACE} 61 | user: primer-export-primer 62 | users: 63 | - name: primer-export-primer 64 | user: 65 | token: ${TOKEN} 66 | current-context: primer-export-primer@mycluster 67 | " > /tmp/kubeconfig 68 | 69 | if [ ${METHOD} == "download" ]; then 70 | mkdir -p /output/repo 71 | cd /output/repo 72 | fi 73 | 74 | export KUBECONFIG=/tmp/kubeconfig 75 | if [ -z "$GROUP" ]; then crane export --export-dir /tmp/export --as ${USER} -l "${LABELS}"; else IFS=';'; read -ra GARR <<< "${GROUP}"; crane export --export-dir /tmp/export --as ${USER} --as-group ${GARR[@]} -l "${LABELS}"; fi 76 | crane transform --export-dir /tmp/export/resources --plugin-dir /opt --transform-dir /tmp/transform --skip-plugins KubernetesPlugin 77 | crane apply --export-dir /tmp/export/resources --transform-dir /tmp/transform --output-dir /output/repo 78 | 79 | 80 | if [ ${METHOD} == "git" ]; then 81 | if [[ $(git status -s) ]]; then 82 | git add * 83 | git commit -am 'bot commit' 84 | git push origin ${BRANCH} -q 85 | echo "Merge to ${BRANCH} completed successfully" 86 | else 87 | exit 0 88 | fi 89 | else 90 | cd /output/repo 91 | if ! [[ -d ${NAMESPACE} ]]; then 92 | mkdir ${NAMESPACE} 93 | echo "There is nothing to extract." > ${NAMESPACE}/empty-namespace.txt 94 | fi 95 | zip -r /output/${NAMESPACE}-${TIME} ${NAMESPACE} 96 | rm -rf /output/repo 97 | fi 98 | 99 | -------------------------------------------------------------------------------- /export/plugins/build-whiteout/build-whiteout: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/migtools/gitops-primer/698fbd6adc93fc4d237945b521dea8b730703a31/export/plugins/build-whiteout/build-whiteout -------------------------------------------------------------------------------- /export/plugins/build-whiteout/build-whiteout.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | 6 | jsonpatch "github.com/evanphx/json-patch" 7 | "github.com/konveyor/crane-lib/transform" 8 | "github.com/konveyor/crane-lib/transform/cli" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | ) 11 | 12 | var defaultConfigMapName = []string{"-global-ca", "-ca", "-sys-config"} 13 | 14 | func main() { 15 | cli.RunAndExit(cli.NewCustomPlugin("WhiteoutBuildsPlugin", "v1", nil, Run)) 16 | } 17 | 18 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 19 | // plugin writers need to write custom code here. 20 | u := &request.Unstructured 21 | var patch jsonpatch.Patch 22 | var err error 23 | var whiteout bool 24 | switch u.GetKind() { 25 | case "Build": 26 | whiteout = true 27 | case "ConfigMap": 28 | whiteout = DefaultConfigMap(*u) 29 | } 30 | if err != nil { 31 | return transform.PluginResponse{}, err 32 | } 33 | return transform.PluginResponse{ 34 | Version: "v1", 35 | IsWhiteOut: whiteout, 36 | Patches: patch, 37 | }, nil 38 | } 39 | 40 | func DefaultConfigMap(u unstructured.Unstructured) bool { 41 | check := u.GetName() 42 | return isDefaultConfigmap(check) 43 | } 44 | 45 | func isDefaultConfigmap(name string) bool { 46 | for _, d := range defaultConfigMapName { 47 | if strings.Contains(name, d) { 48 | return true 49 | } 50 | } 51 | return false 52 | } 53 | -------------------------------------------------------------------------------- /export/plugins/cluster-ip-removal/cluster-ip.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | jsonpatch "github.com/evanphx/json-patch" 7 | "github.com/konveyor/crane-lib/transform" 8 | "github.com/konveyor/crane-lib/transform/cli" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | ) 11 | 12 | func main() { 13 | cli.RunAndExit(cli.NewCustomPlugin("clusterIpRemovalPlugin", "v1", nil, Run)) 14 | } 15 | 16 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 17 | // plugin writers need to write custom code here. 18 | u := &request.Unstructured 19 | var patch jsonpatch.Patch 20 | var err error 21 | switch u.GetKind() { 22 | case "Service": 23 | patch, err = UpdateService(*u) 24 | } 25 | if err != nil { 26 | return transform.PluginResponse{}, err 27 | } 28 | return transform.PluginResponse{ 29 | Version: "v1", 30 | IsWhiteOut: false, 31 | Patches: patch, 32 | }, nil 33 | } 34 | 35 | func UpdateService(u unstructured.Unstructured) (jsonpatch.Patch, error) { 36 | patchJSON := fmt.Sprintf(`[ 37 | { "op": "remove", "path": "/spec/clusterIPs"}, 38 | { "op": "remove", "path": "/spec/clusterIP"} 39 | ]`) 40 | 41 | patch, err := jsonpatch.DecodePatch([]byte(patchJSON)) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return patch, nil 46 | } 47 | -------------------------------------------------------------------------------- /export/plugins/default-object-whiteout/default-object-whiteout.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | 6 | jsonpatch "github.com/evanphx/json-patch" 7 | "github.com/konveyor/crane-lib/transform" 8 | "github.com/konveyor/crane-lib/transform/cli" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | ) 11 | 12 | var defaultSecretName = []string{"builder-dockercfg-", "builder-token", "default-dockercfg-", "default-token", "deployer-dockercfg-", "deployer-token"} 13 | var defaultRoleBindingName = []string{"system:"} 14 | var defaultConfigMapName = []string{"kube-root-ca.crt", "openshift-service-ca.crt", "config-trusted-cabundle"} 15 | var defaultServiceAccountName = []string{"builder", "deployer", "default"} 16 | 17 | func main() { 18 | cli.RunAndExit(cli.NewCustomPlugin("WhiteoutDefaultsPlugin", "v1", nil, Run)) 19 | } 20 | 21 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 22 | // plugin writers need to write custom code here. 23 | u := &request.Unstructured 24 | var patch jsonpatch.Patch 25 | var err error 26 | var whiteout bool 27 | switch u.GetKind() { 28 | case "Secret": 29 | whiteout = DefaultSecret(*u) 30 | case "RoleBinding": 31 | whiteout = DefaultRoleBinding(*u) 32 | case "ConfigMap": 33 | whiteout = DefaultConfigMap(*u) 34 | case "ServiceAccount": 35 | whiteout = DefaultServiceAccount(*u) 36 | } 37 | if err != nil { 38 | return transform.PluginResponse{}, err 39 | } 40 | return transform.PluginResponse{ 41 | Version: "v1", 42 | IsWhiteOut: whiteout, 43 | Patches: patch, 44 | }, nil 45 | } 46 | 47 | func DefaultSecret(u unstructured.Unstructured) bool { 48 | check := u.GetName() 49 | return isDefaultSecret(check) 50 | } 51 | 52 | func isDefaultSecret(name string) bool { 53 | for _, d := range defaultSecretName { 54 | if strings.Contains(name, d) { 55 | return true 56 | } 57 | } 58 | return false 59 | } 60 | 61 | func DefaultRoleBinding(u unstructured.Unstructured) bool { 62 | check := u.GetName() 63 | return isDefaultBinding(check) 64 | } 65 | 66 | func isDefaultBinding(name string) bool { 67 | for _, d := range defaultRoleBindingName { 68 | if strings.Contains(name, d) { 69 | return true 70 | } 71 | } 72 | return false 73 | } 74 | 75 | func DefaultConfigMap(u unstructured.Unstructured) bool { 76 | check := u.GetName() 77 | return isDefaultConfigmap(check) 78 | } 79 | 80 | func isDefaultConfigmap(name string) bool { 81 | for _, d := range defaultConfigMapName { 82 | if strings.Contains(name, d) { 83 | return true 84 | } 85 | } 86 | return false 87 | } 88 | 89 | func DefaultServiceAccount(u unstructured.Unstructured) bool { 90 | check := u.GetName() 91 | return isDefaultServiceAccount(check) 92 | } 93 | 94 | func isDefaultServiceAccount(name string) bool { 95 | for _, d := range defaultServiceAccountName { 96 | if strings.Contains(name, d) { 97 | return true 98 | } 99 | } 100 | return false 101 | } 102 | -------------------------------------------------------------------------------- /export/plugins/gitops-primer/gitops-primer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | 6 | jsonpatch "github.com/evanphx/json-patch" 7 | "github.com/konveyor/crane-lib/transform" 8 | "github.com/konveyor/crane-lib/transform/cli" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | ) 11 | 12 | var exportName = []string{"primer-export-"} 13 | 14 | func main() { 15 | cli.RunAndExit(cli.NewCustomPlugin("WhiteoutExportPlugin", "v1", nil, Run)) 16 | } 17 | 18 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 19 | // plugin writers need to write custom code here. 20 | u := &request.Unstructured 21 | var patch jsonpatch.Patch 22 | var err error 23 | var whiteout bool 24 | switch u.GetKind() { 25 | case "Export": 26 | whiteout = true 27 | case "ServiceAccount": 28 | whiteout = ExportObjects(*u) 29 | case "RoleBinding": 30 | whiteout = ExportObjects(*u) 31 | case "Role": 32 | whiteout = ExportObjects(*u) 33 | case "Job": 34 | whiteout = ExportObjects(*u) 35 | case "Secret": 36 | whiteout = ExportObjects(*u) 37 | case "PersistentVolumeClaim": 38 | whiteout = ExportObjects(*u) 39 | case "NetworkPolicy": 40 | whiteout = ExportObjects(*u) 41 | case "Route": 42 | whiteout = ExportObjects(*u) 43 | case "Service": 44 | whiteout = ExportObjects(*u) 45 | } 46 | if err != nil { 47 | return transform.PluginResponse{}, err 48 | } 49 | return transform.PluginResponse{ 50 | Version: "v1", 51 | IsWhiteOut: whiteout, 52 | Patches: patch, 53 | }, nil 54 | } 55 | 56 | func ExportObjects(u unstructured.Unstructured) bool { 57 | check := u.GetName() 58 | return isDefault(check) 59 | } 60 | 61 | func isDefault(name string) bool { 62 | for _, d := range exportName { 63 | if strings.Contains(name, d) { 64 | return true 65 | } 66 | } 67 | return false 68 | } 69 | -------------------------------------------------------------------------------- /export/plugins/image-whiteout/image-whiteout.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | jsonpatch "github.com/evanphx/json-patch" 5 | "github.com/konveyor/crane-lib/transform" 6 | "github.com/konveyor/crane-lib/transform/cli" 7 | ) 8 | 9 | func main() { 10 | cli.RunAndExit(cli.NewCustomPlugin("ImageWhiteoutPlugin", "v1", nil, Run)) 11 | } 12 | 13 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 14 | // plugin writers need to write custom code here. 15 | u := &request.Unstructured 16 | var patch jsonpatch.Patch 17 | var whiteout bool 18 | if u.GetKind() == "ImageStreamTag" { 19 | whiteout = true 20 | } 21 | if u.GetKind() == "ImageTag" { 22 | whiteout = true 23 | } 24 | return transform.PluginResponse{ 25 | Version: "v1", 26 | IsWhiteOut: whiteout, 27 | Patches: patch, 28 | }, nil 29 | } 30 | -------------------------------------------------------------------------------- /export/plugins/imagestream-trigger/imagestream-trigger.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | 9 | jsonpatch "github.com/evanphx/json-patch" 10 | "github.com/konveyor/crane-lib/transform" 11 | "github.com/konveyor/crane-lib/transform/cli" 12 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 13 | ) 14 | 15 | type Trigger []struct { 16 | From Inner `json:"from"` 17 | FieldPath string `json:"fieldPath"` 18 | Pause string `json:"pause"` 19 | } 20 | 21 | type Inner struct { 22 | Kind string `json:"kind"` 23 | Name string `json:"name"` 24 | } 25 | 26 | func main() { 27 | cli.RunAndExit(cli.NewCustomPlugin("NamespaceRemovalPlugin", "v1", nil, Run)) 28 | } 29 | 30 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 31 | // plugin writers need to write custom code here. 32 | u := &request.Unstructured 33 | var patch jsonpatch.Patch 34 | var err error 35 | switch u.GetKind() { 36 | case "Deployment": 37 | patch, err = RemoveFields(*u) 38 | case "DeploymentConfig": 39 | patch, err = RemoveFields(*u) 40 | } 41 | if err != nil { 42 | return transform.PluginResponse{}, err 43 | } 44 | return transform.PluginResponse{ 45 | Version: "v1", 46 | IsWhiteOut: false, 47 | Patches: patch, 48 | }, nil 49 | } 50 | 51 | func RemoveFields(u unstructured.Unstructured) (jsonpatch.Patch, error) { 52 | val, ok := u.GetAnnotations()["image.openshift.io/triggers"] 53 | var scrub Trigger 54 | var patch jsonpatch.Patch 55 | if ok { 56 | json.Unmarshal([]byte(val), &scrub) 57 | d, err := json.Marshal(scrub) 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | escaped := strconv.Quote(string(d)) 62 | 63 | patchJSON := fmt.Sprintf(`[ 64 | {"op": "replace", "path": "/metadata/annotations/image.openshift.io~1triggers", "value": %s} 65 | ]`, escaped) 66 | patch, err := jsonpatch.DecodePatch([]byte(patchJSON)) 67 | if err != nil { 68 | return nil, err 69 | } 70 | return patch, nil 71 | } else if !ok { 72 | return patch, nil 73 | } 74 | return patch, nil 75 | } 76 | -------------------------------------------------------------------------------- /export/plugins/metadata-remediation/metadata-remediation.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | jsonpatch "github.com/evanphx/json-patch" 7 | "github.com/konveyor/crane-lib/transform" 8 | "github.com/konveyor/crane-lib/transform/cli" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | ) 11 | 12 | func main() { 13 | cli.RunAndExit(cli.NewCustomPlugin("MetadataRemovalPlugin", "v1", nil, Run)) 14 | } 15 | 16 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 17 | // plugin writers need to write custom code here. 18 | u := &request.Unstructured 19 | patch, err := RemoveFields(*u) 20 | 21 | if err != nil { 22 | return transform.PluginResponse{}, err 23 | } 24 | return transform.PluginResponse{ 25 | Version: "v1", 26 | IsWhiteOut: false, 27 | Patches: patch, 28 | }, nil 29 | } 30 | 31 | func RemoveFields(u unstructured.Unstructured) (jsonpatch.Patch, error) { 32 | patchJSON := fmt.Sprintf(`[ 33 | { "op": "remove", "path": "/metadata/managedFields"}, 34 | { "op": "remove", "path": "/metadata/uid"}, 35 | { "op": "remove", "path": "/metadata/creationTimestamp"}, 36 | { "op": "remove", "path": "/metadata/resourceVersion"}, 37 | { "op": "remove", "path": "/metadata/selfLink"}, 38 | { "op": "remove", "path": "/metadata/generation"}, 39 | { "op": "remove", "path": "/metadata/annotations/kubectl.kubernetes.io~1last-applied-configuration"}, 40 | { "op": "remove", "path": "/metadata/annotations/deployment.kubernetes.io~1revision"} 41 | ]`) 42 | 43 | patch, err := jsonpatch.DecodePatch([]byte(patchJSON)) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return patch, nil 48 | } 49 | -------------------------------------------------------------------------------- /export/plugins/namespace-removal/namespace.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | jsonpatch "github.com/evanphx/json-patch" 7 | "github.com/konveyor/crane-lib/transform" 8 | "github.com/konveyor/crane-lib/transform/cli" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | ) 11 | 12 | func main() { 13 | cli.RunAndExit(cli.NewCustomPlugin("NameSpaceScrub", "v1", nil, Run)) 14 | } 15 | 16 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 17 | // plugin writers need to write custom code here. 18 | u := &request.Unstructured 19 | patch, err := RemoveNamespace(*u) 20 | 21 | if err != nil { 22 | return transform.PluginResponse{}, err 23 | } 24 | return transform.PluginResponse{ 25 | Version: "v1", 26 | IsWhiteOut: false, 27 | Patches: patch, 28 | }, nil 29 | } 30 | 31 | func RemoveNamespace(u unstructured.Unstructured) (jsonpatch.Patch, error) { 32 | patchJSON := fmt.Sprintf(`[ 33 | { "op": "remove", "path": "/metadata/namespace"} 34 | ]`) 35 | 36 | patch, err := jsonpatch.DecodePatch([]byte(patchJSON)) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return patch, nil 41 | } 42 | -------------------------------------------------------------------------------- /export/plugins/openshift/openshift.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | jsonpatch "github.com/evanphx/json-patch" 9 | "github.com/konveyor/crane-lib/transform" 10 | "github.com/konveyor/crane-lib/transform/cli" 11 | v1 "k8s.io/api/core/v1" 12 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 13 | ) 14 | 15 | var defaultPullSecrets = []string{"builder-dockercfg-", "default-dockercfg-", "deployer-dockercfg-"} 16 | 17 | func main() { 18 | cli.RunAndExit(cli.NewCustomPlugin("OpenShiftPlugin", "v1", nil, Run)) 19 | } 20 | 21 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 22 | // plugin writers need to write custom code here. 23 | u := &request.Unstructured 24 | var patch jsonpatch.Patch 25 | var err error 26 | switch u.GetKind() { 27 | case "Pod": 28 | patch, err = UpdateDefaultPullSecrets(*u) 29 | case "Route": 30 | patch, err = UpdateRoute(*u) 31 | } 32 | if err != nil { 33 | return transform.PluginResponse{}, err 34 | } 35 | return transform.PluginResponse{ 36 | Version: "v1", 37 | IsWhiteOut: false, 38 | Patches: patch, 39 | }, nil 40 | } 41 | 42 | func UpdateDefaultPullSecrets(u unstructured.Unstructured) (jsonpatch.Patch, error) { 43 | pullSecrets := getPullSecrets(u) 44 | 45 | jsonPatch := jsonpatch.Patch{} 46 | 47 | for n, secret := range pullSecrets { 48 | if isDefault(secret.Name) { 49 | 50 | patchJSON := fmt.Sprintf(`[ 51 | { "op": "remove", "path": "/spec/imagePullSecrets/%v"} 52 | ]`, n) 53 | 54 | patch, err := jsonpatch.DecodePatch([]byte(patchJSON)) 55 | if err != nil { 56 | return nil, err 57 | } 58 | jsonPatch = append(jsonPatch, patch...) 59 | } 60 | } 61 | 62 | return jsonPatch, nil 63 | } 64 | 65 | func UpdateRoute(u unstructured.Unstructured) (jsonpatch.Patch, error) { 66 | patchJSON := fmt.Sprintf(`[ 67 | { "op": "remove", "path": "/spec/host"} 68 | ]`) 69 | 70 | patch, err := jsonpatch.DecodePatch([]byte(patchJSON)) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return patch, nil 75 | } 76 | 77 | func isDefault(name string) bool { 78 | for _, d := range defaultPullSecrets { 79 | if strings.Contains(name, d) { 80 | return true 81 | } 82 | } 83 | return false 84 | } 85 | 86 | func getPullSecrets(u unstructured.Unstructured) []v1.LocalObjectReference { 87 | js, err := u.MarshalJSON() 88 | if err != nil { 89 | return nil 90 | } 91 | 92 | pod := &v1.Pod{} 93 | 94 | err = json.Unmarshal(js, pod) 95 | if err != nil { 96 | return nil 97 | } 98 | 99 | return pod.Spec.ImagePullSecrets 100 | } 101 | -------------------------------------------------------------------------------- /export/plugins/owner-reference/ownerReference.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | jsonpatch "github.com/evanphx/json-patch" 7 | "github.com/konveyor/crane-lib/transform" 8 | "github.com/konveyor/crane-lib/transform/cli" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | ) 11 | 12 | func main() { 13 | cli.RunAndExit(cli.NewCustomPlugin("OwnerReferenceScrub", "v1", nil, Run)) 14 | } 15 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 16 | // plugin writers need to write custom code here. 17 | u := &request.Unstructured 18 | var patch jsonpatch.Patch 19 | var err error 20 | switch u.GetKind() { 21 | case "ConfigMap": 22 | patch, err = RemoveOwner(*u) 23 | case "ReplicaSet": 24 | patch, err = RemoveOwner(*u) 25 | case "Revision": 26 | patch, err = RemoveOwner(*u) 27 | case "Metric": 28 | patch, err = RemoveOwner(*u) 29 | case "PodAutoscaler": 30 | patch, err = RemoveOwner(*u) 31 | case "ServerlessService": 32 | patch, err = RemoveOwner(*u) 33 | case "Service": 34 | patch, err = RemoveOwner(*u) 35 | case "Ingress": 36 | patch, err = RemoveOwner(*u) 37 | case "Configuration": 38 | patch, err = RemoveOwner(*u) 39 | case "InMemoryChannel": 40 | patch, err = RemoveOwner(*u) 41 | case "Route": 42 | patch, err = RemoveOwner(*u) 43 | case "Subscription": 44 | patch, err = RemoveOwner(*u) 45 | } 46 | if err != nil { 47 | return transform.PluginResponse{}, err 48 | } 49 | return transform.PluginResponse{ 50 | Version: "v1", 51 | IsWhiteOut: false, 52 | Patches: patch, 53 | }, nil 54 | } 55 | 56 | func RemoveOwner(u unstructured.Unstructured) (jsonpatch.Patch, error) { 57 | patchJSON := fmt.Sprintf(`[ 58 | { "op": "remove", "path": "/metadata/ownerReferences"} 59 | ]`) 60 | 61 | patch, err := jsonpatch.DecodePatch([]byte(patchJSON)) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return patch, nil 66 | } 67 | -------------------------------------------------------------------------------- /export/plugins/pvc-volume/volume-patch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | jsonpatch "github.com/evanphx/json-patch" 7 | "github.com/konveyor/crane-lib/transform" 8 | "github.com/konveyor/crane-lib/transform/cli" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | ) 11 | 12 | func main() { 13 | cli.RunAndExit(cli.NewCustomPlugin("removeVolumePlugin", "v1", nil, Run)) 14 | } 15 | 16 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 17 | // plugin writers need to write custom code here. 18 | u := &request.Unstructured 19 | var patch jsonpatch.Patch 20 | var err error 21 | switch u.GetKind() { 22 | case "PersistentVolumeClaim": 23 | patch, err = UpdatePVC(*u) 24 | } 25 | if err != nil { 26 | return transform.PluginResponse{}, err 27 | } 28 | return transform.PluginResponse{ 29 | Version: "v1", 30 | IsWhiteOut: false, 31 | Patches: patch, 32 | }, nil 33 | } 34 | 35 | func UpdatePVC(u unstructured.Unstructured) (jsonpatch.Patch, error) { 36 | patchJSON := fmt.Sprintf(`[ 37 | { "op": "remove", "path": "/spec/volumeName"}, 38 | { "op": "remove", "path": "/metadata/annotations/volume.kubernetes.io~1selected-node"}, 39 | { "op": "remove", "path": "/metadata/annotations/pv.kubernetes.io~1bind-completed"}, 40 | { "op": "remove", "path": "/metadata/annotations/pv.kubernetes.io~1bound-by-controller"} 41 | ]`) 42 | 43 | patch, err := jsonpatch.DecodePatch([]byte(patchJSON)) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return patch, nil 48 | } 49 | -------------------------------------------------------------------------------- /export/plugins/replicaset-removal/replicaset-remove.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | jsonpatch "github.com/evanphx/json-patch" 5 | "github.com/konveyor/crane-lib/transform" 6 | "github.com/konveyor/crane-lib/transform/cli" 7 | ) 8 | 9 | func main() { 10 | cli.RunAndExit(cli.NewCustomPlugin("ReplicaSetWhiteoutPlugin", "v1", nil, Run)) 11 | } 12 | 13 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 14 | // plugin writers need to write custom code here. 15 | u := &request.Unstructured 16 | var patch jsonpatch.Patch 17 | var whiteout bool 18 | if u.GetKind() == "ReplicaSet" { 19 | whiteout = true 20 | } 21 | return transform.PluginResponse{ 22 | Version: "v1", 23 | IsWhiteOut: whiteout, 24 | Patches: patch, 25 | }, nil 26 | } 27 | -------------------------------------------------------------------------------- /export/plugins/replication-controller/replicationcontroller.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | jsonpatch "github.com/evanphx/json-patch" 5 | "github.com/konveyor/crane-lib/transform" 6 | "github.com/konveyor/crane-lib/transform/cli" 7 | ) 8 | 9 | func main() { 10 | cli.RunAndExit(cli.NewCustomPlugin("RCWhiteoutPlugin", "v1", nil, Run)) 11 | } 12 | 13 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 14 | // plugin writers need to write custom code here. 15 | u := &request.Unstructured 16 | var patch jsonpatch.Patch 17 | var whiteout bool 18 | if u.GetKind() == "ReplicationController" { 19 | whiteout = true 20 | } 21 | return transform.PluginResponse{ 22 | Version: "v1", 23 | IsWhiteOut: whiteout, 24 | Patches: patch, 25 | }, nil 26 | } 27 | -------------------------------------------------------------------------------- /export/plugins/serverless-whiteout/serverless-whiteout.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | jsonpatch "github.com/evanphx/json-patch" 5 | "github.com/konveyor/crane-lib/transform" 6 | "github.com/konveyor/crane-lib/transform/cli" 7 | "k8s.io/apimachinery/pkg/runtime/schema" 8 | ) 9 | 10 | var knativeRoute = schema.GroupKind{ 11 | Group: "serving.knative.dev", 12 | Kind: "Route", 13 | } 14 | 15 | var knativeIngress = schema.GroupKind{ 16 | Group: "networking.internal.knative.dev", 17 | Kind: "Ingress", 18 | } 19 | 20 | var knativeRevision = schema.GroupKind{ 21 | Group: "serving.knative.dev", 22 | Kind: "Revision", 23 | } 24 | 25 | var knativeServerlessServing = schema.GroupKind{ 26 | Group: "networking.internal.knative.dev", 27 | Kind: "ServerlessService", 28 | } 29 | 30 | var knativeAutoscaler = schema.GroupKind{ 31 | Group: "autoscaling.internal.knative.dev", 32 | Kind: "PodAutoscaler", 33 | } 34 | 35 | var knativeMetrics = schema.GroupKind{ 36 | Group: "autoscaling.internal.knative.dev", 37 | Kind: "Metric", 38 | } 39 | 40 | func main() { 41 | cli.RunAndExit(cli.NewCustomPlugin("Serverlesswhiteout", "v1", nil, Run)) 42 | } 43 | 44 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 45 | // plugin writers need to write custom code here. 46 | u := &request.Unstructured 47 | var patch jsonpatch.Patch 48 | var err error 49 | var whiteout bool 50 | groupKind := u.GroupVersionKind().GroupKind() 51 | if groupKind == knativeRoute { 52 | whiteout = true 53 | } 54 | if groupKind == knativeIngress { 55 | whiteout = true 56 | } 57 | if groupKind == knativeRevision { 58 | whiteout = true 59 | } 60 | if groupKind == knativeServerlessServing { 61 | whiteout = true 62 | } 63 | if groupKind == knativeAutoscaler { 64 | whiteout = true 65 | } 66 | if groupKind == knativeMetrics { 67 | whiteout = true 68 | } 69 | if u.GetKind() == "Service" { 70 | labels := u.GetLabels() 71 | if labels["networking.internal.knative.dev/serviceType"] == "Private" || labels["networking.internal.knative.dev/serviceType"] == "Public" { 72 | whiteout = true 73 | } 74 | } 75 | if err != nil { 76 | return transform.PluginResponse{}, err 77 | } 78 | return transform.PluginResponse{ 79 | Version: "v1", 80 | IsWhiteOut: whiteout, 81 | Patches: patch, 82 | }, nil 83 | } 84 | -------------------------------------------------------------------------------- /export/plugins/status-removal/status-removal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | jsonpatch "github.com/evanphx/json-patch" 7 | "github.com/konveyor/crane-lib/transform" 8 | "github.com/konveyor/crane-lib/transform/cli" 9 | transformtypes "github.com/konveyor/crane-lib/transform/types" 10 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 11 | ) 12 | 13 | func main() { 14 | cli.RunAndExit(cli.NewCustomPlugin("StatusRemovalPlugin", "v1", nil, Run)) 15 | } 16 | 17 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 18 | // plugin writers need to write custom code here. 19 | u := &request.Unstructured 20 | patch, err := RemoveStatus(*u) 21 | 22 | if err != nil { 23 | return transform.PluginResponse{}, err 24 | } 25 | return transform.PluginResponse{ 26 | Version: "v1", 27 | IsWhiteOut: false, 28 | Patches: patch, 29 | }, nil 30 | } 31 | 32 | func RemoveStatus(u unstructured.Unstructured) (jsonpatch.Patch, error) { 33 | jsonPatch := jsonpatch.Patch{} 34 | hasStatus, _ := transformtypes.HasStatusObject(u) 35 | if hasStatus { 36 | patchJSON := fmt.Sprintf(`[ 37 | { "op": "remove", "path": "/status"} 38 | ]`) 39 | patch, err := jsonpatch.DecodePatch([]byte(patchJSON)) 40 | if err != nil { 41 | return nil, err 42 | } 43 | jsonPatch = append(jsonPatch, patch...) 44 | } 45 | return jsonPatch, nil 46 | } 47 | -------------------------------------------------------------------------------- /export/plugins/tekton/tekton.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | 6 | jsonpatch "github.com/evanphx/json-patch" 7 | "github.com/konveyor/crane-lib/transform" 8 | "github.com/konveyor/crane-lib/transform/cli" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | ) 11 | 12 | var defaultRoleBindingName = []string{"pipelines-scc-rolebinding", "edit", "admin"} 13 | var defaultServiceAccountName = []string{"pipeline-", "pipeline"} 14 | var defaultPipelineCM = []string{"config-service-cabundle", "config-trusted-cabundle"} 15 | var defaultPipelineSecret = []string{"pipeline-token-", "pipeline-dockercfg-"} 16 | 17 | func main() { 18 | cli.RunAndExit(cli.NewCustomPlugin("WhiteoutTekton", "v1", nil, Run)) 19 | } 20 | 21 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 22 | // plugin writers need to write custom code here. 23 | u := &request.Unstructured 24 | var patch jsonpatch.Patch 25 | var err error 26 | var whiteout bool 27 | switch u.GetKind() { 28 | case "PipelineRun": 29 | whiteout = true 30 | case "TaskRun": 31 | whiteout = true 32 | case "ServiceAccount": 33 | whiteout = DefaultSA(*u) 34 | case "RoleBinding": 35 | whiteout = DefaultRoleBinding(*u) 36 | case "Secret": 37 | whiteout = DefaultPipelineSecrets(*u) 38 | case "ConfigMap": 39 | whiteout = PipelineCM(*u) 40 | } 41 | if err != nil { 42 | return transform.PluginResponse{}, err 43 | } 44 | return transform.PluginResponse{ 45 | Version: "v1", 46 | IsWhiteOut: whiteout, 47 | Patches: patch, 48 | }, nil 49 | } 50 | 51 | func DefaultRoleBinding(u unstructured.Unstructured) bool { 52 | check := u.GetName() 53 | return isDefaultBinding(check) 54 | } 55 | 56 | func isDefaultBinding(name string) bool { 57 | for _, d := range defaultRoleBindingName { 58 | if strings.Contains(name, d) { 59 | return true 60 | } 61 | } 62 | return false 63 | } 64 | 65 | func DefaultSA(u unstructured.Unstructured) bool { 66 | check := u.GetName() 67 | return isDefaultSA(check) 68 | } 69 | 70 | func isDefaultSA(name string) bool { 71 | for _, d := range defaultServiceAccountName { 72 | if strings.Contains(name, d) { 73 | return true 74 | } 75 | } 76 | return false 77 | } 78 | func PipelineCM(u unstructured.Unstructured) bool { 79 | check := u.GetName() 80 | return isDefaultCM(check) 81 | } 82 | 83 | func isDefaultCM(name string) bool { 84 | for _, d := range defaultPipelineCM { 85 | if strings.Contains(name, d) { 86 | return true 87 | } 88 | } 89 | return false 90 | } 91 | 92 | func DefaultPipelineSecrets(u unstructured.Unstructured) bool { 93 | check := u.GetName() 94 | return isDefaultSecret(check) 95 | } 96 | 97 | func isDefaultSecret(name string) bool { 98 | for _, d := range defaultPipelineSecret { 99 | if strings.Contains(name, d) { 100 | return true 101 | } 102 | } 103 | return false 104 | } 105 | -------------------------------------------------------------------------------- /export/plugins/whiteout-csv/whiteout-csv.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | jsonpatch "github.com/evanphx/json-patch" 5 | "github.com/konveyor/crane-lib/transform" 6 | "github.com/konveyor/crane-lib/transform/cli" 7 | ) 8 | 9 | func main() { 10 | cli.RunAndExit(cli.NewCustomPlugin("WhiteoutCSVPlugin", "v1", nil, Run)) 11 | } 12 | 13 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 14 | // plugin writers need to write custom code here. 15 | u := &request.Unstructured 16 | var patch jsonpatch.Patch 17 | var whiteout bool 18 | if u.GetKind() == "ClusterServiceVersion" { 19 | whiteout = true 20 | } 21 | return transform.PluginResponse{ 22 | Version: "v1", 23 | IsWhiteOut: whiteout, 24 | Patches: patch, 25 | }, nil 26 | } 27 | -------------------------------------------------------------------------------- /export/plugins/whiteout-endpoints/whiteout-endpoints.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | jsonpatch "github.com/evanphx/json-patch" 5 | "github.com/konveyor/crane-lib/transform" 6 | "github.com/konveyor/crane-lib/transform/cli" 7 | ) 8 | 9 | func main() { 10 | cli.RunAndExit(cli.NewCustomPlugin("WhiteoutEndpointsPlugin", "v1", nil, Run)) 11 | } 12 | 13 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 14 | // plugin writers need to write custom code here. 15 | u := &request.Unstructured 16 | var patch jsonpatch.Patch 17 | var whiteout bool 18 | if u.GetKind() == "EndpointSlice" { 19 | whiteout = true 20 | } 21 | if u.GetKind() == "Endpoints" { 22 | whiteout = true 23 | } 24 | return transform.PluginResponse{ 25 | Version: "v1", 26 | IsWhiteOut: whiteout, 27 | Patches: patch, 28 | }, nil 29 | } 30 | -------------------------------------------------------------------------------- /export/plugins/whiteout-pods/whiteout-pods.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | jsonpatch "github.com/evanphx/json-patch" 5 | "github.com/konveyor/crane-lib/transform" 6 | "github.com/konveyor/crane-lib/transform/cli" 7 | ) 8 | 9 | func main() { 10 | cli.RunAndExit(cli.NewCustomPlugin("WhiteoutPodsPlugin", "v1", nil, Run)) 11 | } 12 | 13 | func Run(request transform.PluginRequest) (transform.PluginResponse, error) { 14 | // plugin writers need to write custom code here. 15 | u := &request.Unstructured 16 | var patch jsonpatch.Patch 17 | var whiteout bool 18 | if u.GetKind() == "Pod" { 19 | whiteout = true 20 | } 21 | return transform.PluginResponse{ 22 | Version: "v1", 23 | IsWhiteOut: whiteout, 24 | Patches: patch, 25 | }, nil 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cooktheryan/gitops-primer 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a 7 | github.com/evanphx/json-patch v4.11.0+incompatible 8 | github.com/konveyor/crane-lib v0.0.8 9 | github.com/onsi/ginkgo v1.16.4 10 | github.com/onsi/gomega v1.13.0 11 | github.com/openshift/api v0.0.0-20210625082935-ad54d363d274 12 | github.com/operator-framework/operator-lib v0.1.0 13 | github.com/sethvargo/go-password v0.2.0 14 | k8s.io/api v0.21.2 15 | k8s.io/apimachinery v0.21.3 16 | k8s.io/client-go v0.21.2 17 | sigs.k8s.io/controller-runtime v0.9.2 18 | ) 19 | 20 | require ( 21 | cloud.google.com/go v0.54.0 // indirect 22 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect 23 | github.com/Azure/go-autorest/autorest v0.11.12 // indirect 24 | github.com/Azure/go-autorest/autorest/adal v0.9.5 // indirect 25 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect 26 | github.com/Azure/go-autorest/logger v0.2.0 // indirect 27 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect 28 | github.com/beorn7/perks v1.0.1 // indirect 29 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 30 | github.com/davecgh/go-spew v1.1.1 // indirect 31 | github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect 32 | github.com/fsnotify/fsnotify v1.4.9 // indirect 33 | github.com/go-logr/logr v0.4.0 // indirect 34 | github.com/go-logr/zapr v0.4.0 // indirect 35 | github.com/gogo/protobuf v1.3.2 // indirect 36 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect 37 | github.com/golang/protobuf v1.5.2 // indirect 38 | github.com/google/go-cmp v0.5.5 // indirect 39 | github.com/google/gofuzz v1.1.0 // indirect 40 | github.com/google/uuid v1.1.2 // indirect 41 | github.com/googleapis/gnostic v0.5.5 // indirect 42 | github.com/hashicorp/golang-lru v0.5.4 // indirect 43 | github.com/imdario/mergo v0.3.12 // indirect 44 | github.com/json-iterator/go v1.1.11 // indirect 45 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 46 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 47 | github.com/modern-go/reflect2 v1.0.1 // indirect 48 | github.com/nxadm/tail v1.4.8 // indirect 49 | github.com/pkg/errors v0.9.1 // indirect 50 | github.com/prometheus/client_golang v1.11.0 // indirect 51 | github.com/prometheus/client_model v0.2.0 // indirect 52 | github.com/prometheus/common v0.26.0 // indirect 53 | github.com/prometheus/procfs v0.6.0 // indirect 54 | github.com/sirupsen/logrus v1.8.1 // indirect 55 | github.com/spf13/pflag v1.0.5 // indirect 56 | go.uber.org/atomic v1.7.0 // indirect 57 | go.uber.org/multierr v1.6.0 // indirect 58 | go.uber.org/zap v1.17.0 // indirect 59 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect 60 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect 61 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect 62 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 // indirect 63 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect 64 | golang.org/x/text v0.3.6 // indirect 65 | golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect 66 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 67 | google.golang.org/appengine v1.6.7 // indirect 68 | google.golang.org/protobuf v1.26.0 // indirect 69 | gopkg.in/inf.v0 v0.9.1 // indirect 70 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 71 | gopkg.in/yaml.v2 v2.4.0 // indirect 72 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 73 | k8s.io/apiextensions-apiserver v0.21.2 // indirect 74 | k8s.io/component-base v0.21.2 // indirect 75 | k8s.io/klog/v2 v2.8.0 // indirect 76 | k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 // indirect 77 | k8s.io/utils v0.0.0-20210527160623-6fdb442a123b // indirect 78 | sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect 79 | sigs.k8s.io/yaml v1.2.0 // indirect 80 | ) 81 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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/router-crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | # name must match the spec fields below, and be in the form: . 5 | name: routes.route.openshift.io 6 | spec: 7 | # group name to use for REST API: /apis// 8 | group: route.openshift.io 9 | # list of versions supported by this CustomResourceDefinition 10 | versions: 11 | - name: v1 12 | # Each version can be enabled/disabled by Served flag. 13 | served: true 14 | # One and only one version must be marked as the storage version. 15 | storage: true 16 | # either Namespaced or Cluster 17 | scope: Namespaced 18 | subresources: 19 | # enable spec/status 20 | status: {} 21 | names: 22 | # plural name to be used in the URL: /apis/// 23 | plural: routes 24 | # singular name to be used as an alias on the CLI and for display 25 | singular: route 26 | # kind is normally the CamelCased singular type. Your resource manifests use this. 27 | kind: Route 28 | additionalPrinterColumns: 29 | - name: Host 30 | type: string 31 | JSONPath: .status.ingress[0].host 32 | - name: Admitted 33 | type: string 34 | JSONPath: .status.ingress[0].conditions[?(@.type=="Admitted")].status 35 | - name: Service 36 | type: string 37 | JSONPath: .spec.to.name 38 | - name: TLS 39 | type: string 40 | JSONPath: .spec.tls.type 41 | -------------------------------------------------------------------------------- /hack/run-in-kind.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e -o pipefail 4 | 5 | # cd to top dir 6 | scriptdir="$(dirname "$(realpath "$0")")" 7 | cd "$scriptdir/.." 8 | 9 | 10 | # Make clean 11 | docker rmi `docker images | awk '{print $3}'` --force || true 12 | 13 | # Build the container images 14 | make docker-build 15 | make -C downloader image 16 | make -C export image 17 | make -C webhook image 18 | 19 | # Load the images into kind 20 | # We are using a special tag that should never be pushed to a repo so that it's 21 | # obvious if we try to run a container other than these intended ones. 22 | IMAGES=( 23 | "quay.io/migtools/gitops-primer:latest" 24 | "quay.io/migtools/gitops-primer-export:latest" 25 | "quay.io/migtools/gitops-primer-downloader:latest" 26 | "quay.io/migtools/gitops-primer-webhook:latest" 27 | ) 28 | for i in "${IMAGES[@]}"; do 29 | kind load docker-image "${i}" 30 | done 31 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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 | "context" 21 | "flag" 22 | "k8s.io/apimachinery/pkg/api/errors" 23 | "os" 24 | "strconv" 25 | 26 | valid "github.com/asaskevich/govalidator" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | "k8s.io/client-go/kubernetes" 29 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 30 | // to ensure that exec-entrypoint and run can make use of them. 31 | _ "k8s.io/client-go/plugin/pkg/client/auth" 32 | 33 | routev1 "github.com/openshift/api/route/v1" 34 | appsv1 "k8s.io/api/apps/v1" 35 | batchv1 "k8s.io/api/batch/v1" 36 | corev1 "k8s.io/api/core/v1" 37 | networkingv1 "k8s.io/api/networking/v1" 38 | rbacv1 "k8s.io/api/rbac/v1" 39 | "k8s.io/apimachinery/pkg/labels" 40 | "k8s.io/apimachinery/pkg/runtime" 41 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 42 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 43 | ctrl "sigs.k8s.io/controller-runtime" 44 | "sigs.k8s.io/controller-runtime/pkg/cache" 45 | "sigs.k8s.io/controller-runtime/pkg/healthz" 46 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 47 | 48 | primerv1alpha1 "github.com/cooktheryan/gitops-primer/api/v1alpha1" 49 | "github.com/cooktheryan/gitops-primer/controllers" 50 | //+kubebuilder:scaffold:imports 51 | ) 52 | 53 | var ( 54 | scheme = runtime.NewScheme() 55 | setupLog = ctrl.Log.WithName("setup") 56 | ) 57 | 58 | func init() { 59 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 60 | utilruntime.Must(routev1.AddToScheme(scheme)) 61 | utilruntime.Must(primerv1alpha1.AddToScheme(scheme)) 62 | //+kubebuilder:scaffold:scheme 63 | } 64 | 65 | func main() { 66 | var metricsAddr string 67 | var enableLeaderElection bool 68 | var probeAddr string 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 | Development: true, 76 | } 77 | opts.BindFlags(flag.CommandLine) 78 | flag.Parse() 79 | 80 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 81 | 82 | // setting privileged pod security labels to operator ns 83 | err := addPodSecurityPrivilegedLabels() 84 | if err != nil { 85 | setupLog.Error(err, "error setting privileged pod security labels to operator namespace") 86 | os.Exit(1) 87 | } 88 | 89 | setupLog.Info("Labels added") 90 | 91 | newCacheFunc := cache.BuilderWithOptions(cache.Options{ 92 | Scheme: scheme, 93 | SelectorsByObject: cache.SelectorsByObject{ 94 | &batchv1.Job{}: { 95 | Label: labels.SelectorFromSet(labels.Set{"openshift.gitops.primer": "true"}), 96 | }, 97 | &rbacv1.ClusterRole{}: { 98 | Label: labels.SelectorFromSet(labels.Set{"openshift.gitops.primer": "true"}), 99 | }, 100 | &rbacv1.ClusterRoleBinding{}: { 101 | Label: labels.SelectorFromSet(labels.Set{"openshift.gitops.primer": "true"}), 102 | }, 103 | &corev1.ServiceAccount{}: { 104 | Label: labels.SelectorFromSet(labels.Set{"openshift.gitops.primer": "true"}), 105 | }, 106 | &corev1.PersistentVolumeClaim{}: { 107 | Label: labels.SelectorFromSet(labels.Set{"openshift.gitops.primer": "true"}), 108 | }, 109 | &corev1.Service{}: { 110 | Label: labels.SelectorFromSet(labels.Set{"openshift.gitops.primer": "true"}), 111 | }, 112 | &corev1.Secret{}: { 113 | Label: labels.SelectorFromSet(labels.Set{"openshift.gitops.primer": "true"}), 114 | }, 115 | &appsv1.Deployment{}: { 116 | Label: labels.SelectorFromSet(labels.Set{"openshift.gitops.primer": "true"}), 117 | }, 118 | &routev1.Route{}: { 119 | Label: labels.SelectorFromSet(labels.Set{"openshift.gitops.primer": "true"}), 120 | }, 121 | &networkingv1.NetworkPolicy{}: { 122 | Label: labels.SelectorFromSet(labels.Set{"openshift.gitops.primer": "true"}), 123 | }, 124 | }, 125 | }) 126 | 127 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 128 | Scheme: scheme, 129 | MetricsBindAddress: metricsAddr, 130 | Port: 9443, 131 | HealthProbeBindAddress: probeAddr, 132 | NewCache: newCacheFunc, 133 | }) 134 | if err != nil { 135 | setupLog.Error(err, "unable to start manager") 136 | os.Exit(1) 137 | } 138 | 139 | if err = (&controllers.ExportReconciler{ 140 | Client: mgr.GetClient(), 141 | Scheme: mgr.GetScheme(), 142 | }).SetupWithManager(mgr); err != nil { 143 | setupLog.Error(err, "unable to create controller", "controller", "Export") 144 | os.Exit(1) 145 | } 146 | //+kubebuilder:scaffold:builder 147 | 148 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 149 | setupLog.Error(err, "unable to set up health check") 150 | os.Exit(1) 151 | } 152 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 153 | setupLog.Error(err, "unable to set up ready check") 154 | os.Exit(1) 155 | } 156 | 157 | setupLog.Info("starting manager") 158 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 159 | setupLog.Error(err, "problem running manager") 160 | os.Exit(1) 161 | } 162 | } 163 | 164 | // setting privileged pod security labels to operator namespace 165 | func addPodSecurityPrivilegedLabels() error { 166 | setupLog.Info("patching namespace with PSA labels") 167 | kubeconf := ctrl.GetConfigOrDie() 168 | clientset, err := kubernetes.NewForConfig(kubeconf) 169 | if err != nil { 170 | setupLog.Error(err, "problem getting client") 171 | return err 172 | } 173 | 174 | version, err := clientset.ServerVersion() 175 | if err != nil { 176 | setupLog.Error(err, "problem getting server version") 177 | return err 178 | } 179 | minVer := version.Minor 180 | if !valid.IsInt(minVer) { 181 | minVer = minVer[:len(minVer) - 1] 182 | } 183 | minor, err := strconv.Atoi(minVer) 184 | if err != nil { 185 | setupLog.Error(err, "problem getting minor version") 186 | return err 187 | } 188 | 189 | if minor < 24 { 190 | return nil 191 | } 192 | 193 | operatorNamespace, err := clientset.CoreV1().Namespaces().Get(context.TODO(), "gitops-primer-system", metav1.GetOptions{}) 194 | if err != nil { 195 | if errors.IsNotFound(err) { 196 | setupLog.Info("gitops-primer-system namespace not found. If installing operator in any other namespace, please add appropriate namespace labels to make sure primer works in OpenShift environment 4.11+") 197 | return nil 198 | } 199 | setupLog.Error(err, "problem getting operator namespace") 200 | return err 201 | } 202 | 203 | privilegedLabels := map[string]string{ 204 | "pod-security.kubernetes.io/enforce": "privileged", 205 | "pod-security.kubernetes.io/audit": "privileged", 206 | "pod-security.kubernetes.io/warn": "privileged", 207 | } 208 | 209 | operatorNamespace.SetLabels(privilegedLabels) 210 | 211 | _, err = clientset.CoreV1().Namespaces().Update(context.TODO(), operatorNamespace, metav1.UpdateOptions{}) 212 | if err != nil { 213 | setupLog.Error(err, "problem patching operator namespace for privileged pod security labels") 214 | return err 215 | } 216 | return nil 217 | } 218 | -------------------------------------------------------------------------------- /test-kuttl/e2e/export/00-create-objects.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e -o pipefail 4 | 5 | kubectl create deployment test --image nginx -n test 6 | kubectl create svc clusterip colors --tcp 8080 -n test 7 | kubectl create sa test -n test 8 | -------------------------------------------------------------------------------- /test-kuttl/e2e/export/00-create-objects.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kuttl.dev/v1beta1 3 | kind: TestStep 4 | commands: 5 | - timeout: 90 6 | command: ./00-create-objects.sh 7 | -------------------------------------------------------------------------------- /test-kuttl/e2e/export/05-create-user.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -e -o pipefail 3 | 4 | openssl req -new -newkey rsa:4096 -nodes -keyout bob-k8s.key -out bob-k8s.csr -subj "/CN=bob/O=devops" 5 | SECRET=`cat bob-k8s.csr | base64 | tr -d '\n'` 6 | 7 | kubectl apply -n test -f - < /dev/null || git describe --always --dirty) 5 | 6 | .PHONY: all 7 | all: image 8 | 9 | .PHONY: image 10 | image: 11 | docker build \ 12 | --build-arg "builddate_arg=$(BUILDDATE)" \ 13 | --build-arg "version_arg=$(VERSION)" \ 14 | -t $(IMAGE) \ 15 | -f Dockerfile . 16 | -------------------------------------------------------------------------------- /webhook/api/app.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | primerv1alpha1 "github.com/cooktheryan/gitops-primer/api/v1alpha1" 9 | admissionv1 "k8s.io/api/admission/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | type App struct { 14 | } 15 | 16 | func (app *App) HandleMutate(w http.ResponseWriter, r *http.Request) { 17 | admissionReview := &admissionv1.AdmissionReview{} 18 | 19 | // read the AdmissionReview from the request json body 20 | err := readJSON(r, admissionReview) 21 | if err != nil { 22 | app.HandleError(w, r, err) 23 | return 24 | } 25 | 26 | // unmarshal the export from the AdmissionRequest 27 | ar := admissionReview.Request.UserInfo 28 | if err := json.Unmarshal(admissionReview.Request.Object.Raw, &ar); err != nil { 29 | app.HandleError(w, r, fmt.Errorf("unmarshal to user: %v", err)) 30 | return 31 | } 32 | 33 | // unmarshal the export from the AdmissionRequest 34 | export := &primerv1alpha1.Export{} 35 | if err := json.Unmarshal(admissionReview.Request.Object.Raw, export); err != nil { 36 | app.HandleError(w, r, fmt.Errorf("unmarshal to export: %v", err)) 37 | return 38 | } 39 | 40 | if export.Spec.User != "" { 41 | response := &admissionv1.AdmissionReview{ 42 | TypeMeta: metav1.TypeMeta{ 43 | Kind: "AdmissionReview", 44 | APIVersion: "admission.k8s.io/v1", 45 | }, 46 | Response: &admissionv1.AdmissionResponse{ 47 | UID: admissionReview.Request.UID, 48 | Allowed: true, 49 | }, 50 | } 51 | jsonOk(w, &response) 52 | return 53 | 54 | } 55 | 56 | userName, err := json.Marshal(&ar.Username) 57 | group, err := json.Marshal(&ar.Groups) 58 | if err != nil { 59 | app.HandleError(w, r, fmt.Errorf("marshall user: %v", err)) 60 | } 61 | 62 | // build json patch 63 | patch := []JSONPatchEntry{ 64 | JSONPatchEntry{ 65 | OP: "add", 66 | Path: "/spec/user", 67 | Value: userName, 68 | }, 69 | JSONPatchEntry{ 70 | OP: "add", 71 | Path: "/spec/group", 72 | Value: group, 73 | }, 74 | } 75 | 76 | patchBytes, err := json.Marshal(&patch) 77 | if err != nil { 78 | app.HandleError(w, r, fmt.Errorf("marshall jsonpatch: %v", err)) 79 | return 80 | } 81 | 82 | patchType := admissionv1.PatchTypeJSONPatch 83 | 84 | // build admission response 85 | admissionResponse := &admissionv1.AdmissionResponse{ 86 | UID: admissionReview.Request.UID, 87 | Allowed: true, 88 | Patch: patchBytes, 89 | PatchType: &patchType, 90 | } 91 | 92 | respAdmissionReview := &admissionv1.AdmissionReview{ 93 | TypeMeta: metav1.TypeMeta{ 94 | Kind: "AdmissionReview", 95 | APIVersion: "admission.k8s.io/v1", 96 | }, 97 | Response: admissionResponse, 98 | } 99 | 100 | jsonOk(w, &respAdmissionReview) 101 | } 102 | 103 | type JSONPatchEntry struct { 104 | OP string `json:"op"` 105 | Path string `json:"path"` 106 | Value json.RawMessage `json:"value,omitempty"` 107 | } 108 | -------------------------------------------------------------------------------- /webhook/api/helpers.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | // http helpers 10 | 11 | // HandleError depending on error type 12 | func (app *App) HandleError(w http.ResponseWriter, r *http.Request, err error) { 13 | jsonError(w, err.Error(), http.StatusBadRequest) 14 | } 15 | 16 | // readJSON from request body 17 | func readJSON(r *http.Request, v interface{}) error { 18 | err := json.NewDecoder(r.Body).Decode(v) 19 | if err != nil { 20 | return fmt.Errorf("invalid JSON input") 21 | } 22 | 23 | return nil 24 | } 25 | 26 | // jsonOk renders json with 200 ok 27 | func jsonOk(w http.ResponseWriter, v interface{}) { 28 | w.Header().Set("Content-Type", "application/json") 29 | writeJSON(w, v) 30 | } 31 | 32 | // writeJSON to response body 33 | func writeJSON(w http.ResponseWriter, v interface{}) { 34 | b, err := json.Marshal(v) 35 | if err != nil { 36 | http.Error(w, fmt.Sprintf("json encoding error: %v", err), http.StatusInternalServerError) 37 | return 38 | } 39 | 40 | writeBytes(w, b) 41 | } 42 | 43 | // writeBytes to response body 44 | func writeBytes(w http.ResponseWriter, b []byte) { 45 | _, err := w.Write(b) 46 | if err != nil { 47 | http.Error(w, fmt.Sprintf("write error: %v", err), http.StatusInternalServerError) 48 | return 49 | } 50 | } 51 | 52 | // jsonError renders json with error 53 | func jsonError(w http.ResponseWriter, errStr string, code int) { 54 | w.Header().Set("Content-Type", "application/json") 55 | w.WriteHeader(code) 56 | writeJSON(w, &jsonErr{Err: errStr}) 57 | } 58 | 59 | // jsonErr err 60 | type jsonErr struct { 61 | Err string `json:"err"` 62 | } 63 | -------------------------------------------------------------------------------- /webhook/api/router.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/go-chi/chi" 5 | "github.com/go-chi/chi/middleware" 6 | ) 7 | 8 | // BuildRouter builds the router 9 | func BuildRouter(app *App) *chi.Mux { 10 | r := chi.NewRouter() 11 | 12 | r.Use(middleware.RequestID) 13 | r.Use(middleware.RealIP) 14 | r.Use(middleware.Logger) 15 | r.Use(middleware.Recoverer) 16 | 17 | r.Post("/mutate", app.HandleMutate) 18 | 19 | return r 20 | } 21 | -------------------------------------------------------------------------------- /webhook/api/server.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | ) 8 | 9 | // StartServer starts the server 10 | func StartServer() error { 11 | port := os.Getenv("PORT") 12 | if port == "" { 13 | port = "8000" 14 | } 15 | 16 | app := &App{} 17 | 18 | mux := BuildRouter(app) 19 | 20 | fmt.Printf("Listening on port %s\n", port) 21 | 22 | return http.ListenAndServeTLS(fmt.Sprintf(":%s", port), "/tls/tls.crt", "/tls/tls.key", mux) 23 | } 24 | -------------------------------------------------------------------------------- /webhook/api/user.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type User struct { 4 | ID int `json:"id,omitempty"` 5 | Name string `json:"name,omitempty"` 6 | } 7 | 8 | type Group struct { 9 | ID int `json:"id,omitempty"` 10 | Name string `json:"name,omitempty"` 11 | } 12 | -------------------------------------------------------------------------------- /webhook/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/didil/k8s-hello-mutating-webhook/webhook 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/cooktheryan/gitops-primer v0.0.0-20220805191006-8ed0c1266b43 7 | github.com/go-chi/chi v4.1.2+incompatible 8 | k8s.io/api v0.21.2 9 | k8s.io/apimachinery v0.21.3 10 | ) 11 | 12 | require ( 13 | github.com/go-logr/logr v0.4.0 // indirect 14 | github.com/gogo/protobuf v1.3.2 // indirect 15 | github.com/google/go-cmp v0.5.5 // indirect 16 | github.com/google/gofuzz v1.1.0 // indirect 17 | github.com/json-iterator/go v1.1.11 // indirect 18 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 19 | github.com/modern-go/reflect2 v1.0.1 // indirect 20 | github.com/operator-framework/operator-lib v0.1.0 // indirect 21 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect 22 | golang.org/x/text v0.3.6 // indirect 23 | gopkg.in/inf.v0 v0.9.1 // indirect 24 | gopkg.in/yaml.v2 v2.4.0 // indirect 25 | k8s.io/klog/v2 v2.8.0 // indirect 26 | sigs.k8s.io/controller-runtime v0.9.2 // indirect 27 | sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /webhook/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/didil/k8s-hello-mutating-webhook/webhook/api" 7 | ) 8 | 9 | func main() { 10 | err := api.StartServer() 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | } 15 | --------------------------------------------------------------------------------