├── .codecov.yml ├── .dockerignore ├── .gitattributes ├── .github ├── dependabot.yaml └── workflows │ ├── ci-e2e.yaml │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── .mockery.yaml ├── .readthedocs.yaml ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── api └── v1alpha1 │ ├── argocdcommitstatus_types.go │ ├── changetransferpolicy_types.go │ ├── clusterscmprovider_types.go │ ├── commitstatus_types.go │ ├── common_types.go │ ├── constants.go │ ├── controllerconfiguration_types.go │ ├── gitrepository_types.go │ ├── groupversion_info.go │ ├── promotionstrategy_types.go │ ├── pullrequest_types.go │ ├── revertcommit_types.go │ ├── scmprovider_types.go │ └── zz_generated.deepcopy.go ├── cmd └── main.go ├── config ├── config │ ├── controllerconfiguration.yaml │ └── kustomization.yaml ├── crd │ ├── bases │ │ ├── promoter.argoproj.io_argocdcommitstatuses.yaml │ │ ├── promoter.argoproj.io_changetransferpolicies.yaml │ │ ├── promoter.argoproj.io_clusterscmproviders.yaml │ │ ├── promoter.argoproj.io_commitstatuses.yaml │ │ ├── promoter.argoproj.io_controllerconfigurations.yaml │ │ ├── promoter.argoproj.io_gitrepositories.yaml │ │ ├── promoter.argoproj.io_promotionstrategies.yaml │ │ ├── promoter.argoproj.io_pullrequests.yaml │ │ ├── promoter.argoproj.io_revertcommits.yaml │ │ └── promoter.argoproj.io_scmproviders.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ └── manager_config_patch.yaml ├── manager │ ├── deployment.yaml │ ├── kustomization.yaml │ ├── namespace.yaml │ └── webhook_service.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── argocdcommitstatus_editor_role.yaml │ ├── argocdcommitstatus_viewer_role.yaml │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── changetransferpolicy_editor_role.yaml │ ├── changetransferpolicy_viewer_role.yaml │ ├── clusterscmprovider_admin_role.yaml │ ├── clusterscmprovider_editor_role.yaml │ ├── clusterscmprovider_viewer_role.yaml │ ├── commitstatus_editor_role.yaml │ ├── commitstatus_viewer_role.yaml │ ├── controllerconfiguration_admin_role.yaml │ ├── controllerconfiguration_editor_role.yaml │ ├── controllerconfiguration_viewer_role.yaml │ ├── gitrepository_editor_role.yaml │ ├── gitrepository_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── promotionstrategy_editor_role.yaml │ ├── promotionstrategy_viewer_role.yaml │ ├── pullrequest_editor_role.yaml │ ├── pullrequest_viewer_role.yaml │ ├── revertcommit_editor_role.yaml │ ├── revertcommit_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── scmprovider_editor_role.yaml │ ├── scmprovider_viewer_role.yaml │ └── service_account.yaml ├── release │ └── kustomization.yaml └── samples │ ├── kustomization.yaml │ ├── promoter_v1alpha1_argocdcommitstatus.yaml │ ├── promoter_v1alpha1_changetransferpolicy.yaml │ ├── promoter_v1alpha1_clusterscmprovider.yaml │ ├── promoter_v1alpha1_commitstatus.yaml │ ├── promoter_v1alpha1_controllerconfiguration.yaml │ ├── promoter_v1alpha1_gitrepository.yaml │ ├── promoter_v1alpha1_promotionstrategy.yaml │ ├── promoter_v1alpha1_pullrequest.yaml │ ├── promoter_v1alpha1_revertcommit.yaml │ └── promoter_v1alpha1_scmprovider.yaml ├── dist └── install.yaml ├── docs ├── architecture.md ├── assets │ ├── architecture-diag.excalidraw │ ├── architecture-diag.png │ ├── architecture.png │ └── logo.png ├── commit-status-controllers │ └── argocd.md ├── crd-specs.md ├── example-resources │ ├── ArgoCDCommitStatus.yaml │ ├── ChangeTransferPolicy.yaml │ ├── ClusterScmProvider.yaml │ ├── CommitStatus.yaml │ ├── ControllerConfiguration.yaml │ ├── GitRepository.yaml │ ├── PromotionStrategy.yaml │ ├── PullRequest.yaml │ └── ScmProvider.yaml ├── faqs.md ├── gating-promotions.md ├── getting-started.md ├── index.md ├── metrics.md ├── multi-tenancy.md ├── requirements.txt └── tool-comparison.md ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── git │ └── promoter_askpass.sh └── manifests-release.sh ├── internal ├── controller │ ├── argocdcommitstatus_controller.go │ ├── argocdcommitstatus_controller_test.go │ ├── changetransferpolicy_controller.go │ ├── changetransferpolicy_controller_test.go │ ├── clusterscmprovider_controller.go │ ├── clusterscmprovider_controller_test.go │ ├── commitstatus_controller.go │ ├── commitstatus_controller_test.go │ ├── controllerconfiguration_controller.go │ ├── controllerconfiguration_controller_test.go │ ├── gitrepository_controller.go │ ├── gitrepository_controller_test.go │ ├── promotionstrategy_controller.go │ ├── promotionstrategy_controller_test.go │ ├── pullrequest_controller.go │ ├── pullrequest_controller_test.go │ ├── revertcommit_controller.go │ ├── revertcommit_controller_test.go │ ├── scmprovider_controller.go │ ├── scmprovider_controller_test.go │ └── suite_test.go ├── git │ └── git.go ├── metrics │ └── metrics.go ├── scms │ ├── commitstatus.go │ ├── common.go │ ├── fake │ │ ├── commit_status.go │ │ ├── git_operations.go │ │ └── pullrequest.go │ ├── git_operations.go │ ├── github │ │ ├── commit_status.go │ │ ├── git_operations.go │ │ ├── git_operations_test.go │ │ ├── pullrequest.go │ │ └── utils.go │ ├── gitlab │ │ ├── commit_status.go │ │ ├── git_operations.go │ │ ├── pullrequest.go │ │ └── utils.go │ ├── mock │ │ ├── mock_CommitStatusProvider.go │ │ ├── mock_GitOperationsProvider.go │ │ └── mock_PullRequestProvider.go │ └── pullrequest.go ├── settings │ └── manager.go ├── types │ ├── argocd │ │ └── argocd_type.go │ └── constants │ │ └── events.go ├── utils │ ├── gitpaths │ │ └── gitpaths.go │ ├── suite_test.go │ ├── template.go │ ├── template_test.go │ └── utils.go └── webhookreceiver │ └── server.go ├── mkdocs.yml ├── release.Dockerfile └── test ├── e2e ├── e2e_suite_test.go └── e2e_test.go ├── external_crds └── argocd_application.yaml └── utils └── utils.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | # allow test coverage to drop by 0.1%, assume that it's typically due to CI problems 4 | patch: 5 | default: 6 | threshold: 0.1 7 | project: 8 | default: 9 | threshold: 0.1 10 | ignore: 11 | - 'api/v1alpha1' 12 | - 'test' 13 | - 'cmd' 14 | - 'config' 15 | - 'dist' 16 | - 'hack' 17 | - 'internal/scms/mock' 18 | 19 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | config/crd/bases/*.yaml linguist-generated=true 2 | dist/install.yaml linguist-generated=true 3 | go.sum linguist-generated=true 4 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | groups: 8 | k8s-deps: 9 | patterns: 10 | - "k8s.io/api" # a single dependency name 11 | - "k8s.io/apimachinery" 12 | - "k8s.io/client-go" 13 | 14 | - package-ecosystem: "github-actions" 15 | directory: "/" 16 | schedule: 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/workflows/ci-e2e.yaml: -------------------------------------------------------------------------------- 1 | name: test-e2e 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | jobs: 11 | ci: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | kubernetes-minor-version: 16 | - 1.28.0 17 | - 1.29.0 18 | - 1.30.0 19 | name: E2E Tests 20 | runs-on: ubuntu-latest 21 | timeout-minutes: 10 22 | steps: 23 | - name: Set up Go 24 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@v5 25 | with: 26 | go-version: '1.24' 27 | # see https://github.com/actions/setup-go?tab=readme-ov-file#caching-dependency-files-and-build-outputs 28 | cache-dependency-path: | 29 | go.sum 30 | - name: Create k8s Kind Cluster 31 | uses: helm/kind-action@v1 32 | with: 33 | cluster_name: kind 34 | version: v0.23.0 35 | node_image: kindest/node:v${{ matrix.kubernetes-minor-version }} 36 | kubectl_version: v${{ matrix.kubernetes-minor-version }} 37 | - name: Checkout Repo 38 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4 39 | - name: Run E2E-Tests 40 | run: make test-e2e 41 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | jobs: 11 | ci: 12 | name: Continuous Integration 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 30 15 | steps: 16 | - name: Checkout Repo 17 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4 18 | - name: Set up Go 19 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@v5 20 | with: 21 | go-version: "1.24" 22 | - name: Get dependencies 23 | run: go mod download 24 | - name: Restore build output from cache 25 | id: cache-build 26 | uses: actions/cache@v4 27 | with: 28 | path: bin/manager 29 | key: ${{ runner.os }}-go-build-${{ hashFiles('**/*.go', 'go.sum') }} 30 | - name: Lint 31 | uses: golangci/golangci-lint-action@v8 32 | with: 33 | version: v2.1.0 34 | args: --timeout=5m 35 | skip-cache: false 36 | - name: Build 37 | if: steps.cache-build.outputs.cache-hit != 'true' 38 | run: make 39 | - name: Run Integration Tests 40 | run: make test-parallel 41 | - name: Generate code coverage artifacts 42 | if: ${{ !cancelled() }} 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: code-coverage 46 | path: cover.out 47 | - name: Upload code coverage information to codecov.io 48 | if: ${{ !cancelled() }} 49 | uses: codecov/codecov-action@v5.4.3 50 | with: 51 | files: cover.out 52 | fail_ci_if_error: false 53 | env: 54 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 55 | - name: Upload test results to codecov.io 56 | if: ${{ !cancelled() }} 57 | uses: codecov/test-results-action@v1 58 | with: 59 | token: ${{ secrets.CODECOV_TOKEN }} 60 | codegen: 61 | name: Check Codegen 62 | runs-on: ubuntu-latest 63 | timeout-minutes: 30 64 | steps: 65 | - name: Checkout Repo 66 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4 67 | - name: Set up Go 68 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # pin@v5 69 | with: 70 | go-version: "1.24" 71 | - name: go mod tidy 72 | run: | 73 | go mod tidy 74 | if ! diff=$(git diff --exit-code --unified=0 -- go.sum); then 75 | line=$(echo "$diff" | sed -nr 's/@@ -([0-9]+),.*/\1/p' | head -n 1 | tr -d '\n') 76 | echo "::error file=go.sum,line=$line::go.sum is out of date. Run 'go mod tidy' and commit the changes." 77 | exit 1 78 | fi 79 | - name: make build-installer 80 | run: | 81 | make build-installer 82 | if ! git diff --exit-code; then 83 | echo "::error ::Manifests are out of date. Run 'make build-installer' and commit the changes." 84 | exit 1 85 | fi 86 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | # see https://github.com/argoproj-labs/argocd-ephemeral-access/blob/main/.github/workflows/release.yaml 2 | name: release 3 | 4 | on: 5 | push: 6 | tags: 7 | - 'v*' 8 | env: 9 | # If set in the repo env vars it will use this tag to build the release notes. 10 | # Useful when creating a release tag after a release candidate tags phase. 11 | GORELEASER_PREVIOUS_TAG: ${{vars.GORELEASER_PREVIOUS_TAG}} 12 | 13 | defaults: 14 | run: 15 | shell: bash 16 | 17 | jobs: 18 | release: 19 | runs-on: ubuntu-24.04 20 | if: github.repository == 'argoproj-labs/gitops-promoter' 21 | name: Release 22 | steps: 23 | - name: Set up QEMU 24 | uses: docker/setup-qemu-action@v3 25 | 26 | - name: Checkout 27 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 28 | with: 29 | fetch-depth: 0 30 | 31 | - name: Setup Go 32 | uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 33 | with: 34 | go-version: "1.24" 35 | 36 | - name: Docker Login in quay.io 37 | uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 38 | with: 39 | registry: quay.io 40 | username: ${{ secrets.QUAY_USERNAME }} 41 | password: ${{ secrets.QUAY_TOKEN }} 42 | 43 | - name: Run GoReleaser 44 | uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 45 | with: 46 | distribution: goreleaser 47 | version: v2.6.1 48 | args: release --clean --verbose 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/* 8 | Dockerfile.cross 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | *.out.* 16 | 17 | # Go workspace file 18 | go.work 19 | 20 | # Kubernetes Generated files - skip generated files, except for vendored files 21 | !vendor/**/zz_generated.* 22 | 23 | # editor and IDE paraphernalia 24 | .idea 25 | .vscode 26 | *.swp 27 | *.swo 28 | *~ 29 | 30 | # Github Certificates 31 | *.pem 32 | secret.yaml 33 | install.yaml 34 | 35 | junit.xml 36 | 37 | ./install.yaml 38 | release_dist 39 | 40 | venv 41 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | allow-parallel-runners: true 4 | linters: 5 | enable: 6 | - bodyclose 7 | - canonicalheader 8 | - containedctx 9 | - contextcheck 10 | - copyloopvar 11 | - dupl 12 | - durationcheck 13 | - errchkjson 14 | - errname 15 | - errorlint 16 | - fatcontext 17 | - forcetypeassert 18 | - gocheckcompilerdirectives 19 | - goconst 20 | - gocritic 21 | - gocyclo 22 | - lll 23 | - loggercheck 24 | - makezero 25 | - misspell 26 | - musttag 27 | - nakedret 28 | - noctx 29 | - nolintlint 30 | - nosprintfhostport 31 | - paralleltest 32 | - perfsprint 33 | - prealloc 34 | - promlinter 35 | - testifylint 36 | - thelper 37 | - tparallel 38 | - unconvert 39 | - unparam 40 | - usestdlibvars 41 | - wastedassign 42 | - whitespace 43 | - wrapcheck 44 | settings: 45 | copyloopvar: 46 | check-alias: true 47 | gocritic: 48 | disabled-checks: 49 | - assignOp 50 | - ifElseChain 51 | exclusions: 52 | rules: 53 | - linters: 54 | - lll 55 | path: api/.* 56 | - linters: 57 | - lll 58 | path: internal/.* 59 | - linters: 60 | - dupl 61 | path: .*_test.go 62 | paths: 63 | - third_party$ 64 | - builtin$ 65 | - examples$ 66 | issues: 67 | max-issues-per-linter: 0 68 | max-same-issues: 0 69 | formatters: 70 | enable: 71 | - gofmt 72 | - gofumpt 73 | - goimports 74 | exclusions: 75 | paths: 76 | - third_party$ 77 | - builtin$ 78 | - examples$ 79 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # Based on: https://github.com/argoproj-labs/gitops-promoter/blob/main/.goreleaser.yaml 2 | 3 | # The lines below are called `modelines`. See `:help modeline` 4 | # yaml-language-server: $schema=https://raw.githubusercontent.com/goreleaser/goreleaser/v2.3.2/www/docs/static/schema.json 5 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 6 | 7 | project_name: gitops-promoter 8 | version: 2 9 | 10 | dist: release_dist 11 | 12 | before: 13 | hooks: 14 | - go mod tidy 15 | - go mod download 16 | - make manifests-release IMAGE_TAG={{ .Tag }} 17 | 18 | builds: 19 | - id: gitops-promoter 20 | main: ./cmd 21 | binary: gitops-promoter 22 | goarch: 23 | - amd64 24 | - arm64 25 | env: 26 | - CGO_ENABLED=0 27 | flags: 28 | - -v 29 | goos: 30 | - linux 31 | ldflags: 32 | - -X github.com/argoproj-labs/gitops-promoter/common.version={{ .Version }} 33 | - -X github.com/argoproj-labs/gitops-promoter/common.buildDate={{ .Date }} 34 | - -extldflags="-static" 35 | 36 | dockers: 37 | - image_templates: 38 | - quay.io/argoprojlabs/gitops-promoter:{{ .Tag }}-amd64 39 | dockerfile: release.Dockerfile 40 | skip_push: "{{ .IsSnapshot }}" 41 | use: buildx 42 | goarch: amd64 43 | extra_files: 44 | - hack/git/promoter_askpass.sh 45 | build_flag_templates: 46 | - "--pull" 47 | - "--label=org.opencontainers.image.created={{.Date}}" 48 | - "--label=org.opencontainers.image.title={{.ProjectName}}" 49 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 50 | - "--label=org.opencontainers.image.version={{.Version}}" 51 | - "--platform=linux/amd64" 52 | - image_templates: 53 | - quay.io/argoprojlabs/gitops-promoter:{{ .Tag }}-arm64 54 | dockerfile: release.Dockerfile 55 | extra_files: 56 | - hack/git/promoter_askpass.sh 57 | skip_push: "{{ .IsSnapshot }}" 58 | use: buildx 59 | goarch: arm64 60 | build_flag_templates: 61 | - "--pull" 62 | - "--label=org.opencontainers.image.created={{.Date}}" 63 | - "--label=org.opencontainers.image.title={{.ProjectName}}" 64 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 65 | - "--label=org.opencontainers.image.version={{.Version}}" 66 | - "--platform=linux/arm64" 67 | docker_manifests: 68 | - name_template: "quay.io/argoprojlabs/gitops-promoter:{{ .Tag }}" 69 | image_templates: 70 | - "quay.io/argoprojlabs/gitops-promoter:{{ .Tag }}-amd64" 71 | - "quay.io/argoprojlabs/gitops-promoter:{{ .Tag }}-arm64" 72 | 73 | archives: 74 | - id: binary 75 | format: tar.gz 76 | builds: 77 | - "gitops-promoter" 78 | # this name template makes the OS and Arch compatible with the results of `uname`. 79 | name_template: >- 80 | {{ .ProjectName }}_ 81 | {{- title .Os }}_ 82 | {{- if eq .Arch "amd64" }}x86_64 83 | {{- else if eq .Arch "386" }}i386 84 | {{- else }}{{ .Arch }}{{ end }} 85 | {{- if .Arm }}v{{ .Arm }}{{ end }} 86 | 87 | release: 88 | prerelease: auto 89 | draft: true 90 | extra_files: 91 | - glob: ./install.yaml 92 | header: | 93 | ## gitops-promoter 94 | {{ .Date }} 95 | 96 | ### Container images 97 | 98 | - quay.io/argoprojlabs/gitops-promoter:{{ .Tag }} 99 | 100 | footer: | 101 | **Full Changelog**: https://github.com/argoproj-labs/gitops-promoter/compare/{{ .PreviousTag }}...{{ .Tag }} 102 | 103 | changelog: 104 | use: 105 | github 106 | sort: asc 107 | abbrev: 0 108 | groups: # Regex use RE2 syntax as defined here: https://github.com/google/re2/wiki/Syntax. 109 | - title: 'Features' 110 | regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$' 111 | order: 100 112 | - title: 'Bug fixes' 113 | regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$' 114 | order: 200 115 | - title: 'Documentation' 116 | regexp: '^.*?docs(\([[:word:]]+\))??!?:.+$' 117 | order: 300 118 | - title: 'Dependency updates' 119 | regexp: '^.*?(feat|fix|chore)\(deps?.+\)!?:.+$' 120 | order: 400 121 | - title: 'Other work' 122 | order: 999 123 | filters: 124 | exclude: 125 | - '^test:' 126 | - '^.*?Bump(\([[:word:]]+\))?.+$' 127 | -------------------------------------------------------------------------------- /.mockery.yaml: -------------------------------------------------------------------------------- 1 | with-expecter: true 2 | packages: 3 | github.com/argoproj-labs/gitops-promoter/internal/scms: 4 | # place your package-specific config here 5 | config: 6 | interfaces: 7 | # select the interfaces you want mocked 8 | PullRequestProvider: 9 | # Modify package-level config for this specific interface (if applicable) 10 | config: 11 | dir: "internal/scms/mock" 12 | outpkg: "mock" 13 | CommitStatusProvider: 14 | # Modify package-level config for this specific interface (if applicable) 15 | config: 16 | dir: "internal/scms/mock" 17 | outpkg: "mock" 18 | GitOperationsProvider: 19 | # Modify package-level config for this specific interface (if applicable) 20 | config: 21 | dir: "internal/scms/mock" 22 | outpkg: "mock" -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for MkDocs projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the version of Python and other tools you might need 8 | build: 9 | os: ubuntu-24.04 10 | tools: 11 | python: "3.12" 12 | 13 | mkdocs: 14 | configuration: mkdocs.yml 15 | 16 | # Optionally declare the Python requirements required to build your docs 17 | python: 18 | install: 19 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the gitops-promoter binary 2 | FROM golang:1.24 AS builder 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | WORKDIR /workspace 7 | # Copy the Go Modules manifests 8 | COPY go.mod go.mod 9 | COPY go.sum go.sum 10 | # cache deps before building and copying source so that we don't need to re-download as much 11 | # and so that source changes don't invalidate our downloaded layer 12 | RUN go mod download 13 | 14 | # Copy the go source 15 | COPY cmd/main.go cmd/main.go 16 | COPY api/ api/ 17 | COPY internal/ internal/ 18 | COPY hack/git/promoter_askpass.sh hack/git/promoter_askpass.sh 19 | 20 | # Build 21 | # the GOARCH has not a default value to allow the binary be built according to the host where the command 22 | # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO 23 | # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, 24 | # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. 25 | RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o gitops-promoter cmd/main.go 26 | 27 | # Use distroless as minimal base image to package the gitops-promoter binary 28 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 29 | #FROM gcr.io/distroless/static:nonroot #TODO: figure out smallest/safest way to get git installed 30 | FROM golang:1.24 31 | WORKDIR / 32 | RUN mkdir /git 33 | COPY --from=builder /workspace/gitops-promoter . 34 | COPY --from=builder /workspace/hack/git/promoter_askpass.sh /git/promoter_askpass.sh 35 | ENV PATH="${PATH}:/git" 36 | RUN echo "${PATH}" >> /etc/bash.bashrc 37 | USER 65532:65532 38 | 39 | ENTRYPOINT ["/gitops-promoter"] 40 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: argoproj.io 6 | layout: 7 | - go.kubebuilder.io/v4 8 | projectName: promoter 9 | repo: github.com/argoproj-labs/gitops-promoter 10 | resources: 11 | - api: 12 | crdVersion: v1 13 | namespaced: true 14 | controller: true 15 | domain: argoproj.io 16 | group: promoter 17 | kind: PullRequest 18 | path: github.com/argoproj-labs/gitops-promoter/api/v1alpha1 19 | version: v1alpha1 20 | - api: 21 | crdVersion: v1 22 | namespaced: true 23 | controller: true 24 | domain: argoproj.io 25 | group: promoter 26 | kind: CommitStatus 27 | path: github.com/argoproj-labs/gitops-promoter/api/v1alpha1 28 | version: v1alpha1 29 | - api: 30 | crdVersion: v1 31 | namespaced: true 32 | controller: true 33 | domain: argoproj.io 34 | group: promoter 35 | kind: RevertCommit 36 | path: github.com/argoproj-labs/gitops-promoter/api/v1alpha1 37 | version: v1alpha1 38 | - api: 39 | crdVersion: v1 40 | namespaced: true 41 | controller: true 42 | domain: argoproj.io 43 | group: promoter 44 | kind: PromotionStrategy 45 | path: github.com/argoproj-labs/gitops-promoter/api/v1alpha1 46 | version: v1alpha1 47 | - api: 48 | crdVersion: v1 49 | namespaced: true 50 | controller: true 51 | domain: argoproj.io 52 | group: promoter 53 | kind: ScmProvider 54 | path: github.com/argoproj-labs/gitops-promoter/api/v1alpha1 55 | version: v1alpha1 56 | - api: 57 | crdVersion: v1 58 | namespaced: true 59 | controller: true 60 | domain: argoproj.io 61 | group: promoter 62 | kind: GitRepository 63 | path: github.com/argoproj-labs/gitops-promoter/api/v1alpha1 64 | version: v1alpha1 65 | - api: 66 | crdVersion: v1 67 | namespaced: true 68 | controller: true 69 | domain: argoproj.io 70 | group: promoter 71 | kind: ChangeTransferPolicy 72 | path: github.com/argoproj-labs/gitops-promoter/api/v1alpha1 73 | version: v1alpha1 74 | - api: 75 | crdVersion: v1 76 | namespaced: true 77 | controller: true 78 | domain: argoproj.io 79 | group: promoter 80 | kind: ArgoCDCommitStatus 81 | path: github.com/argoproj-labs/gitops-promoter/api/v1alpha1 82 | version: v1alpha1 83 | - api: 84 | crdVersion: v1 85 | namespaced: true 86 | controller: true 87 | domain: argoproj.io 88 | group: promoter 89 | kind: ControllerConfiguration 90 | path: github.com/argoproj-labs/gitops-promoter/api/v1alpha1 91 | version: v1alpha1 92 | - api: 93 | crdVersion: v1 94 | controller: true 95 | domain: argoproj.io 96 | group: promoter 97 | kind: ClusterScmProvider 98 | path: github.com/argoproj-labs/gitops-promoter/api/v1alpha1 99 | version: v1alpha1 100 | version: "3" 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![codecov](https://codecov.io/gh/argoproj-labs/gitops-promoter/graph/badge.svg?token=Nbye3NDioO)](https://codecov.io/gh/argoproj-labs/gitops-promoter) 2 | 3 | # GitOps Promoter 4 | 5 | GitOps Promoter facilitates environment promotion for config managed via GitOps. 6 | 7 | ## Key Features 8 | 9 | * Drift-free promotion process 10 | * Robust promotion gating system 11 | * Complete integration with git and SCM tooling 12 | * No fragile automated changes to user-facing files 13 | 14 | The main ideas behind the project are explained in ["Space Age GitOps: The Rise of the Humble Pull Request"](https://www.youtube.com/watch?v=p5EPKY3vM-E). 15 | 16 | A live demo is presented in ["Space Age GitOps: Lifting off with Argo Promotions"](https://www.youtube.com/watch?v=2JmLCqM1nTM). 17 | 18 | The promotion gating system is detailed in ["No More Pipelines: Reconciling Environment Promotion Via Commit Statuses"](https://www.youtube.com/watch?v=Usi38ly1pe0). 19 | 20 | ## Example 21 | 22 | ```yaml 23 | apiVersion: promoter.argoproj.io/v1alpha1 24 | kind: PromotionStrategy 25 | metadata: 26 | name: example-promotion-strategy 27 | spec: 28 | gitRepositoryRef: 29 | name: example-git-repo 30 | activeCommitStatuses: 31 | - key: argocd-app-health 32 | proposedCommitStatuses: 33 | - key: security-scan 34 | environments: 35 | - branch: environment/dev 36 | - branch: environment/test 37 | - branch: environment/prod 38 | autoMerge: false 39 | activeCommitStatuses: 40 | - key: performance-test 41 | proposedCommitStatuses: 42 | - key: deployment-freeze 43 | ``` 44 | 45 | ## Getting Started 46 | 47 | The project is currently experimental, please use with caution. See the 48 | [docs site](https://argo-gitops-promoter.readthedocs.io/en/latest/getting-started/) for setup instructions. 49 | -------------------------------------------------------------------------------- /api/v1alpha1/argocdcommitstatus_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // ArgoCDCommitStatusSpec defines the desired state of ArgoCDCommitStatus. 27 | type ArgoCDCommitStatusSpec struct { 28 | // +kubebuilder:validation:Required 29 | PromotionStrategyRef ObjectReference `json:"promotionStrategyRef,omitempty"` 30 | 31 | // +kubebuilder:validation:Required 32 | ApplicationSelector *metav1.LabelSelector `json:"applicationSelector,omitempty"` 33 | } 34 | 35 | // ArgoCDCommitStatusStatus defines the observed state of ArgoCDCommitStatus. 36 | type ArgoCDCommitStatusStatus struct { 37 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 38 | // Important: Run "make" to regenerate code after modifying this file 39 | ApplicationsSelected []ApplicationsSelected `json:"applicationsSelected,omitempty"` 40 | } 41 | 42 | type ApplicationsSelected struct { 43 | Namespace string `json:"namespace"` 44 | Name string `json:"name"` 45 | Phase CommitStatusPhase `json:"phase"` 46 | Sha string `json:"sha"` 47 | // +kubebuilder:validation:Optional 48 | LastTransitionTime *metav1.Time `json:"lastTransitionTime"` 49 | } 50 | 51 | // +kubebuilder:object:root=true 52 | // +kubebuilder:subresource:status 53 | 54 | // ArgoCDCommitStatus is the Schema for the argocdcommitstatuses API. 55 | type ArgoCDCommitStatus struct { 56 | metav1.TypeMeta `json:",inline"` 57 | metav1.ObjectMeta `json:"metadata,omitempty"` 58 | 59 | Spec ArgoCDCommitStatusSpec `json:"spec,omitempty"` 60 | Status ArgoCDCommitStatusStatus `json:"status,omitempty"` 61 | } 62 | 63 | // +kubebuilder:object:root=true 64 | 65 | // ArgoCDCommitStatusList contains a list of ArgoCDCommitStatus. 66 | type ArgoCDCommitStatusList struct { 67 | metav1.TypeMeta `json:",inline"` 68 | metav1.ListMeta `json:"metadata,omitempty"` 69 | Items []ArgoCDCommitStatus `json:"items"` 70 | } 71 | 72 | func init() { 73 | SchemeBuilder.Register(&ArgoCDCommitStatus{}, &ArgoCDCommitStatusList{}) 74 | } 75 | -------------------------------------------------------------------------------- /api/v1alpha1/clusterscmprovider_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 | "reflect" 21 | 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | ) 24 | 25 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 26 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 27 | 28 | var ClusterScmProviderKind = reflect.TypeOf(ClusterScmProvider{}).Name() 29 | 30 | // +kubebuilder:object:root=true 31 | // +kubebuilder:subresource:status 32 | // +kubebuilder:resource:scope=Cluster 33 | 34 | // ClusterScmProvider is the Schema for the clusterscmproviders API. 35 | type ClusterScmProvider struct { 36 | metav1.TypeMeta `json:",inline"` 37 | metav1.ObjectMeta `json:"metadata,omitempty"` 38 | 39 | Spec ScmProviderSpec `json:"spec,omitempty"` 40 | Status ScmProviderStatus `json:"status,omitempty"` 41 | } 42 | 43 | // +kubebuilder:object:root=true 44 | 45 | // ClusterScmProviderList contains a list of ClusterScmProvider. 46 | type ClusterScmProviderList struct { 47 | metav1.TypeMeta `json:",inline"` 48 | metav1.ListMeta `json:"metadata,omitempty"` 49 | Items []ClusterScmProvider `json:"items"` 50 | } 51 | 52 | func init() { 53 | SchemeBuilder.Register(&ClusterScmProvider{}, &ClusterScmProviderList{}) 54 | } 55 | 56 | // +kubebuilder:object:root:false 57 | // +kubebuilder:object:generate:false 58 | var _ GenericScmProvider = &ClusterScmProvider{} 59 | 60 | func (s *ClusterScmProvider) GetSpec() *ScmProviderSpec { 61 | return &s.Spec 62 | } 63 | -------------------------------------------------------------------------------- /api/v1alpha1/commitstatus_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // CommitStatusSpec defines the desired state of CommitStatus 27 | type CommitStatusSpec struct { 28 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 29 | // Important: Run "make" to regenerate code after modifying this file 30 | 31 | // +kubebuilder:validation:Required 32 | RepositoryReference ObjectReference `json:"gitRepositoryRef"` 33 | 34 | // +kubebuilder:validation:Required 35 | Sha string `json:"sha"` 36 | 37 | // +kubebuilder:validation:Required 38 | Name string `json:"name"` 39 | 40 | Description string `json:"description"` 41 | 42 | // +kubebuilder:validation:Required 43 | // +kubebuilder:default:=pending 44 | // +kubebuilder:validation:Enum:=pending;success;failure 45 | Phase CommitStatusPhase `json:"phase"` // pending, success, failure 46 | // (Github: error, failure, pending, success) 47 | // (Gitlab: pending, running, success, failed, canceled) 48 | // (Bitbucket: INPROGRESS, STOPPED, SUCCESSFUL, FAILED) 49 | 50 | // Url is a URL that the user can follow to see more details about the status 51 | Url string `json:"url"` 52 | } 53 | 54 | // CommitStatusStatus defines the observed state of CommitStatus 55 | type CommitStatusStatus struct { 56 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 57 | // Important: Run "make" to regenerate code after modifying this file 58 | ObservedGeneration int64 `json:"observedGeneration"` 59 | // Id is the unique identifier of the commit status, set by the SCM 60 | Id string `json:"id"` 61 | Sha string `json:"sha"` 62 | // +kubebuilder:default:=pending 63 | // +kubebuilder:validation:Enum:=pending;success;failure 64 | Phase CommitStatusPhase `json:"phase"` 65 | } 66 | 67 | //+kubebuilder:object:root=true 68 | //+kubebuilder:subresource:status 69 | 70 | // +kubebuilder:printcolumn:name="Sha",type=string,JSONPath=`.status.sha` 71 | // +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase` 72 | // CommitStatus is the Schema for the commitstatuses API 73 | type CommitStatus struct { 74 | metav1.TypeMeta `json:",inline"` 75 | metav1.ObjectMeta `json:"metadata,omitempty"` 76 | 77 | Spec CommitStatusSpec `json:"spec,omitempty"` 78 | Status CommitStatusStatus `json:"status,omitempty"` 79 | } 80 | 81 | //+kubebuilder:object:root=true 82 | 83 | // CommitStatusList contains a list of CommitStatus 84 | type CommitStatusList struct { 85 | metav1.TypeMeta `json:",inline"` 86 | metav1.ListMeta `json:"metadata,omitempty"` 87 | Items []CommitStatus `json:"items"` 88 | } 89 | 90 | func init() { 91 | SchemeBuilder.Register(&CommitStatus{}, &CommitStatusList{}) 92 | } 93 | 94 | type CommitStatusPhase string 95 | 96 | const ( 97 | CommitPhaseFailure CommitStatusPhase = "failure" 98 | CommitPhaseSuccess CommitStatusPhase = "success" 99 | CommitPhasePending CommitStatusPhase = "pending" 100 | ) 101 | -------------------------------------------------------------------------------- /api/v1alpha1/common_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | type GitHub struct { 4 | // Domain is the GitHub domain, such as "github.mycompany.com". If using the default GitHub domain, leave this field 5 | // empty. 6 | // +kubebuilder:validation:XValidation:rule=`self != "github.com"`, message="Instead of setting the domain to github.com, leave the field blank" 7 | Domain string `json:"domain,omitempty"` 8 | // AppID is the GitHub App ID. 9 | // +kubebuilder:validation:Required 10 | AppID int64 `json:"appID"` 11 | // InstallationID is the GitHub App Installation ID. 12 | // +kubebuilder:validation:Required 13 | InstallationID int64 `json:"installationID"` 14 | } 15 | 16 | type GitLab struct { 17 | Domain string `json:"domain,omitempty"` 18 | } 19 | 20 | type Fake struct { 21 | Domain string `json:"domain,omitempty"` 22 | } 23 | 24 | type ObjectReference struct { 25 | // +kubebuilder:validation:Required 26 | Name string `json:"name"` 27 | } 28 | 29 | type GitHubRepo struct { 30 | // +kubebuilder:validation:Required 31 | Owner string `json:"owner"` 32 | // +kubebuilder:validation:Required 33 | Name string `json:"name"` 34 | } 35 | 36 | type GitLabRepo struct { 37 | // User, group or group with subgroup (e.g. group/subgroup). 38 | // +kubebuilder:validation:Required 39 | // +kubebuilder:validation:Pattern=^[a-zA-Z0-9_\-\/.]+$ 40 | Namespace string `json:"namespace"` 41 | // Project slug of the repository. 42 | // +kubebuilder:validation:Required 43 | // +kubebuilder:validation:Pattern=^[a-zA-Z0-9_\-\/.]+$ 44 | Name string `json:"name"` 45 | // +kubebuilder:validation:Required 46 | ProjectID int `json:"projectId"` 47 | } 48 | 49 | type FakeRepo struct { 50 | // +kubebuilder:validation:Required 51 | Owner string `json:"owner"` 52 | // +kubebuilder:validation:Required 53 | Name string `json:"name"` 54 | } 55 | -------------------------------------------------------------------------------- /api/v1alpha1/constants.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | // CommitStatusLabel is the label used to identify commit statuses, this is used to look up commit statuses configured in the 4 | // PromotionStrategy CR 5 | const CommitStatusLabel = "promoter.argoproj.io/commit-status" 6 | 7 | // CommitStatusCopyLabel is the label used to identify copied commit statuses (true or false) 8 | const CommitStatusCopyLabel = "promoter.argoproj.io/commit-status-copy" 9 | 10 | // PreviousEnvProposedCommitPrefixNameLabel is the prefix name for copied proposed commits 11 | const PreviousEnvProposedCommitPrefixNameLabel = "promoter-previous-env-" 12 | 13 | // CopiedCommitStatusFromLabel is the commit status that we were copied from 14 | const CopiedCommitStatusFromLabel = "promoter.argoproj.io/commit-status-copy-from" 15 | 16 | // CommmitStatusFromShaLabel is the commit status hydrated sha that we were copied from 17 | const CommmitStatusFromShaLabel = "promoter.argoproj.io/commit-status-copy-from-sha" 18 | 19 | // CommitStatusFromBranchLabel the branch/environment that we were copied from 20 | const CommitStatusFromBranchLabel = "promoter.argoproj.io/commit-status-copy-from-branch" 21 | 22 | // PromotionStrategyLabel the promotion strategy which the proposed commit is associated with 23 | const PromotionStrategyLabel = "promoter.argoproj.io/promotion-strategy" 24 | 25 | // EnvironmentLabel the environment branch for the proposed commit 26 | const EnvironmentLabel = "promoter.argoproj.io/environment" 27 | 28 | const ChangeTransferPolicyLabel = "promoter.argoproj.io/change-transfer-policy" 29 | 30 | // PreviousEnvironmentCommitStatusKey the commit status key name used to indicate the previous environment health 31 | const PreviousEnvironmentCommitStatusKey = "promoter-previous-environment" 32 | 33 | // ReconcileAtAnnotation is the annotation used to indicate when the webhook triggered a reconcile 34 | const ReconcileAtAnnotation = "promoter.argoproj.io/reconcile-at" 35 | 36 | // CommitStatusPreviousEnvironmentStatusesAnnotation is the label used to identify commit statuses that make up the aggregated active commit status 37 | const CommitStatusPreviousEnvironmentStatusesAnnotation = "promoter.argoproj.io/previous-environment-statuses" 38 | -------------------------------------------------------------------------------- /api/v1alpha1/controllerconfiguration_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // ControllerConfigurationSpec defines the desired state of ControllerConfiguration. 27 | type ControllerConfigurationSpec struct { 28 | // All fields in this struct should be required. Defaults should be set in manifests, not in code. 29 | 30 | // +required 31 | PullRequest PullRequestConfiguration `json:"pullRequest,omitempty"` 32 | 33 | // How frequently to requeue promotion strategy resources for auto reconciliation. Default: "5m". 34 | // Format is go's time.Duration, e.g. "5m" for 5 minutes. 35 | // +required 36 | PromotionStrategyRequeueDuration metav1.Duration `json:"promotionStrategyRequeueDuration,omitempty"` 37 | 38 | // How frequently to requeue proposed commit resources for auto reconciliation. Default: "5m". 39 | // Format is go's time.Duration, e.g. "5m" for 5 minutes. 40 | // +required 41 | ChangeTransferPolicyRequeueDuration metav1.Duration `json:"changeTransferPolicyRequeueDuration,omitempty"` 42 | 43 | // How frequently to requeue commit status resources for auto reconciliation. Default: "15s". 44 | // Format is go's time.Duration, e.g. "5m" for 5 minutes. 45 | // +required 46 | ArgoCDCommitStatusRequeueDuration metav1.Duration `json:"argocdCommitStatusRequeueDuration,omitempty"` 47 | 48 | // How frequently to requeue pull request resources for auto reconciliation. Default: "5m". 49 | // Format is go's time.Duration, e.g. "5m" for 5 minutes. 50 | // +required 51 | PullRequestRequeueDuration metav1.Duration `json:"pullRequestRequeueDuration,omitempty"` 52 | } 53 | 54 | type PullRequestConfiguration struct { 55 | // +required 56 | Template PullRequestTemplate `json:"template,omitempty"` 57 | } 58 | 59 | type PullRequestTemplate struct { 60 | // Template used to generate the title of the pull request. 61 | // Uses Go template syntax and Sprig functions are available. 62 | // +required 63 | Title string `json:"title,omitempty"` 64 | // Template used to generate the description of the pull request. 65 | // Uses Go template syntax and Sprig functions are available. 66 | // +required 67 | Description string `json:"description,omitempty"` 68 | } 69 | 70 | // ControllerConfigurationStatus defines the observed state of ControllerConfiguration. 71 | type ControllerConfigurationStatus struct { 72 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 73 | // Important: Run "make" to regenerate code after modifying this file 74 | } 75 | 76 | // +kubebuilder:object:root=true 77 | // +kubebuilder:subresource:status 78 | 79 | // ControllerConfiguration is the Schema for the controllerconfigurations API. 80 | type ControllerConfiguration struct { 81 | metav1.TypeMeta `json:",inline"` 82 | metav1.ObjectMeta `json:"metadata,omitempty"` 83 | 84 | Spec ControllerConfigurationSpec `json:"spec,omitempty"` 85 | Status ControllerConfigurationStatus `json:"status,omitempty"` 86 | } 87 | 88 | // +kubebuilder:object:root=true 89 | 90 | // ControllerConfigurationList contains a list of ControllerConfiguration. 91 | type ControllerConfigurationList struct { 92 | metav1.TypeMeta `json:",inline"` 93 | metav1.ListMeta `json:"metadata,omitempty"` 94 | Items []ControllerConfiguration `json:"items"` 95 | } 96 | 97 | func init() { 98 | SchemeBuilder.Register(&ControllerConfiguration{}, &ControllerConfigurationList{}) 99 | } 100 | -------------------------------------------------------------------------------- /api/v1alpha1/gitrepository_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // GitRepositorySpec defines the desired state of GitRepository 27 | type GitRepositorySpec struct { 28 | GitHub *GitHubRepo `json:"github,omitempty"` 29 | GitLab *GitLabRepo `json:"gitlab,omitempty"` 30 | Fake *FakeRepo `json:"fake,omitempty"` 31 | // +kubebuilder:validation:Required 32 | ScmProviderRef ScmProviderObjectReference `json:"scmProviderRef"` 33 | } 34 | 35 | type ScmProviderObjectReference struct { 36 | // Kind is the type of resource being referenced 37 | // +kubebuilder:validation:Required 38 | // +kubebuilder:default:=ScmProvider 39 | // +kubebuilder:validation:Enum:=ScmProvider;ClusterScmProvider 40 | Kind string `json:"kind"` 41 | // +kubebuilder:validation:Required 42 | Name string `json:"name"` 43 | } 44 | 45 | // GitRepositoryStatus defines the observed state of GitRepository 46 | type GitRepositoryStatus struct { 47 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 48 | // Important: Run "make" to regenerate code after modifying this file 49 | } 50 | 51 | //+kubebuilder:object:root=true 52 | //+kubebuilder:subresource:status 53 | 54 | // GitRepository is the Schema for the gitrepositories API 55 | type GitRepository struct { 56 | metav1.TypeMeta `json:",inline"` 57 | metav1.ObjectMeta `json:"metadata,omitempty"` 58 | 59 | Spec GitRepositorySpec `json:"spec,omitempty"` 60 | Status GitRepositoryStatus `json:"status,omitempty"` 61 | } 62 | 63 | //+kubebuilder:object:root=true 64 | 65 | // GitRepositoryList contains a list of GitRepository 66 | type GitRepositoryList struct { 67 | metav1.TypeMeta `json:",inline"` 68 | metav1.ListMeta `json:"metadata,omitempty"` 69 | Items []GitRepository `json:"items"` 70 | } 71 | 72 | func init() { 73 | SchemeBuilder.Register(&GitRepository{}, &GitRepositoryList{}) 74 | } 75 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 promoter v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=promoter.argoproj.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: "promoter.argoproj.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/pullrequest_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // PullRequestSpec defines the desired state of PullRequest 27 | type PullRequestSpec struct { 28 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 29 | // Important: Run "make" to regenerate code after modifying this file 30 | 31 | // RepositoryReference what repository to open the PR on. 32 | // +kubebuilder:validation:Required 33 | RepositoryReference ObjectReference `json:"gitRepositoryRef"` 34 | // Title is the title of the pull request. 35 | // +kubebuilder:validation:Required 36 | Title string `json:"title"` 37 | // Head the git reference we are merging from Head ---> Base 38 | // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" 39 | // +kubebuilder:validation:Required 40 | TargetBranch string `json:"targetBranch"` 41 | // Base the git reference that we are merging into Head ---> Base 42 | // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" 43 | // +kubebuilder:validation:Required 44 | SourceBranch string `json:"sourceBranch"` 45 | // Body the description body of the pull/merge request 46 | Description string `json:"description,omitempty"` 47 | // State of the merge request closed/merged/open 48 | // +kubebuilder:default:=open 49 | // +kubebuilder:validation:Required 50 | // +kubebuilder:validation:Enum=closed;merged;open 51 | State PullRequestState `json:"state"` 52 | } 53 | 54 | // PullRequestStatus defines the observed state of PullRequest 55 | type PullRequestStatus struct { 56 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 57 | // Important: Run "make" to regenerate code after modifying this file 58 | 59 | // ObservedGeneration the generation observed by the controller 60 | ObservedGeneration int64 `json:"observedGeneration"` 61 | 62 | // ID the id of the pull request 63 | ID string `json:"id,omitempty"` 64 | // State of the merge request closed/merged/open 65 | // +kubebuilder:validation:Enum="";closed;merged;open 66 | State PullRequestState `json:"state,omitempty"` 67 | // PRCreationTime the time the PR was created 68 | PRCreationTime metav1.Time `json:"prCreationTime,omitempty"` 69 | } 70 | 71 | //+kubebuilder:object:root=true 72 | //+kubebuilder:subresource:status 73 | 74 | // PullRequest is the Schema for the pullrequests API 75 | // +kubebuilder:printcolumn:name="ID",type=string,JSONPath=`.status.id` 76 | // +kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.state` 77 | type PullRequest struct { 78 | metav1.TypeMeta `json:",inline"` 79 | metav1.ObjectMeta `json:"metadata,omitempty"` 80 | 81 | Spec PullRequestSpec `json:"spec,omitempty"` 82 | Status PullRequestStatus `json:"status,omitempty"` 83 | } 84 | 85 | //+kubebuilder:object:root=true 86 | 87 | // PullRequestList contains a list of PullRequest 88 | type PullRequestList struct { 89 | metav1.TypeMeta `json:",inline"` 90 | metav1.ListMeta `json:"metadata,omitempty"` 91 | Items []PullRequest `json:"items"` 92 | } 93 | 94 | func init() { 95 | SchemeBuilder.Register(&PullRequest{}, &PullRequestList{}) 96 | } 97 | 98 | type PullRequestState string 99 | 100 | const ( 101 | PullRequestClosed PullRequestState = "closed" 102 | PullRequestOpen PullRequestState = "open" 103 | PullRequestMerged PullRequestState = "merged" 104 | ) 105 | -------------------------------------------------------------------------------- /api/v1alpha1/revertcommit_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // RevertCommitSpec defines the desired state of RevertCommit 27 | type RevertCommitSpec struct { 28 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 29 | // Important: Run "make" to regenerate code after modifying this file 30 | 31 | // Foo is an example field of RevertCommit. Edit revertcommit_types.go to remove/update 32 | Foo string `json:"foo,omitempty"` 33 | } 34 | 35 | // RevertCommitStatus defines the observed state of RevertCommit 36 | type RevertCommitStatus struct { 37 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 38 | // Important: Run "make" to regenerate code after modifying this file 39 | } 40 | 41 | //+kubebuilder:object:root=true 42 | //+kubebuilder:subresource:status 43 | 44 | // RevertCommit is the Schema for the revertcommits API 45 | type RevertCommit struct { 46 | metav1.TypeMeta `json:",inline"` 47 | metav1.ObjectMeta `json:"metadata,omitempty"` 48 | 49 | Spec RevertCommitSpec `json:"spec,omitempty"` 50 | Status RevertCommitStatus `json:"status,omitempty"` 51 | } 52 | 53 | //+kubebuilder:object:root=true 54 | 55 | // RevertCommitList contains a list of RevertCommit 56 | type RevertCommitList struct { 57 | metav1.TypeMeta `json:",inline"` 58 | metav1.ListMeta `json:"metadata,omitempty"` 59 | Items []RevertCommit `json:"items"` 60 | } 61 | 62 | func init() { 63 | SchemeBuilder.Register(&RevertCommit{}, &RevertCommitList{}) 64 | } 65 | -------------------------------------------------------------------------------- /api/v1alpha1/scmprovider_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 | "reflect" 21 | 22 | v1 "k8s.io/api/core/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 28 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 29 | 30 | var ScmProviderKind = reflect.TypeOf(ScmProvider{}).Name() 31 | 32 | // ScmProviderSpec defines the desired state of ScmProvider 33 | type ScmProviderSpec struct { 34 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 35 | // Important: Run "make" to regenerate code after modifying this file 36 | 37 | // SecretRef contains the credentials required to auth to a specific provider 38 | SecretRef *v1.LocalObjectReference `json:"secretRef,omitempty"` 39 | 40 | // GitHub required configuration for GitHub as the SCM provider 41 | GitHub *GitHub `json:"github,omitempty"` 42 | 43 | // GitLab required configuration for GitLab as the SCM provider 44 | GitLab *GitLab `json:"gitlab,omitempty"` 45 | 46 | // Fake required configuration for Fake as the SCM provider 47 | Fake *Fake `json:"fake,omitempty"` 48 | } 49 | 50 | // ScmProviderStatus defines the observed state of ScmProvider 51 | type ScmProviderStatus struct { 52 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 53 | // Important: Run "make" to regenerate code after modifying this file 54 | } 55 | 56 | //+kubebuilder:object:root=true 57 | //+kubebuilder:subresource:status 58 | 59 | // ScmProvider is the Schema for the scmproviders API 60 | type ScmProvider struct { 61 | metav1.TypeMeta `json:",inline"` 62 | metav1.ObjectMeta `json:"metadata,omitempty"` 63 | 64 | Spec ScmProviderSpec `json:"spec,omitempty"` 65 | Status ScmProviderStatus `json:"status,omitempty"` 66 | } 67 | 68 | //+kubebuilder:object:root=true 69 | 70 | // ScmProviderList contains a list of ScmProvider 71 | type ScmProviderList struct { 72 | metav1.TypeMeta `json:",inline"` 73 | metav1.ListMeta `json:"metadata,omitempty"` 74 | Items []ScmProvider `json:"items"` 75 | } 76 | 77 | func init() { 78 | SchemeBuilder.Register(&ScmProvider{}, &ScmProviderList{}) 79 | } 80 | 81 | // +kubebuilder:object:root=false 82 | // +kubebuilder:object:generate:false 83 | // +k8s:deepcopy-gen:interfaces=nil 84 | // +k8s:deepcopy-gen=nil 85 | 86 | // GenericScmProvider is a common interface for interacting with either cluster-scoped ClusterScmProvider 87 | // or namespaced ScmProviders. 88 | type GenericScmProvider interface { 89 | runtime.Object 90 | metav1.Object 91 | GetSpec() *ScmProviderSpec 92 | } 93 | 94 | // +kubebuilder:object:root:false 95 | // +kubebuilder:object:generate:false 96 | var _ GenericScmProvider = &ScmProvider{} 97 | 98 | func (s *ScmProvider) GetSpec() *ScmProviderSpec { 99 | return &s.Spec 100 | } 101 | -------------------------------------------------------------------------------- /config/config/controllerconfiguration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: ControllerConfiguration 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/part-of: promoter 7 | app.kubernetes.io/managed-by: kustomize 8 | name: controller-configuration 9 | spec: 10 | pullRequest: 11 | template: 12 | title: "Promote {{ trunc 5 .ChangeTransferPolicy.Status.Proposed.Dry.Sha }} to `{{ .ChangeTransferPolicy.Spec.ActiveBranch }}`" 13 | description: "This PR is promoting the environment branch `{{ .ChangeTransferPolicy.Spec.ActiveBranch }}` which is currently on dry sha {{ .ChangeTransferPolicy.Status.Active.Dry.Sha }} to dry sha {{ .ChangeTransferPolicy.Status.Proposed.Dry.Sha }}." 14 | promotionStrategyRequeueDuration: "5m" 15 | changeTransferPolicyRequeueDuration: "5m" 16 | argocdCommitStatusRequeueDuration: "5m" 17 | pullRequestRequeueDuration: "5m" 18 | -------------------------------------------------------------------------------- /config/config/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - controllerconfiguration.yaml 6 | -------------------------------------------------------------------------------- /config/crd/bases/promoter.argoproj.io_commitstatuses.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.16.3 7 | name: commitstatuses.promoter.argoproj.io 8 | spec: 9 | group: promoter.argoproj.io 10 | names: 11 | kind: CommitStatus 12 | listKind: CommitStatusList 13 | plural: commitstatuses 14 | singular: commitstatus 15 | scope: Namespaced 16 | versions: 17 | - additionalPrinterColumns: 18 | - jsonPath: .status.sha 19 | name: Sha 20 | type: string 21 | - jsonPath: .status.phase 22 | name: Phase 23 | type: string 24 | name: v1alpha1 25 | schema: 26 | openAPIV3Schema: 27 | description: CommitStatus is the Schema for the commitstatuses API 28 | properties: 29 | apiVersion: 30 | description: |- 31 | APIVersion defines the versioned schema of this representation of an object. 32 | Servers should convert recognized schemas to the latest internal value, and 33 | may reject unrecognized values. 34 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 35 | type: string 36 | kind: 37 | description: |- 38 | Kind is a string value representing the REST resource this object represents. 39 | Servers may infer this from the endpoint the client submits requests to. 40 | Cannot be updated. 41 | In CamelCase. 42 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 43 | type: string 44 | metadata: 45 | type: object 46 | spec: 47 | description: CommitStatusSpec defines the desired state of CommitStatus 48 | properties: 49 | description: 50 | type: string 51 | gitRepositoryRef: 52 | properties: 53 | name: 54 | type: string 55 | required: 56 | - name 57 | type: object 58 | name: 59 | type: string 60 | phase: 61 | default: pending 62 | enum: 63 | - pending 64 | - success 65 | - failure 66 | type: string 67 | sha: 68 | type: string 69 | url: 70 | description: Url is a URL that the user can follow to see more details 71 | about the status 72 | type: string 73 | required: 74 | - description 75 | - gitRepositoryRef 76 | - name 77 | - phase 78 | - sha 79 | - url 80 | type: object 81 | status: 82 | description: CommitStatusStatus defines the observed state of CommitStatus 83 | properties: 84 | id: 85 | description: Id is the unique identifier of the commit status, set 86 | by the SCM 87 | type: string 88 | observedGeneration: 89 | description: |- 90 | INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 91 | Important: Run "make" to regenerate code after modifying this file 92 | format: int64 93 | type: integer 94 | phase: 95 | default: pending 96 | enum: 97 | - pending 98 | - success 99 | - failure 100 | type: string 101 | sha: 102 | type: string 103 | required: 104 | - id 105 | - observedGeneration 106 | - phase 107 | - sha 108 | type: object 109 | type: object 110 | served: true 111 | storage: true 112 | subresources: 113 | status: {} 114 | -------------------------------------------------------------------------------- /config/crd/bases/promoter.argoproj.io_gitrepositories.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.16.3 7 | name: gitrepositories.promoter.argoproj.io 8 | spec: 9 | group: promoter.argoproj.io 10 | names: 11 | kind: GitRepository 12 | listKind: GitRepositoryList 13 | plural: gitrepositories 14 | singular: gitrepository 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: GitRepository is the Schema for the gitrepositories API 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: GitRepositorySpec defines the desired state of GitRepository 41 | properties: 42 | fake: 43 | properties: 44 | name: 45 | type: string 46 | owner: 47 | type: string 48 | required: 49 | - name 50 | - owner 51 | type: object 52 | github: 53 | properties: 54 | name: 55 | type: string 56 | owner: 57 | type: string 58 | required: 59 | - name 60 | - owner 61 | type: object 62 | gitlab: 63 | properties: 64 | name: 65 | description: Project slug of the repository. 66 | pattern: ^[a-zA-Z0-9_\-\/.]+$ 67 | type: string 68 | namespace: 69 | description: User, group or group with subgroup (e.g. group/subgroup). 70 | pattern: ^[a-zA-Z0-9_\-\/.]+$ 71 | type: string 72 | projectId: 73 | type: integer 74 | required: 75 | - name 76 | - namespace 77 | - projectId 78 | type: object 79 | scmProviderRef: 80 | properties: 81 | kind: 82 | default: ScmProvider 83 | description: Kind is the type of resource being referenced 84 | enum: 85 | - ScmProvider 86 | - ClusterScmProvider 87 | type: string 88 | name: 89 | type: string 90 | required: 91 | - kind 92 | - name 93 | type: object 94 | required: 95 | - scmProviderRef 96 | type: object 97 | status: 98 | description: GitRepositoryStatus defines the observed state of GitRepository 99 | type: object 100 | type: object 101 | served: true 102 | storage: true 103 | subresources: 104 | status: {} 105 | -------------------------------------------------------------------------------- /config/crd/bases/promoter.argoproj.io_revertcommits.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.16.3 7 | name: revertcommits.promoter.argoproj.io 8 | spec: 9 | group: promoter.argoproj.io 10 | names: 11 | kind: RevertCommit 12 | listKind: RevertCommitList 13 | plural: revertcommits 14 | singular: revertcommit 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: RevertCommit is the Schema for the revertcommits API 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: RevertCommitSpec defines the desired state of RevertCommit 41 | properties: 42 | foo: 43 | description: Foo is an example field of RevertCommit. Edit revertcommit_types.go 44 | to remove/update 45 | type: string 46 | type: object 47 | status: 48 | description: RevertCommitStatus defines the observed state of RevertCommit 49 | type: object 50 | type: object 51 | served: true 52 | storage: true 53 | subresources: 54 | status: {} 55 | -------------------------------------------------------------------------------- /config/crd/bases/promoter.argoproj.io_scmproviders.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.16.3 7 | name: scmproviders.promoter.argoproj.io 8 | spec: 9 | group: promoter.argoproj.io 10 | names: 11 | kind: ScmProvider 12 | listKind: ScmProviderList 13 | plural: scmproviders 14 | singular: scmprovider 15 | scope: Namespaced 16 | versions: 17 | - name: v1alpha1 18 | schema: 19 | openAPIV3Schema: 20 | description: ScmProvider is the Schema for the scmproviders API 21 | properties: 22 | apiVersion: 23 | description: |- 24 | APIVersion defines the versioned schema of this representation of an object. 25 | Servers should convert recognized schemas to the latest internal value, and 26 | may reject unrecognized values. 27 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 28 | type: string 29 | kind: 30 | description: |- 31 | Kind is a string value representing the REST resource this object represents. 32 | Servers may infer this from the endpoint the client submits requests to. 33 | Cannot be updated. 34 | In CamelCase. 35 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 36 | type: string 37 | metadata: 38 | type: object 39 | spec: 40 | description: ScmProviderSpec defines the desired state of ScmProvider 41 | properties: 42 | fake: 43 | description: Fake required configuration for Fake as the SCM provider 44 | properties: 45 | domain: 46 | type: string 47 | type: object 48 | github: 49 | description: GitHub required configuration for GitHub as the SCM provider 50 | properties: 51 | appID: 52 | description: AppID is the GitHub App ID. 53 | format: int64 54 | type: integer 55 | domain: 56 | description: |- 57 | Domain is the GitHub domain, such as "github.mycompany.com". If using the default GitHub domain, leave this field 58 | empty. 59 | type: string 60 | x-kubernetes-validations: 61 | - message: Instead of setting the domain to github.com, leave 62 | the field blank 63 | rule: self != "github.com" 64 | installationID: 65 | description: InstallationID is the GitHub App Installation ID. 66 | format: int64 67 | type: integer 68 | required: 69 | - appID 70 | - installationID 71 | type: object 72 | gitlab: 73 | description: GitLab required configuration for GitLab as the SCM provider 74 | properties: 75 | domain: 76 | type: string 77 | type: object 78 | secretRef: 79 | description: SecretRef contains the credentials required to auth to 80 | a specific provider 81 | properties: 82 | name: 83 | default: "" 84 | description: |- 85 | Name of the referent. 86 | This field is effectively required, but due to backwards compatibility is 87 | allowed to be empty. Instances of this type with an empty value here are 88 | almost certainly wrong. 89 | More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names 90 | type: string 91 | type: object 92 | x-kubernetes-map-type: atomic 93 | type: object 94 | status: 95 | description: ScmProviderStatus defines the observed state of ScmProvider 96 | type: object 97 | type: object 98 | served: true 99 | storage: true 100 | subresources: 101 | status: {} 102 | -------------------------------------------------------------------------------- /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/promoter.argoproj.io_pullrequests.yaml 6 | - bases/promoter.argoproj.io_commitstatuses.yaml 7 | - bases/promoter.argoproj.io_revertcommits.yaml 8 | - bases/promoter.argoproj.io_promotionstrategies.yaml 9 | - bases/promoter.argoproj.io_scmproviders.yaml 10 | - bases/promoter.argoproj.io_gitrepositories.yaml 11 | - bases/promoter.argoproj.io_changetransferpolicies.yaml 12 | - bases/promoter.argoproj.io_argocdcommitstatuses.yaml 13 | - bases/promoter.argoproj.io_controllerconfigurations.yaml 14 | - bases/promoter.argoproj.io_clusterscmproviders.yaml 15 | #+kubebuilder:scaffold:crdkustomizeresource 16 | 17 | patches: 18 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 19 | # patches here are for enabling the conversion webhook for each CRD 20 | #- path: patches/webhook_in_pullrequests.yaml 21 | #- path: patches/webhook_in_commitstatuses.yaml 22 | #- path: patches/webhook_in_revertcommits.yaml 23 | #- path: patches/webhook_in_promotionstrategies.yaml 24 | #- path: patches/webhook_in_scmproviders.yaml 25 | #- path: patches/webhook_in_gitrepositories.yaml 26 | #- path: patches/webhook_in_changetransferpolicies.yaml 27 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 28 | 29 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 30 | # patches here are for enabling the CA injection for each CRD 31 | #- path: patches/cainjection_in_pullrequests.yaml 32 | #- path: patches/cainjection_in_commitstatuses.yaml 33 | #- path: patches/cainjection_in_revertcommits.yaml 34 | #- path: patches/cainjection_in_promotionstrategies.yaml 35 | #- path: patches/cainjection_in_scmproviders.yaml 36 | #- path: patches/cainjection_in_gitrepositories.yaml 37 | #- path: patches/cainjection_in_changetransferpolicies.yaml 38 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 39 | 40 | # [WEBHOOK] To enable webhook, uncomment the following section 41 | # the following config is for teaching kustomize how to do kustomization for CRDs. 42 | 43 | #configurations: 44 | #- kustomizeconfig.yaml 45 | -------------------------------------------------------------------------------- /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/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 | securityContext: 14 | allowPrivilegeEscalation: false 15 | capabilities: 16 | drop: 17 | - "ALL" 18 | image: quay.io/brancz/kube-rbac-proxy:v0.17.0 19 | args: 20 | - "--secure-listen-address=0.0.0.0:8443" 21 | - "--upstream=http://127.0.0.1:8080/" 22 | - "--logtostderr=true" 23 | - "--v=0" 24 | ports: 25 | - containerPort: 8443 26 | protocol: TCP 27 | name: https 28 | resources: 29 | limits: 30 | cpu: 500m 31 | memory: 128Mi 32 | requests: 33 | cpu: 5m 34 | memory: 64Mi 35 | - name: manager 36 | args: 37 | - "--health-probe-bind-address=:8081" 38 | - "--metrics-bind-address=127.0.0.1:8080" 39 | - "--leader-elect" 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/manager/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | labels: 7 | control-plane: controller-manager 8 | app.kubernetes.io/name: deployment 9 | app.kubernetes.io/instance: controller-manager 10 | app.kubernetes.io/component: manager 11 | app.kubernetes.io/created-by: promoter 12 | app.kubernetes.io/part-of: promoter 13 | app.kubernetes.io/managed-by: kustomize 14 | spec: 15 | selector: 16 | matchLabels: 17 | control-plane: controller-manager 18 | replicas: 1 19 | template: 20 | metadata: 21 | annotations: 22 | kubectl.kubernetes.io/default-container: manager 23 | labels: 24 | control-plane: controller-manager 25 | spec: 26 | securityContext: 27 | runAsNonRoot: true 28 | seccompProfile: 29 | type: RuntimeDefault 30 | containers: 31 | - command: 32 | - /gitops-promoter 33 | args: 34 | - --leader-elect 35 | image: quay.io/argoprojlabs/gitops-promoter:latest 36 | name: manager 37 | securityContext: 38 | allowPrivilegeEscalation: false 39 | capabilities: 40 | drop: 41 | - 'ALL' 42 | livenessProbe: 43 | httpGet: 44 | path: /healthz 45 | port: 8081 46 | initialDelaySeconds: 15 47 | periodSeconds: 20 48 | readinessProbe: 49 | httpGet: 50 | path: /readyz 51 | port: 8081 52 | initialDelaySeconds: 5 53 | periodSeconds: 10 54 | # TODO(user): Configure the resources accordingly based on the project requirements. 55 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 56 | resources: 57 | limits: 58 | cpu: 500m 59 | memory: 128Mi 60 | requests: 61 | cpu: 10m 62 | memory: 64Mi 63 | serviceAccountName: controller-manager 64 | terminationGracePeriodSeconds: 10 65 | affinity: 66 | podAntiAffinity: 67 | preferredDuringSchedulingIgnoredDuringExecution: 68 | - weight: 100 69 | podAffinityTerm: 70 | labelSelector: 71 | matchLabels: 72 | app.kubernetes.io/component: manager 73 | topologyKey: kubernetes.io/hostname 74 | - weight: 5 75 | podAffinityTerm: 76 | labelSelector: 77 | matchLabels: 78 | app.kubernetes.io/name: deployment 79 | topologyKey: kubernetes.io/hostname 80 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - namespace.yaml 6 | - deployment.yaml 7 | - webhook_service.yaml 8 | -------------------------------------------------------------------------------- /config/manager/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: namespace 7 | app.kubernetes.io/instance: system 8 | app.kubernetes.io/component: manager 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: system 13 | -------------------------------------------------------------------------------- /config/manager/webhook_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: webhook-receiver 5 | spec: 6 | selector: 7 | control-plane: controller-manager 8 | ports: 9 | - protocol: TCP 10 | port: 3333 11 | targetPort: 3333 -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | # Prometheus Monitor Service (Metrics) 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | labels: 6 | control-plane: controller-manager 7 | app.kubernetes.io/name: servicemonitor 8 | app.kubernetes.io/instance: controller-manager-metrics-monitor 9 | app.kubernetes.io/component: metrics 10 | app.kubernetes.io/created-by: promoter 11 | app.kubernetes.io/part-of: promoter 12 | app.kubernetes.io/managed-by: kustomize 13 | name: controller-manager-metrics-monitor 14 | namespace: system 15 | spec: 16 | endpoints: 17 | - path: /metrics 18 | port: https 19 | scheme: https 20 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 21 | tlsConfig: 22 | insecureSkipVerify: true 23 | selector: 24 | matchLabels: 25 | control-plane: controller-manager 26 | -------------------------------------------------------------------------------- /config/rbac/argocdcommitstatus_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit argocdcommitstatuses. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: promoter 7 | app.kubernetes.io/managed-by: kustomize 8 | name: argocdcommitstatus-editor-role 9 | rules: 10 | - apiGroups: 11 | - promoter.argoproj.io 12 | resources: 13 | - argocdcommitstatuses 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - promoter.argoproj.io 24 | resources: 25 | - argocdcommitstatuses/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/argocdcommitstatus_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view argocdcommitstatuses. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: promoter 7 | app.kubernetes.io/managed-by: kustomize 8 | name: argocdcommitstatus-viewer-role 9 | rules: 10 | - apiGroups: 11 | - promoter.argoproj.io 12 | resources: 13 | - argocdcommitstatuses 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - promoter.argoproj.io 20 | resources: 21 | - argocdcommitstatuses/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: metrics-reader 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: promoter 9 | app.kubernetes.io/part-of: promoter 10 | app.kubernetes.io/managed-by: kustomize 11 | name: metrics-reader 12 | rules: 13 | - nonResourceURLs: 14 | - "/metrics" 15 | verbs: 16 | - get 17 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrole 6 | app.kubernetes.io/instance: proxy-role 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: promoter 9 | app.kubernetes.io/part-of: promoter 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-role 12 | rules: 13 | - apiGroups: 14 | - authentication.k8s.io 15 | resources: 16 | - tokenreviews 17 | verbs: 18 | - create 19 | - apiGroups: 20 | - authorization.k8s.io 21 | resources: 22 | - subjectaccessreviews 23 | verbs: 24 | - create 25 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: proxy-rolebinding 7 | app.kubernetes.io/component: kube-rbac-proxy 8 | app.kubernetes.io/created-by: promoter 9 | app.kubernetes.io/part-of: promoter 10 | app.kubernetes.io/managed-by: kustomize 11 | name: proxy-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: proxy-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: controller-manager-metrics-service 8 | app.kubernetes.io/component: kube-rbac-proxy 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: controller-manager-metrics-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - name: https 17 | port: 8443 18 | protocol: TCP 19 | targetPort: https 20 | selector: 21 | control-plane: controller-manager 22 | -------------------------------------------------------------------------------- /config/rbac/changetransferpolicy_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit changetransferpolicies. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: changetransferpolicy-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: changetransferpolicy-editor-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - changetransferpolicies 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - promoter.argoproj.io 28 | resources: 29 | - changetransferpolicies/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/changetransferpolicy_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view changetransferpolicies. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: changetransferpolicy-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: changetransferpolicy-viewer-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - changetransferpolicies 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - promoter.argoproj.io 24 | resources: 25 | - changetransferpolicies/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/clusterscmprovider_admin_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project promoter itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants full permissions ('*') over promoter.argoproj.io. 5 | # This role is intended for users authorized to modify roles and bindings within the cluster, 6 | # enabling them to delegate specific permissions to other users or groups as needed. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: promoter 13 | app.kubernetes.io/managed-by: kustomize 14 | name: clusterscmprovider-admin-role 15 | rules: 16 | - apiGroups: 17 | - promoter.argoproj.io 18 | resources: 19 | - clusterscmproviders 20 | verbs: 21 | - '*' 22 | - apiGroups: 23 | - promoter.argoproj.io 24 | resources: 25 | - clusterscmproviders/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/clusterscmprovider_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project promoter itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants permissions to create, update, and delete resources within the promoter.argoproj.io. 5 | # This role is intended for users who need to manage these resources 6 | # but should not control RBAC or manage permissions for others. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: promoter 13 | app.kubernetes.io/managed-by: kustomize 14 | name: clusterscmprovider-editor-role 15 | rules: 16 | - apiGroups: 17 | - promoter.argoproj.io 18 | resources: 19 | - clusterscmproviders 20 | verbs: 21 | - create 22 | - delete 23 | - get 24 | - list 25 | - patch 26 | - update 27 | - watch 28 | - apiGroups: 29 | - promoter.argoproj.io 30 | resources: 31 | - clusterscmproviders/status 32 | verbs: 33 | - get 34 | -------------------------------------------------------------------------------- /config/rbac/clusterscmprovider_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project promoter itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants read-only access to promoter.argoproj.io resources. 5 | # This role is intended for users who need visibility into these resources 6 | # without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: promoter 13 | app.kubernetes.io/managed-by: kustomize 14 | name: clusterscmprovider-viewer-role 15 | rules: 16 | - apiGroups: 17 | - promoter.argoproj.io 18 | resources: 19 | - clusterscmproviders 20 | verbs: 21 | - get 22 | - list 23 | - watch 24 | - apiGroups: 25 | - promoter.argoproj.io 26 | resources: 27 | - clusterscmproviders/status 28 | verbs: 29 | - get 30 | -------------------------------------------------------------------------------- /config/rbac/commitstatus_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit commitstatuses. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: commitstatus-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: commitstatus-editor-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - commitstatuses 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - promoter.argoproj.io 28 | resources: 29 | - commitstatuses/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/commitstatus_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view commitstatuses. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: commitstatus-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: commitstatus-viewer-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - commitstatuses 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - promoter.argoproj.io 24 | resources: 25 | - commitstatuses/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/controllerconfiguration_admin_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project promoter itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants full permissions ('*') over promoter.argoproj.io. 5 | # This role is intended for users authorized to modify roles and bindings within the cluster, 6 | # enabling them to delegate specific permissions to other users or groups as needed. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: promoter 13 | app.kubernetes.io/managed-by: kustomize 14 | name: controllerconfiguration-admin-role 15 | rules: 16 | - apiGroups: 17 | - promoter.argoproj.io 18 | resources: 19 | - controllerconfigurations 20 | verbs: 21 | - '*' 22 | - apiGroups: 23 | - promoter.argoproj.io 24 | resources: 25 | - controllerconfigurations/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/controllerconfiguration_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project promoter itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants permissions to create, update, and delete resources within the promoter.argoproj.io. 5 | # This role is intended for users who need to manage these resources 6 | # but should not control RBAC or manage permissions for others. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: promoter 13 | app.kubernetes.io/managed-by: kustomize 14 | name: controllerconfiguration-editor-role 15 | rules: 16 | - apiGroups: 17 | - promoter.argoproj.io 18 | resources: 19 | - controllerconfigurations 20 | verbs: 21 | - create 22 | - delete 23 | - get 24 | - list 25 | - patch 26 | - update 27 | - watch 28 | - apiGroups: 29 | - promoter.argoproj.io 30 | resources: 31 | - controllerconfigurations/status 32 | verbs: 33 | - get 34 | -------------------------------------------------------------------------------- /config/rbac/controllerconfiguration_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # This rule is not used by the project promoter itself. 2 | # It is provided to allow the cluster admin to help manage permissions for users. 3 | # 4 | # Grants read-only access to promoter.argoproj.io resources. 5 | # This role is intended for users who need visibility into these resources 6 | # without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. 7 | 8 | apiVersion: rbac.authorization.k8s.io/v1 9 | kind: ClusterRole 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: promoter 13 | app.kubernetes.io/managed-by: kustomize 14 | name: controllerconfiguration-viewer-role 15 | rules: 16 | - apiGroups: 17 | - promoter.argoproj.io 18 | resources: 19 | - controllerconfigurations 20 | verbs: 21 | - get 22 | - list 23 | - watch 24 | - apiGroups: 25 | - promoter.argoproj.io 26 | resources: 27 | - controllerconfigurations/status 28 | verbs: 29 | - get 30 | -------------------------------------------------------------------------------- /config/rbac/gitrepository_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit gitrepositories. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: gitrepository-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: gitrepository-editor-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - gitrepositories 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - promoter.argoproj.io 28 | resources: 29 | - gitrepositories/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/gitrepository_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view gitrepositories. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: gitrepository-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: gitrepository-viewer-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - gitrepositories 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - promoter.argoproj.io 24 | resources: 25 | - gitrepositories/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /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 | # For each CRD, "Editor" and "Viewer" roles are scaffolded by 20 | # default, aiding admins in cluster management. Those roles are 21 | # not used by the Project itself. You can comment the following lines 22 | # if you do not want those helpers be installed with your Project. 23 | - argocdcommitstatus_editor_role.yaml 24 | - argocdcommitstatus_viewer_role.yaml 25 | 26 | # For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by 27 | # default, aiding admins in cluster management. Those roles are 28 | # not used by the {{ .ProjectName }} itself. You can comment the following lines 29 | # if you do not want those helpers be installed with your Project. 30 | - clusterscmprovider_admin_role.yaml 31 | - clusterscmprovider_editor_role.yaml 32 | - clusterscmprovider_viewer_role.yaml 33 | - controllerconfiguration_admin_role.yaml 34 | - controllerconfiguration_editor_role.yaml 35 | - controllerconfiguration_viewer_role.yaml 36 | -------------------------------------------------------------------------------- /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 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/instance: leader-election-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: leader-election-role 13 | rules: 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - configmaps 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - create 23 | - update 24 | - patch 25 | - delete 26 | - apiGroups: 27 | - coordination.k8s.io 28 | resources: 29 | - leases 30 | verbs: 31 | - get 32 | - list 33 | - watch 34 | - create 35 | - update 36 | - patch 37 | - delete 38 | - apiGroups: 39 | - "" 40 | resources: 41 | - events 42 | verbs: 43 | - create 44 | - patch 45 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: rolebinding 6 | app.kubernetes.io/instance: leader-election-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: promoter 9 | app.kubernetes.io/part-of: promoter 10 | app.kubernetes.io/managed-by: kustomize 11 | name: leader-election-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: Role 15 | name: leader-election-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/promotionstrategy_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit promotionstrategies. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: promotionstrategy-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: promotionstrategy-editor-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - promotionstrategies 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - promoter.argoproj.io 28 | resources: 29 | - promotionstrategies/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/promotionstrategy_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view promotionstrategies. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: promotionstrategy-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: promotionstrategy-viewer-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - promotionstrategies 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - promoter.argoproj.io 24 | resources: 25 | - promotionstrategies/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/pullrequest_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit pullrequests. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: pullrequest-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: pullrequest-editor-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - pullrequests 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - promoter.argoproj.io 28 | resources: 29 | - pullrequests/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/pullrequest_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view pullrequests. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: pullrequest-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: pullrequest-viewer-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - pullrequests 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - promoter.argoproj.io 24 | resources: 25 | - pullrequests/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/revertcommit_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit revertcommits. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: revertcommit-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: revertcommit-editor-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - revertcommits 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - promoter.argoproj.io 28 | resources: 29 | - revertcommits/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/revertcommit_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view revertcommits. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: revertcommit-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: revertcommit-viewer-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - revertcommits 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - promoter.argoproj.io 24 | resources: 25 | - revertcommits/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - secrets 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - argoproj.io 17 | resources: 18 | - applications 19 | verbs: 20 | - get 21 | - list 22 | - watch 23 | - apiGroups: 24 | - promoter.argoproj.io 25 | resources: 26 | - argocdcommitstatuses 27 | - changetransferpolicies 28 | - clusterscmproviders 29 | - commitstatuses 30 | - controllerconfigurations 31 | - gitrepositories 32 | - promotionstrategies 33 | - pullrequests 34 | - revertcommits 35 | - scmproviders 36 | verbs: 37 | - create 38 | - delete 39 | - get 40 | - list 41 | - patch 42 | - update 43 | - watch 44 | - apiGroups: 45 | - promoter.argoproj.io 46 | resources: 47 | - argocdcommitstatuses/finalizers 48 | - changetransferpolicies/finalizers 49 | - clusterscmproviders/finalizers 50 | - commitstatuses/finalizers 51 | - controllerconfigurations/finalizers 52 | - gitrepositories/finalizers 53 | - promotionstrategies/finalizers 54 | - pullrequests/finalizers 55 | - revertcommits/finalizers 56 | - scmproviders/finalizers 57 | verbs: 58 | - update 59 | - apiGroups: 60 | - promoter.argoproj.io 61 | resources: 62 | - argocdcommitstatuses/status 63 | - changetransferpolicies/status 64 | - clusterscmproviders/status 65 | - commitstatuses/status 66 | - controllerconfigurations/status 67 | - gitrepositories/status 68 | - promotionstrategies/status 69 | - pullrequests/status 70 | - revertcommits/status 71 | - scmproviders/status 72 | verbs: 73 | - get 74 | - patch 75 | - update 76 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/instance: manager-rolebinding 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/created-by: promoter 9 | app.kubernetes.io/part-of: promoter 10 | app.kubernetes.io/managed-by: kustomize 11 | name: manager-rolebinding 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: manager-role 16 | subjects: 17 | - kind: ServiceAccount 18 | name: controller-manager 19 | namespace: system 20 | -------------------------------------------------------------------------------- /config/rbac/scmprovider_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit scmproviders. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: scmprovider-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: scmprovider-editor-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - scmproviders 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - promoter.argoproj.io 28 | resources: 29 | - scmproviders/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/scmprovider_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view scmproviders. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: scmprovider-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: promoter 10 | app.kubernetes.io/part-of: promoter 11 | app.kubernetes.io/managed-by: kustomize 12 | name: scmprovider-viewer-role 13 | rules: 14 | - apiGroups: 15 | - promoter.argoproj.io 16 | resources: 17 | - scmproviders 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - promoter.argoproj.io 24 | resources: 25 | - scmproviders/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/managed-by: kustomize 7 | name: controller-manager 8 | namespace: system 9 | -------------------------------------------------------------------------------- /config/release/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../default 3 | 4 | images: 5 | - name: quay.io/argoprojlabs/gitops-promoter 6 | newName: quay.io/argoprojlabs/gitops-promoter 7 | newTag: latest 8 | apiVersion: kustomize.config.k8s.io/v1beta1 9 | kind: Kustomization 10 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - promoter_v1alpha1_pullrequest.yaml 3 | - promoter_v1alpha1_commitstatus.yaml 4 | - promoter_v1alpha1_revertcommit.yaml 5 | - promoter_v1alpha1_promotionstrategy.yaml 6 | - promoter_v1alpha1_scmprovider.yaml 7 | - promoter_v1alpha1_gitrepository.yaml 8 | - promoter_v1alpha1_changetransferpolicy.yaml 9 | - promoter_v1alpha1_argocdcommitstatus.yaml 10 | - promoter_v1alpha1_controllerconfiguration.yaml 11 | - promoter_v1alpha1_clusterscmprovider.yaml 12 | #+kubebuilder:scaffold:manifestskustomizesamples 13 | -------------------------------------------------------------------------------- /config/samples/promoter_v1alpha1_argocdcommitstatus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: ArgoCDCommitStatus 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/managed-by: kustomize 7 | name: argocdcommitstatus-sample 8 | spec: 9 | promotionStrategyRef: 10 | name: ps-name 11 | applicationSelector: 12 | matchLabels: 13 | app: webservice-tier-1 14 | -------------------------------------------------------------------------------- /config/samples/promoter_v1alpha1_changetransferpolicy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: ChangeTransferPolicy 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/managed-by: kustomize 7 | name: changetransferpolicy-sample 8 | spec: 9 | gitRepositoryRef: 10 | name: promoter-testing 11 | proposedBranch: environment/development-next 12 | activeBranch: environment/development 13 | proposedCommitStatuses: 14 | - key: deployment-freeze 15 | activeCommitStatuses: 16 | - key: healthy 17 | -------------------------------------------------------------------------------- /config/samples/promoter_v1alpha1_clusterscmprovider.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: ClusterScmProvider 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/managed-by: kustomize 7 | name: clusterscmprovider-sample 8 | spec: 9 | github: {} 10 | secretRef: 11 | name: my-auth 12 | -------------------------------------------------------------------------------- /config/samples/promoter_v1alpha1_commitstatus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: CommitStatus 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/managed-by: kustomize 7 | promoter.argoproj.io/commit-status: healthy 8 | name: commitstatus-sample 9 | spec: 10 | sha: 68522faaf5591f98c7a89dd74069e79195e4d6c6 11 | gitRepositoryRef: 12 | name: scmprovider-sample 13 | phase: pending 14 | name: health 15 | description: "The build succeeded!" 16 | url: "https://example.com" 17 | -------------------------------------------------------------------------------- /config/samples/promoter_v1alpha1_controllerconfiguration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: ControllerConfiguration 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/managed-by: kustomize 7 | name: controller-configuration 8 | spec: 9 | pullRequest: 10 | template: 11 | title: "Promote {{ trunc 5 .ChangeTransferPolicy.Status.Proposed.Dry.Sha }} to `{{ .ChangeTransferPolicy.Spec.ActiveBranch }}`" 12 | description: "This PR is promoting the environment branch `{{ .ChangeTransferPolicy.Spec.ActiveBranch }}` which is currently on dry sha {{ .ChangeTransferPolicy.Status.Active.Dry.Sha }} to dry sha {{ .ChangeTransferPolicy.Status.Proposed.Dry.Sha }}." 13 | 14 | -------------------------------------------------------------------------------- /config/samples/promoter_v1alpha1_gitrepository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: GitRepository 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/managed-by: kustomize 7 | name: gitrepository-sample 8 | spec: 9 | repo: 10 | owner: 11 | scmProviderRef: 12 | name: example-scm-provider 13 | -------------------------------------------------------------------------------- /config/samples/promoter_v1alpha1_promotionstrategy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: PromotionStrategy 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/managed-by: kustomize 7 | name: promotionstrategy-sample 8 | spec: 9 | gitRepositoryRef: 10 | name: promoter-testing 11 | activeCommitStatuses: 12 | - key: healthy 13 | - key: healthy-load 14 | # proposedCommitStatuses: 15 | # - key: healthy-lint 16 | environments: 17 | - branch: environment/development 18 | autoMerge: true 19 | - branch: environment/staging 20 | - branch: environment/production 21 | activeCommitStatuses: 22 | - key: healthy 23 | - key: healthy-load -------------------------------------------------------------------------------- /config/samples/promoter_v1alpha1_pullrequest.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: PullRequest 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/managed-by: kustomize 7 | name: pullrequest-sample 8 | spec: 9 | gitRepositoryRef: 10 | name: promoter-testing 11 | title: "My awesome pull request 1" 12 | description: "This is the description of the PR created with the package `github. com/ google/ go-github/ github`" 13 | sourceBranch: "environment/development-next" 14 | targetBranch: "environment/development" 15 | 16 | -------------------------------------------------------------------------------- /config/samples/promoter_v1alpha1_revertcommit.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: RevertCommit 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/managed-by: kustomize 7 | name: revertcommit-sample 8 | spec: 9 | # TODO(user): Add fields here 10 | -------------------------------------------------------------------------------- /config/samples/promoter_v1alpha1_scmprovider.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: ScmProvider 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: promoter 6 | app.kubernetes.io/managed-by: kustomize 7 | name: scmprovider-sample 8 | spec: 9 | github: {} 10 | secretRef: 11 | name: my-auth 12 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | GitOps Promoter enables developers to "make a change and forget it." The change is made once in the "DRY branch" for all 4 | environments, then GitOps Promoter does the work of moving the change through the environment-specific "hydrated 5 | branches." 6 | 7 | This diagram shows a hypothetical setup where the "hydrator" is Helm, and the tool syncing the promoted changes is Argo 8 | CD. 9 | 10 | Commit `3f7e` is the user's "DRY" change that applied to all environments. The SHAs of the hydrated commits are 11 | represented as `3f7e` with an environment-specific subscript. But in reality, the hydrated commits are different SHAs 12 | since they are on different branches and represent environment-specific contents. 13 | 14 | [![GitOps Promoter Architecture](./assets/architecture.png)](./assets/architecture.png) 15 | 16 | # Controller Flow 17 | 18 | The image illustrates the general flow of how the GitOps Promoter controller operates. The diagram 19 | outlines the steps involved in promoting changes through different environments in a GitOps workflow. It shows the 20 | interaction between various components such as the Git repository, the GitOps Promoter controller, and the environment-specific 21 | branches. 22 | 23 | [![GitOps Promoter Architecture](./assets/architecture-diag.png)](./assets/architecture-diag.png) 24 | -------------------------------------------------------------------------------- /docs/assets/architecture-diag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/gitops-promoter/827b6bdf0d5f7665a5f3aa14cc438c4fc3fe63cf/docs/assets/architecture-diag.png -------------------------------------------------------------------------------- /docs/assets/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/gitops-promoter/827b6bdf0d5f7665a5f3aa14cc438c4fc3fe63cf/docs/assets/architecture.png -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/argoproj-labs/gitops-promoter/827b6bdf0d5f7665a5f3aa14cc438c4fc3fe63cf/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/commit-status-controllers/argocd.md: -------------------------------------------------------------------------------- 1 | # Argo CD Commit Status Controller 2 | 3 | The Argo CD Commit Status controller is a controller that enables environment gating 4 | based on Argo CD's concept of a healthy application. The controller listens for updates on 5 | Applications based on a common label on the Application resources managed by a particular 6 | PromotionStrategy. 7 | 8 | !!! important 9 | Currently this controller only works with Argo CD Applications that are configured to use the hydrator. 10 | 11 | !!! important 12 | Currently the repo URL configured in the PromotionStrategy must be exactly the same as the repo URL configured in the Argo CD Application. 13 | 14 | 15 | ## Example Configurations 16 | 17 | In this example we see an ArgoCDCommitStatus resource that is configured to select all Argo CD Applications 18 | that have the label `app: webservice-tier-1`. These Applications must also be associated with the PromotionStrategy 19 | named `webservice-tier-1`. Once this is configured, the controller will create a CommitStatus resource for each application 20 | that matches the selector. 21 | 22 | ```yaml 23 | apiVersion: promoter.argoproj.io/v1alpha1 24 | kind: ArgoCDCommitStatus 25 | metadata: 26 | name: webservice-tier-1 27 | spec: 28 | promotionStrategyRef: 29 | name: webservice-tier-1 30 | applicationSelector: 31 | matchLabels: 32 | app: webservice-tier-1 33 | ``` 34 | 35 | To configure the PromotionStrategy, we need to specify the active commit statuses that are required for the promotion to proceed. 36 | You can see this in the example below with the `activeCommitStatuses` field, having a key of `argocd-health`. This key is the 37 | same key that the Argo CD commit status controller will use when it creates its CommitStatus resources. 38 | 39 | 40 | ```yaml 41 | apiVersion: promoter.argoproj.io/v1alpha1 42 | kind: PromotionStrategy 43 | metadata: 44 | name: argocon-demo 45 | spec: 46 | activeCommitStatuses: 47 | - key: argocd-health 48 | environments: 49 | - branch: environments/development 50 | - branch: environments/staging 51 | - branch: environments/production 52 | gitRepositoryRef: 53 | name: webservice-tier-1 54 | ``` -------------------------------------------------------------------------------- /docs/crd-specs.md: -------------------------------------------------------------------------------- 1 | ## PromotionStrategy 2 | 3 | The PromotionStrategy is the user's interface to controlling how changes are promoted through their environments. In 4 | this CR, the user configures the list of live hydrated environment branches in their order of promotion. They'll also 5 | configure the checks which must pass between promotion steps. 6 | 7 | ```yaml 8 | {!docs/example-resources/PromotionStrategy.yaml!} 9 | ``` 10 | 11 | ## ChangeTransferPolicy 12 | 13 | A ChangeTransferPolicy represents a pair hydrated environment branch pair: the proposed environment branch and the live 14 | environment branch. When a new commit appears in the proposed branch, the ChangeTransferPolicy will open a PR against 15 | the live branch. When all the configured checks pass, the ChangeTransferPolicy will merge the PR. 16 | 17 | A PromotionStrategy will create a ChangeTransferPolicy for each configured environment. For each environment besides the 18 | first one, the PromotionStrategy controller will inject a `proposedCommitStatus` to represent the active status of the 19 | previous environment. This is how the PromotionStrategy ensures that the environment PRs are merged in order, respecting 20 | the previous environments' active commit statuses. 21 | 22 | 23 | ```yaml 24 | {!docs/example-resources/ChangeTransferPolicy.yaml!} 25 | ``` 26 | 27 | ## PullRequest 28 | 29 | A PullRequest is a thin wrapper around the SCM's pull request API. ChangeTransferPolicies use PullRequests to manage 30 | promotions. 31 | 32 | ```yaml 33 | {!docs/example-resources/PullRequest.yaml!} 34 | ``` 35 | 36 | ## CommitStatus 37 | 38 | A CommitStatus is a thin wrapper for the SCM's commit status API. CommitStatuses are the primary source of truth for 39 | promotion gates. In the ideal case, the CommitStatus will write its state to the SCM's API so that the appropriate 40 | checkmarks/failures appear in the SCM's UI. But even if the SCM API calls fail, the ChangeTransferPolicy controller will 41 | use the contents of the CommitStatuses `spec` fields. 42 | 43 | ```yaml 44 | {!docs/example-resources/CommitStatus.yaml!} 45 | ``` 46 | 47 | ## GitRepository 48 | 49 | A GitRepository represents a single git repository. It references an ScmProvider to enable access via some configured 50 | auth mechanism. 51 | 52 | ```yaml 53 | {!docs/example-resources/GitRepository.yaml!} 54 | ``` 55 | 56 | ## ScmProvider 57 | 58 | An ScmProvider represents a scm instance (such as github). It references a Secret to enable access via some configured 59 | auth mechanism. 60 | 61 | ```yaml 62 | {!docs/example-resources/ScmProvider.yaml!} 63 | ``` 64 | 65 | ## ClusterScmProvider 66 | 67 | A ClusterScmProvider represents a SCM instance (such as GitHub). ClusterScmProvider is the cluster-scoped alternative to the ScmProvider. It references a Secret in the same namespace where the promoter is running to enable access via some configured 68 | auth mechanism. A ClusterScmProvider can be referenced by any GitRepository in the cluster, regardless of namespace. 69 | 70 | ```yaml 71 | {!docs/example-resources/ClusterScmProvider.yaml!} 72 | ``` 73 | 74 | ## ArgoCDCommitStatus 75 | 76 | An ArgoCDCommitStatus is used as a way to aggregate all the Argo CD Applications that are being used in the promotion strategy. It is used 77 | to check the status of the Argo CD Applications that are being used in the promotion strategy. 78 | 79 | ```yaml 80 | {!docs/example-resources/ArgoCDCommitStatus.yaml!} 81 | ``` 82 | ## ControllerConfiguration 83 | 84 | A ControllerConfiguration is used to configure the behavior of the promoter. 85 | 86 | A global ControllerConfiguration is deployed alongside the controller and applies to all promotions. 87 | 88 | All fields are required, but defaults are provided in the installation manifests. 89 | 90 | ```yaml 91 | {!docs/example-resources/ControllerConfiguration.yaml!} 92 | ``` 93 | -------------------------------------------------------------------------------- /docs/example-resources/ArgoCDCommitStatus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: ArgoCDCommitStatus 3 | metadata: 4 | name: argocdcommitstatus-sample 5 | spec: 6 | applicationSelector: 7 | matchLabels: 8 | app: demo 9 | promotionStrategyRef: 10 | name: argocon-demo -------------------------------------------------------------------------------- /docs/example-resources/ChangeTransferPolicy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: ChangeTransferPolicy 3 | metadata: 4 | name: environment 5 | spec: 6 | gitRepositoryRef: 7 | name: example-git-repository 8 | proposedBranch: environment/dev-next 9 | activeBranch: environment/dev 10 | activeCommitStatuses: 11 | - key: argocd-app-health 12 | proposedCommitStatuses: 13 | - key: security-scan 14 | - key: promoter-previous-environment 15 | -------------------------------------------------------------------------------- /docs/example-resources/ClusterScmProvider.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: ClusterScmProvider 3 | metadata: 4 | name: example-cluster-scm-provider 5 | spec: 6 | secretRef: 7 | # Secret must be in the same namespace where the promoter is running 8 | name: example-cluster-scm-provider-secret 9 | 10 | # You must specify either github or gitlab. Both are provided here as examples. 11 | # If you do not need to specify any sub-fields, just set the field to {}. 12 | 13 | github: 14 | domain: github.example.com # Optional, leave empty for default github.com 15 | appID: 16 | installationID: 17 | 18 | gitlab: 19 | domain: gitlab.com # Optional 20 | -------------------------------------------------------------------------------- /docs/example-resources/CommitStatus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: CommitStatus 3 | metadata: 4 | name: example-commit-status 5 | spec: 6 | gitRepositoryRef: 7 | name: example-git-repo 8 | sha: 1234567890abcdef 9 | name: argocd-app-health 10 | description: Argo CD application `example-app` is healthy 11 | 12 | # Can be pending, success, failure. Default is pending. 13 | phase: success 14 | 15 | # Optional URL to link to more information about the commit status. 16 | url: https://argocd.example.com/applications/example-app 17 | -------------------------------------------------------------------------------- /docs/example-resources/ControllerConfiguration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: ControllerConfiguration 3 | metadata: 4 | name: example-controller-configuration 5 | spec: 6 | pullRequest: 7 | template: 8 | title: "Promote {{ trunc 5 .ChangeTransferPolicy.Status.Proposed.Dry.Sha }} to `{{ .ChangeTransferPolicy.Spec.ActiveBranch }}`" 9 | description: "This PR is promoting the environment branch `{{ .ChangeTransferPolicy.Spec.ActiveBranch }}` which is currently on dry sha {{ .ChangeTransferPolicy.Status.Active.Dry.Sha }} to dry sha {{ .ChangeTransferPolicy.Status.Proposed.Dry.Sha }}." 10 | promotionStrategyRequeueDuration: "5m" 11 | changeTransferPolicyRequeueDuration: "5m" 12 | argocdCommitStatusRequeueDuration: "15s" 13 | -------------------------------------------------------------------------------- /docs/example-resources/GitRepository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: GitRepository 3 | metadata: 4 | name: example-git-repository 5 | spec: 6 | owner: 7 | name: 8 | scmProviderRef: 9 | kind: ScmProvider 10 | name: example-scm-provider 11 | -------------------------------------------------------------------------------- /docs/example-resources/PromotionStrategy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: PromotionStrategy 3 | metadata: 4 | name: example-promotion-strategy 5 | spec: 6 | gitRepositoryRef: 7 | name: example-git-repo 8 | activeCommitStatuses: 9 | - key: argocd-app-health 10 | proposedCommitStatuses: 11 | - key: security-scan 12 | environments: 13 | - branch: environment/dev 14 | - branch: environment/test 15 | - branch: environment/prod 16 | autoMerge: false 17 | activeCommitStatuses: 18 | - key: performance-test 19 | proposedCommitStatuses: 20 | - key: deployment-freeze 21 | status: 22 | environments: 23 | - branch: environment/dev 24 | active: 25 | dry: 26 | sha: 27 | commitTime: 28 | hydrated: 29 | sha: 30 | commitTime: 31 | commitStatus: 32 | sha: 33 | phase: 34 | proposed: 35 | # same fields as active 36 | lastHealthyDryShas: 37 | - sha: 38 | time: 39 | - branch: environment/test 40 | # same fields as dev 41 | - branch: environment/prod 42 | # same fields as dev -------------------------------------------------------------------------------- /docs/example-resources/PullRequest.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: PullRequest 3 | metadata: 4 | name: example-proposed-commit 5 | spec: 6 | gitRepositoryRef: 7 | name: example-git-repository 8 | title: 9 | targetBranch: 10 | sourceBranch: 11 | description: 12 | 13 | # Must be closed, merged, or open. Default is open. 14 | state: 15 | -------------------------------------------------------------------------------- /docs/example-resources/ScmProvider.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: promoter.argoproj.io/v1alpha1 2 | kind: ScmProvider 3 | metadata: 4 | name: example-scm-provider 5 | spec: 6 | secretRef: 7 | name: example-scm-provider-secret 8 | 9 | # You must specify either github or gitlab. Both are provided here as examples. 10 | # If you do not need to specify any sub-fields, just set the field to {}. 11 | 12 | github: 13 | domain: github.example.com # Optional, leave empty for default github.com 14 | appID: 15 | installationID: 16 | 17 | gitlab: 18 | domain: gitlab.com # Optional 19 | -------------------------------------------------------------------------------- /docs/faqs.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## Does GitOps Promoter use branches for environments? 4 | 5 | You've probably read that [using branches for environments is an anti-pattern](https://medium.com/containers-101/stop-using-branches-for-deploying-to-different-gitops-environments-7111d0632402). 6 | GitOps Promoter does not use branches for environments in the way that causes problems. In fact, GitOps Promoter 7 | _requires_ that all environments are represented in a single DRY branch. 8 | 9 | The problems with using branches for environments are all related to the user experience of managing those branches. 10 | When using GitOps promoter, the user only pushes to a single branch. Hydrated manifests are automatically managed in 11 | environment-specific branches, but the user never has to interact with them directly. 12 | 13 | ## How should I bump image tags? 14 | 15 | However you want! GitOps Promoter doesn't know how you structure your manifests, so it can't bump image tags, change 16 | resource limits, or anything else. It's up to you to decide how to manage your manifests. 17 | 18 | What GitOps Promoter _does_ do is make sure that your changes are applied to all environments in a consistent way. So if 19 | you bump an image tag in a Kustomize base or in a global Helm values file, GitOps Promoter will make sure that change is 20 | applied to all environments. 21 | 22 | By focusing on the promotion part and leaving manifest manipulation to other tools, GitOps Promoter is able to reliably 23 | handle whatever manifest structure your organization prefers. 24 | 25 | ## How does GitOps Promoter handle concurrent releases? 26 | 27 | GitOps Promoter always works on releasing the latest DRY commit. If a new commit is pushed while another commit is still 28 | moving through environments, GitOps Promoter stops working on the old commit and waits for the new commit to work its 29 | way through the environments. 30 | 31 | This model has some major advantages. 32 | 33 | 1. **Simplicity**: The user never has to think about the progress of commits other than the most recent one. After 34 | pushing a commit, they know that higher environments will hold their current state while the new change works its way 35 | through the environments. 36 | 37 | 2. **Ease of use**: The pending change is always represented as a simple PR from the HEAD of the proposed branch to the 38 | HEAD of the active branch. Hotfixes require simply clicking "Merge" on the PR to promote the change to a given 39 | environment. 40 | 41 | 3. **Reliability**: Concurrent releases would require somehow queueing commits (in a series of PRs or some other state 42 | store). The implementation would be far more complex and error-prone. 43 | 44 | The "release latest" model has some drawbacks. 45 | 46 | 1. **Delays in high-churn applications**: Frequent changes could delay releases to higher environments. An environment 47 | must wait the sum of all previous environments' delays before receiving a change. If commits arrive faster than that 48 | sum, the environment will wait at an old state until a change can clear all prior environments. 49 | 50 | 2. **Skipped commits**: There's no guarantee that every change will be released to every environment. For example, if 51 | the second environment is running commit A, and active commit statuses never pass for commits B and C, then the 52 | second environment will never see commits B and C. It will skip straight to D. This could confuse for users who are 53 | used to a queue-based deployment. It may make it more difficult to diagnose which commit caused a problem in a given 54 | environment. 55 | 56 | For most use cases, these tradeoffs are worth it. 57 | 58 | To mitigate the downsides, try to speed up active commit statuses (maybe by shifting more of those validations left) or 59 | adopting a "slower" release model, such as by batching multiple changes in a single DRY commit. 60 | 61 | Once the "release latest" model is validated in production environments, we may consider adding a queue-based model for 62 | users who need it. 63 | -------------------------------------------------------------------------------- /docs/metrics.md: -------------------------------------------------------------------------------- 1 | # Metrics 2 | 3 | GitOps Promoter produces metrics counter and histogram metrics for all network-bound operations. 4 | 5 | !!!important "Metrics Subject to Change" 6 | 7 | The metrics produced by GitOps Promoter are subject to change as the project evolves until the 1.0 release. 8 | Please refer to this document for the latest metrics. 9 | 10 | ## git_operations_total 11 | 12 | A counter of git clone operations. 13 | 14 | Labels: 15 | 16 | * `git_repository`: The name of the GitRepository resource associated with the operation. 17 | * `scm_provider`: The name of the ScmProvider resource associated with the operation. 18 | * `operation`: The type of git operation (clone, fetch, pull, push, ls-remote). 19 | * `result`: Whether the operation succeeded (success, failure). 20 | 21 | ## git_operations_duration_seconds 22 | 23 | A histogram of the duration of git clone operations. 24 | 25 | Labels: 26 | 27 | * `git_repository`: The name of the GitRepository resource associated with the operation. 28 | * `scm_provider`: The name of the ScmProvider resource associated with the operation. 29 | * `operation`: The type of git operation (clone, fetch, pull, push, ls-remote). 30 | * `result`: Whether the operation succeeded (success, failure). 31 | 32 | ## scm_calls_total 33 | 34 | A counter of SCM API calls. 35 | 36 | Labels: 37 | 38 | * `git_repository`: The name of the GitRepository resource associated with the operation. 39 | * `scm_provider`: The name of the ScmProvider resource associated with the operation. 40 | * `api`: The SCM API being called (CommitStatus, PullRequest) 41 | * `operation`: The type of SCM operation. 42 | * For CommitStatus, this is always create. 43 | * For PullRequest, this is create, update, merge, close, or list. 44 | * `response_code`: The HTTP response code. 45 | 46 | ## scm_calls_duration_seconds 47 | 48 | A histogram of the duration of SCM API calls. 49 | 50 | Labels: 51 | 52 | * `git_repository`: The name of the GitRepository resource associated with the operation. 53 | * `api`: The SCM API being called (CommitStatus, PullRequest) 54 | * `operation`: The type of SCM operation. 55 | * For CommitStatus, this is always create. 56 | * For PullRequest, this is create, update, merge, close, or list. 57 | * `response_code`: The HTTP response code. 58 | 59 | ## scm_calls_rate_limit_limit 60 | 61 | A counter for the rate limit of SCM API calls. 62 | 63 | This metric is currently only produced for GitHub. 64 | 65 | Labels: 66 | 67 | * `scm_provider`: The name of the ScmProvider resource associated with the operation. 68 | 69 | ## scm_calls_rate_limit_remaining 70 | 71 | A counter for the remaining rate limit of SCM API calls. 72 | 73 | This metric is currently only produced for GitHub. 74 | 75 | Labels: 76 | 77 | * `scm_provider`: The name of the ScmProvider resource associated with the operation. 78 | 79 | ## scm_calls_rate_limit_reset_remaining_seconds 80 | 81 | A gauge for the remaining seconds until the SCM API rate limit resets. 82 | 83 | This metric is currently only produced for GitHub. 84 | 85 | Labels: 86 | 87 | * `scm_provider`: The name of the ScmProvider resource associated with the operation. 88 | -------------------------------------------------------------------------------- /docs/multi-tenancy.md: -------------------------------------------------------------------------------- 1 | # Multi-Tenancy 2 | 3 | ## PromotionStrategy Tenancy 4 | 5 | GitOps Promoter provides namespace-based tenancy for PromotionStrategies. 6 | 7 | To enable environment promotion, a user must install these namespaced resources: 8 | 9 | * PromotionStrategy 10 | * GitRepository 11 | * ScmProvider 12 | * Secret (for SCM access) 13 | 14 | To enable self-service PromotionStrategy management for multiple tenants, a GitOps Promoter admin can give each 15 | tenant write access to a namespace to manage these resources. As long as the GitOps Promoter controller has access to 16 | those namespaces, it will reconcile the resources. 17 | 18 | Secrets with SCM credentials may only be referenced by ScmProviders in the same namespace, which in turn may only be 19 | referenced by GitRepositories in the same namespace, which may only be referenced by PromotionStrategies in the same 20 | namespace. Limiting these references to a namespace prevents one tenant from referencing a Secret in another tenant's 21 | namespace and thereby gaining write access to another tenant's repositories. 22 | 23 | **Important**: Provision Secrets securely! 24 | 25 | We recommend using a GitOps-friendly Secret provisioning system that populates the Secret resource on-cluster, such as 26 | an external secrets operator or sealed secrets. 27 | 28 | If an administrator does not want to use namespace-based tenancy, they must either fully manage GitOps Promoter 29 | resources themselves or build some other system to regulate Secret access among tenants (for example, by validating 30 | that one tenant's resources do not reference another tenant's resources within the same namespace). 31 | 32 | If there are no trust boundaries to be enforced among PromotionStrategy users, a GitOps Promoter admin may choose to 33 | host all resources in a single namespace, keeping in mind the need to avoid resource name collisions. 34 | 35 | ## CommitStatus Tenancy 36 | 37 | As with PromotionStrategies, all references from CommitStatuses (to GitRepositories, then ScmProviders, and finally to 38 | SCM Secrets) must resolve within the same namespace as the CommitStatus. 39 | 40 | Various actors may want to manage CommitStatuses: 41 | 42 | 1. GitOps Promoter administrators 43 | 2. Special interest teams (for example, a compliance team) 44 | 3. PromotionStrategy users 45 | 46 | A given PromotionStrategy may need to reference CommitStatuses from any or all of these actors. 47 | 48 | To facilitate the cross-team communication, _PromotionStrategy references to CommitStatuses are cluster-scoped_. If any 49 | CommitStatus on a cluster matches the key specified in a PromotionStrategy, then the PromotionStrategy controller will 50 | take that CommitStatus into account for the promotion process. This allows different actors to host CommitStatuses in 51 | their own namespaces, using their own SCM credentials. 52 | 53 | This cluster-scoped reference is reasonably safe in a multi-tenant setup because: 54 | 55 | 1. The reference is read-only. When referencing a CommitStatus in another namespace, a PromotionStrategy does not leak 56 | any information about itself. It just reads the status. 57 | 2. A CommitStatus's commit SHA must match the SHA of a commit being promoted to affect promotion. In other 58 | words, the CommitStatus's creator must already have knowledge about the SHAs in the PromotionStrategy's repository. 59 | 3. The worst a malicious or faulty CommitStatus can do is block an environment's promotion. If a promotion is 60 | erroneously blocked, the PromotionStrategy user can take advantage of an override mechanism (such as manually 61 | merging the blocked PR), and the GitOps Promoter's admin can investigate and remediate the faulty blocker. 62 | 63 | # ScmProvider and ClusterScmProvider Tenancy 64 | 65 | ScmProvider and ClusterScmProvider are the same resource but are available for different scopes and have different tenancy considerations: 66 | 67 | - ScmProvider is a namespaced resource and must exist in the same namespace as the GitRepository referencing it. It is ideal for teams that want to manage the access to their SCM themselves and stay isolated from other tenants. 68 | The secret referenced by the ScmProvider must be in the same namespace as the ScmProvider. 69 | - ClusterScmProvider is a cluster-scoped resource and can be referenced by any GitRepository from all namespaces. This allows a centralized way to configure the SCM access for all tenants in the cluster. 70 | The secret referenced by a ClusterScmProvider must be in the namespace where the promoter is deployed. 71 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | markdown_include==0.8.1 2 | mkdocs==1.6.1 3 | mkdocs-get-deps==0.2.0 4 | mkdocs-material==9.5.42 5 | mkdocs-material-extensions==1.3.1 6 | pygments==2.18.0 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/argoproj-labs/gitops-promoter 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/bradleyfalzon/ghinstallation/v2 v2.15.0 9 | github.com/cespare/xxhash/v2 v2.3.0 10 | github.com/go-logr/logr v1.4.3 11 | github.com/go-task/slim-sprig/v3 v3.0.0 12 | github.com/google/go-github/v71 v71.0.0 13 | github.com/onsi/ginkgo/v2 v2.23.4 14 | github.com/onsi/gomega v1.37.0 15 | github.com/prometheus/client_golang v1.22.0 16 | github.com/relvacode/iso8601 v1.6.0 17 | github.com/sosedoff/gitkit v0.4.0 18 | github.com/spf13/pflag v1.0.6 19 | github.com/stretchr/testify v1.10.0 20 | github.com/tidwall/gjson v1.18.0 21 | gitlab.com/gitlab-org/api/client-go v0.129.0 22 | go.uber.org/zap v1.27.0 23 | gopkg.in/yaml.v3 v3.0.1 24 | k8s.io/api v0.33.1 25 | k8s.io/apimachinery v0.33.1 26 | k8s.io/client-go v0.33.1 27 | k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e 28 | sigs.k8s.io/controller-runtime v0.21.0 29 | ) 30 | 31 | require ( 32 | github.com/beorn7/perks v1.0.1 // indirect 33 | github.com/blang/semver/v4 v4.0.0 // indirect 34 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 35 | github.com/emicklei/go-restful/v3 v3.12.2 // indirect 36 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 37 | github.com/fsnotify/fsnotify v1.9.0 // indirect 38 | github.com/fxamacker/cbor/v2 v2.8.0 // indirect 39 | github.com/go-logr/zapr v1.3.0 // indirect 40 | github.com/go-openapi/jsonpointer v0.21.1 // indirect 41 | github.com/go-openapi/jsonreference v0.21.0 // indirect 42 | github.com/go-openapi/swag v0.23.1 // indirect 43 | github.com/gofrs/uuid v4.4.0+incompatible // indirect 44 | github.com/gogo/protobuf v1.3.2 // indirect 45 | github.com/golang-jwt/jwt/v4 v4.5.2 // indirect 46 | github.com/google/btree v1.1.3 // indirect 47 | github.com/google/gnostic-models v0.6.9 // indirect 48 | github.com/google/go-cmp v0.7.0 // indirect 49 | github.com/google/go-querystring v1.1.0 // indirect 50 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect 51 | github.com/google/uuid v1.6.0 // indirect 52 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 53 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 54 | github.com/josharian/intern v1.0.0 // indirect 55 | github.com/json-iterator/go v1.1.12 // indirect 56 | github.com/mailru/easyjson v0.9.0 // indirect 57 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 58 | github.com/modern-go/reflect2 v1.0.2 // indirect 59 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 60 | github.com/pkg/errors v0.9.1 // indirect 61 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 62 | github.com/prometheus/client_model v0.6.2 // indirect 63 | github.com/prometheus/common v0.63.0 // indirect 64 | github.com/prometheus/procfs v0.16.0 // indirect 65 | github.com/stretchr/objx v0.5.2 // indirect 66 | github.com/tidwall/match v1.1.1 // indirect 67 | github.com/tidwall/pretty v1.2.1 // indirect 68 | github.com/x448/float16 v0.8.4 // indirect 69 | go.uber.org/automaxprocs v1.6.0 // indirect 70 | go.uber.org/multierr v1.11.0 // indirect 71 | golang.org/x/crypto v0.37.0 // indirect 72 | golang.org/x/net v0.39.0 // indirect 73 | golang.org/x/oauth2 v0.30.0 // indirect 74 | golang.org/x/sync v0.13.0 // indirect 75 | golang.org/x/sys v0.32.0 // indirect 76 | golang.org/x/term v0.31.0 // indirect 77 | golang.org/x/text v0.24.0 // indirect 78 | golang.org/x/time v0.11.0 // indirect 79 | golang.org/x/tools v0.32.0 // indirect 80 | gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect 81 | google.golang.org/protobuf v1.36.6 // indirect 82 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 83 | gopkg.in/inf.v0 v0.9.1 // indirect 84 | k8s.io/apiextensions-apiserver v0.33.0 // indirect 85 | k8s.io/klog/v2 v2.130.1 // indirect 86 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 87 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 88 | sigs.k8s.io/randfill v1.0.0 // indirect 89 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 90 | sigs.k8s.io/yaml v1.4.0 // indirect 91 | ) 92 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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/git/promoter_askpass.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script is used as the command supplied to GIT_ASKPASS as a way to supply username/password 3 | # credentials to git, without having to use git credentials helpers, or having on-disk config. 4 | case "$1" in 5 | Username*) echo "${GIT_USERNAME}" ;; 6 | Password*) echo "${GIT_PASSWORD}" ;; 7 | esac -------------------------------------------------------------------------------- /hack/manifests-release.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | set -euox pipefail 3 | 4 | SRCROOT="$( CDPATH='' cd -- "$(dirname "$0")/.." && pwd -P )" 5 | AUTOGENMSG="# This is an auto-generated file. DO NOT EDIT" 6 | 7 | KUSTOMIZE="${1:-}" 8 | if [ -z "$KUSTOMIZE" ]; then 9 | echo "Path to kustomize not provided" 10 | exit 1 11 | fi 12 | 13 | IMAGE_TAG="${2:-}" 14 | if [ -z "$IMAGE_TAG" ]; then 15 | echo "Image tag not provided" 16 | exit 1 17 | fi 18 | 19 | IMAGE_NAMESPACE="${IMAGE_NAMESPACE:-quay.io/argoprojlabs/gitops-promoter}" 20 | IMAGE_FQN="$IMAGE_NAMESPACE:$IMAGE_TAG" 21 | 22 | $KUSTOMIZE version 23 | cd "${SRCROOT}/config/release" && $KUSTOMIZE edit set image "quay.io/argoprojlabs/gitops-promoter=${IMAGE_FQN}" 24 | echo "${AUTOGENMSG}" > "${SRCROOT}/install.yaml" 25 | $KUSTOMIZE build "${SRCROOT}/config/release" >> "${SRCROOT}/install.yaml" 26 | -------------------------------------------------------------------------------- /internal/controller/argocdcommitstatus_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 controller 18 | 19 | import ( 20 | . "github.com/onsi/ginkgo/v2" 21 | ) 22 | 23 | var _ = Describe("ArgoCDCommitStatus Controller", func() { 24 | Context("When reconciling a resource", func() { 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /internal/controller/clusterscmprovider_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 controller 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "k8s.io/apimachinery/pkg/runtime" 24 | ctrl "sigs.k8s.io/controller-runtime" 25 | "sigs.k8s.io/controller-runtime/pkg/client" 26 | "sigs.k8s.io/controller-runtime/pkg/log" 27 | 28 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 29 | ) 30 | 31 | // ClusterScmProviderReconciler reconciles a ClusterScmProvider object 32 | type ClusterScmProviderReconciler struct { 33 | client.Client 34 | Scheme *runtime.Scheme 35 | } 36 | 37 | // +kubebuilder:rbac:groups=promoter.argoproj.io,resources=clusterscmproviders,verbs=get;list;watch;create;update;patch;delete 38 | // +kubebuilder:rbac:groups=promoter.argoproj.io,resources=clusterscmproviders/status,verbs=get;update;patch 39 | // +kubebuilder:rbac:groups=promoter.argoproj.io,resources=clusterscmproviders/finalizers,verbs=update 40 | 41 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 42 | // move the current state of the cluster closer to the desired state. 43 | // TODO(user): Modify the Reconcile function to compare the state specified by 44 | // the ClusterScmProvider object against the actual cluster state, and then 45 | // perform operations to make the cluster state reflect the state specified by 46 | // the user. 47 | // 48 | // For more details, check Reconcile and its Result here: 49 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.2/pkg/reconcile 50 | func (r *ClusterScmProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 51 | _ = log.FromContext(ctx) 52 | 53 | // TODO(user): your logic here 54 | 55 | return ctrl.Result{}, nil 56 | } 57 | 58 | // SetupWithManager sets up the controller with the Manager. 59 | func (r *ClusterScmProviderReconciler) SetupWithManager(mgr ctrl.Manager) error { 60 | err := ctrl.NewControllerManagedBy(mgr). 61 | For(&promoterv1alpha1.ClusterScmProvider{}). 62 | Named("clusterscmprovider"). 63 | Complete(r) 64 | if err != nil { 65 | return fmt.Errorf("failed to create controller: %w", err) 66 | } 67 | 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /internal/controller/clusterscmprovider_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 controller 18 | 19 | import ( 20 | "context" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | "k8s.io/apimachinery/pkg/api/errors" 25 | "k8s.io/apimachinery/pkg/types" 26 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 27 | 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | 30 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 31 | ) 32 | 33 | var _ = Describe("ClusterScmProvider Controller", func() { 34 | Context("When reconciling a resource", func() { 35 | const resourceName = "test-resource" 36 | 37 | ctx := context.Background() 38 | 39 | typeNamespacedName := types.NamespacedName{ 40 | Name: resourceName, 41 | Namespace: "default", // TODO(user):Modify as needed 42 | } 43 | clusterscmprovider := &promoterv1alpha1.ClusterScmProvider{} 44 | 45 | BeforeEach(func() { 46 | By("creating the custom resource for the Kind ClusterScmProvider") 47 | err := k8sClient.Get(ctx, typeNamespacedName, clusterscmprovider) 48 | if err != nil && errors.IsNotFound(err) { 49 | resource := &promoterv1alpha1.ClusterScmProvider{ 50 | ObjectMeta: metav1.ObjectMeta{ 51 | Name: resourceName, 52 | Namespace: "default", 53 | }, 54 | // TODO(user): Specify other spec details if needed. 55 | } 56 | Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 57 | } 58 | }) 59 | 60 | AfterEach(func() { 61 | // TODO(user): Cleanup logic after each test, like removing the resource instance. 62 | resource := &promoterv1alpha1.ClusterScmProvider{} 63 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 64 | Expect(err).NotTo(HaveOccurred()) 65 | 66 | By("Cleanup the specific resource instance ClusterScmProvider") 67 | Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 68 | }) 69 | It("should successfully reconcile the resource", func() { 70 | By("Reconciling the created resource") 71 | controllerReconciler := &ClusterScmProviderReconciler{ 72 | Client: k8sClient, 73 | Scheme: k8sClient.Scheme(), 74 | } 75 | 76 | _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ 77 | NamespacedName: typeNamespacedName, 78 | }) 79 | Expect(err).NotTo(HaveOccurred()) 80 | // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. 81 | // Example: If you expect a certain status condition after reconciliation, verify it here. 82 | }) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /internal/controller/controllerconfiguration_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 controller 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "k8s.io/apimachinery/pkg/runtime" 24 | ctrl "sigs.k8s.io/controller-runtime" 25 | "sigs.k8s.io/controller-runtime/pkg/client" 26 | "sigs.k8s.io/controller-runtime/pkg/log" 27 | 28 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 29 | ) 30 | 31 | // ControllerConfigurationReconciler reconciles a ControllerConfiguration object 32 | type ControllerConfigurationReconciler struct { 33 | client.Client 34 | Scheme *runtime.Scheme 35 | } 36 | 37 | // +kubebuilder:rbac:groups=promoter.argoproj.io,resources=controllerconfigurations,verbs=get;list;watch;create;update;patch;delete 38 | // +kubebuilder:rbac:groups=promoter.argoproj.io,resources=controllerconfigurations/status,verbs=get;update;patch 39 | // +kubebuilder:rbac:groups=promoter.argoproj.io,resources=controllerconfigurations/finalizers,verbs=update 40 | 41 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 42 | // move the current state of the cluster closer to the desired state. 43 | // TODO(user): Modify the Reconcile function to compare the state specified by 44 | // the ControllerConfiguration object against the actual cluster state, and then 45 | // perform operations to make the cluster state reflect the state specified by 46 | // the user. 47 | // 48 | // For more details, check Reconcile and its Result here: 49 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.2/pkg/reconcile 50 | func (r *ControllerConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 51 | _ = log.FromContext(ctx) 52 | 53 | // TODO(user): your logic here 54 | 55 | return ctrl.Result{}, nil 56 | } 57 | 58 | // SetupWithManager sets up the controller with the Manager. 59 | func (r *ControllerConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { 60 | err := ctrl.NewControllerManagedBy(mgr). 61 | For(&promoterv1alpha1.ControllerConfiguration{}). 62 | Named("controllerconfiguration"). 63 | Complete(r) 64 | if err != nil { 65 | return fmt.Errorf("failed to create controller: %w", err) 66 | } 67 | 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /internal/controller/controllerconfiguration_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 controller 18 | 19 | import ( 20 | "context" 21 | "time" 22 | 23 | . "github.com/onsi/ginkgo/v2" 24 | . "github.com/onsi/gomega" 25 | "k8s.io/apimachinery/pkg/api/errors" 26 | "k8s.io/apimachinery/pkg/types" 27 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 28 | 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | 31 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 32 | ) 33 | 34 | var _ = Describe("ControllerConfiguration Controller", func() { 35 | Context("When reconciling a resource", func() { 36 | const resourceName = "test-resource" 37 | 38 | ctx := context.Background() 39 | 40 | typeNamespacedName := types.NamespacedName{ 41 | Name: resourceName, 42 | Namespace: "default", // TODO(user):Modify as needed 43 | } 44 | controllerconfiguration := &promoterv1alpha1.ControllerConfiguration{} 45 | 46 | BeforeEach(func() { 47 | By("creating the custom resource for the Kind ControllerConfiguration") 48 | err := k8sClient.Get(ctx, typeNamespacedName, controllerconfiguration) 49 | if err != nil && errors.IsNotFound(err) { 50 | resource := &promoterv1alpha1.ControllerConfiguration{ 51 | ObjectMeta: metav1.ObjectMeta{ 52 | Name: resourceName, 53 | Namespace: "default", 54 | }, 55 | Spec: promoterv1alpha1.ControllerConfigurationSpec{ 56 | PullRequest: promoterv1alpha1.PullRequestConfiguration{ 57 | Template: promoterv1alpha1.PullRequestTemplate{ 58 | Title: "Automated PR for {{.PromotionStrategy}}", 59 | Description: "This PR is automatically generated by GitOps Promoter for the {{.PromotionStrategy}} promotion strategy.", 60 | }, 61 | }, 62 | PromotionStrategyRequeueDuration: metav1.Duration{Duration: time.Minute * 5}, 63 | ChangeTransferPolicyRequeueDuration: metav1.Duration{Duration: time.Minute * 5}, 64 | ArgoCDCommitStatusRequeueDuration: metav1.Duration{Duration: time.Second * 15}, 65 | }, 66 | } 67 | Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 68 | } 69 | }) 70 | 71 | AfterEach(func() { 72 | // TODO(user): Cleanup logic after each test, like removing the resource instance. 73 | resource := &promoterv1alpha1.ControllerConfiguration{} 74 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 75 | Expect(err).NotTo(HaveOccurred()) 76 | 77 | By("Cleanup the specific resource instance ControllerConfiguration") 78 | Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 79 | }) 80 | It("should successfully reconcile the resource", func() { 81 | By("Reconciling the created resource") 82 | controllerReconciler := &ControllerConfigurationReconciler{ 83 | Client: k8sClient, 84 | Scheme: k8sClient.Scheme(), 85 | } 86 | 87 | _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ 88 | NamespacedName: typeNamespacedName, 89 | }) 90 | Expect(err).NotTo(HaveOccurred()) 91 | // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. 92 | // Example: If you expect a certain status condition after reconciliation, verify it here. 93 | }) 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /internal/controller/gitrepository_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 controller 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "k8s.io/apimachinery/pkg/runtime" 24 | ctrl "sigs.k8s.io/controller-runtime" 25 | "sigs.k8s.io/controller-runtime/pkg/client" 26 | "sigs.k8s.io/controller-runtime/pkg/log" 27 | 28 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 29 | ) 30 | 31 | // GitRepositoryReconciler reconciles a GitRepository object 32 | type GitRepositoryReconciler struct { 33 | client.Client 34 | Scheme *runtime.Scheme 35 | } 36 | 37 | //+kubebuilder:rbac:groups=promoter.argoproj.io,resources=gitrepositories,verbs=get;list;watch;create;update;patch;delete 38 | //+kubebuilder:rbac:groups=promoter.argoproj.io,resources=gitrepositories/status,verbs=get;update;patch 39 | //+kubebuilder:rbac:groups=promoter.argoproj.io,resources=gitrepositories/finalizers,verbs=update 40 | 41 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 42 | // move the current state of the cluster closer to the desired state. 43 | // TODO(user): Modify the Reconcile function to compare the state specified by 44 | // the GitRepository object against the actual cluster state, and then 45 | // perform operations to make the cluster state reflect the state specified by 46 | // the user. 47 | // 48 | // For more details, check Reconcile and its Result here: 49 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.2/pkg/reconcile 50 | func (r *GitRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 51 | _ = log.FromContext(ctx) 52 | 53 | // TODO(user): your logic here 54 | 55 | return ctrl.Result{}, nil 56 | } 57 | 58 | // SetupWithManager sets up the controller with the Manager. 59 | func (r *GitRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error { 60 | err := ctrl.NewControllerManagedBy(mgr). 61 | For(&promoterv1alpha1.GitRepository{}). 62 | Complete(r) 63 | if err != nil { 64 | return fmt.Errorf("failed to create controller: %w", err) 65 | } 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /internal/controller/gitrepository_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 controller 18 | 19 | import ( 20 | "context" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | "k8s.io/apimachinery/pkg/api/errors" 25 | "k8s.io/apimachinery/pkg/types" 26 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 27 | 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | 30 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 31 | ) 32 | 33 | var _ = Describe("GitRepository Controller", func() { 34 | Context("When reconciling a resource", func() { 35 | const resourceName = "test-resource" 36 | 37 | ctx := context.Background() 38 | 39 | typeNamespacedName := types.NamespacedName{ 40 | Name: resourceName, 41 | Namespace: "default", // TODO(user):Modify as needed 42 | } 43 | gitrepository := &promoterv1alpha1.GitRepository{} 44 | 45 | BeforeEach(func() { 46 | By("creating the custom resource for the Kind GitRepository") 47 | err := k8sClient.Get(ctx, typeNamespacedName, gitrepository) 48 | if err != nil && errors.IsNotFound(err) { 49 | resource := &promoterv1alpha1.GitRepository{ 50 | ObjectMeta: metav1.ObjectMeta{ 51 | Name: resourceName, 52 | Namespace: "default", 53 | }, 54 | Spec: promoterv1alpha1.GitRepositorySpec{ 55 | ScmProviderRef: promoterv1alpha1.ScmProviderObjectReference{ 56 | Kind: promoterv1alpha1.ScmProviderKind, 57 | }, 58 | }, 59 | // TODO(user): Specify other spec details if needed. 60 | } 61 | Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 62 | } 63 | }) 64 | 65 | AfterEach(func() { 66 | // TODO(user): Cleanup logic after each test, like removing the resource instance. 67 | resource := &promoterv1alpha1.GitRepository{} 68 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 69 | Expect(err).NotTo(HaveOccurred()) 70 | 71 | By("Cleanup the specific resource instance GitRepository") 72 | Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 73 | }) 74 | It("should successfully reconcile the resource", func() { 75 | By("Reconciling the created resource") 76 | controllerReconciler := &GitRepositoryReconciler{ 77 | Client: k8sClient, 78 | Scheme: k8sClient.Scheme(), 79 | } 80 | 81 | _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ 82 | NamespacedName: typeNamespacedName, 83 | }) 84 | Expect(err).NotTo(HaveOccurred()) 85 | // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. 86 | // Example: If you expect a certain status condition after reconciliation, verify it here. 87 | }) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /internal/controller/revertcommit_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 controller 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "k8s.io/client-go/tools/record" 24 | 25 | "k8s.io/apimachinery/pkg/runtime" 26 | ctrl "sigs.k8s.io/controller-runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/log" 29 | 30 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 31 | ) 32 | 33 | // RevertCommitReconciler reconciles a RevertCommit object 34 | type RevertCommitReconciler struct { 35 | client.Client 36 | Scheme *runtime.Scheme 37 | Recorder record.EventRecorder 38 | } 39 | 40 | //+kubebuilder:rbac:groups=promoter.argoproj.io,resources=revertcommits,verbs=get;list;watch;create;update;patch;delete 41 | //+kubebuilder:rbac:groups=promoter.argoproj.io,resources=revertcommits/status,verbs=get;update;patch 42 | //+kubebuilder:rbac:groups=promoter.argoproj.io,resources=revertcommits/finalizers,verbs=update 43 | 44 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 45 | // move the current state of the cluster closer to the desired state. 46 | // TODO(user): Modify the Reconcile function to compare the state specified by 47 | // the RevertCommit object against the actual cluster state, and then 48 | // perform operations to make the cluster state reflect the state specified by 49 | // the user. 50 | // 51 | // For more details, check Reconcile and its Result here: 52 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.2/pkg/reconcile 53 | func (r *RevertCommitReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 54 | _ = log.FromContext(ctx) 55 | 56 | // TODO(user): your logic here 57 | 58 | return ctrl.Result{}, nil 59 | } 60 | 61 | // SetupWithManager sets up the controller with the Manager. 62 | func (r *RevertCommitReconciler) SetupWithManager(mgr ctrl.Manager) error { 63 | err := ctrl.NewControllerManagedBy(mgr). 64 | For(&promoterv1alpha1.RevertCommit{}). 65 | Complete(r) 66 | if err != nil { 67 | return fmt.Errorf("failed to create controller: %w", err) 68 | } 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /internal/controller/revertcommit_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 controller 18 | 19 | import ( 20 | "context" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | "k8s.io/apimachinery/pkg/api/errors" 25 | "k8s.io/apimachinery/pkg/types" 26 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 27 | 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | 30 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 31 | ) 32 | 33 | var _ = Describe("RevertCommit Controller", func() { 34 | Context("When reconciling a resource", func() { 35 | const resourceName = "test-resource" 36 | 37 | ctx := context.Background() 38 | 39 | typeNamespacedName := types.NamespacedName{ 40 | Name: resourceName, 41 | Namespace: "default", // TODO(user):Modify as needed 42 | } 43 | revertcommit := &promoterv1alpha1.RevertCommit{} 44 | 45 | BeforeEach(func() { 46 | By("creating the custom resource for the Kind RevertCommit") 47 | err := k8sClient.Get(ctx, typeNamespacedName, revertcommit) 48 | if err != nil && errors.IsNotFound(err) { 49 | resource := &promoterv1alpha1.RevertCommit{ 50 | ObjectMeta: metav1.ObjectMeta{ 51 | Name: resourceName, 52 | Namespace: "default", 53 | }, 54 | // TODO(user): Specify other spec details if needed. 55 | } 56 | Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 57 | } 58 | }) 59 | 60 | AfterEach(func() { 61 | // TODO(user): Cleanup logic after each test, like removing the resource instance. 62 | resource := &promoterv1alpha1.RevertCommit{} 63 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 64 | Expect(err).NotTo(HaveOccurred()) 65 | 66 | By("Cleanup the specific resource instance RevertCommit") 67 | Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 68 | }) 69 | It("should successfully reconcile the resource", func() { 70 | By("Reconciling the created resource") 71 | controllerReconciler := &RevertCommitReconciler{ 72 | Client: k8sClient, 73 | Scheme: k8sClient.Scheme(), 74 | } 75 | 76 | _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ 77 | NamespacedName: typeNamespacedName, 78 | }) 79 | Expect(err).NotTo(HaveOccurred()) 80 | // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. 81 | // Example: If you expect a certain status condition after reconciliation, verify it here. 82 | }) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /internal/controller/scmprovider_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 controller 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "k8s.io/client-go/tools/record" 24 | 25 | "k8s.io/apimachinery/pkg/runtime" 26 | ctrl "sigs.k8s.io/controller-runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/log" 29 | 30 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 31 | ) 32 | 33 | // ScmProviderReconciler reconciles a ScmProvider object 34 | type ScmProviderReconciler struct { 35 | client.Client 36 | Scheme *runtime.Scheme 37 | Recorder record.EventRecorder 38 | } 39 | 40 | //+kubebuilder:rbac:groups=promoter.argoproj.io,resources=scmproviders,verbs=get;list;watch;create;update;patch;delete 41 | //+kubebuilder:rbac:groups=promoter.argoproj.io,resources=scmproviders/status,verbs=get;update;patch 42 | //+kubebuilder:rbac:groups=promoter.argoproj.io,resources=scmproviders/finalizers,verbs=update 43 | 44 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 45 | // move the current state of the cluster closer to the desired state. 46 | // TODO(user): Modify the Reconcile function to compare the state specified by 47 | // the ScmProvider object against the actual cluster state, and then 48 | // perform operations to make the cluster state reflect the state specified by 49 | // the user. 50 | // 51 | // For more details, check Reconcile and its Result here: 52 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.2/pkg/reconcile 53 | func (r *ScmProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 54 | _ = log.FromContext(ctx) 55 | 56 | // TODO(user): your logic here 57 | 58 | return ctrl.Result{}, nil 59 | } 60 | 61 | // SetupWithManager sets up the controller with the Manager. 62 | func (r *ScmProviderReconciler) SetupWithManager(mgr ctrl.Manager) error { 63 | err := ctrl.NewControllerManagedBy(mgr). 64 | For(&promoterv1alpha1.ScmProvider{}). 65 | Complete(r) 66 | if err != nil { 67 | return fmt.Errorf("failed to create controller: %w", err) 68 | } 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /internal/controller/scmprovider_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 controller 18 | 19 | import ( 20 | "context" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | "k8s.io/apimachinery/pkg/api/errors" 25 | "k8s.io/apimachinery/pkg/types" 26 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 27 | 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | 30 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 31 | ) 32 | 33 | var _ = Describe("ScmProvider Controller", func() { 34 | Context("When reconciling a resource", func() { 35 | const resourceName = "test-resource" 36 | 37 | ctx := context.Background() 38 | 39 | typeNamespacedName := types.NamespacedName{ 40 | Name: resourceName, 41 | Namespace: "default", // TODO(user):Modify as needed 42 | } 43 | scmprovider := &promoterv1alpha1.ScmProvider{} 44 | 45 | BeforeEach(func() { 46 | By("creating the custom resource for the Kind ScmProvider") 47 | err := k8sClient.Get(ctx, typeNamespacedName, scmprovider) 48 | if err != nil && errors.IsNotFound(err) { 49 | resource := &promoterv1alpha1.ScmProvider{ 50 | ObjectMeta: metav1.ObjectMeta{ 51 | Name: resourceName, 52 | Namespace: "default", 53 | }, 54 | // TODO(user): Specify other spec details if needed. 55 | } 56 | Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 57 | } 58 | }) 59 | 60 | AfterEach(func() { 61 | // TODO(user): Cleanup logic after each test, like removing the resource instance. 62 | resource := &promoterv1alpha1.ScmProvider{} 63 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 64 | Expect(err).NotTo(HaveOccurred()) 65 | 66 | By("Cleanup the specific resource instance ScmProvider") 67 | Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 68 | }) 69 | It("should successfully reconcile the resource", func() { 70 | By("Reconciling the created resource") 71 | controllerReconciler := &ScmProviderReconciler{ 72 | Client: k8sClient, 73 | Scheme: k8sClient.Scheme(), 74 | } 75 | 76 | _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ 77 | NamespacedName: typeNamespacedName, 78 | }) 79 | Expect(err).NotTo(HaveOccurred()) 80 | // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. 81 | // Example: If you expect a certain status condition after reconciliation, verify it here. 82 | }) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /internal/scms/commitstatus.go: -------------------------------------------------------------------------------- 1 | package scms 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 7 | ) 8 | 9 | type CommitStatusProvider interface { 10 | Set(ctx context.Context, commitStatus *v1alpha1.CommitStatus) (*v1alpha1.CommitStatus, error) 11 | } 12 | -------------------------------------------------------------------------------- /internal/scms/common.go: -------------------------------------------------------------------------------- 1 | package scms 2 | 3 | type ScmProviderType string 4 | 5 | const ( 6 | Fake ScmProviderType = "fake" 7 | GitHub ScmProviderType = "github" 8 | GitLab ScmProviderType = "gitlab" 9 | ) 10 | -------------------------------------------------------------------------------- /internal/scms/fake/commit_status.go: -------------------------------------------------------------------------------- 1 | package fake 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 8 | "github.com/argoproj-labs/gitops-promoter/internal/scms" 9 | v1 "k8s.io/api/core/v1" 10 | ) 11 | 12 | type CommitStatus struct{} 13 | 14 | var _ scms.CommitStatusProvider = &CommitStatus{} 15 | 16 | func NewFakeCommitStatusProvider(secret v1.Secret) (*CommitStatus, error) { 17 | return &CommitStatus{}, nil 18 | } 19 | 20 | func (cs CommitStatus) Set(ctx context.Context, commitStatus *promoterv1alpha1.CommitStatus) (*promoterv1alpha1.CommitStatus, error) { 21 | if commitStatus.Spec.Sha == "" { 22 | return nil, errors.New("sha is required") 23 | } 24 | commitStatus.Status.Phase = commitStatus.Spec.Phase 25 | commitStatus.Status.Sha = commitStatus.Spec.Sha 26 | return commitStatus, nil 27 | } 28 | -------------------------------------------------------------------------------- /internal/scms/fake/git_operations.go: -------------------------------------------------------------------------------- 1 | package fake 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 9 | ginkov2 "github.com/onsi/ginkgo/v2" 10 | v1 "k8s.io/api/core/v1" 11 | ) 12 | 13 | type GitAuthenticationProvider struct { 14 | scmProvider v1alpha1.GenericScmProvider 15 | secret *v1.Secret 16 | } 17 | 18 | func NewFakeGitAuthenticationProvider(scmProvider v1alpha1.GenericScmProvider, secret *v1.Secret) GitAuthenticationProvider { 19 | return GitAuthenticationProvider{ 20 | scmProvider: scmProvider, 21 | secret: secret, 22 | } 23 | } 24 | 25 | func (gh GitAuthenticationProvider) GetGitHttpsRepoUrl(gitRepo v1alpha1.GitRepository) string { 26 | gitServerPort := 5000 + ginkov2.GinkgoParallelProcess() 27 | gitServerPortStr := strconv.Itoa(gitServerPort) 28 | 29 | if gh.scmProvider.GetSpec().Fake != nil && gh.scmProvider.GetSpec().Fake.Domain == "" { 30 | return fmt.Sprintf("http://localhost:%s/%s/%s", gitServerPortStr, gitRepo.Spec.Fake.Owner, gitRepo.Spec.Fake.Name) 31 | } 32 | return fmt.Sprintf("http://localhost:%s/%s/%s", gitServerPortStr, gitRepo.Spec.Fake.Owner, gitRepo.Spec.Fake.Name) 33 | } 34 | 35 | func (gh GitAuthenticationProvider) GetToken(ctx context.Context) (string, error) { 36 | return "", nil 37 | } 38 | 39 | func (gh GitAuthenticationProvider) GetUser(ctx context.Context) (string, error) { 40 | return "git", nil 41 | } 42 | -------------------------------------------------------------------------------- /internal/scms/git_operations.go: -------------------------------------------------------------------------------- 1 | package scms 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 7 | ) 8 | 9 | type GitOperationsProvider interface { 10 | GetGitHttpsRepoUrl(gitRepo v1alpha1.GitRepository) string 11 | GetToken(ctx context.Context) (string, error) 12 | GetUser(ctx context.Context) (string, error) 13 | } 14 | -------------------------------------------------------------------------------- /internal/scms/github/commit_status.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/google/go-github/v71/github" 10 | v1 "k8s.io/api/core/v1" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | "sigs.k8s.io/controller-runtime/pkg/log" 13 | 14 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 15 | "github.com/argoproj-labs/gitops-promoter/internal/metrics" 16 | "github.com/argoproj-labs/gitops-promoter/internal/scms" 17 | "github.com/argoproj-labs/gitops-promoter/internal/utils" 18 | ) 19 | 20 | type CommitStatus struct { 21 | client *github.Client 22 | k8sClient client.Client 23 | } 24 | 25 | var _ scms.CommitStatusProvider = &CommitStatus{} 26 | 27 | func NewGithubCommitStatusProvider(k8sClient client.Client, scmProvider promoterv1alpha1.GenericScmProvider, secret v1.Secret) (*CommitStatus, error) { 28 | client, err := GetClient(scmProvider, secret) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return &CommitStatus{ 34 | client: client, 35 | k8sClient: k8sClient, 36 | }, nil 37 | } 38 | 39 | func (cs CommitStatus) Set(ctx context.Context, commitStatus *promoterv1alpha1.CommitStatus) (*promoterv1alpha1.CommitStatus, error) { 40 | logger := log.FromContext(ctx) 41 | logger.Info("Setting Commit Phase") 42 | 43 | commitStatusS := &github.RepoStatus{ 44 | State: github.Ptr(string(commitStatus.Spec.Phase)), 45 | TargetURL: github.Ptr(commitStatus.Spec.Url), 46 | Description: github.Ptr(commitStatus.Spec.Description), 47 | Context: github.Ptr(commitStatus.Spec.Name), 48 | } 49 | 50 | gitRepo, err := utils.GetGitRepositoryFromObjectKey(ctx, cs.k8sClient, client.ObjectKey{Namespace: commitStatus.Namespace, Name: commitStatus.Spec.RepositoryReference.Name}) 51 | if err != nil { 52 | return nil, fmt.Errorf("failed to get GitRepository: %w", err) 53 | } 54 | 55 | start := time.Now() 56 | repoStatus, response, err := cs.client.Repositories.CreateStatus(ctx, gitRepo.Spec.GitHub.Owner, gitRepo.Spec.GitHub.Name, commitStatus.Spec.Sha, commitStatusS) 57 | if response != nil { 58 | metrics.RecordSCMCall(gitRepo, metrics.SCMAPICommitStatus, metrics.SCMOperationCreate, response.StatusCode, time.Since(start), getRateLimitMetrics(response.Rate)) 59 | } 60 | if err != nil { 61 | return nil, fmt.Errorf("failed to create status: %w", err) 62 | } 63 | logger.Info("github rate limit", 64 | "limit", response.Rate.Limit, 65 | "remaining", response.Rate.Remaining, 66 | "reset", response.Rate.Reset, 67 | "url", response.Request.URL) 68 | logger.V(4).Info("github response status", 69 | "status", response.Status) 70 | 71 | commitStatus.Status.Id = strconv.FormatInt(*repoStatus.ID, 10) 72 | commitStatus.Status.Phase = promoterv1alpha1.CommitStatusPhase(*repoStatus.State) 73 | commitStatus.Status.Sha = commitStatus.Spec.Sha 74 | return commitStatus, nil 75 | } 76 | -------------------------------------------------------------------------------- /internal/scms/github/git_operations.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/bradleyfalzon/ghinstallation/v2" 9 | "github.com/google/go-github/v71/github" 10 | v1 "k8s.io/api/core/v1" 11 | 12 | "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 13 | ) 14 | 15 | const ( 16 | // githubAppPrivateKeySecretKey is the key in the secret that contains the private key for the GitHub App. 17 | githubAppPrivateKeySecretKey = "githubAppPrivateKey" 18 | ) 19 | 20 | type GitAuthenticationProvider struct { 21 | scmProvider v1alpha1.GenericScmProvider 22 | secret *v1.Secret 23 | transport *ghinstallation.Transport 24 | } 25 | 26 | func NewGithubGitAuthenticationProvider(scmProvider v1alpha1.GenericScmProvider, secret *v1.Secret) GitAuthenticationProvider { 27 | itr, err := ghinstallation.New(http.DefaultTransport, scmProvider.GetSpec().GitHub.AppID, scmProvider.GetSpec().GitHub.InstallationID, secret.Data[githubAppPrivateKeySecretKey]) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | if scmProvider.GetSpec().GitHub != nil && scmProvider.GetSpec().GitHub.Domain != "" { 33 | itr.BaseURL = fmt.Sprintf("https://%s/api/v3", scmProvider.GetSpec().GitHub.Domain) 34 | } 35 | 36 | return GitAuthenticationProvider{ 37 | scmProvider: scmProvider, 38 | secret: secret, 39 | transport: itr, 40 | } 41 | } 42 | 43 | func (gh GitAuthenticationProvider) GetGitHttpsRepoUrl(gitRepository v1alpha1.GitRepository) string { 44 | if gh.scmProvider.GetSpec().GitHub != nil && gh.scmProvider.GetSpec().GitHub.Domain != "" { 45 | return fmt.Sprintf("https://git@%s/%s/%s.git", gh.scmProvider.GetSpec().GitHub.Domain, gitRepository.Spec.GitHub.Owner, gitRepository.Spec.GitHub.Name) 46 | } 47 | return fmt.Sprintf("https://git@github.com/%s/%s.git", gitRepository.Spec.GitHub.Owner, gitRepository.Spec.GitHub.Name) 48 | } 49 | 50 | func (gh GitAuthenticationProvider) GetToken(ctx context.Context) (string, error) { 51 | token, err := gh.transport.Token(ctx) 52 | if err != nil { 53 | return "", fmt.Errorf("failed to get token: %w", err) 54 | } 55 | return token, nil 56 | } 57 | 58 | func (gh GitAuthenticationProvider) GetUser(ctx context.Context) (string, error) { 59 | return "git", nil 60 | } 61 | 62 | func GetClient(scmProvider v1alpha1.GenericScmProvider, secret v1.Secret) (*github.Client, error) { 63 | itr, err := ghinstallation.New(http.DefaultTransport, scmProvider.GetSpec().GitHub.AppID, scmProvider.GetSpec().GitHub.InstallationID, secret.Data[githubAppPrivateKeySecretKey]) 64 | if err != nil { 65 | return nil, fmt.Errorf("failed to create GitHub installation transport: %w", err) 66 | } 67 | 68 | var client *github.Client 69 | if scmProvider.GetSpec().GitHub.Domain == "" { 70 | client = github.NewClient(&http.Client{Transport: itr}) 71 | } else { 72 | baseURL := fmt.Sprintf("https://%s/api/v3", scmProvider.GetSpec().GitHub.Domain) 73 | itr.BaseURL = baseURL 74 | uploadsURL := fmt.Sprintf("https://%s/api/uploads", scmProvider.GetSpec().GitHub.Domain) 75 | client, err = github.NewClient(&http.Client{Transport: itr}).WithEnterpriseURLs(baseURL, uploadsURL) 76 | if err != nil { 77 | return nil, fmt.Errorf("failed to create GitHub enterprise client: %w", err) 78 | } 79 | } 80 | 81 | return client, nil 82 | } 83 | -------------------------------------------------------------------------------- /internal/scms/github/git_operations_test.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetClient(t *testing.T) { 8 | t.Parallel() 9 | // itr, _ := ghinstallation.NewKeyFromFile(http.DefaultTransport, 865488, 49029877, "/Users/zaller/Development/promoter/argoproj-promoter.2024-04-09.private-key.pem") 10 | // token, err := itr.Token(context.Background()) 11 | // fmt.Println(token, err) 12 | 13 | // client := github.NewClient(&http.Client{Transport: itr}) 14 | 15 | // at := ghinstallation.NewAppsTransportFromPrivateKey(http.DefaultTransport, 865488, "/Users/zaller/Development/promoter/argoproj-promoter.2024-04-09.private-key.pem") 16 | } 17 | -------------------------------------------------------------------------------- /internal/scms/github/utils.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/google/go-github/v71/github" 7 | 8 | "github.com/argoproj-labs/gitops-promoter/internal/metrics" 9 | ) 10 | 11 | // getRateLimitMetrics converts the GitHub rate limit struct to one acceptable for the metrics package. 12 | func getRateLimitMetrics(rate github.Rate) *metrics.RateLimit { 13 | return &metrics.RateLimit{ 14 | Limit: rate.Limit, 15 | Remaining: rate.Remaining, 16 | ResetRemaining: time.Until(rate.Reset.Time), 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /internal/scms/gitlab/commit_status.go: -------------------------------------------------------------------------------- 1 | package gitlab 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | "time" 8 | 9 | gitlab "gitlab.com/gitlab-org/api/client-go" 10 | v1 "k8s.io/api/core/v1" 11 | "sigs.k8s.io/controller-runtime/pkg/client" 12 | "sigs.k8s.io/controller-runtime/pkg/log" 13 | 14 | "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 15 | "github.com/argoproj-labs/gitops-promoter/internal/metrics" 16 | "github.com/argoproj-labs/gitops-promoter/internal/scms" 17 | "github.com/argoproj-labs/gitops-promoter/internal/utils" 18 | ) 19 | 20 | type CommitStatus struct { 21 | client *gitlab.Client 22 | k8sClient client.Client 23 | } 24 | 25 | var _ scms.CommitStatusProvider = &CommitStatus{} 26 | 27 | func NewGitlabCommitStatusProvider(k8sClient client.Client, secret v1.Secret, domain string) (*CommitStatus, error) { 28 | client, err := GetClient(secret, domain) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return &CommitStatus{client: client, k8sClient: k8sClient}, nil 34 | } 35 | 36 | func (cs *CommitStatus) Set(ctx context.Context, commitStatus *v1alpha1.CommitStatus) (*v1alpha1.CommitStatus, error) { 37 | logger := log.FromContext(ctx) 38 | logger.Info("Setting Commit Phase") 39 | 40 | repo, err := utils.GetGitRepositoryFromObjectKey(ctx, cs.k8sClient, client.ObjectKey{ 41 | Namespace: commitStatus.Namespace, 42 | Name: commitStatus.Spec.RepositoryReference.Name, 43 | }) 44 | if err != nil { 45 | return nil, fmt.Errorf("failed to get repo: %w", err) 46 | } 47 | 48 | commitStatusOptions := &gitlab.SetCommitStatusOptions{ 49 | State: phaseToBuildState(commitStatus.Spec.Phase), 50 | TargetURL: gitlab.Ptr(commitStatus.Spec.Url), 51 | Name: gitlab.Ptr(commitStatus.Spec.Name), 52 | Description: gitlab.Ptr(commitStatus.Spec.Description), 53 | } 54 | 55 | start := time.Now() 56 | glStatus, resp, err := cs.client.Commits.SetCommitStatus( 57 | repo.Spec.GitLab.ProjectID, 58 | commitStatus.Spec.Sha, 59 | commitStatusOptions, 60 | gitlab.WithContext(ctx), 61 | ) 62 | if resp != nil { 63 | metrics.RecordSCMCall(repo, metrics.SCMAPICommitStatus, metrics.SCMOperationCreate, resp.StatusCode, time.Since(start), nil) 64 | } 65 | if err != nil { 66 | return nil, fmt.Errorf("failed to create status: %w", err) 67 | } 68 | 69 | logGitLabRateLimitsIfAvailable( 70 | logger, 71 | repo.Spec.ScmProviderRef.Name, 72 | resp, 73 | ) 74 | logger.V(4).Info("gitlab response status", 75 | "status", resp.Status) 76 | 77 | commitStatus.Status.Id = strconv.Itoa(glStatus.ID) 78 | commitStatus.Status.Phase = buildStateToPhase(gitlab.BuildStateValue(glStatus.Status)) 79 | commitStatus.Status.Sha = commitStatus.Spec.Sha 80 | return commitStatus, nil 81 | } 82 | -------------------------------------------------------------------------------- /internal/scms/gitlab/git_operations.go: -------------------------------------------------------------------------------- 1 | package gitlab 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/url" 7 | 8 | "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 9 | gitlab "gitlab.com/gitlab-org/api/client-go" 10 | v1 "k8s.io/api/core/v1" 11 | ) 12 | 13 | type GitAuthenticationProvider struct { 14 | scmProvider v1alpha1.GenericScmProvider 15 | secret *v1.Secret 16 | client *gitlab.Client 17 | } 18 | 19 | func NewGitlabGitAuthenticationProvider(scmProvider v1alpha1.GenericScmProvider, secret *v1.Secret) (*GitAuthenticationProvider, error) { 20 | client, err := GetClient(*secret, scmProvider.GetSpec().GitLab.Domain) 21 | if err != nil { 22 | return nil, fmt.Errorf("failed to create GitLab Client: %w", err) 23 | } 24 | 25 | return &GitAuthenticationProvider{ 26 | scmProvider: scmProvider, 27 | secret: secret, 28 | client: client, 29 | }, nil 30 | } 31 | 32 | func (gl GitAuthenticationProvider) GetGitHttpsRepoUrl(repo v1alpha1.GitRepository) string { 33 | var repoUrl string 34 | if gl.scmProvider.GetSpec().GitLab != nil && gl.scmProvider.GetSpec().GitLab.Domain != "" { 35 | repoUrl = fmt.Sprintf("https://%s/%s/%s.git", gl.scmProvider.GetSpec().GitLab.Domain, repo.Spec.GitLab.Namespace, repo.Spec.GitLab.Name) 36 | } else { 37 | repoUrl = fmt.Sprintf("https://gitlab.com/%s/%s.git", repo.Spec.GitLab.Namespace, repo.Spec.GitLab.Name) 38 | } 39 | if _, err := url.Parse(repoUrl); err != nil { 40 | return "" 41 | } 42 | return repoUrl 43 | } 44 | 45 | func (gl GitAuthenticationProvider) GetToken(ctx context.Context) (string, error) { 46 | return string(gl.secret.Data["token"]), nil 47 | } 48 | 49 | func (gl GitAuthenticationProvider) GetUser(ctx context.Context) (string, error) { 50 | return "oauth2", nil 51 | } 52 | 53 | func GetClient(secret v1.Secret, domain string) (*gitlab.Client, error) { 54 | token := string(secret.Data["token"]) 55 | if token == "" { 56 | return nil, fmt.Errorf("secret %q is missing required data key 'token'", secret.Name) 57 | } 58 | 59 | opts := []gitlab.ClientOptionFunc{} 60 | if domain != "" { 61 | opts = append(opts, gitlab.WithBaseURL(fmt.Sprintf("https://%s/api/v4", domain))) 62 | } 63 | 64 | client, err := gitlab.NewClient(token, opts...) 65 | if err != nil { 66 | return nil, fmt.Errorf("failed to create GitLab client: %w", err) 67 | } 68 | 69 | return client, nil 70 | } 71 | -------------------------------------------------------------------------------- /internal/scms/gitlab/utils.go: -------------------------------------------------------------------------------- 1 | package gitlab 2 | 3 | import ( 4 | "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 5 | "github.com/go-logr/logr" 6 | gitlab "gitlab.com/gitlab-org/api/client-go" 7 | ) 8 | 9 | func logGitLabRateLimitsIfAvailable( 10 | logger logr.Logger, 11 | scmProvider string, 12 | resp *gitlab.Response, 13 | ) { 14 | limit := resp.Header.Get("Ratelimit-Limit") 15 | remaining := resp.Header.Get("Ratelimit-Remaining") 16 | reset := resp.Header.Get("Ratelimit-Reset") 17 | 18 | if limit != "" || remaining != "" || reset != "" { 19 | logger.Info("GitLab rate limits", 20 | "scmProvider", scmProvider, 21 | "limit", limit, 22 | "remaining", remaining, 23 | "reset", reset, 24 | "url", resp.Request.URL.String(), 25 | ) 26 | } 27 | } 28 | 29 | func mapMergeRequestState(gl string) v1alpha1.PullRequestState { 30 | switch gl { 31 | case "opened": 32 | return v1alpha1.PullRequestOpen 33 | case "closed": 34 | return v1alpha1.PullRequestClosed 35 | default: 36 | return v1alpha1.PullRequestMerged 37 | } 38 | } 39 | 40 | func phaseToBuildState(phase v1alpha1.CommitStatusPhase) gitlab.BuildStateValue { 41 | switch phase { 42 | case v1alpha1.CommitPhaseSuccess: 43 | return gitlab.Success 44 | case v1alpha1.CommitPhasePending: 45 | return gitlab.Pending 46 | default: 47 | return gitlab.Failed 48 | } 49 | } 50 | 51 | func buildStateToPhase(buildState gitlab.BuildStateValue) v1alpha1.CommitStatusPhase { 52 | switch buildState { 53 | case gitlab.Success: 54 | return v1alpha1.CommitPhaseSuccess 55 | case gitlab.Pending: 56 | return v1alpha1.CommitPhasePending 57 | default: 58 | return v1alpha1.CommitPhaseFailure 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /internal/scms/mock/mock_CommitStatusProvider.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.42.2. DO NOT EDIT. 2 | 3 | package mock 4 | 5 | import ( 6 | context "context" 7 | 8 | mock "github.com/stretchr/testify/mock" 9 | 10 | v1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 11 | ) 12 | 13 | // MockCommitStatusProvider is an autogenerated mock type for the CommitStatusProvider type 14 | type MockCommitStatusProvider struct { 15 | mock.Mock 16 | } 17 | 18 | type MockCommitStatusProvider_Expecter struct { 19 | mock *mock.Mock 20 | } 21 | 22 | func (_m *MockCommitStatusProvider) EXPECT() *MockCommitStatusProvider_Expecter { 23 | return &MockCommitStatusProvider_Expecter{mock: &_m.Mock} 24 | } 25 | 26 | // Set provides a mock function with given fields: ctx, commitStatus 27 | func (_m *MockCommitStatusProvider) Set(ctx context.Context, commitStatus *v1alpha1.CommitStatus) (*v1alpha1.CommitStatus, error) { 28 | ret := _m.Called(ctx, commitStatus) 29 | 30 | if len(ret) == 0 { 31 | panic("no return value specified for Set") 32 | } 33 | 34 | var r0 *v1alpha1.CommitStatus 35 | var r1 error 36 | if rf, ok := ret.Get(0).(func(context.Context, *v1alpha1.CommitStatus) (*v1alpha1.CommitStatus, error)); ok { 37 | return rf(ctx, commitStatus) 38 | } 39 | if rf, ok := ret.Get(0).(func(context.Context, *v1alpha1.CommitStatus) *v1alpha1.CommitStatus); ok { 40 | r0 = rf(ctx, commitStatus) 41 | } else { 42 | if ret.Get(0) != nil { 43 | r0 = ret.Get(0).(*v1alpha1.CommitStatus) 44 | } 45 | } 46 | 47 | if rf, ok := ret.Get(1).(func(context.Context, *v1alpha1.CommitStatus) error); ok { 48 | r1 = rf(ctx, commitStatus) 49 | } else { 50 | r1 = ret.Error(1) 51 | } 52 | 53 | return r0, r1 54 | } 55 | 56 | // MockCommitStatusProvider_Set_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Set' 57 | type MockCommitStatusProvider_Set_Call struct { 58 | *mock.Call 59 | } 60 | 61 | // Set is a helper method to define mock.On call 62 | // - ctx context.Context 63 | // - commitStatus *v1alpha1.CommitStatus 64 | func (_e *MockCommitStatusProvider_Expecter) Set(ctx interface{}, commitStatus interface{}) *MockCommitStatusProvider_Set_Call { 65 | return &MockCommitStatusProvider_Set_Call{Call: _e.mock.On("Set", ctx, commitStatus)} 66 | } 67 | 68 | func (_c *MockCommitStatusProvider_Set_Call) Run(run func(ctx context.Context, commitStatus *v1alpha1.CommitStatus)) *MockCommitStatusProvider_Set_Call { 69 | _c.Call.Run(func(args mock.Arguments) { 70 | run(args[0].(context.Context), args[1].(*v1alpha1.CommitStatus)) 71 | }) 72 | return _c 73 | } 74 | 75 | func (_c *MockCommitStatusProvider_Set_Call) Return(_a0 *v1alpha1.CommitStatus, _a1 error) *MockCommitStatusProvider_Set_Call { 76 | _c.Call.Return(_a0, _a1) 77 | return _c 78 | } 79 | 80 | func (_c *MockCommitStatusProvider_Set_Call) RunAndReturn(run func(context.Context, *v1alpha1.CommitStatus) (*v1alpha1.CommitStatus, error)) *MockCommitStatusProvider_Set_Call { 81 | _c.Call.Return(run) 82 | return _c 83 | } 84 | 85 | // NewMockCommitStatusProvider creates a new instance of MockCommitStatusProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 86 | // The first argument is typically a *testing.T value. 87 | func NewMockCommitStatusProvider(t interface { 88 | mock.TestingT 89 | Cleanup(func()) 90 | }) *MockCommitStatusProvider { 91 | mock := &MockCommitStatusProvider{} 92 | mock.Mock.Test(t) 93 | 94 | t.Cleanup(func() { mock.AssertExpectations(t) }) 95 | 96 | return mock 97 | } 98 | -------------------------------------------------------------------------------- /internal/scms/pullrequest.go: -------------------------------------------------------------------------------- 1 | package scms 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 7 | ) 8 | 9 | type PullRequestProvider interface { 10 | Create(ctx context.Context, title, head, base, description string, pullRequest *v1alpha1.PullRequest) (string, error) 11 | Close(ctx context.Context, pullRequest *v1alpha1.PullRequest) error 12 | Update(ctx context.Context, title, description string, pullRequest *v1alpha1.PullRequest) error 13 | Merge(ctx context.Context, commitMessage string, pullRequest *v1alpha1.PullRequest) error 14 | FindOpen(ctx context.Context, pullRequest *v1alpha1.PullRequest) (bool, string, error) 15 | } 16 | -------------------------------------------------------------------------------- /internal/settings/manager.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | promoterv1alpha1 "github.com/argoproj-labs/gitops-promoter/api/v1alpha1" 9 | "sigs.k8s.io/controller-runtime/pkg/client" 10 | ) 11 | 12 | const ( 13 | ControllerConfigurationName = "promoter-controller-configuration" 14 | ) 15 | 16 | type ManagerConfig struct { 17 | ControllerNamespace string 18 | } 19 | 20 | type Manager struct { 21 | client client.Client 22 | config ManagerConfig 23 | } 24 | 25 | func (m *Manager) GetControllerConfiguration(ctx context.Context) (*promoterv1alpha1.ControllerConfiguration, error) { 26 | controllerConfiguration := &promoterv1alpha1.ControllerConfiguration{} 27 | if err := m.client.Get(ctx, client.ObjectKey{Name: ControllerConfigurationName, Namespace: m.config.ControllerNamespace}, controllerConfiguration); err != nil { 28 | return nil, fmt.Errorf("failed to get global promotion configuration: %w", err) 29 | } 30 | 31 | return controllerConfiguration, nil 32 | } 33 | 34 | func (m *Manager) GetPromotionStrategyRequeueDuration(ctx context.Context) (time.Duration, error) { 35 | controllerConfiguration, err := m.GetControllerConfiguration(ctx) 36 | if err != nil { 37 | return time.Duration(0), fmt.Errorf("failed to get controller configuration: %w", err) 38 | } 39 | 40 | return controllerConfiguration.Spec.PromotionStrategyRequeueDuration.Duration, nil 41 | } 42 | 43 | func (m *Manager) GetChangeTransferPolicyRequeueDuration(ctx context.Context) (time.Duration, error) { 44 | controllerConfiguration, err := m.GetControllerConfiguration(ctx) 45 | if err != nil { 46 | return time.Duration(0), fmt.Errorf("failed to get controller configuration: %w", err) 47 | } 48 | 49 | return controllerConfiguration.Spec.ChangeTransferPolicyRequeueDuration.Duration, nil 50 | } 51 | 52 | func (m *Manager) GetArgoCDCommitStatusRequeueDuration(ctx context.Context) (time.Duration, error) { 53 | controllerConfiguration, err := m.GetControllerConfiguration(ctx) 54 | if err != nil { 55 | return time.Duration(0), fmt.Errorf("failed to get controller configuration: %w", err) 56 | } 57 | 58 | return controllerConfiguration.Spec.ArgoCDCommitStatusRequeueDuration.Duration, nil 59 | } 60 | 61 | func (m *Manager) GetPullRequestRequeueDuration(ctx context.Context) (time.Duration, error) { 62 | controllerConfiguration, err := m.GetControllerConfiguration(ctx) 63 | if err != nil { 64 | return time.Duration(0), fmt.Errorf("failed to get controller configuration: %w", err) 65 | } 66 | 67 | return controllerConfiguration.Spec.PullRequestRequeueDuration.Duration, nil 68 | } 69 | 70 | func (m *Manager) GetControllerNamespace() string { 71 | return m.config.ControllerNamespace 72 | } 73 | 74 | func NewManager(client client.Client, config ManagerConfig) *Manager { 75 | return &Manager{ 76 | client: client, 77 | config: config, 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /internal/types/argocd/argocd_type.go: -------------------------------------------------------------------------------- 1 | // +kubebuilder:skip 2 | package argocd 3 | 4 | import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | 6 | // TODO: trim down the struct to only the fields that are needed 7 | 8 | type ArgoCDApplication struct { 9 | metav1.TypeMeta `json:",inline"` 10 | metav1.ObjectMeta `json:"metadata"` 11 | Spec ApplicationSpec `json:"spec"` 12 | Status ApplicationStatus `json:"status,omitempty"` 13 | } 14 | 15 | type ApplicationSpec struct { 16 | Destination ApplicationDestination `json:"destination"` 17 | Project string `json:"project"` 18 | SourceHydrator *SourceHydrator `json:"sourceHydrator,omitempty"` 19 | } 20 | 21 | type ApplicationDestination struct { 22 | Server string `json:"server,omitempty"` 23 | Namespace string `json:"namespace,omitempty"` 24 | Name string `json:"name,omitempty"` 25 | } 26 | 27 | type SyncStatus struct { 28 | Status SyncStatusCode `json:"status"` 29 | Revision string `json:"revision,omitempty"` 30 | Revisions []string `json:"revisions,omitempty"` 31 | } 32 | 33 | type SourceHydrator struct { 34 | DrySource DrySource `json:"drySource"` 35 | SyncSource SyncSource `json:"syncSource"` 36 | HydrateTo *HydrateTo `json:"hydrateTo,omitempty"` 37 | } 38 | 39 | type DrySource struct { 40 | RepoURL string `json:"repoURL"` 41 | TargetRevision string `json:"targetRevision"` 42 | Path string `json:"path"` 43 | } 44 | 45 | type SyncSource struct { 46 | TargetBranch string `json:"targetBranch"` 47 | Path string `json:"path"` 48 | } 49 | 50 | type HydrateTo struct { 51 | TargetBranch string `json:"targetBranch"` 52 | } 53 | 54 | type ApplicationStatus struct { 55 | Sync SyncStatus `json:"sync,omitempty"` 56 | Health HealthStatus `json:"health,omitempty"` 57 | SourceHydrator SourceHydratorStatus `json:"sourceHydrator,omitempty"` 58 | } 59 | 60 | // HealthStatus contains information about the currently observed health state of an application or resource 61 | type HealthStatus struct { 62 | Status HealthStatusCode `json:"status,omitempty"` 63 | Message string `json:"message,omitempty"` 64 | LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` 65 | } 66 | 67 | type SourceHydratorStatus struct { 68 | LastSuccessfulOperation *SuccessfulHydrateOperation `json:"lastSuccessfulOperation,omitempty"` 69 | CurrentOperation *HydrateOperation `json:"currentOperation,omitempty"` 70 | } 71 | 72 | // HydrateOperation contains information about the most recent hydrate operation 73 | type HydrateOperation struct { 74 | StartedAt metav1.Time `json:"startedAt,omitempty"` 75 | FinishedAt *metav1.Time `json:"finishedAt,omitempty"` 76 | Phase HydrateOperationPhase `json:"phase"` 77 | Message string `json:"message"` 78 | DrySHA string `json:"drySHA,omitempty"` 79 | HydratedSHA string `json:"hydratedSHA,omitempty"` 80 | SourceHydrator SourceHydrator `json:"sourceHydrator,omitempty"` 81 | } 82 | 83 | type HydrateOperationPhase string 84 | 85 | const ( 86 | HydrateOperationPhaseHydrating HydrateOperationPhase = "Hydrating" 87 | HydrateOperationPhaseFailed HydrateOperationPhase = "Failed" 88 | HydrateOperationPhaseHydrated HydrateOperationPhase = "Hydrated" 89 | ) 90 | 91 | // SuccessfulHydrateOperation contains information about the most recent successful hydrate operation 92 | type SuccessfulHydrateOperation struct { 93 | DrySHA string `json:"drySHA,omitempty"` 94 | HydratedSHA string `json:"hydratedSHA,omitempty"` 95 | SourceHydrator SourceHydrator `json:"sourceHydrator,omitempty"` 96 | } 97 | 98 | type HealthStatusCode string 99 | 100 | const ( 101 | HealthStatusUnknown HealthStatusCode = "Unknown" 102 | HealthStatusProgressing HealthStatusCode = "Progressing" 103 | HealthStatusHealthy HealthStatusCode = "Healthy" 104 | HealthStatusSuspended HealthStatusCode = "Suspended" 105 | HealthStatusDegraded HealthStatusCode = "Degraded" 106 | HealthStatusMissing HealthStatusCode = "Missing" 107 | ) 108 | 109 | type SyncStatusCode string 110 | 111 | const ( 112 | SyncStatusCodeUnknown SyncStatusCode = "Unknown" 113 | SyncStatusCodeSynced SyncStatusCode = "Synced" 114 | SyncStatusCodeOutOfSync SyncStatusCode = "OutOfSync" 115 | ) 116 | -------------------------------------------------------------------------------- /internal/types/constants/events.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | const ( 4 | ResolvedConflictReason = "ResolvedConflict" 5 | ResolvedConflictMessage = "Merged %s into %s with 'ours' strategy to resolve conflicts" 6 | 7 | TooManyMatchingShaReason = "TooManyMatchingSha" 8 | TooManyMatchingShaActiveMessage = "There are to many matching SHAs for the active commit status" 9 | TooManyMatchingShaProposedMessage = "There are to many matching SHAs for the proposed commit status" 10 | 11 | PullRequestCreatedReason = "PullRequestCreated" 12 | PullRequestCreatedMessage = "Pull Request %s created" 13 | 14 | PullRequestMergedReason = "PullRequestMerged" 15 | PullRequestMergedMessage = "Pull Request %s merged" 16 | ) 17 | -------------------------------------------------------------------------------- /internal/utils/gitpaths/gitpaths.go: -------------------------------------------------------------------------------- 1 | package gitpaths 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var storage sync.Map 8 | 9 | func Get(key string) string { 10 | v, ok := storage.Load(key) 11 | if !ok { 12 | return "" 13 | } 14 | //nolint:forcetypeassert 15 | return v.(string) 16 | } 17 | 18 | func GetValues() []string { 19 | var values []string 20 | storage.Range(func(key, value any) bool { 21 | //nolint:forcetypeassert 22 | values = append(values, value.(string)) 23 | return true 24 | }) 25 | return values 26 | } 27 | 28 | func Set(key string, value string) { 29 | storage.Store(key, value) 30 | } 31 | -------------------------------------------------------------------------------- /internal/utils/suite_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestInternalUtils(t *testing.T) { 11 | t.Parallel() 12 | 13 | RegisterFailHandler(Fail) 14 | 15 | c, _ := GinkgoConfiguration() 16 | 17 | RunSpecs(t, "Internal Utils Suite", c) 18 | } 19 | 20 | var _ = BeforeSuite(func() { 21 | }) 22 | 23 | var _ = AfterSuite(func() { 24 | }) 25 | -------------------------------------------------------------------------------- /internal/utils/template.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "text/template" 7 | 8 | sprig "github.com/go-task/slim-sprig/v3" 9 | ) 10 | 11 | var sanitizedSprigFuncMap = sprig.GenericFuncMap() 12 | 13 | func init() { 14 | delete(sanitizedSprigFuncMap, "env") 15 | delete(sanitizedSprigFuncMap, "expandenv") 16 | delete(sanitizedSprigFuncMap, "getHostByName") 17 | } 18 | 19 | func RenderStringTemplate(templateStr string, data any) (string, error) { 20 | tmpl, err := template.New("").Funcs(sanitizedSprigFuncMap).Parse(templateStr) 21 | if err != nil { 22 | return "", fmt.Errorf("failed to parse template: %w", err) 23 | } 24 | 25 | var buf bytes.Buffer 26 | if err := tmpl.Execute(&buf, data); err != nil { 27 | return "", fmt.Errorf("failed to execute template: %w", err) 28 | } 29 | 30 | return buf.String(), nil 31 | } 32 | -------------------------------------------------------------------------------- /internal/utils/template_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "github.com/argoproj-labs/gitops-promoter/internal/utils" 5 | . "github.com/onsi/ginkgo/v2" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("test rendering a template", func() { 10 | tests := map[string]struct { 11 | template string 12 | data any 13 | expected string 14 | wantErr bool 15 | }{ 16 | "can render template successfully": { 17 | template: "Name: {{ .Name }}", 18 | data: map[string]string{ 19 | "Name": "John", 20 | }, 21 | expected: "Name: John", 22 | }, 23 | "can render using sprig functions": { 24 | template: "Name: {{ trunc 1 .Name }}", 25 | data: map[string]string{ 26 | "Name": "John", 27 | }, 28 | expected: "Name: J", 29 | }, 30 | "cannot render using sensitive sprig functions": { 31 | template: "{{ env HOME }}", 32 | wantErr: true, 33 | }, 34 | } 35 | 36 | for name, test := range tests { 37 | It(name, func() { 38 | result, err := utils.RenderStringTemplate(test.template, test.data) 39 | if test.wantErr { 40 | Expect(err).To(HaveOccurred()) 41 | } else { 42 | Expect(err).ToNot(HaveOccurred()) 43 | } 44 | Expect(result).To(Equal(test.expected)) 45 | }) 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: GitOps Promoter - A GitOps First Environment Promotion Tool 2 | repo_url: https://github.com/argoproj-labs/gitops-promoter 3 | site_url: https://argo-gitops-promoter.readthedocs.io/ 4 | theme: 5 | font: 6 | text: Work Sans 7 | logo: assets/logo.png 8 | name: material 9 | palette: 10 | - primary: teal 11 | scheme: default 12 | toggle: 13 | icon: material/toggle-switch-off-outline 14 | name: Switch to dark mode 15 | - scheme: slate 16 | toggle: 17 | icon: material/toggle-switch 18 | name: Switch to light mode 19 | markdown_extensions: 20 | - markdown_include.include 21 | - codehilite: 22 | css_class: highlight 23 | - admonition 24 | - pymdownx.superfences 25 | nav: 26 | - Overview: index.md 27 | - Getting Started: getting-started.md 28 | - Architecture: architecture.md 29 | - CRD Specs: crd-specs.md 30 | - Gating Promotions: gating-promotions.md 31 | - CommitStatus Controllers: 32 | - Argo CD: commit-status-controllers/argocd.md 33 | - Multi-Tenancy: multi-tenancy.md 34 | - Metrics: metrics.md 35 | - Tool Comparison: tool-comparison.md 36 | - FAQs: faqs.md -------------------------------------------------------------------------------- /release.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24 2 | 3 | WORKDIR / 4 | 5 | # goreleaser runs docker build in a context that contains just the Dockerfile and the binary. 6 | 7 | COPY gitops-promoter . 8 | RUN mkdir /git 9 | COPY hack/git/promoter_askpass.sh /git/promoter_askpass.sh 10 | ENV PATH="${PATH}:/git" 11 | RUN echo "${PATH}" >> /etc/bash.bashrc 12 | USER 65532:65532 13 | ENTRYPOINT ["/gitops-promoter"] 14 | -------------------------------------------------------------------------------- /test/e2e/e2e_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 e2e 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo/v2" 24 | . "github.com/onsi/gomega" 25 | ) 26 | 27 | // Run e2e tests using the Ginkgo runner. 28 | func TestE2E(t *testing.T) { 29 | t.Parallel() 30 | RegisterFailHandler(Fail) 31 | 32 | fmt.Fprintf(GinkgoWriter, "Starting promoter suite\n") //nolint:errcheck 33 | RunSpecs(t, "e2e suite") 34 | } 35 | -------------------------------------------------------------------------------- /test/e2e/e2e_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 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 e2e 18 | 19 | import ( 20 | "fmt" 21 | "os/exec" 22 | "time" 23 | 24 | . "github.com/onsi/ginkgo/v2" 25 | . "github.com/onsi/gomega" 26 | 27 | "github.com/argoproj-labs/gitops-promoter/test/utils" 28 | ) 29 | 30 | const namespace = "promoter-system" 31 | 32 | var _ = Describe("controller", Ordered, func() { 33 | BeforeAll(func() { 34 | By("installing prometheus operator") 35 | Expect(utils.InstallPrometheusOperator()).To(Succeed()) 36 | 37 | By("installing the cert-manager") 38 | Expect(utils.InstallCertManager()).To(Succeed()) 39 | 40 | By("creating manager namespace") 41 | cmd := exec.Command("kubectl", "create", "ns", namespace) 42 | _, _ = utils.Run(cmd) 43 | }) 44 | 45 | AfterAll(func() { 46 | By("uninstalling the Prometheus manager bundle") 47 | utils.UninstallPrometheusOperator() 48 | 49 | By("uninstalling the cert-manager bundle") 50 | utils.UninstallCertManager() 51 | 52 | By("removing manager namespace") 53 | cmd := exec.Command("kubectl", "delete", "ns", namespace) 54 | _, _ = utils.Run(cmd) 55 | }) 56 | 57 | Context("Operator", func() { 58 | It("should run successfully", func() { 59 | var controllerPodName string 60 | var err error 61 | 62 | // projectimage stores the name of the image used in the example 63 | var ( 64 | imageTag = "0.0.0-test-e2e" 65 | projectimage = "quay.io/argoprojlabs/gitops-promoter:" + imageTag 66 | ) 67 | 68 | By("building the manager(Operator) image") 69 | cmd := exec.Command("make", "docker-build", "IMAGE_TAG="+imageTag) 70 | _, err = utils.Run(cmd) 71 | ExpectWithOffset(1, err).NotTo(HaveOccurred()) 72 | 73 | By("loading the the manager(Operator) image on Kind") 74 | err = utils.LoadImageToKindClusterWithName(projectimage) 75 | ExpectWithOffset(1, err).NotTo(HaveOccurred()) 76 | 77 | By("installing CRDs") 78 | cmd = exec.Command("make", "install") 79 | _, err = utils.Run(cmd) 80 | ExpectWithOffset(1, err).NotTo(HaveOccurred()) 81 | 82 | By("deploying the controller-manager") 83 | cmd = exec.Command("make", "deploy", "IMAGE_TAG="+imageTag) 84 | _, err = utils.Run(cmd) 85 | ExpectWithOffset(1, err).NotTo(HaveOccurred()) 86 | 87 | By("validating that the controller-manager pod is running as expected") 88 | verifyControllerUp := func() error { 89 | // Get pod name 90 | 91 | cmd = exec.Command("kubectl", "get", 92 | "pods", "-l", "control-plane=controller-manager", 93 | "-o", "go-template={{ range .items }}"+ 94 | "{{ if not .metadata.deletionTimestamp }}"+ 95 | "{{ .metadata.name }}"+ 96 | "{{ \"\\n\" }}{{ end }}{{ end }}", 97 | "-n", namespace, 98 | ) 99 | 100 | podOutput, err := utils.Run(cmd) 101 | ExpectWithOffset(2, err).NotTo(HaveOccurred()) 102 | podNames := utils.GetNonEmptyLines(string(podOutput)) 103 | if len(podNames) != 1 { 104 | return fmt.Errorf("expect 1 controller pods running, but got %d", len(podNames)) 105 | } 106 | controllerPodName = podNames[0] 107 | ExpectWithOffset(2, controllerPodName).Should(ContainSubstring("controller-manager")) 108 | 109 | // Validate pod status 110 | cmd = exec.Command("kubectl", "get", 111 | "pods", controllerPodName, "-o", "jsonpath={.status.phase}", 112 | "-n", namespace, 113 | ) 114 | status, err := utils.Run(cmd) 115 | ExpectWithOffset(2, err).NotTo(HaveOccurred()) 116 | if string(status) != "Running" { 117 | return fmt.Errorf("controller pod in %s status", status) 118 | } 119 | return nil 120 | } 121 | EventuallyWithOffset(1, verifyControllerUp, time.Minute, time.Second).Should(Succeed()) 122 | }) 123 | }) 124 | }) 125 | --------------------------------------------------------------------------------