├── .dockerignore ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── design_proposal.md │ ├── feature_request.md │ └── question.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── build.yml │ ├── codeql-analysis.yml │ └── olm.yml ├── .gitignore ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── PROJECT ├── README.md ├── REVIEWING.md ├── api └── v1 │ ├── agent_types.go │ ├── agentaction_types.go │ ├── agentaction_types_test.go │ ├── agentconfig_types.go │ ├── agentconfig_types_test.go │ ├── const.go │ ├── credentialset_types.go │ ├── credentialset_types_test.go │ ├── groupversion_info.go │ ├── groupversion_info_test.go │ ├── installation_types.go │ ├── installation_types_test.go │ ├── installationoutput_types.go │ ├── parameterset_types.go │ ├── parameterset_types_test.go │ ├── porter_resource.go │ ├── porter_resource_test.go │ ├── porterconfig_types.go │ ├── porterconfig_types_test.go │ ├── testdata │ ├── credential-set.yaml │ ├── installation.yaml │ ├── parameter-set.yaml │ ├── plugins.yaml │ └── porter-config.yaml │ └── zz_generated.deepcopy.go ├── codecov.yaml ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ ├── getporter.org_agentactions.yaml │ │ ├── getporter.org_agentconfigs.yaml │ │ ├── getporter.org_credentialsets.yaml │ │ ├── getporter.org_installationoutputs.yaml │ │ ├── getporter.org_installations.yaml │ │ ├── getporter.org_parametersets.yaml │ │ └── getporter.org_porterconfigs.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_agentactions.yaml │ │ ├── cainjection_in_agentconfig.yaml │ │ ├── cainjection_in_credentialsets.yaml │ │ ├── cainjection_in_installationoutputs.yaml │ │ ├── cainjection_in_installations.yaml │ │ ├── cainjection_in_parametersets.yaml │ │ ├── cainjection_in_porterconfigs.yaml │ │ ├── webhook_in_agentactions.yaml │ │ ├── webhook_in_agentconfig.yaml │ │ ├── webhook_in_credentialsets.yaml │ │ ├── webhook_in_installationoutputs.yaml │ │ ├── webhook_in_installations.yaml │ │ ├── webhook_in_parametersets.yaml │ │ └── webhook_in_porterconfigs.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ └── manager_config_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── agent_role.yaml │ ├── agentaction_editor_role.yaml │ ├── agentaction_viewer_role.yaml │ ├── agentconfig_editor_role.yaml │ ├── agentconfig_viewer_role.yaml │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── credentialset_editor_role.yaml │ ├── credentialset_viewer_role.yaml │ ├── installation_editor_role.yaml │ ├── installation_viewer_role.yaml │ ├── installationoutput_editor_role.yaml │ ├── installationoutput_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── parameterset_editor_role.yaml │ ├── parameterset_viewer_role.yaml │ ├── porterconfig_editor_role.yaml │ ├── porterconfig_viewer_role.yaml │ ├── role.yaml │ └── role_binding.yaml ├── samples │ ├── _v1_agentaction.yaml │ ├── _v1_agentconfig.yaml │ ├── _v1_credentialset.yaml │ ├── _v1_installationoutput.yaml │ ├── _v1_parameterset.yaml │ ├── _v1_porterconfig.yaml │ ├── exec-outputs.yaml │ ├── hello-llama.yaml │ ├── kubeflow.yaml │ ├── kustomization.yaml │ └── porter-hello.yaml └── scorecard │ ├── bases │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ ├── basic.config.yaml │ └── olm.config.yaml ├── controllers ├── agentaction_controller.go ├── agentaction_controller_test.go ├── agentconfig_controller.go ├── agentconfig_controller_test.go ├── credentialset_controller.go ├── credentialset_controller_test.go ├── generate.go ├── installation_controller.go ├── installation_controller_test.go ├── logs.go ├── parameterset_controller.go ├── parameterset_controller_test.go ├── porter_resource.go ├── porter_resource_test.go └── types.go ├── docker-bake.json ├── docs └── content │ ├── _index.md │ ├── administrators │ └── configure-porter-agent.md │ ├── file-formats.md │ ├── glossary.md │ ├── install.md │ ├── operator.png │ └── quickstart │ ├── _index.md │ └── llama.yaml ├── go.mod ├── go.sum ├── hack ├── arm-mongodb-params.yaml ├── arm-mongodb-vals.tmpl.yaml ├── boilerplate.go.txt ├── creds.yaml ├── dev-build-params.yaml └── website-redirect.html ├── installer-olm ├── .dockerignore ├── .gitignore ├── README.md ├── helpers.sh └── porter.yaml ├── installer ├── .dockerignore ├── .gitignore ├── Dockerfile.tmpl ├── azure.porter.yaml ├── helpers.sh ├── manifests │ ├── kustomization.yaml │ └── namespace │ │ ├── agentconfig │ │ └── .keep │ │ ├── defaults │ │ └── porter-config-spec.yaml │ │ ├── namespace.yaml │ │ ├── porter-agent-binding.yaml │ │ ├── porter-agent.yaml │ │ ├── porter-agentconfig.yaml │ │ └── porter-config.yaml └── vanilla.porter.yaml ├── mage.go ├── mage ├── README.md ├── docs │ ├── docs.go │ └── docs_test.go ├── env.go └── tests │ ├── kind.config.yaml │ └── local-registry.yaml ├── magefiles └── magefile.go ├── main.go ├── mocks └── grpc │ ├── clientconn_mocks.go │ └── grpc_mocks.go ├── netlify.toml ├── staticcheck.conf └── tests └── integration ├── agentconfig_test.go ├── credset_test.go ├── installation_test.go ├── paramset_test.go ├── suite_test.go ├── testdata └── operator_porter_config.yaml └── utils_test.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore all files which are not go type 3 | !**/*.go 4 | !**/*.mod 5 | !**/*.sum 6 | 7 | # Don't pull in the dependencies for mage 8 | mage.go 9 | magefile.go 10 | magefiles/* 11 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/articles/about-code-owners#codeowners-syntax 2 | # Add your name to this file if you want to be automatically assign to pull requests 3 | # See OWNERS.md for a list of all maintainers 4 | 5 | /docs/content/ @iennae 6 | 7 | * @schristoff @sgettys @bdegeeter @troy0820 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | A clear and concise description of what the bug is. 12 | 13 | ## To Reproduce 14 | Steps to reproduce the behavior: 15 | 1. Run `...` 16 | 2. Use this custom-resource.yaml from this repository or gist 17 | 3. Run this operator command `...` 18 | 4. See error 19 | 20 | ## Expected behavior 21 | A clear and concise description of what you expected to happen. 22 | 23 | ## Operator execution and Output 24 | ``` 25 | $ kubectl apply -f .yaml 26 | lots of output 27 | ``` 28 | 29 | ## Version 30 | Please provide the AgentConfig for the porter-agent and the operator version number that's used to reproduce the bug -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/design_proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Design Proposal 3 | about: Propose a new design 4 | title: '' 5 | labels: proposal 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What design is being proposed?** 11 | Supply a summary of the design, perhaps with a problem statement that the proposal is intending to solve. 12 | 13 | **Additional Context** 14 | Provide further details and context around the proposal. What areas of Operator will it affect? (For example: CRD, controller, etc.) 15 | 16 | **Risks/Concerns** 17 | What are some potential concerns with the proposal? (For example: heavy development cost, extensive refactoring, high complexity.) 18 | 19 | **What other approaches were considered?** 20 | Provide other approaches considered prior to deciding on the submitted proposal. 21 | 22 | **Implementation details** 23 | If applicable, provide potential implementation details assuming the proposal is accepted. 24 | 25 | # Checklist 26 | - [ ] This proposal has remained open for at least one week, to allow time for community feedback. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: suggestion 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What is your question?** 11 | Clearly describe the task that you are trying to accomplish and what is confusing. 12 | 13 | **What have you tried already?** 14 | If you've already tried making this work, please share your custom resource definition and anything else to give us context, such as porter's output. Explain why it didn't work for you. 15 | 16 | **Where have you looked already to figure this out?** 17 | Help us improve our documentation! Let us know what pages you looked at first to find this information so that we can put the answer where people are looking. 18 | 19 | **Was the existing documentation unclear or had gaps?** 20 | If you found a page that should have answered your question but didn't, let us know what would have been helpful to have on that page so we can improve it. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: gomod 9 | directory: / 10 | schedule: 11 | interval: weekly 12 | day: sunday 13 | labels: 14 | - "dependabot 🤖" 15 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # What does this change 2 | _Give a summary of the change, and how it affects end-users. It's okay to copy/paste your commit messages._ 3 | 4 | _For example if it introduces a new command or modifies a commands output, give an example of you running the command and showing real output here._ 5 | 6 | # What issue does it fix 7 | Closes # _(issue)_ 8 | 9 | _If there is not an existing issue, please make sure we have context on why this change is needed. See our Contributing Guide for [examples of when an existing issue isn't necessary][1]._ 10 | 11 | [1]: https://getporter.org/src/CONTRIBUTING.md#when-to-open-a-pull-request 12 | 13 | # Notes for the reviewer 14 | _Put any questions or notes for the reviewer here._ 15 | 16 | # Checklist 17 | - [ ] Did you write tests? 18 | - [ ] Did you write documentation? 19 | - [ ] Did you make any API changes? Update the corresponding API documentation. 20 | 21 | [contributors]: https://getporter.org/src/CONTRIBUTORS.md -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | workflow_dispatch: {} 4 | push: 5 | branches: 6 | - "main" 7 | tags: 8 | - 'v*' 9 | pull_request: {} 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | - uses: actions/setup-go@v3 20 | with: 21 | go-version-file: go.mod 22 | cache: true 23 | cache-dependency-path: go.sum 24 | - name: Set up Mage 25 | run: go run mage.go EnsureMage 26 | - name: VetLint 27 | run: mage -v vet lint 28 | - name: Test 29 | run: mage -v Test 30 | env: 31 | PORTER_TEST_WAIT_TIMEOUT: 2m 32 | - name: Report Unit Test Coverage 33 | uses: codecov/codecov-action@v3 34 | with: 35 | files: ./coverage-unit.out 36 | flags: unittests 37 | - name: Report Integration Test Coverage 38 | uses: codecov/codecov-action@v3 39 | with: 40 | files: ./coverage-integration.out 41 | flags: integration-tests 42 | - name: Run Trivy vulnerability scanner 43 | uses: aquasecurity/trivy-action@master 44 | with: 45 | image-ref: ${{ env.MANAGER_IMAGE }} 46 | format: sarif 47 | output: trivy-results.sarif 48 | - name: Upload Trivy scan results to GitHub Security tab 49 | uses: github/codeql-action/upload-sarif@v2 50 | with: 51 | sarif_file: trivy-results.sarif 52 | - name: Login to Docker Hub 53 | if: ${{ github.event_name != 'pull_request' }} 54 | uses: docker/login-action@v1 55 | with: 56 | registry: ghcr.io 57 | username: ${{ secrets.GHCR_USER }} 58 | password: ${{ secrets.GHCR_TOKEN }} 59 | - name: Publish 60 | if: ${{ github.event_name != 'pull_request' }} 61 | run: | 62 | if [[ "${GITHUB_REPOSITORY}" != "getporter/operator" ]]; then 63 | # If publishing from fork PORTER_OPERATOR_REGISTRY 64 | # must be set as a CI env variable and GHCR_USER and GHCR_TOKEN set 65 | # as secrets with permissions to publish images 66 | export PORTER_ENV=fork 67 | export PORTER_OPERATOR_REGISTRY=${{ vars.PORTER_OPERATOR_REGISTRY }} 68 | fi 69 | mage -v Publish 70 | env: 71 | PORTER_ENV: production 72 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/github/codeql-action#usage 2 | name: "CodeQL" 3 | 4 | on: 5 | workflow_dispatch: {} 6 | push: 7 | branches: [ main ] 8 | pull_request: 9 | # The branches below must be a subset of the branches above 10 | branches: [ main ] 11 | schedule: 12 | - cron: '34 21 * * 4' 13 | 14 | jobs: 15 | analyze: 16 | name: Analyze 17 | runs-on: ubuntu-latest 18 | permissions: 19 | actions: read 20 | contents: read 21 | security-events: write 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | language: [ 'go' ] 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v3 31 | 32 | # Initializes the CodeQL tools for scanning. 33 | - name: Initialize CodeQL 34 | uses: github/codeql-action/init@v2 35 | with: 36 | languages: ${{ matrix.language }} 37 | 38 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 39 | # If this step fails, then you should remove it and run the build manually (see below) 40 | - name: Autobuild 41 | uses: github/codeql-action/autobuild@v2 42 | 43 | # ℹ️ Command-line programs to run using the OS shell. 44 | # 📚 https://git.io/JvXDl 45 | 46 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 47 | # and modify them (or add more) to build your code if your project 48 | # uses a compiled language 49 | 50 | #- run: | 51 | # make bootstrap 52 | # make release 53 | 54 | - name: Perform CodeQL Analysis 55 | uses: github/codeql-action/analyze@v2 56 | -------------------------------------------------------------------------------- /.github/workflows/olm.yml: -------------------------------------------------------------------------------- 1 | name: Publish OLM Bundle 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'installer-olm/**' 9 | pull_request: 10 | paths: 11 | - 'installer-olm/**' 12 | 13 | jobs: 14 | publish: 15 | name: Publish OLM 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Setup Porter 19 | uses: getporter/gh-action@v0.1.3 20 | - name: Login to Docker Hub 21 | if: ${{ github.event_name != 'pull_request' }} 22 | uses: docker/login-action@v1 23 | with: 24 | registry: ghcr.io 25 | username: ${{ secrets.GHCR_USER }} 26 | password: ${{ secrets.GHCR_TOKEN }} 27 | - name: Porter Publish 28 | if: ${{ github.event_name != 'pull_request' }} 29 | run: porter publish -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin 8 | docs/public/ 9 | testbin/* 10 | operator 11 | /kind.config 12 | /kind.config.yaml 13 | 14 | # Custom dev config 15 | hack/arm-mongodb-vals.yaml 16 | 17 | # Test binary, build with `go test -c` 18 | *.test 19 | 20 | # Output of the go coverage tool, specifically when used with LiteIDE 21 | *.out 22 | 23 | # Kubernetes Generated files - skip generated files, except for vendored files 24 | !vendor/**/zz_generated.* 25 | /manifests.yaml 26 | installer/manifests/operator.yaml 27 | installer/porter.yaml 28 | 29 | # editor and IDE paraphernalia 30 | .env 31 | .envrc 32 | .idea 33 | *.swp 34 | *.swo 35 | *~ 36 | coverage/coverage.html 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.buildFlags": [ 3 | "-tags=integration", 4 | ], 5 | "go.testTags": "!integration,!mage", 6 | "go.testFlags": [ 7 | "-count=1", 8 | "-v" 9 | ], 10 | "go.coverOnSave": true, 11 | "go.coverageDecorator": { 12 | "type": "gutter" 13 | }, 14 | "go.coverOnSingleTest": true 15 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported to project admins via email - Carolyn Van Slyck (`me@carolynvanslyck.com`) 59 | - or via direct message in [Slack] to Carolyn Van Slyck (`@carolynvs`) or Jeremy Rickard (`@Jeremy Rickard`). 60 | All complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | 74 | [homepage]: https://www.contributor-covenant.org 75 | [slack]: https://getporter.org/community#slack 76 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.4 2 | 3 | # Build the manager binary 4 | FROM golang:1.21 as builder 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 --mount=type=cache,target=/go/pkg/mod \ 13 | go mod download 14 | 15 | # Copy the go source 16 | COPY main.go main.go 17 | COPY --link api/ api/ 18 | COPY --link controllers/ controllers/ 19 | 20 | # Build 21 | RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \ 22 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o manager main.go 23 | 24 | # Use distroless as minimal base image to package the manager binary 25 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 26 | FROM gcr.io/distroless/static 27 | WORKDIR /app 28 | COPY --from=builder --chown=65532.0 /workspace/manager . 29 | USER 65532 30 | 31 | ENTRYPOINT ["/app/manager"] 32 | -------------------------------------------------------------------------------- /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: getporter.org 6 | layout: 7 | - go.kubebuilder.io/v3 8 | plugins: 9 | manifests.sdk.operatorframework.io/v2: {} 10 | scorecard.sdk.operatorframework.io/v2: {} 11 | projectName: porter-operator 12 | repo: get.porter.sh/operator 13 | resources: 14 | - api: 15 | crdVersion: v1 16 | domain: getporter.org 17 | kind: Installation 18 | path: get.porter.sh/operator/api/v1 19 | version: v1 20 | - api: 21 | crdVersion: v1 22 | domain: getporter.org 23 | kind: AgentConfig 24 | path: get.porter.sh/operator/api/v1 25 | version: v1 26 | - api: 27 | crdVersion: v1 28 | domain: getporter.org 29 | kind: PorterConfig 30 | path: get.porter.sh/operator/api/v1 31 | version: v1 32 | - api: 33 | crdVersion: v1 34 | domain: getporter.org 35 | kind: AgentAction 36 | path: get.porter.sh/operator/api/v1 37 | version: v1 38 | - api: 39 | crdVersion: v1 40 | namespaced: true 41 | controller: true 42 | domain: getporter.org 43 | kind: CredentialSet 44 | path: get.porter.sh/operator/api/v1 45 | version: v1 46 | - api: 47 | crdVersion: v1 48 | namespaced: true 49 | controller: true 50 | domain: getporter.org 51 | kind: ParameterSet 52 | path: get.porter.sh/operator/api/v1 53 | version: v1 54 | - api: 55 | crdVersion: v1 56 | namespaced: true 57 | domain: getporter.org 58 | kind: InstallationOutput 59 | path: get.porter.sh/operator/api/v1 60 | version: v1 61 | version: "3" 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Build Status](https://github.com/getporter/operator/workflows/build/badge.svg)](https://github.com/getporter/operator/actions?query=workflow:pr) 4 | 5 | # Porter Operator 6 | 7 | 🚨 **This is a new project; the goals below are aspirational and not all implemented yet.** 8 | 9 | The Porter Operator gives you a native, integrated experience for managing your 10 | bundles from Kubernetes. It is the recommended way to automate your bundle 11 | pipeline with support for GitOps. 12 | 13 | * Manage bundle installations using desired state configuration. 14 | * Installs the bundle when an installation CRD is added. 15 | * Upgrades the bundle when the bundle definition or values used to install the bundle change. 16 | * Uninstalls the bundle when the installation CRD is deleted or when spec.uninstalled is set to true. 17 | * Automatically deploy new versions of bundles when a new version is pushed, and update an 18 | installation when changes are pushed in git, through integration with Flux. 19 | * Isolated environments for running bundles in your organization, limiting 20 | access to secrets used by the bundles using namespaces and RBAC. 21 | * Create and respond to events on your cluster to integrate bundles into your 22 | pipeline. 23 | 24 |

Learn all about Porter at getporter.org/operator/

25 | 26 | # Project Status 27 | 28 | 🚧 **It is not safe to use in production or with production secrets.**🚧 29 | 30 | We are planning a security review and audit before it is released. 31 | 32 | # Try it out 33 | 34 | Follow our [QuickStart] to install the Porter Operator on an existing Kubernetes cluster. 35 | If you want to build from source, instructions are in the [Contributor's Guide]. 36 | 37 | # Contact 38 | 39 | * [Mailing List] - Great for following the project at a high level because it is low traffic, mostly release notes and blog posts on new features. 40 | * [Slack] - Discuss #porter or #cnab with other users and the maintainers. 41 | * [Open an Issue] - If you have a bug, feature request or question about Porter, ask on GitHub so that we can prioritize it and make sure you get an answer. 42 | If you ask on Slack, we will probably turn around and make an issue anyway. 😉 43 | 44 | [Mailing List]: https://getporter.org/mailing-list 45 | [Slack]: https://getporter.org/community/#slack 46 | [Open an Issue]: https://github.com/getporter/operator/issues/new 47 | 48 | --- 49 | 50 | # Looking for Contributors 51 | 52 | Want to work on Porter with us? 💖 We are actively seeking out new contributors 53 | with the hopes of building up both casual contributors and enticing some of you 54 | into becoming reviewers and maintainers. 55 | 56 |

Start with our New Contributors Guide 57 | 58 | Porter wouldn't be possible without our [contributors][contributors], carrying 59 | the load and making it better every day! 🙇‍♀️ 60 | 61 | [contributors]: https://getporter.org/src/CONTRIBUTORS.md 62 | 63 | --- 64 | 65 | # Roadmap 66 | 67 | Porter is an open-source project and things get done as quickly as we have motivated contributors working on features that interest them. 😉 68 | 69 | We use a single [project board][board] across all of our repositories to track open issues and pull requests. 70 | 71 | The roadmap represents what the core maintainers have said that they are currently working on and plan to work on over the next few months. We use the 72 | "on-hold" bucket to communicate items of interest that doesn't have a core maintainer who will be working on it. 73 | 74 |

Check out our roadmap

75 | 76 | [board]: https://getporter.org/board 77 | [Contributor's Guide]: CONTRIBUTING.md 78 | [connect]: CONTRIBUTING.md#connect-to-the-in-cluster-mongo-database 79 | [QuickStart]: /docs/content/quickstart/_index.md 80 | -------------------------------------------------------------------------------- /REVIEWING.md: -------------------------------------------------------------------------------- 1 | # Cut a Release 2 | 3 | 🧀💨 4 | 5 | Our CI system watches for tags, and when a tag is pushed, cuts a release 6 | of Porter. When you are asked to cut a new release, here is the process: 7 | 8 | 1. Figure out the correct version number using our [version strategy]. 9 | * Bump the major segment if there are any breaking changes, and the 10 | version is greater than v1.0.0 11 | * Bump the minor segment if there are new features only. 12 | * Bump the patch segment if there are bug fixes only. 13 | * Bump the pre-release number (version-prerelease.NUMBER) if this is 14 | a pre-release, e.g. alpha/beta/rc. 15 | 1. First, ensure that the main CI build has already passed for 16 | the [commit that you want to tag][commits], and has published the canary binaries. 17 | 18 | Then create the tag and push it: 19 | 20 | ``` 21 | git checkout main 22 | git pull 23 | git tag VERSION -a -m "" 24 | git push --tags 25 | ``` 26 | If the CI build failed to build for the release, fix the problem first. 27 | Then increment the PATCH version, e.g. v0.7.0->v0.7.1, and go through the above steps again to publish the binaries. 28 | It's often a good pratice to finish the release first before updating any of our docs that references the latest release. 29 | 30 | 1. Generate some release notes and put them into the release on GitHub. 31 | - Go to Operator Github repository and find the newly created release tag. You should see a 32 | "auto generate release notes" button to create release notes for the release. 33 | - Modify the generated release note to call out any breaking or notable changes in the release. 34 | - Include instructions for installing or upgrading to the new release: 35 | ``` 36 | # Install or Upgrade 37 | Run (or re-run) the installation from https://getporter.org/operator/install/ to get the 38 | latest version of operator. 39 | ``` 40 | 1. Announce the new release in the community. 41 | - Email the [mailing list](https://getporter.org/mailing-list) to announce the release. In your email, call out any breaking or notable changes. 42 | - Post a message in [Porter's slack channel](https://getporter.org/community/#slack). 43 | 1. If there are any issues fixed in the release and someone is waiting for the fix, comment on the issue to let them know and link to the release. 44 | 1. If the release contains new features, it should be announced through a [blog](https://getporter.org/blog/) post and on Porter's twitter account. 45 | 46 | [maintainers]: https://github.com/orgs/getporter/teams/maintainers 47 | [admins]: https://github.com/orgs/getporter/teams/admins 48 | [commits]: https://github.com/getporter/porter/commits/main 49 | [version strategy]: https://getporter.org/project/version-strategy/ 50 | -------------------------------------------------------------------------------- /api/v1/agent_types.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | // AgentPhase are valid statuses of a Porter agent job 4 | // that is managing a change to a Porter resource. 5 | type AgentPhase string 6 | 7 | const ( 8 | // PhaseUnknown means that we don't know what porter is doing yet. 9 | PhaseUnknown AgentPhase = "Unknown" 10 | 11 | // PhasePending means that Porter's execution is pending. 12 | PhasePending AgentPhase = "Pending" 13 | 14 | // PhaseRunning indicates that Porter is running. 15 | PhaseRunning AgentPhase = "Running" 16 | 17 | // PhaseSucceeded means that calling Porter succeeded. 18 | PhaseSucceeded AgentPhase = "Succeeded" 19 | 20 | // PhaseFailed means that calling Porter failed. 21 | PhaseFailed AgentPhase = "Failed" 22 | ) 23 | 24 | // AgentConditionType are valid conditions of a Porter agent job 25 | // that is managing a change to a Porter resource. 26 | type AgentConditionType string 27 | 28 | const ( 29 | // ConditionScheduled means that the Porter agent has been scheduled. 30 | ConditionScheduled AgentConditionType = "Scheduled" 31 | 32 | // ConditionStarted means that the Porter agent has started. 33 | ConditionStarted AgentConditionType = "Started" 34 | 35 | // ConditionComplete means the Porter agent has completed successfully. 36 | ConditionComplete AgentConditionType = "Completed" 37 | 38 | // ConditionFailed means the Porter agent failed. 39 | ConditionFailed AgentConditionType = "Failed" 40 | ) 41 | -------------------------------------------------------------------------------- /api/v1/agentaction_types.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // AgentActionSpec defines the desired state of AgentAction 9 | type AgentActionSpec struct { 10 | // AgentConfig is the name of an AgentConfig to use instead of the AgentConfig defined at the namespace or system level. 11 | // +optional 12 | AgentConfig *corev1.LocalObjectReference `json:"agentConfig,omitempty"` 13 | 14 | // Command to run inside the Porter Agent job. Defaults to running the agent. 15 | Command []string `json:"command,omitempty"` 16 | 17 | // Args to pass to the Porter Agent job. This should be the porter command that you want to run. 18 | Args []string `json:"args,omitempty"` 19 | 20 | // Files that should be present in the working directory where the command is run. 21 | Files map[string][]byte `json:"files,omitempty"` 22 | 23 | // Env variables to set on the Porter Agent job. 24 | Env []corev1.EnvVar `json:"env,omitempty"` 25 | 26 | // EnvFrom allows setting environment variables on the Porter Agent job, using secrets or config maps as the source. 27 | EnvFrom []corev1.EnvFromSource `json:"envFrom,omitempty"` 28 | 29 | // VolumeMounts that should be defined on the Porter Agent job. 30 | VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` 31 | 32 | // Volumes that should be defined on the Porter Agent job. 33 | Volumes []corev1.Volume `json:"volumes,omitempty"` 34 | } 35 | 36 | // AgentActionStatus defines the observed state of AgentAction 37 | type AgentActionStatus struct { 38 | // The last generation observed by the controller. 39 | ObservedGeneration int64 `json:"observedGeneration,omitempty"` 40 | 41 | // The currently active job that is running the Porter Agent. 42 | Job *corev1.LocalObjectReference `json:"job,omitempty"` 43 | 44 | // The current status of the agent. 45 | // Possible values are: Unknown, Pending, Running, Succeeded, and Failed. 46 | // +kubebuilder:validation:Type=string 47 | Phase AgentPhase `json:"phase,omitempty"` 48 | 49 | // Conditions store a list of states that have been reached. 50 | // Each condition refers to the status of the Job 51 | // Possible conditions are: Scheduled, Started, Completed, and Failed 52 | Conditions []metav1.Condition `json:"conditions,omitempty"` 53 | } 54 | 55 | // +kubebuilder:object:root=true 56 | // +kubebuilder:subresource:status 57 | 58 | // AgentAction is the Schema for the agentactions API 59 | type AgentAction struct { 60 | metav1.TypeMeta `json:",inline"` 61 | metav1.ObjectMeta `json:"metadata,omitempty"` 62 | 63 | Spec AgentActionSpec `json:"spec,omitempty"` 64 | Status AgentActionStatus `json:"status,omitempty"` 65 | } 66 | 67 | func (a *AgentAction) GetConditions() *[]metav1.Condition { 68 | return &a.Status.Conditions 69 | } 70 | 71 | // GetRetryLabelValue returns a value that is safe to use 72 | // as a label value and represents the retry annotation used 73 | // to trigger reconciliation. 74 | func (a *AgentAction) GetRetryLabelValue() string { 75 | return getRetryLabelValue(a.Annotations) 76 | } 77 | 78 | // CreatedByAgentConfig checks if an AgentAction is running on behalf of an agent config. 79 | func (a *AgentAction) CreatedByAgentConfig() bool { 80 | for _, ref := range a.GetOwnerReferences() { 81 | if ref.Kind == KindAgentConfig { 82 | return true 83 | } 84 | } 85 | 86 | return false 87 | } 88 | 89 | // SetRetryAnnotation flags the resource to retry its last operation. 90 | func (a *AgentAction) SetRetryAnnotation(retry string) { 91 | if a.Annotations == nil { 92 | a.Annotations = make(map[string]string, 1) 93 | } 94 | a.Annotations[AnnotationRetry] = retry 95 | } 96 | 97 | // +kubebuilder:object:root=true 98 | 99 | // AgentActionList contains a list of AgentAction 100 | type AgentActionList struct { 101 | metav1.TypeMeta `json:",inline"` 102 | metav1.ListMeta `json:"metadata,omitempty"` 103 | Items []AgentAction `json:"items"` 104 | } 105 | 106 | func init() { 107 | objectTypes = append(objectTypes, &AgentAction{}, &AgentActionList{}) 108 | } 109 | -------------------------------------------------------------------------------- /api/v1/agentaction_types_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestAgentAction_SetRetryAnnotation(t *testing.T) { 10 | action := AgentAction{} 11 | action.SetRetryAnnotation("retry-1") 12 | assert.Equal(t, "retry-1", action.Annotations[AnnotationRetry]) 13 | } 14 | -------------------------------------------------------------------------------- /api/v1/const.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | const ( 4 | // DefaultPorterAgentRepository is the default image repository of the Porter 5 | // Agent to use when it is not configured in the operator. 6 | DefaultPorterAgentRepository = "ghcr.io/getporter/porter-agent" 7 | 8 | // DefaultPorterAgentVersion is the default version of the Porter Agent to 9 | // use when it is not configured in the operator. 10 | // 11 | // As we test out the operator with new versions of Porter, keep this value 12 | // up-to-date so that the default version is guaranteed to work. 13 | DefaultPorterAgentVersion = "v1.0.14" 14 | 15 | // LabelJobType is a label applied to jobs created by the operator. It 16 | // indicates the purpose of the job. 17 | LabelJobType = Prefix + "jobType" 18 | 19 | // JobTypeAgent is the value of job type label applied to the Porter Agent. 20 | JobTypeAgent = "porter-agent" 21 | 22 | // JobTypeInstaller is the value of the job type label applied to the job 23 | // that runs the bundle. 24 | JobTypeInstaller = "bundle-installer" 25 | 26 | // LabelSecretType is a label applied to secrets created by the operator. It 27 | // indicates the purpose of the secret. 28 | LabelSecretType = Prefix + "secretType" 29 | 30 | // SecretTypeConfig is the value of the secret type label applied to the 31 | // secret that contains files to copy into the porter home directory. 32 | SecretTypeConfig = "porter-config" 33 | 34 | // SecretTypeWorkdir is the value of the secret type label applied to the 35 | // secret that contains files to copy into the working directory of the 36 | // Porter Agent. 37 | SecretTypeWorkdir = "workdir" 38 | 39 | // LabelManaged is a label applied to resources created by the Porter 40 | // Operator. 41 | LabelManaged = Prefix + "managed" 42 | 43 | LabelPluginsHash = Prefix + "plugins-hash" 44 | 45 | // LabelResourceKind is a label applied to resources created by the Porter 46 | // Operator, representing the kind of owning resource. It is used to help the 47 | // operator determine if a resource has already been created. 48 | LabelResourceKind = Prefix + "resourceKind" 49 | 50 | // LabelResourceName is a label applied to the resources created by the 51 | // Porter Operator, representing the name of the owning resource. It is used 52 | // to help the operator determine if a resource has 53 | // already been created. 54 | LabelResourceName = Prefix + "resourceName" 55 | 56 | // LabelResourceGeneration is a label applied to the resources created by the 57 | // Porter Operator, representing the generation of the owning resource. It is 58 | // used to help the operator determine if a resource has 59 | // already been created. 60 | LabelResourceGeneration = Prefix + "resourceGeneration" 61 | 62 | // LabelRetry is a label applied to the resources created by the 63 | // Porter Operator, representing the retry attempt identifier. 64 | LabelRetry = Prefix + "retry" 65 | 66 | // FinalizerName is the name of the finalizer applied to Porter Operator 67 | // resources that should be reconciled by the operator before allowing it to 68 | // be deleted. 69 | FinalizerName = Prefix + "finalizer" 70 | 71 | // VolumePorterSharedName is the name of the volume shared between the porter 72 | // agent and the invocation image. 73 | VolumePorterSharedName = "porter-shared" 74 | 75 | // VolumePorterSharedPath is the mount path of the volume shared between the 76 | // porter agent and the invocation image. 77 | VolumePorterSharedPath = "/porter-shared" 78 | 79 | // VolumePorterConfigName is the name of the volume that contains Porter's config 80 | // file. 81 | VolumePorterConfigName = "porter-config" 82 | 83 | // VolumePorterConfigPath is the mount path of the volume containing Porter's 84 | // config file. 85 | VolumePorterConfigPath = "/porter-config" 86 | 87 | // VolumePorterWorkDirName is the name of the volume that is used as the Porter's 88 | // working directory. 89 | VolumePorterWorkDirName = "porter-workdir" 90 | 91 | // VolumePorterWorkDirPath is the mount path of the volume that is used as the 92 | // Porter's working directory. 93 | VolumePorterWorkDirPath = "/porter-workdir" 94 | 95 | // VolumeImgPullSecretName is the name of the volume that contains 96 | // .docker/config.json file. 97 | VolumeImgPullSecretName = "img-pull-secret" 98 | 99 | // VolumeImgPullSecretPath is the mount path of the volume containing for docker 100 | // auth for image pull secrets. 101 | VolumeImgPullSecretPath = "/home/nonroot" 102 | 103 | // VolumePorterPluginsName is the name of the volume shared between the porter 104 | // agent and the invocation image. 105 | VolumePorterPluginsName = "porter-plugins" 106 | 107 | // VolumePorterPluginsPath is the mount path of the volume containing Porter's 108 | // config file. 109 | VolumePorterPluginsPath = "/app/.porter/plugins" 110 | ) 111 | -------------------------------------------------------------------------------- /api/v1/credentialset_types.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "gopkg.in/yaml.v3" 6 | corev1 "k8s.io/api/core/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | ) 9 | 10 | // SERIALIZATION NOTE: 11 | // * json tags are required for Kubernetes. Any new fields you add must have json tags for the fields to be serialized. 12 | // * yaml tags are required for Porter. Any new fields you add must have yaml tags for the fields to be serialized. 13 | 14 | // Credential defines a element in a CredentialSet 15 | type Credential struct { 16 | //Name is the bundle credential name 17 | Name string `json:"name" yaml:"name"` 18 | 19 | //Source is the bundle credential source 20 | //supported: secret 21 | //unsupported: file path(via configMap), specific value, env var, shell cmd 22 | Source CredentialSource `json:"source" yaml:"source"` 23 | } 24 | 25 | // CredentialSource defines a element in a CredentialSet 26 | type CredentialSource struct { 27 | //Secret is a credential source using a secret plugin 28 | Secret string `json:"secret,omitempty" yaml:"secret,omitempty"` 29 | } 30 | 31 | // CredentialSetSpec defines the desired state of CredentialSet 32 | type CredentialSetSpec struct { 33 | // AgentConfig is the name of an AgentConfig to use instead of the AgentConfig defined at the namespace or system level. 34 | // +optional 35 | AgentConfig *corev1.LocalObjectReference `json:"agentConfig,omitempty" yaml:"-"` 36 | 37 | // 38 | // These are fields from the Porter credential set resource. 39 | // Your goal is that someone can copy/paste a resource from Porter into the 40 | // spec and have it work. So be consistent! 41 | // 42 | 43 | // SchemaVersion is the version of the credential set state schema. 44 | SchemaVersion string `json:"schemaVersion" yaml:"schemaVersion"` 45 | 46 | // Name is the name of the credential set in Porter. Immutable. 47 | Name string `json:"name" yaml:"name"` 48 | 49 | // Namespace (in Porter) where the credential set is defined. 50 | Namespace string `json:"namespace" yaml:"namespace"` 51 | 52 | //Credentials list of bundle credentials in the credential set. 53 | Credentials []Credential `json:"credentials" yaml:"credentials"` 54 | } 55 | 56 | func (cs CredentialSetSpec) ToPorterDocument() ([]byte, error) { 57 | b, err := yaml.Marshal(cs) 58 | return b, errors.Wrap(err, "error converting the CredentialSet spec into its Porter resource representation") 59 | } 60 | 61 | // CredentialSetStatus defines the observed state of CredentialSet 62 | type CredentialSetStatus struct { 63 | PorterResourceStatus `json:",inline"` 64 | } 65 | 66 | //+kubebuilder:object:root=true 67 | //+kubebuilder:subresource:status 68 | 69 | // CredentialSet is the Schema for the credentialsets API 70 | type CredentialSet struct { 71 | metav1.TypeMeta `json:",inline"` 72 | metav1.ObjectMeta `json:"metadata,omitempty"` 73 | 74 | Spec CredentialSetSpec `json:"spec,omitempty"` 75 | Status CredentialSetStatus `json:"status,omitempty"` 76 | } 77 | 78 | func (cs *CredentialSet) GetStatus() PorterResourceStatus { 79 | return cs.Status.PorterResourceStatus 80 | } 81 | 82 | func (cs *CredentialSet) SetStatus(value PorterResourceStatus) { 83 | cs.Status.PorterResourceStatus = value 84 | } 85 | 86 | // GetRetryLabelValue returns a value that is safe to use 87 | // as a label value and represents the retry annotation used 88 | // to trigger reconciliation. 89 | func (cs *CredentialSet) GetRetryLabelValue() string { 90 | return getRetryLabelValue(cs.Annotations) 91 | } 92 | 93 | // SetRetryAnnotation flags the resource to retry its last operation. 94 | func (cs *CredentialSet) SetRetryAnnotation(retry string) { 95 | if cs.Annotations == nil { 96 | cs.Annotations = make(map[string]string, 1) 97 | } 98 | cs.Annotations[AnnotationRetry] = retry 99 | } 100 | 101 | //+kubebuilder:object:root=true 102 | 103 | // CredentialSetList contains a list of CredentialSet 104 | type CredentialSetList struct { 105 | metav1.TypeMeta `json:",inline"` 106 | metav1.ListMeta `json:"metadata,omitempty"` 107 | Items []CredentialSet `json:"items"` 108 | } 109 | 110 | func init() { 111 | objectTypes = append(objectTypes, &CredentialSet{}, &CredentialSetList{}) 112 | } 113 | -------------------------------------------------------------------------------- /api/v1/credentialset_types_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "testing" 5 | 6 | "get.porter.sh/porter/pkg/storage" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | 10 | portertest "get.porter.sh/porter/pkg/test" 11 | portertests "get.porter.sh/porter/tests" 12 | corev1 "k8s.io/api/core/v1" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | ) 15 | 16 | func TestCredentialSetSpec_ToPorterDocument(t *testing.T) { 17 | wantGoldenFile := "testdata/credential-set.yaml" 18 | type fields struct { 19 | AgentConfig *corev1.LocalObjectReference 20 | PorterConfig *corev1.LocalObjectReference 21 | SchemaVersion string 22 | Name string 23 | Namespace string 24 | Credentials []Credential 25 | } 26 | tests := []struct { 27 | name string 28 | fields fields 29 | wantFile string 30 | wantErrMsg string 31 | }{ 32 | { 33 | name: "golden file test", 34 | fields: fields{SchemaVersion: string(storage.DefaultCredentialSetSchemaVersion), 35 | Name: "porter-test-me", 36 | Namespace: "dev", 37 | Credentials: []Credential{{ 38 | Name: "insecureValue", 39 | Source: CredentialSource{Secret: "test-secret"}, 40 | }, 41 | }, 42 | }, 43 | wantFile: wantGoldenFile, 44 | wantErrMsg: "", 45 | }, 46 | } 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | cs := CredentialSetSpec{ 50 | AgentConfig: tt.fields.AgentConfig, 51 | SchemaVersion: tt.fields.SchemaVersion, 52 | Name: tt.fields.Name, 53 | Namespace: tt.fields.Namespace, 54 | Credentials: tt.fields.Credentials, 55 | } 56 | got, err := cs.ToPorterDocument() 57 | if tt.wantErrMsg == "" { 58 | require.NoError(t, err) 59 | portertest.CompareGoldenFile(t, "testdata/credential-set.yaml", string(got)) 60 | } else { 61 | portertests.RequireErrorContains(t, err, tt.wantErrMsg) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | func TestCredentialSet_SetRetryAnnotation(t *testing.T) { 68 | type fields struct { 69 | TypeMeta metav1.TypeMeta 70 | ObjectMeta metav1.ObjectMeta 71 | Spec CredentialSetSpec 72 | Status CredentialSetStatus 73 | } 74 | type args struct { 75 | retry string 76 | } 77 | tests := []struct { 78 | name string 79 | fields fields 80 | args args 81 | }{ 82 | { 83 | name: "set retry 1", 84 | fields: fields{ 85 | TypeMeta: metav1.TypeMeta{}, 86 | ObjectMeta: metav1.ObjectMeta{}, 87 | Spec: CredentialSetSpec{}, 88 | Status: CredentialSetStatus{}, 89 | }, 90 | args: args{retry: "1"}, 91 | }, 92 | } 93 | for _, tt := range tests { 94 | t.Run(tt.name, func(t *testing.T) { 95 | cs := &CredentialSet{ 96 | TypeMeta: tt.fields.TypeMeta, 97 | ObjectMeta: tt.fields.ObjectMeta, 98 | Spec: tt.fields.Spec, 99 | Status: tt.fields.Status, 100 | } 101 | cs.SetRetryAnnotation(tt.args.retry) 102 | assert.Equal(t, tt.args.retry, cs.Annotations[AnnotationRetry]) 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /api/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | // Package v1 contains API Schema definitions for the v1 API group 2 | // +kubebuilder:object:generate=true 3 | // +groupName=getporter.org 4 | package v1 5 | 6 | import ( 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | "k8s.io/apimachinery/pkg/runtime/schema" 10 | ) 11 | 12 | var ( 13 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 14 | 15 | // GroupVersion is group version used to register these objects 16 | GroupVersion = schema.GroupVersion{Group: "getporter.org", Version: "v1"} 17 | 18 | // AddToScheme adds the types in this group-version to the given scheme. 19 | AddToScheme = SchemeBuilder.AddToScheme 20 | 21 | objectTypes = []runtime.Object{} 22 | ) 23 | 24 | func addKnownTypes(scheme *runtime.Scheme) error { 25 | scheme.AddKnownTypes(GroupVersion, objectTypes...) 26 | metav1.AddToGroupVersion(scheme, GroupVersion) 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /api/v1/groupversion_info_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "testing" 5 | 6 | "k8s.io/apimachinery/pkg/runtime" 7 | ) 8 | 9 | func TestAddKnownTypes(t *testing.T) { 10 | scheme := runtime.NewScheme() 11 | AddToScheme(scheme) 12 | err := addKnownTypes(scheme) 13 | if err != nil { 14 | t.Fatalf("failure to add known types %v", err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /api/v1/installation_types.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/pkg/errors" 7 | "gopkg.in/yaml.v3" 8 | corev1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/runtime" 11 | ) 12 | 13 | const ( 14 | Prefix = "getporter.org/" 15 | AnnotationRetry = Prefix + "retry" 16 | PorterDeletePolicyAnnotation = "getporter.org/deletion-policy" 17 | PorterDeletePolicyDelete = "delete" 18 | PorterDeletePolicyOrphan = "orphan" 19 | ) 20 | 21 | // We marshal installation spec to yaml when converting to a porter object 22 | var _ yaml.Marshaler = InstallationSpec{} 23 | 24 | // InstallationSpec defines the desired state of Installation 25 | // 26 | // SERIALIZATION NOTE: 27 | // * The json serialization is for persisting this to Kubernetes. 28 | // * The yaml serialization is for creating a Porter representation of the resource. 29 | type InstallationSpec struct { 30 | // AgentConfig is the name of an AgentConfig to use instead of the AgentConfig defined at the namespace or system level. 31 | // +optional 32 | AgentConfig *corev1.LocalObjectReference `json:"agentConfig,omitempty" yaml:"-"` 33 | 34 | // 35 | // These are fields from the Porter installation resource. 36 | // Your goal is that someone can copy/paste a resource from Porter into the 37 | // spec and have it work. So be consistent! 38 | // 39 | 40 | // SchemaVersion is the version of the installation state schema. 41 | SchemaVersion string `json:"schemaVersion" yaml:"schemaVersion"` 42 | 43 | // Name is the name of the installation in Porter. Immutable. 44 | Name string `json:"name" yaml:"name"` 45 | 46 | // Namespace (in Porter) where the installation is defined. 47 | Namespace string `json:"namespace" yaml:"namespace"` 48 | 49 | // Uninstalled specifies if the installation should be uninstalled. 50 | Uninstalled bool `json:"uninstalled,omitempty" yaml:"uninstalled,omitempty"` 51 | 52 | // Bundle definition for the installation. 53 | Bundle OCIReferenceParts `json:"bundle" yaml:"bundle"` 54 | 55 | // Labels applied to the installation. 56 | Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` 57 | 58 | // Parameters specified by the user through overrides. 59 | // Does not include defaults, or values resolved from parameter sources. 60 | // +kubebuilder:pruning:PreserveUnknownFields 61 | Parameters runtime.RawExtension `json:"parameters,omitempty" yaml:"-"` // See custom marshaler below 62 | 63 | // CredentialSets that should be included when the bundle is reconciled. 64 | CredentialSets []string `json:"credentialSets,omitempty" yaml:"credentialSets,omitempty"` 65 | 66 | // ParameterSets that should be included when the bundle is reconciled. 67 | ParameterSets []string `json:"parameterSets,omitempty" yaml:"parameterSets,omitempty"` 68 | } 69 | 70 | type OCIReferenceParts struct { 71 | // Repository is the OCI repository of the current bundle definition. 72 | Repository string `json:"repository" yaml:"repository"` 73 | 74 | // Version is the current version of the bundle. 75 | Version string `json:"version,omitempty" yaml:"version,omitempty"` 76 | 77 | // Digest is the current digest of the bundle. 78 | Digest string `json:"digest,omitempty" yaml:"digest,omitempty"` 79 | 80 | // Tag is the OCI tag of the current bundle definition. 81 | Tag string `json:"tag,omitempty" yaml:"tag,omitempty"` 82 | } 83 | 84 | // ToPorterDocument converts from the Kubernetes representation of the Installation into Porter's resource format. 85 | func (in InstallationSpec) ToPorterDocument() ([]byte, error) { 86 | b, err := yaml.Marshal(in) 87 | return b, errors.Wrap(err, "error converting the Installation spec into its Porter resource representation") 88 | } 89 | 90 | func (in InstallationSpec) MarshalYAML() (interface{}, error) { 91 | type Alias InstallationSpec 92 | 93 | raw := struct { 94 | Alias `yaml:",inline"` 95 | Parameters map[string]interface{} `yaml:"parameters,omitempty"` 96 | }{ 97 | Alias: Alias(in), 98 | } 99 | 100 | if in.Parameters.Raw != nil { 101 | err := json.Unmarshal(in.Parameters.Raw, &raw.Parameters) 102 | if err != nil { 103 | return nil, errors.Wrapf(err, "error unmarshaling raw parameters\n%s", string(in.Parameters.Raw)) 104 | } 105 | } 106 | 107 | return raw, nil 108 | } 109 | 110 | // InstallationStatus defines the observed state of Installation 111 | type InstallationStatus struct { 112 | PorterResourceStatus `json:",inline"` 113 | } 114 | 115 | // +kubebuilder:object:root=true 116 | // +kubebuilder:subresource:status 117 | 118 | // Installation is the Schema for the installations API 119 | // +kubebuilder:printcolumn:name="Porter Name",type="string",JSONPath=".spec.name" 120 | // +kubebuilder:printcolumn:name="Porter Namespace",type="string",JSONPath=".spec.namespace" 121 | // +kubebuilder:printcolumn:name="Last Action",type="string",JSONPath=".status.action.name" 122 | // +kubebuilder:printcolumn:name="Last Status",type="string",JSONPath=".status.phase" 123 | // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" 124 | type Installation struct { 125 | metav1.TypeMeta `json:",inline"` 126 | metav1.ObjectMeta `json:"metadata,omitempty"` 127 | 128 | Spec InstallationSpec `json:"spec,omitempty"` 129 | Status InstallationStatus `json:"status,omitempty"` 130 | } 131 | 132 | func (i *Installation) GetStatus() PorterResourceStatus { 133 | return i.Status.PorterResourceStatus 134 | } 135 | 136 | func (i *Installation) SetStatus(value PorterResourceStatus) { 137 | i.Status.PorterResourceStatus = value 138 | } 139 | 140 | // GetRetryLabelValue returns a value that is safe to use 141 | // as a label value and represents the retry annotation used 142 | // to trigger reconciliation. 143 | func (i *Installation) GetRetryLabelValue() string { 144 | return getRetryLabelValue(i.Annotations) 145 | } 146 | 147 | // SetRetryAnnotation flags the resource to retry its last operation. 148 | func (i *Installation) SetRetryAnnotation(retry string) { 149 | if i.Annotations == nil { 150 | i.Annotations = make(map[string]string, 1) 151 | } 152 | i.Annotations[AnnotationRetry] = retry 153 | } 154 | 155 | // +kubebuilder:object:root=true 156 | 157 | // InstallationList contains a list of Installation 158 | type InstallationList struct { 159 | metav1.TypeMeta `json:",inline"` 160 | metav1.ListMeta `json:"metadata,omitempty"` 161 | Items []Installation `json:"items"` 162 | } 163 | 164 | func init() { 165 | objectTypes = append(objectTypes, &Installation{}, &InstallationList{}) 166 | } 167 | -------------------------------------------------------------------------------- /api/v1/installation_types_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "testing" 5 | 6 | "get.porter.sh/porter/pkg/storage" 7 | "get.porter.sh/porter/pkg/test" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | corev1 "k8s.io/api/core/v1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/runtime" 13 | ) 14 | 15 | func TestInstallationSpec_ToPorterDocument(t *testing.T) { 16 | // Validate the special handling for the arbitrary parameters 17 | // which the CRD can't directly represent as map[string]interface{} 18 | spec := InstallationSpec{ 19 | SchemaVersion: string(storage.DefaultInstallationSchemaVersion), 20 | Name: "mybuns", 21 | Namespace: "dev", 22 | Bundle: OCIReferenceParts{ 23 | Repository: "ghcr.io/getporter/porter-hello", 24 | Version: "0.1.1", 25 | }, 26 | Parameters: runtime.RawExtension{ 27 | Raw: []byte(`{"name":"Porter Operator"}`), 28 | }, 29 | } 30 | 31 | b, err := spec.ToPorterDocument() 32 | require.NoError(t, err) 33 | 34 | test.CompareGoldenFile(t, "testdata/installation.yaml", string(b)) 35 | } 36 | 37 | func TestInstallationStatus_Initialize(t *testing.T) { 38 | s := &InstallationStatus{ 39 | PorterResourceStatus: PorterResourceStatus{ 40 | ObservedGeneration: 2, 41 | Action: &corev1.LocalObjectReference{Name: "something"}, 42 | Phase: PhaseSucceeded, 43 | Conditions: []metav1.Condition{ 44 | {Type: string(ConditionComplete), Status: metav1.ConditionTrue}, 45 | }, 46 | }, 47 | } 48 | 49 | s.Initialize() 50 | 51 | assert.Equal(t, int64(2), s.ObservedGeneration, "ObservedGeneration should not be reset") 52 | assert.Empty(t, s.Conditions, "Conditions should be empty") 53 | assert.Nil(t, s.Action, "Active should be nil") 54 | assert.Equal(t, PhaseUnknown, s.Phase, "Phase should reset to Unknown") 55 | } 56 | 57 | func TestInstallation_SetRetryAnnotation(t *testing.T) { 58 | inst := Installation{} 59 | inst.SetRetryAnnotation("retry-1") 60 | assert.Equal(t, "retry-1", inst.Annotations[AnnotationRetry]) 61 | } 62 | -------------------------------------------------------------------------------- /api/v1/installationoutput_types.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | const ( 8 | InstallationOutputSucceeded = "InstallationOutputSucceeded" 9 | AnnotationInstallationOutput = Prefix + "installationoutput" 10 | ) 11 | 12 | type Output struct { 13 | Name string `json:"name"` 14 | Type string `json:"type"` 15 | Sensitive bool `json:"sensitive"` 16 | Value string `json:"value"` 17 | } 18 | 19 | // InstallationOutputSpec defines the desired state of InstallationOutput 20 | type InstallationOutputSpec struct { 21 | Name string `json:"name,omitempty"` 22 | 23 | Namespace string `json:"namespace,omitempty"` 24 | } 25 | 26 | // InstallationOutputStatus defines the observed state of InstallationOutput 27 | type InstallationOutputStatus struct { 28 | Phase AgentPhase `json:"phase,omitempty"` 29 | 30 | Conditions []metav1.Condition `json:"conditions,omitempty"` 31 | 32 | Outputs []Output `json:"outputs,omitempty"` 33 | 34 | OutputNames string `json:"outputNames,omitempty"` 35 | } 36 | 37 | //+kubebuilder:object:root=true 38 | //+kubebuilder:subresource:status 39 | 40 | // +kubebuilder:printcolumn:name="Porter Name",type="string",JSONPath=".spec.name" 41 | // +kubebuilder:printcolumn:name="Porter Namespace",type="string",JSONPath=".spec.namespace" 42 | // +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase" 43 | // +kubebuilder:printcolumn:name="Output Names",type="string",JSONPath=".status.outputNames",priority=1 44 | // InstallationOutput is the Schema for the installationoutputs API 45 | type InstallationOutput struct { 46 | metav1.TypeMeta `json:",inline"` 47 | metav1.ObjectMeta `json:"metadata,omitempty"` 48 | 49 | Spec InstallationOutputSpec `json:"spec,omitempty"` 50 | Status InstallationOutputStatus `json:"status,omitempty"` 51 | } 52 | 53 | //+kubebuilder:object:root=true 54 | 55 | // InstallationOutputList contains a list of InstallationOutput 56 | type InstallationOutputList struct { 57 | metav1.TypeMeta `json:",inline"` 58 | metav1.ListMeta `json:"metadata,omitempty"` 59 | Items []InstallationOutput `json:"items"` 60 | } 61 | 62 | func init() { 63 | objectTypes = append(objectTypes, &InstallationOutput{}, &InstallationOutputList{}) 64 | } 65 | -------------------------------------------------------------------------------- /api/v1/parameterset_types.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "gopkg.in/yaml.v3" 6 | corev1 "k8s.io/api/core/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | ) 9 | 10 | // SERIALIZATION NOTE: 11 | // * json tags are required for Kubernetes. Any new fields you add must have json tags for the fields to be serialized. 12 | // * yaml tags are required for Porter. Any new fields you add must have yaml tags for the fields to be serialized. 13 | 14 | // Parameter defines an element in a ParameterSet 15 | type Parameter struct { 16 | // Name is the bundle parameter name 17 | Name string `json:"name" yaml:"name"` 18 | 19 | //Source is the bundle parameter source 20 | //supported: secret, value 21 | //unsupported: file path(via configMap), env var, shell cmd 22 | Source ParameterSource `json:"source" yaml:"source"` 23 | } 24 | 25 | type ParameterSource struct { 26 | // Secret is a parameter source using a secret plugin 27 | Secret string `json:"secret,omitempty" yaml:"secret,omitempty"` 28 | // Value is a paremeter source using plaintext value 29 | Value string `json:"value,omitempty" yaml:"value,omitempty"` 30 | } 31 | 32 | // ParameterSetSpec defines the desired state of ParameterSet 33 | type ParameterSetSpec struct { 34 | // AgentConfig is the name of an AgentConfig to use instead of the AgentConfig defined at the namespace or system level. 35 | // +optional 36 | AgentConfig *corev1.LocalObjectReference `json:"agentConfig,omitempty" yaml:"-"` 37 | 38 | // 39 | // These are fields from the Porter parameter set resource. 40 | // Your goal is that someone can copy/paste a resource from Porter into the 41 | // spec and have it work. So be consistent! 42 | // 43 | 44 | // SchemaVersion is the version of the parameter set state schema. 45 | SchemaVersion string `json:"schemaVersion" yaml:"schemaVersion"` 46 | 47 | // Name is the name of the parameter set in Porter. Immutable. 48 | Name string `json:"name" yaml:"name"` 49 | 50 | // Namespace (in Porter) where the parameter set is defined. 51 | Namespace string `json:"namespace" yaml:"namespace"` 52 | // Parameters list of bundle parameters in the parameter set. 53 | Parameters []Parameter `json:"parameters" yaml:"parameters"` 54 | } 55 | 56 | func (ps ParameterSetSpec) ToPorterDocument() ([]byte, error) { 57 | b, err := yaml.Marshal(ps) 58 | return b, errors.Wrap(err, "error converting the ParameterSet spec into its Porter resource representation") 59 | } 60 | 61 | // ParameterSetStatus defines the observed state of ParameterSet 62 | type ParameterSetStatus struct { 63 | PorterResourceStatus `json:",inline"` 64 | } 65 | 66 | //+kubebuilder:object:root=true 67 | //+kubebuilder:subresource:status 68 | 69 | // ParameterSet is the Schema for the parametersets API 70 | type ParameterSet struct { 71 | metav1.TypeMeta `json:",inline"` 72 | metav1.ObjectMeta `json:"metadata,omitempty"` 73 | 74 | Spec ParameterSetSpec `json:"spec,omitempty"` 75 | Status ParameterSetStatus `json:"status,omitempty"` 76 | } 77 | 78 | func (ps *ParameterSet) GetStatus() PorterResourceStatus { 79 | return ps.Status.PorterResourceStatus 80 | } 81 | 82 | func (ps *ParameterSet) SetStatus(value PorterResourceStatus) { 83 | ps.Status.PorterResourceStatus = value 84 | } 85 | 86 | // GetRetryLabelValue returns a value that is safe to use 87 | // as a label value and represents the retry annotation used 88 | // to trigger reconciliation. 89 | func (ps *ParameterSet) GetRetryLabelValue() string { 90 | return getRetryLabelValue(ps.Annotations) 91 | } 92 | 93 | // SetRetryAnnotation flags the resource to retry its last operation. 94 | func (ps *ParameterSet) SetRetryAnnotation(retry string) { 95 | if ps.Annotations == nil { 96 | ps.Annotations = make(map[string]string, 1) 97 | } 98 | ps.Annotations[AnnotationRetry] = retry 99 | } 100 | 101 | //+kubebuilder:object:root=true 102 | 103 | // ParameterSetList contains a list of ParameterSet 104 | type ParameterSetList struct { 105 | metav1.TypeMeta `json:",inline"` 106 | metav1.ListMeta `json:"metadata,omitempty"` 107 | Items []ParameterSet `json:"items"` 108 | } 109 | 110 | func init() { 111 | objectTypes = append(objectTypes, &ParameterSet{}, &ParameterSetList{}) 112 | } 113 | -------------------------------------------------------------------------------- /api/v1/parameterset_types_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "testing" 5 | 6 | "get.porter.sh/porter/pkg/storage" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | 10 | portertest "get.porter.sh/porter/pkg/test" 11 | portertests "get.porter.sh/porter/tests" 12 | corev1 "k8s.io/api/core/v1" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | ) 15 | 16 | func TestParameterSetSpec_ToPorterDocument(t *testing.T) { 17 | wantGoldenFile := "testdata/parameter-set.yaml" 18 | type fields struct { 19 | AgentConfig *corev1.LocalObjectReference 20 | PorterConfig *corev1.LocalObjectReference 21 | SchemaVersion string 22 | Name string 23 | Namespace string 24 | Parameters []Parameter 25 | } 26 | tests := []struct { 27 | name string 28 | fields fields 29 | wantFile string 30 | wantErrMsg string 31 | }{ 32 | { 33 | name: "golden file test", 34 | fields: fields{SchemaVersion: string(storage.DefaultParameterSetSchemaVersion), 35 | Name: "porter-test-me", 36 | Namespace: "dev", 37 | Parameters: []Parameter{ 38 | { 39 | Name: "param1", 40 | Source: ParameterSource{Value: "test-param"}, 41 | }, 42 | { 43 | Name: "param2", 44 | Source: ParameterSource{Secret: "test-secret"}, 45 | }, 46 | }, 47 | }, 48 | wantFile: wantGoldenFile, 49 | wantErrMsg: "", 50 | }, 51 | } 52 | for _, tt := range tests { 53 | t.Run(tt.name, func(t *testing.T) { 54 | cs := ParameterSetSpec{ 55 | AgentConfig: tt.fields.AgentConfig, 56 | SchemaVersion: tt.fields.SchemaVersion, 57 | Name: tt.fields.Name, 58 | Namespace: tt.fields.Namespace, 59 | Parameters: tt.fields.Parameters, 60 | } 61 | got, err := cs.ToPorterDocument() 62 | if tt.wantErrMsg == "" { 63 | require.NoError(t, err) 64 | portertest.CompareGoldenFile(t, tt.wantFile, string(got)) 65 | } else { 66 | portertests.RequireErrorContains(t, err, tt.wantErrMsg) 67 | } 68 | }) 69 | } 70 | } 71 | 72 | func TestParameterSet_SetRetryAnnotation(t *testing.T) { 73 | type fields struct { 74 | TypeMeta metav1.TypeMeta 75 | ObjectMeta metav1.ObjectMeta 76 | Spec ParameterSetSpec 77 | Status ParameterSetStatus 78 | } 79 | type args struct { 80 | retry string 81 | } 82 | tests := []struct { 83 | name string 84 | fields fields 85 | args args 86 | }{ 87 | { 88 | name: "set retry 1", 89 | fields: fields{ 90 | TypeMeta: metav1.TypeMeta{}, 91 | ObjectMeta: metav1.ObjectMeta{}, 92 | Spec: ParameterSetSpec{}, 93 | Status: ParameterSetStatus{}, 94 | }, 95 | args: args{retry: "1"}, 96 | }, 97 | } 98 | for _, tt := range tests { 99 | t.Run(tt.name, func(t *testing.T) { 100 | ps := &ParameterSet{ 101 | TypeMeta: tt.fields.TypeMeta, 102 | ObjectMeta: tt.fields.ObjectMeta, 103 | Spec: tt.fields.Spec, 104 | Status: tt.fields.Status, 105 | } 106 | ps.SetRetryAnnotation(tt.args.retry) 107 | assert.Equal(t, tt.args.retry, ps.Annotations[AnnotationRetry]) 108 | }) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /api/v1/porter_resource.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | type PorterResourceStatus struct { 12 | // The last generation observed by the controller. 13 | ObservedGeneration int64 `json:"observedGeneration,omitempty"` 14 | 15 | // The most recent action executed for the resource 16 | Action *corev1.LocalObjectReference `json:"action,omitempty"` 17 | 18 | // The current status of the agent. 19 | // Possible values are: Unknown, Pending, Running, Succeeded, and Failed. 20 | // +kubebuilder:validation:Type=string 21 | Phase AgentPhase `json:"phase,omitempty"` 22 | 23 | // Conditions store a list of states that have been reached. 24 | // Each condition refers to the status of the ActiveJob 25 | // Possible conditions are: Scheduled, Started, Completed, and Failed 26 | Conditions []metav1.Condition `json:"conditions,omitempty"` 27 | } 28 | 29 | // Initialize resets the resource status before Porter is run. 30 | // This wipes out the status from any previous runs. 31 | func (s *PorterResourceStatus) Initialize() { 32 | s.Conditions = []metav1.Condition{} 33 | s.Phase = PhaseUnknown 34 | s.Action = nil 35 | } 36 | 37 | // GetRetryLabelValue returns a value that is safe to use 38 | // as a label value and represents the retry annotation used 39 | // to trigger reconciliation. Annotations don't have limits on 40 | // the value, but labels are restricted to alphanumeric and .-_ 41 | // I am just hashing the annotation value here to avoid problems 42 | // using it directly as a label value. 43 | func getRetryLabelValue(annotations map[string]string) string { 44 | retry := annotations[AnnotationRetry] 45 | if retry == "" { 46 | return "" 47 | } 48 | sum := md5.Sum([]byte(retry)) 49 | return hex.EncodeToString(sum[:]) 50 | } 51 | -------------------------------------------------------------------------------- /api/v1/porter_resource_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestGetRetryLabelValue(t *testing.T) { 10 | annotations := map[string]string{ 11 | AnnotationRetry: "123", 12 | } 13 | 14 | assert.Equal(t, "202cb962ac59075b964b07152d234b70", getRetryLabelValue(annotations), "retry label value should be populated when the annotation is set") 15 | 16 | delete(annotations, AnnotationRetry) 17 | 18 | assert.Empty(t, getRetryLabelValue(annotations), "retry label value should be empty when no annotation is set") 19 | } 20 | -------------------------------------------------------------------------------- /api/v1/porterconfig_types_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | "k8s.io/apimachinery/pkg/runtime" 9 | "k8s.io/utils/ptr" 10 | ) 11 | 12 | func TestPorterConfigSpec_MergeConfig(t *testing.T) { 13 | t.Run("empty is ignored", func(t *testing.T) { 14 | nsConfig := PorterConfigSpec{ 15 | Verbosity: ptr.To("info"), 16 | } 17 | 18 | instConfig := PorterConfigSpec{} 19 | 20 | config, err := nsConfig.MergeConfig(instConfig) 21 | require.NoError(t, err) 22 | assert.Equal(t, ptr.To("info"), config.Verbosity) 23 | }) 24 | 25 | t.Run("override", func(t *testing.T) { 26 | nsConfig := PorterConfigSpec{ 27 | Verbosity: ptr.To("info"), 28 | } 29 | 30 | instConfig := PorterConfigSpec{ 31 | Verbosity: ptr.To("debug"), 32 | Telemetry: TelemetryConfig{ 33 | Enabled: ptr.To(true), 34 | }, 35 | } 36 | 37 | config, err := nsConfig.MergeConfig(instConfig) 38 | require.NoError(t, err) 39 | assert.Equal(t, ptr.To("debug"), config.Verbosity) 40 | assert.Equal(t, ptr.To(true), config.Telemetry.Enabled) 41 | }) 42 | } 43 | 44 | func TestPorterConfigSpec_ToPorterDocument(t *testing.T) { 45 | // Check that we can marshal from the CRD representation to Porter's 46 | tests := []struct { 47 | name string 48 | cfg PorterConfigSpec 49 | expDocument []byte 50 | }{ 51 | { 52 | name: "All fields set", 53 | cfg: PorterConfigSpec{ 54 | Verbosity: ptr.To("debug"), 55 | Namespace: ptr.To("test"), 56 | Experimental: []string{"build-drivers"}, 57 | BuildDriver: ptr.To("buildkit"), 58 | DefaultStorage: ptr.To("in-cluster-mongodb"), 59 | DefaultSecrets: ptr.To("keyvault"), 60 | DefaultStoragePlugin: ptr.To("mongodb"), 61 | DefaultSecretsPlugin: ptr.To("kubernetes.secrets"), 62 | Storage: []StorageConfig{ 63 | {PluginConfig{ 64 | Name: "in-cluster-mongodb", 65 | PluginSubKey: "mongodb", 66 | Config: runtime.RawExtension{Raw: []byte(`{"url":"mongodb://..."}`)}, 67 | }}, 68 | }, 69 | Secrets: []SecretsConfig{ 70 | {PluginConfig{ 71 | Name: "keyvault", 72 | PluginSubKey: "azure.keyvault", 73 | Config: runtime.RawExtension{Raw: []byte(`{"vault": "mysecrets"}`)}, 74 | }}, 75 | }, 76 | }, 77 | expDocument: []byte(`verbosity: debug 78 | namespace: test 79 | experimental: 80 | - build-drivers 81 | build-driver: buildkit 82 | default-storage: in-cluster-mongodb 83 | default-secrets: keyvault 84 | default-storage-plugin: mongodb 85 | default-secrets-plugin: kubernetes.secrets 86 | storage: 87 | - config: 88 | url: mongodb://... 89 | name: in-cluster-mongodb 90 | plugin: mongodb 91 | secrets: 92 | - config: 93 | vault: mysecrets 94 | name: keyvault 95 | plugin: azure.keyvault 96 | `), 97 | }, 98 | { 99 | name: "Storage config not provided", 100 | cfg: PorterConfigSpec{ 101 | DefaultSecretsPlugin: ptr.To("kubernetes.secrets"), 102 | DefaultStorage: ptr.To("in-cluster-mongodb"), 103 | Storage: []StorageConfig{ 104 | {PluginConfig{ 105 | Name: "in-cluster-mongodb", 106 | PluginSubKey: "mongodb", 107 | }}, 108 | }, 109 | }, 110 | expDocument: []byte(`default-storage: in-cluster-mongodb 111 | default-secrets-plugin: kubernetes.secrets 112 | storage: 113 | - name: in-cluster-mongodb 114 | plugin: mongodb 115 | `), 116 | }, 117 | { 118 | name: "Secrets config not provided", 119 | cfg: PorterConfigSpec{ 120 | DefaultStorage: ptr.To("in-cluster-mongodb"), 121 | DefaultSecrets: ptr.To("kubernetes-secrets"), 122 | Storage: []StorageConfig{ 123 | {PluginConfig{ 124 | Name: "in-cluster-mongodb", 125 | PluginSubKey: "mongodb", 126 | Config: runtime.RawExtension{Raw: []byte(`{"url": "mongodb://..."}`)}, 127 | }}, 128 | }, 129 | Secrets: []SecretsConfig{ 130 | {PluginConfig{ 131 | Name: "kubernetes-secrets", 132 | PluginSubKey: "kubernetes.secrets", 133 | }}, 134 | }, 135 | }, 136 | expDocument: []byte(`default-storage: in-cluster-mongodb 137 | default-secrets: kubernetes-secrets 138 | storage: 139 | - config: 140 | url: mongodb://... 141 | name: in-cluster-mongodb 142 | plugin: mongodb 143 | secrets: 144 | - name: kubernetes-secrets 145 | plugin: kubernetes.secrets 146 | `), 147 | }, 148 | { 149 | name: "All Telemetry config provided", 150 | cfg: PorterConfigSpec{ 151 | DefaultStorage: ptr.To("in-cluster-mongodb"), 152 | DefaultSecrets: ptr.To("kubernetes-secrets"), 153 | Storage: []StorageConfig{ 154 | {PluginConfig{ 155 | Name: "in-cluster-mongodb", 156 | PluginSubKey: "mongodb", 157 | Config: runtime.RawExtension{Raw: []byte(`{"url": "mongodb://..."}`)}, 158 | }}, 159 | }, 160 | Secrets: []SecretsConfig{ 161 | {PluginConfig{ 162 | Name: "kubernetes-secrets", 163 | PluginSubKey: "kubernetes.secrets", 164 | }}, 165 | }, 166 | Telemetry: TelemetryConfig{ 167 | Enabled: ptr.To(true), 168 | Protocol: ptr.To("grpc"), 169 | Endpoint: ptr.To("127.0.0.1:4317"), 170 | Insecure: ptr.To(true), 171 | Compression: ptr.To("gzip"), 172 | Timeout: ptr.To("3s"), 173 | StartTimeout: ptr.To("100ms"), 174 | RedirectToFile: ptr.To("foo"), 175 | }, 176 | }, 177 | expDocument: []byte(`default-storage: in-cluster-mongodb 178 | default-secrets: kubernetes-secrets 179 | storage: 180 | - config: 181 | url: mongodb://... 182 | name: in-cluster-mongodb 183 | plugin: mongodb 184 | secrets: 185 | - name: kubernetes-secrets 186 | plugin: kubernetes.secrets 187 | telemetry: 188 | enabled: true 189 | endpoint: 127.0.0.1:4317 190 | protocol: grpc 191 | insecure: true 192 | timeout: 3s 193 | compression: gzip 194 | start-timeout: 100ms 195 | redirect-to-file: foo 196 | `), 197 | }, 198 | } 199 | for _, test := range tests { 200 | t.Run(test.name, func(t *testing.T) { 201 | b, err := test.cfg.ToPorterDocument() 202 | require.NoError(t, err) 203 | require.Equal(t, string(test.expDocument), string(b)) 204 | }) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /api/v1/testdata/credential-set.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.1 2 | name: porter-test-me 3 | namespace: dev 4 | credentials: 5 | - name: insecureValue 6 | source: 7 | secret: test-secret 8 | -------------------------------------------------------------------------------- /api/v1/testdata/installation.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.2 2 | name: mybuns 3 | namespace: dev 4 | bundle: 5 | repository: ghcr.io/getporter/porter-hello 6 | version: 0.1.1 7 | parameters: 8 | name: Porter Operator 9 | -------------------------------------------------------------------------------- /api/v1/testdata/parameter-set.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.1 2 | name: porter-test-me 3 | namespace: dev 4 | parameters: 5 | - name: param1 6 | source: 7 | value: test-param 8 | - name: param2 9 | source: 10 | secret: test-secret 11 | -------------------------------------------------------------------------------- /api/v1/testdata/plugins.yaml: -------------------------------------------------------------------------------- 1 | schemaType: Plugins 2 | schemaVersion: 1.0.0 3 | plugins: 4 | plugin1: 5 | feedurl: http://example.com 6 | url: test 7 | mirror: http://example.com 8 | version: v1.0.0 9 | plugin2: 10 | feedurl: http://example.com 11 | url: test 12 | mirror: http://example.com 13 | version: v2.0.0 14 | -------------------------------------------------------------------------------- /api/v1/testdata/porter-config.yaml: -------------------------------------------------------------------------------- 1 | debug: true 2 | debug-plugins: true 3 | namespace: test 4 | experimental: 5 | - build-drivers 6 | build-driver: buildkit 7 | default-storage: in-cluster-mongodb 8 | default-secrets: keyvault 9 | default-storage-plugin: mongodb 10 | default-secrets-plugin: kubernetes.secrets 11 | storage: 12 | - config: 13 | url: mongodb://... 14 | name: in-cluster-mongodb 15 | plugin: mongodb 16 | secrets: 17 | - config: 18 | vault: mysecrets 19 | name: keyvault 20 | plugin: azure.keyvault 21 | -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "zz_generated*.*" 3 | - "mage/" 4 | - "magefile.go" 5 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. 4 | apiVersion: cert-manager.io/v1 5 | kind: Issuer 6 | metadata: 7 | name: selfsigned-issuer 8 | namespace: system 9 | spec: 10 | selfSigned: {} 11 | --- 12 | apiVersion: cert-manager.io/v1 13 | kind: Certificate 14 | metadata: 15 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 16 | namespace: system 17 | spec: 18 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 19 | dnsNames: 20 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 22 | issuerRef: 23 | kind: Issuer 24 | name: selfsigned-issuer 25 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 26 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/crd/bases/getporter.org_installationoutputs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.14.0 7 | name: installationoutputs.getporter.org 8 | spec: 9 | group: getporter.org 10 | names: 11 | kind: InstallationOutput 12 | listKind: InstallationOutputList 13 | plural: installationoutputs 14 | singular: installationoutput 15 | scope: Namespaced 16 | versions: 17 | - additionalPrinterColumns: 18 | - jsonPath: .spec.name 19 | name: Porter Name 20 | type: string 21 | - jsonPath: .spec.namespace 22 | name: Porter Namespace 23 | type: string 24 | - jsonPath: .status.phase 25 | name: Phase 26 | type: string 27 | - jsonPath: .status.outputNames 28 | name: Output Names 29 | priority: 1 30 | type: string 31 | name: v1 32 | schema: 33 | openAPIV3Schema: 34 | description: InstallationOutput is the Schema for the installationoutputs 35 | API 36 | properties: 37 | apiVersion: 38 | description: |- 39 | APIVersion defines the versioned schema of this representation of an object. 40 | Servers should convert recognized schemas to the latest internal value, and 41 | may reject unrecognized values. 42 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 43 | type: string 44 | kind: 45 | description: |- 46 | Kind is a string value representing the REST resource this object represents. 47 | Servers may infer this from the endpoint the client submits requests to. 48 | Cannot be updated. 49 | In CamelCase. 50 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 51 | type: string 52 | metadata: 53 | type: object 54 | spec: 55 | description: InstallationOutputSpec defines the desired state of InstallationOutput 56 | properties: 57 | name: 58 | type: string 59 | namespace: 60 | type: string 61 | type: object 62 | status: 63 | description: InstallationOutputStatus defines the observed state of InstallationOutput 64 | properties: 65 | conditions: 66 | items: 67 | description: "Condition contains details for one aspect of the current 68 | state of this API Resource.\n---\nThis struct is intended for 69 | direct use as an array at the field path .status.conditions. For 70 | example,\n\n\n\ttype FooStatus struct{\n\t // Represents the 71 | observations of a foo's current state.\n\t // Known .status.conditions.type 72 | are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // 73 | +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t 74 | \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" 75 | patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t 76 | \ // other fields\n\t}" 77 | properties: 78 | lastTransitionTime: 79 | description: |- 80 | lastTransitionTime is the last time the condition transitioned from one status to another. 81 | This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. 82 | format: date-time 83 | type: string 84 | message: 85 | description: |- 86 | message is a human readable message indicating details about the transition. 87 | This may be an empty string. 88 | maxLength: 32768 89 | type: string 90 | observedGeneration: 91 | description: |- 92 | observedGeneration represents the .metadata.generation that the condition was set based upon. 93 | For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date 94 | with respect to the current state of the instance. 95 | format: int64 96 | minimum: 0 97 | type: integer 98 | reason: 99 | description: |- 100 | reason contains a programmatic identifier indicating the reason for the condition's last transition. 101 | Producers of specific condition types may define expected values and meanings for this field, 102 | and whether the values are considered a guaranteed API. 103 | The value should be a CamelCase string. 104 | This field may not be empty. 105 | maxLength: 1024 106 | minLength: 1 107 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ 108 | type: string 109 | status: 110 | description: status of the condition, one of True, False, Unknown. 111 | enum: 112 | - "True" 113 | - "False" 114 | - Unknown 115 | type: string 116 | type: 117 | description: |- 118 | type of condition in CamelCase or in foo.example.com/CamelCase. 119 | --- 120 | Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be 121 | useful (see .node.status.conditions), the ability to deconflict is important. 122 | The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) 123 | maxLength: 316 124 | pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ 125 | type: string 126 | required: 127 | - lastTransitionTime 128 | - message 129 | - reason 130 | - status 131 | - type 132 | type: object 133 | type: array 134 | outputNames: 135 | type: string 136 | outputs: 137 | items: 138 | properties: 139 | name: 140 | type: string 141 | sensitive: 142 | type: boolean 143 | type: 144 | type: string 145 | value: 146 | type: string 147 | required: 148 | - name 149 | - sensitive 150 | - type 151 | - value 152 | type: object 153 | type: array 154 | phase: 155 | description: |- 156 | AgentPhase are valid statuses of a Porter agent job 157 | that is managing a change to a Porter resource. 158 | type: string 159 | type: object 160 | type: object 161 | served: true 162 | storage: true 163 | subresources: 164 | status: {} 165 | -------------------------------------------------------------------------------- /config/crd/bases/getporter.org_porterconfigs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.14.0 7 | name: porterconfigs.getporter.org 8 | spec: 9 | group: getporter.org 10 | names: 11 | kind: PorterConfig 12 | listKind: PorterConfigList 13 | plural: porterconfigs 14 | singular: porterconfig 15 | scope: Namespaced 16 | versions: 17 | - name: v1 18 | schema: 19 | openAPIV3Schema: 20 | description: PorterConfig is the Schema for the porterconfigs 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: "PorterConfigSpec defines the desired state of PorterConfig\n\n\nSERIALIZATION 41 | NOTE:\n\n\n\tUse json to persist this resource to Kubernetes.\n\tUse 42 | yaml to convert to Porter's representation of the resource.\n\tThe mapstructure 43 | tags are used internally for PorterConfigSpec.MergeConfig." 44 | properties: 45 | build-driver: 46 | description: |- 47 | BuildDriver specifies the name of the current build driver. 48 | Requires that the build-drivers experimental feature is enabled. 49 | type: string 50 | default-secrets: 51 | description: DefaultSecrets is the name of the secrets configuration 52 | to use. 53 | type: string 54 | default-secrets-plugin: 55 | description: DefaultSecretsPlugin is the name of the storage plugin 56 | to use when DefaultSecrets is unspecified. 57 | type: string 58 | default-storage: 59 | description: DefaultStorage is the name of the storage configuration 60 | to use. 61 | type: string 62 | default-storage-plugin: 63 | description: DefaultStoragePlugin is the name of the storage plugin 64 | to use when DefaultStorage is unspecified. 65 | type: string 66 | experimental: 67 | description: Experimental specifies which experimental features are 68 | enabled. 69 | items: 70 | type: string 71 | type: array 72 | namespace: 73 | description: Namespace is the default Porter namespace. 74 | type: string 75 | secrets: 76 | description: Secrets is a list of named secrets configurations. 77 | items: 78 | description: SecretsConfig is the plugin stanza for secrets. 79 | properties: 80 | config: 81 | type: object 82 | x-kubernetes-preserve-unknown-fields: true 83 | name: 84 | type: string 85 | plugin: 86 | type: string 87 | required: 88 | - name 89 | - plugin 90 | type: object 91 | type: array 92 | storage: 93 | description: Storage is a list of named storage configurations. 94 | items: 95 | description: StorageConfig is the plugin stanza for storage. 96 | properties: 97 | config: 98 | type: object 99 | x-kubernetes-preserve-unknown-fields: true 100 | name: 101 | type: string 102 | plugin: 103 | type: string 104 | required: 105 | - name 106 | - plugin 107 | type: object 108 | type: array 109 | telemetry: 110 | description: Telemetry is settings related to Porter's tracing with 111 | open telemetry. 112 | properties: 113 | certificate: 114 | type: string 115 | compression: 116 | type: string 117 | enabled: 118 | type: boolean 119 | endpoint: 120 | type: string 121 | headers: 122 | additionalProperties: 123 | type: string 124 | type: object 125 | insecure: 126 | type: boolean 127 | protocol: 128 | type: string 129 | redirect-to-file: 130 | type: string 131 | start-timeout: 132 | type: string 133 | timeout: 134 | type: string 135 | type: object 136 | verbosity: 137 | description: |- 138 | Threshold for printing messages to the console 139 | Allowed values are: debug, info, warn, error 140 | type: string 141 | type: object 142 | type: object 143 | served: true 144 | storage: true 145 | subresources: 146 | status: {} 147 | -------------------------------------------------------------------------------- /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/getporter.org_agentconfigs.yaml 6 | - bases/getporter.org_installations.yaml 7 | - bases/getporter.org_porterconfigs.yaml 8 | - bases/getporter.org_agentactions.yaml 9 | - bases/getporter.org_credentialsets.yaml 10 | - bases/getporter.org_parametersets.yaml 11 | - bases/getporter.org_installationoutputs.yaml 12 | # +kubebuilder:scaffold:crdkustomizeresource 13 | 14 | patchesStrategicMerge: 15 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 16 | # patches here are for enabling the conversion webhook for each CRD 17 | #- patches/webhook_in_installations.yaml 18 | #- patches/webhook_in_porterconfigs.yaml 19 | #- patches/webhook_in_credentialsets.yaml 20 | #- patches/webhook_in_agentactions.yaml 21 | #- patches/webhook_in_parametersets.yaml 22 | #- patches/webhook_in_agentconfig.yaml 23 | #- patches/webhook_in_installationoutputs.yaml 24 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 25 | 26 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 27 | # patches here are for enabling the CA injection for each CRD 28 | #- patches/cainjection_in_installations.yaml 29 | #- patches/cainjection_in_porterconfigs.yaml 30 | #- patches/cainjection_in_credentialsets.yaml 31 | #- patches/cainjection_in_agentactions.yaml 32 | #- patches/cainjection_in_parametersets.yaml 33 | #- patches/cainjection_in_agentconfig.yaml 34 | #- patches/cainjection_in_installationoutputs.yaml 35 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 36 | 37 | # the following config is for teaching kustomize how to do kustomization for CRDs. 38 | configurations: 39 | - kustomizeconfig.yaml 40 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_agentactions.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: agentactions.getporter.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_agentconfig.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: agentconfig.getporter.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_credentialsets.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: credentialsets.getporter.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_installationoutputs.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: installationoutputs.getporter.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_installations.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: installations.getporter.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_parametersets.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: parametersets.getporter.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_porterconfigs.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: porterconfigs.getporter.org 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_agentactions.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: agentactions.getporter.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_agentconfig.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: agentconfig.getporter.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_credentialsets.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: credentialsets.getporter.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_installationoutputs.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: installationoutputs.getporter.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_installations.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: installations.getporter.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_parametersets.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: parametersets.getporter.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_porterconfigs.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: porterconfigs.getporter.org 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: porter-operator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: porter-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | #- ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | #- manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | #- webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 49 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 50 | # objref: 51 | # kind: Certificate 52 | # group: cert-manager.io 53 | # version: v1 54 | # name: serving-cert # this name should match the one in certificate.yaml 55 | # fieldref: 56 | # fieldpath: metadata.namespace 57 | #- name: CERTIFICATE_NAME 58 | # objref: 59 | # kind: Certificate 60 | # group: cert-manager.io 61 | # version: v1 62 | # name: serving-cert # this name should match the one in certificate.yaml 63 | #- name: SERVICE_NAMESPACE # namespace of the service 64 | # objref: 65 | # kind: Service 66 | # version: v1 67 | # name: webhook-service 68 | # fieldref: 69 | # fieldpath: metadata.namespace 70 | #- name: SERVICE_NAME 71 | # objref: 72 | # kind: Service 73 | # version: v1 74 | # name: webhook-service 75 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | name: https 22 | - name: manager 23 | args: 24 | - "--health-probe-bind-address=:8081" 25 | - "--metrics-bind-address=127.0.0.1:8080" 26 | - "--leader-elect" 27 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: c58eb551.getporter.org 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - manager.yaml 6 | 7 | generatorOptions: 8 | disableNameSuffixHash: true 9 | 10 | configMapGenerator: 11 | - files: 12 | - controller_manager_config.yaml 13 | name: manager-config 14 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | revisionHistoryLimit: 1 21 | strategy: 22 | type: Recreate 23 | template: 24 | metadata: 25 | labels: 26 | control-plane: controller-manager 27 | spec: 28 | securityContext: 29 | runAsUser: 65532 30 | containers: 31 | - command: 32 | - /app/manager 33 | args: 34 | - --leader-elect 35 | image: manager 36 | imagePullPolicy: Always 37 | name: manager 38 | securityContext: 39 | allowPrivilegeEscalation: false 40 | livenessProbe: 41 | httpGet: 42 | path: /healthz 43 | port: 8081 44 | initialDelaySeconds: 15 45 | periodSeconds: 20 46 | readinessProbe: 47 | httpGet: 48 | path: /readyz 49 | port: 8081 50 | initialDelaySeconds: 5 51 | periodSeconds: 10 52 | resources: 53 | limits: 54 | cpu: 100m 55 | memory: 30Mi 56 | requests: 57 | cpu: 100m 58 | memory: 20Mi 59 | terminationGracePeriodSeconds: 10 60 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | selector: 15 | matchLabels: 16 | control-plane: controller-manager 17 | -------------------------------------------------------------------------------- /config/rbac/agent_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for the porter job agents 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | name: agent-role 7 | rules: 8 | - apiGroups: 9 | - "" 10 | resources: 11 | - pods 12 | - namespaces 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - pods/log 21 | verbs: 22 | - get 23 | - list 24 | - apiGroups: 25 | - "" 26 | resources: 27 | - secrets 28 | verbs: 29 | - create 30 | - delete 31 | - get 32 | - list 33 | - watch 34 | - apiGroups: 35 | - batch 36 | resources: 37 | - jobs 38 | verbs: 39 | - create 40 | - delete 41 | - get 42 | - list 43 | - patch 44 | - update 45 | - watch 46 | -------------------------------------------------------------------------------- /config/rbac/agentaction_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit agentactions. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: agentaction-editor-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - agentactions 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - getporter.org 21 | resources: 22 | - agentactions/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/agentaction_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view agentactions. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: agentaction-viewer-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - agentactions 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - getporter.org 17 | resources: 18 | - agentactions/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/agentconfig_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit agnetconfig. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: agentconfigs-editor-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - agentconfigs 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - getporter.org 21 | resources: 22 | - agentconfigs/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/agentconfig_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view credentialsets. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: agentconfigs-viewer-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - agentconfigs 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - getporter.org 17 | resources: 18 | - agentconfigs/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | -------------------------------------------------------------------------------- /config/rbac/credentialset_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit credentialsets. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: credentialset-editor-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - credentialsets 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - getporter.org 21 | resources: 22 | - credentialsets/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/credentialset_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view credentialsets. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: credentialset-viewer-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - credentialsets 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - getporter.org 17 | resources: 18 | - credentialsets/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/installation_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit installations. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: installation-editor-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - installations 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - getporter.org 21 | resources: 22 | - installations/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/installation_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view installations. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: installation-viewer-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - installations 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - getporter.org 17 | resources: 18 | - installations/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/installationoutput_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit installationoutputs. 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: installationoutput-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: porter-operator 10 | app.kubernetes.io/part-of: porter-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: installationoutput-editor-role 13 | rules: 14 | - apiGroups: 15 | - getporter.org 16 | resources: 17 | - installationoutputs 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - getporter.org 28 | resources: 29 | - installationoutputs/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/installationoutput_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view installationoutputs. 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: installationoutput-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: porter-operator 10 | app.kubernetes.io/part-of: porter-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: installationoutput-viewer-role 13 | rules: 14 | - apiGroups: 15 | - getporter.org 16 | resources: 17 | - installationoutputs 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - getporter.org 24 | resources: 25 | - installationoutputs/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - agent_role.yaml 5 | - leader_election_role.yaml 6 | - leader_election_role_binding.yaml 7 | # Comment the following 4 lines if you want to disable 8 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 9 | # which protects your /metrics endpoint. 10 | - auth_proxy_service.yaml 11 | - auth_proxy_role.yaml 12 | - auth_proxy_role_binding.yaml 13 | - auth_proxy_client_clusterrole.yaml 14 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | - coordination.k8s.io 10 | resources: 11 | - configmaps 12 | - leases 13 | verbs: 14 | - get 15 | - list 16 | - watch 17 | - create 18 | - update 19 | - patch 20 | - delete 21 | - apiGroups: 22 | - "" 23 | resources: 24 | - events 25 | verbs: 26 | - create 27 | - patch 28 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/parameterset_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit parametersets. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: parameterset-editor-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - parametersets 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - getporter.org 21 | resources: 22 | - parametersets/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/parameterset_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view parametersets. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: parameterset-viewer-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - parametersets 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - getporter.org 17 | resources: 18 | - parametersets/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/porterconfig_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit porterconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: porterconfig-editor-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - porterconfigs 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - getporter.org 21 | resources: 22 | - porterconfigs/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/porterconfig_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view porterconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: porterconfig-viewer-role 6 | rules: 7 | - apiGroups: 8 | - getporter.org 9 | resources: 10 | - porterconfigs 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - getporter.org 17 | resources: 18 | - porterconfigs/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /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 | - persistentvolumeclaims 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - persistentvolumes 23 | verbs: 24 | - create 25 | - delete 26 | - get 27 | - list 28 | - patch 29 | - update 30 | - watch 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - secrets 35 | verbs: 36 | - create 37 | - delete 38 | - get 39 | - list 40 | - patch 41 | - update 42 | - watch 43 | - apiGroups: 44 | - "" 45 | resources: 46 | - serviceaccounts 47 | verbs: 48 | - get 49 | - list 50 | - watch 51 | - apiGroups: 52 | - batch 53 | resources: 54 | - jobs 55 | verbs: 56 | - create 57 | - delete 58 | - get 59 | - list 60 | - patch 61 | - update 62 | - watch 63 | - apiGroups: 64 | - getporter.org 65 | resources: 66 | - agentactions 67 | verbs: 68 | - create 69 | - delete 70 | - get 71 | - list 72 | - patch 73 | - update 74 | - watch 75 | - apiGroups: 76 | - getporter.org 77 | resources: 78 | - agentactions/finalizers 79 | verbs: 80 | - update 81 | - apiGroups: 82 | - getporter.org 83 | resources: 84 | - agentactions/status 85 | verbs: 86 | - get 87 | - patch 88 | - update 89 | - apiGroups: 90 | - getporter.org 91 | resources: 92 | - agentconfigs 93 | verbs: 94 | - create 95 | - delete 96 | - get 97 | - list 98 | - patch 99 | - update 100 | - watch 101 | - apiGroups: 102 | - getporter.org 103 | resources: 104 | - agentconfigs/finalizers 105 | verbs: 106 | - update 107 | - apiGroups: 108 | - getporter.org 109 | resources: 110 | - agentconfigs/status 111 | verbs: 112 | - get 113 | - patch 114 | - update 115 | - apiGroups: 116 | - getporter.org 117 | resources: 118 | - credentialsets 119 | verbs: 120 | - create 121 | - delete 122 | - get 123 | - list 124 | - patch 125 | - update 126 | - watch 127 | - apiGroups: 128 | - getporter.org 129 | resources: 130 | - credentialsets/finalizers 131 | verbs: 132 | - update 133 | - apiGroups: 134 | - getporter.org 135 | resources: 136 | - credentialsets/status 137 | verbs: 138 | - get 139 | - patch 140 | - update 141 | - apiGroups: 142 | - getporter.org 143 | resources: 144 | - installationoutputs 145 | verbs: 146 | - create 147 | - delete 148 | - get 149 | - list 150 | - patch 151 | - update 152 | - watch 153 | - apiGroups: 154 | - getporter.org 155 | resources: 156 | - installationoutputs/status 157 | verbs: 158 | - get 159 | - patch 160 | - update 161 | - apiGroups: 162 | - getporter.org 163 | resources: 164 | - installations 165 | verbs: 166 | - create 167 | - delete 168 | - get 169 | - list 170 | - patch 171 | - update 172 | - watch 173 | - apiGroups: 174 | - getporter.org 175 | resources: 176 | - installations/finalizers 177 | verbs: 178 | - patch 179 | - update 180 | - apiGroups: 181 | - getporter.org 182 | resources: 183 | - installations/status 184 | verbs: 185 | - get 186 | - patch 187 | - update 188 | - apiGroups: 189 | - getporter.org 190 | resources: 191 | - parametersets 192 | verbs: 193 | - create 194 | - delete 195 | - get 196 | - list 197 | - patch 198 | - update 199 | - watch 200 | - apiGroups: 201 | - getporter.org 202 | resources: 203 | - parametersets/finalizers 204 | verbs: 205 | - update 206 | - apiGroups: 207 | - getporter.org 208 | resources: 209 | - parametersets/status 210 | verbs: 211 | - get 212 | - patch 213 | - update 214 | - apiGroups: 215 | - getporter.org 216 | resources: 217 | - porterconfigs 218 | verbs: 219 | - create 220 | - delete 221 | - get 222 | - list 223 | - patch 224 | - update 225 | - watch 226 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/samples/_v1_agentaction.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: AgentAction 3 | metadata: 4 | name: agentaction-sample 5 | spec: 6 | args: ["installation", "apply", "installation.yaml"] 7 | files: 8 | # base64 encoded file contents 9 | installation.yaml: c2NoZW1hVmVyc2lvbjogMS4wLjAKbmFtZXNwYWNlOiBvcGVyYXRvcgpuYW1lOiBoZWxsbwpidW5kbGU6CiAgcmVwb3NpdG9yeTogZ2hjci5pby9nZXRwb3J0ZXIvdGVzdC9wb3J0ZXItaGVsbG8KICB2ZXJzaW9uOiAwLjIuMApwYXJhbWV0ZXJzOgogIG5hbWU6IGxsYW1hcyAK 10 | -------------------------------------------------------------------------------- /config/samples/_v1_agentconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: AgentConfig 3 | metadata: 4 | name: agentconfig-sample 5 | labels: 6 | getporter.org/testdata: "true" 7 | spec: 8 | porterRepository: ghcr.io/getporter/porter-agent 9 | porterVersion: canary 10 | serviceAccount: porter-agent 11 | volumeSize: 64Mi 12 | pullPolicy: Always 13 | installationServiceAccount: installation-agent 14 | pluginConfigFile: 15 | schemaVersion: 1.0.0 16 | plugins: 17 | kubernetes: 18 | version: v1.0.1 -------------------------------------------------------------------------------- /config/samples/_v1_credentialset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: CredentialSet 3 | metadata: 4 | name: credentialset-sample 5 | spec: 6 | schemaVersion: 1.0.1 7 | namespace: operator 8 | name: porter-test-me 9 | credentials: 10 | - name: insecureValue 11 | source: 12 | secret: test-secret 13 | -------------------------------------------------------------------------------- /config/samples/_v1_installationoutput.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: InstallationOutput 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: installationoutput 6 | app.kubernetes.io/instance: installationoutput-sample 7 | app.kubernetes.io/part-of: porter-operator 8 | app.kubernetes.io/managed-by: kustomize 9 | app.kubernetes.io/created-by: porter-operator 10 | name: installationoutput-sample 11 | spec: 12 | # TODO(user): Add fields here 13 | -------------------------------------------------------------------------------- /config/samples/_v1_parameterset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: ParameterSet 3 | metadata: 4 | name: parameterset-sample 5 | spec: 6 | schemaVersion: 1.0.1 7 | namespace: operator 8 | name: porter-test-me 9 | parameters: 10 | - name: test-secret 11 | source: 12 | value: test-value 13 | - name: test-secret 14 | source: 15 | secret: test-secret -------------------------------------------------------------------------------- /config/samples/_v1_porterconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: PorterConfig 3 | metadata: 4 | name: porterconfig-sample 5 | labels: 6 | getporter.org/testdata: "true" 7 | spec: 8 | verbosity: debug 9 | default-secrets-plugin: kubernetes.secrets 10 | default-storage: in-cluster-mongodb 11 | storage: 12 | - name: in-cluster-mongodb 13 | plugin: mongodb 14 | config: 15 | url: "mongodb://mongodb.porter-operator-system.svc.cluster.local" 16 | -------------------------------------------------------------------------------- /config/samples/exec-outputs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: Installation 3 | metadata: 4 | name: exec-outputs 5 | labels: 6 | getporter.org/testdata: "true" 7 | spec: 8 | schemaVersion: 1.0.2 9 | namespace: operator 10 | name: outputs 11 | bundle: 12 | repository: getporter/exec-outputs 13 | version: 0.1.1 14 | -------------------------------------------------------------------------------- /config/samples/hello-llama.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: Installation 3 | metadata: 4 | name: hello-llama 5 | labels: 6 | getporter.org/testdata: "true" 7 | spec: 8 | schemaVersion: 1.0.2 9 | namespace: operator 10 | name: mellama 11 | bundle: 12 | repository: getporter/hello-llama 13 | version: 0.1.1 14 | parameters: 15 | name: "Porter Operator" 16 | -------------------------------------------------------------------------------- /config/samples/kubeflow.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: Installation 3 | metadata: 4 | name: kubeflow 5 | labels: 6 | getporter.org/testdata: "true" 7 | spec: 8 | schemaVersion: 1.0.2 9 | namespace: operator 10 | name: kubeflow 11 | bundle: 12 | repository: ghcr.io/squillace/aks-kubeflow-msi 13 | version: 0.1.7 14 | credentialSets: 15 | - aks 16 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - _v1_installation.yaml 4 | - _v1_porterconfig.yaml 5 | - _v1_agentconfig.yaml 6 | - _v1_agentaction.yaml 7 | - _v1_credentialset.yaml 8 | - _v1_parameterset.yaml 9 | # +kubebuilder:scaffold:manifestskustomizesamples 10 | -------------------------------------------------------------------------------- /config/samples/porter-hello.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: Installation 3 | metadata: 4 | name: porter-hello 5 | labels: 6 | getporter.org/testdata: "true" 7 | spec: 8 | schemaVersion: 1.0.2 9 | namespace: operator 10 | name: hello 11 | bundle: 12 | repository: ghcr.io/getporter/test/porter-hello 13 | version: 0.2.0 14 | parameters: 15 | name: llamas 16 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | # +kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.3.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.3.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.3.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.3.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.3.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.3.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /controllers/generate.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | //go:generate mockery --name=PorterClient --filename=grpc_mocks.go --outpkg=mocks --output=../mocks/grpc 4 | //go:generate mockery --name=ClientConn --filename=clientconn_mocks.go --outpkg=mocks --output=../mocks/grpc 5 | -------------------------------------------------------------------------------- /controllers/logs.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | const ( 4 | // Includes information about the code path 5 | Log5Trace = iota 6 | 7 | // Includes additional logs and data that help with debugging 8 | Log4Debug 9 | 10 | // Includes information about the state of the host 11 | Log3SystemState 12 | 13 | // Includes information about the state of the controllers 14 | Log2ApplicationState 15 | 16 | // Includes information about notable actions performed 17 | Log1Info 18 | 19 | // Error and warning messages only 20 | Log0Error 21 | ) 22 | -------------------------------------------------------------------------------- /controllers/porter_resource.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "reflect" 7 | "time" 8 | 9 | porterv1 "get.porter.sh/operator/api/v1" 10 | "github.com/go-logr/logr" 11 | "github.com/pkg/errors" 12 | corev1 "k8s.io/api/core/v1" 13 | apierrors "k8s.io/apimachinery/pkg/api/errors" 14 | apimeta "k8s.io/apimachinery/pkg/api/meta" 15 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 | "sigs.k8s.io/controller-runtime/pkg/client" 17 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 18 | "sigs.k8s.io/controller-runtime/pkg/event" 19 | "sigs.k8s.io/controller-runtime/pkg/predicate" 20 | ) 21 | 22 | type PorterResource interface { 23 | client.Object 24 | GetStatus() porterv1.PorterResourceStatus 25 | SetStatus(value porterv1.PorterResourceStatus) 26 | } 27 | 28 | type patchFunc func(ctx context.Context, obj client.Object, patch client.Patch, _ ...client.PatchOption) error 29 | type patchStatusFunc func(ctx context.Context, obj client.Object, patch client.Patch, _ ...client.SubResourcePatchOption) error 30 | 31 | func PatchObjectWithRetry(ctx context.Context, log logr.Logger, clnt client.Client, patchFn patchFunc, obj client.Object, newObj func() client.Object) error { 32 | ctx, cancel := context.WithTimeout(ctx, time.Minute) 33 | defer cancel() 34 | 35 | kind := obj.GetObjectKind().GroupVersionKind().Kind 36 | 37 | for { 38 | key := client.ObjectKeyFromObject(obj) 39 | latest := newObj() 40 | if err := clnt.Get(ctx, key, latest); err != nil { 41 | return errors.Wrap(err, fmt.Sprintf("could not get the latest %s definition", kind)) 42 | } 43 | 44 | patchObj := client.MergeFrom(latest) 45 | err := patchFn(ctx, obj, patchObj) 46 | if err != nil { 47 | if apierrors.IsConflict(err) { 48 | continue // try again 49 | } 50 | return errors.Wrapf(err, "failed to patch %s", kind) 51 | } 52 | 53 | if log.V(Log4Debug).Enabled() { 54 | patchDump, _ := patchObj.Data(obj) 55 | log.V(Log4Debug).Info("Applied patch", "data", string(patchDump)) 56 | } 57 | return nil 58 | } 59 | } 60 | 61 | func PatchStatusWithRetry(ctx context.Context, log logr.Logger, clnt client.Client, patchStatusFn patchStatusFunc, obj client.Object, newObj func() client.Object) error { 62 | convert := func(patchStatusFn patchStatusFunc) patchFunc { 63 | return func(ctx context.Context, obj client.Object, patch client.Patch, _ ...client.PatchOption) error { 64 | return patchStatusFn(ctx, obj, patch) 65 | } 66 | } 67 | patchFn := convert(patchStatusFn) 68 | return PatchObjectWithRetry(ctx, log, clnt, patchFn, obj, newObj) 69 | } 70 | 71 | func applyAgentAction(log logr.Logger, resource PorterResource, action *porterv1.AgentAction) { 72 | log.V(Log5Trace).Info(fmt.Sprintf("Syncing AgentAction status with %s", resource.GetObjectKind().GroupVersionKind().Kind)) 73 | status := resource.GetStatus() 74 | status.ObservedGeneration = resource.GetGeneration() 75 | status.Phase = porterv1.PhaseUnknown 76 | 77 | if action == nil { 78 | status.Action = nil 79 | status.Conditions = nil 80 | log.V(Log5Trace).Info("Cleared status because there is no current agent action") 81 | } else { 82 | status.Action = &corev1.LocalObjectReference{Name: action.Name} 83 | if action.Status.Phase != "" { 84 | status.Phase = action.Status.Phase 85 | } 86 | status.Conditions = make([]metav1.Condition, len(action.Status.Conditions)) 87 | copy(status.Conditions, action.Status.Conditions) 88 | 89 | if log.V(Log5Trace).Enabled() { 90 | conditions := make([]string, len(status.Conditions)) 91 | for i, condition := range status.Conditions { 92 | conditions[i] = condition.Type 93 | } 94 | log.V(Log5Trace).Info("Copied status from agent action", "action", action.Name, "phase", action.Status.Phase, "conditions", conditions) 95 | } 96 | } 97 | 98 | resource.SetStatus(status) 99 | } 100 | 101 | // isDeleted checks whether a porter resource is deleted. 102 | func isDeleted(resource PorterResource) bool { 103 | timestamp := resource.GetDeletionTimestamp() 104 | return timestamp != nil && !timestamp.IsZero() 105 | } 106 | 107 | // isDeletedProcessed ensures delete action is completed before delete 108 | func isDeleteProcessed(resource PorterResource) bool { 109 | status := resource.GetStatus() 110 | return isDeleted(resource) && apimeta.IsStatusConditionTrue(status.Conditions, string(porterv1.ConditionComplete)) 111 | } 112 | 113 | func isFinalizerSet(resource PorterResource) bool { 114 | for _, finalizer := range resource.GetFinalizers() { 115 | if finalizer == porterv1.FinalizerName { 116 | return true 117 | } 118 | } 119 | return false 120 | } 121 | 122 | // ensureFinalizerSet sets a finalizer on the resource and saves it, if necessary. 123 | func ensureFinalizerSet(ctx context.Context, log logr.Logger, client client.Client, resource PorterResource) (updated bool, err error) { 124 | // Ensure all resources have a finalizer to we can react when they are deleted 125 | if !isDeleted(resource) { 126 | // The object is not being deleted, so if it does not have our finalizer, 127 | // then lets add the finalizer and update the object. This is equivalent 128 | // registering our finalizer. 129 | if !isFinalizerSet(resource) { 130 | log.V(Log5Trace).Info("adding finalizer") 131 | controllerutil.AddFinalizer(resource, porterv1.FinalizerName) 132 | return true, client.Update(ctx, resource) 133 | } 134 | } 135 | return false, nil 136 | } 137 | 138 | // removeFinalizer deletes the porter finalizer from the specified resource and saves it. 139 | func removeFinalizer(ctx context.Context, log logr.Logger, client client.Client, inst *porterv1.Installation) error { 140 | log.V(Log5Trace).Info("removing finalizer") 141 | controllerutil.RemoveFinalizer(inst, porterv1.FinalizerName) 142 | return client.Update(ctx, inst) 143 | } 144 | 145 | // Build the set of labels used to uniquely identify the associated AgentAction. 146 | func getActionLabels(resource metav1.Object) map[string]string { 147 | typeInfo, err := apimeta.TypeAccessor(resource) 148 | if err != nil { 149 | panic(err) 150 | } 151 | 152 | return map[string]string{ 153 | porterv1.LabelManaged: "true", 154 | porterv1.LabelResourceKind: typeInfo.GetKind(), 155 | porterv1.LabelResourceName: resource.GetName(), 156 | porterv1.LabelResourceGeneration: fmt.Sprintf("%d", resource.GetGeneration()), 157 | } 158 | } 159 | 160 | // resourceChanged is a predicate that filters events that are sent to Reconcile 161 | // only triggers when the spec or the finalizer was changed. 162 | // Allows forcing Reconcile with the retry annotation as well. 163 | type resourceChanged struct { 164 | predicate.Funcs 165 | } 166 | 167 | func (resourceChanged) Update(e event.UpdateEvent) bool { 168 | if e.ObjectNew.GetGeneration() != e.ObjectOld.GetGeneration() { 169 | return true 170 | } 171 | 172 | if !reflect.DeepEqual(e.ObjectNew.GetFinalizers(), e.ObjectOld.GetFinalizers()) { 173 | return true 174 | } 175 | 176 | if e.ObjectNew.GetAnnotations()[porterv1.AnnotationRetry] != e.ObjectOld.GetAnnotations()[porterv1.AnnotationRetry] { 177 | return true 178 | } 179 | 180 | return false 181 | } 182 | -------------------------------------------------------------------------------- /controllers/porter_resource_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | porterv1 "get.porter.sh/operator/api/v1" 8 | "github.com/go-logr/logr" 9 | "github.com/stretchr/testify/assert" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "sigs.k8s.io/controller-runtime/pkg/event" 12 | ) 13 | 14 | func Test_resourceChanged_Update(t *testing.T) { 15 | predicate := resourceChanged{} 16 | 17 | t.Run("spec changed", func(t *testing.T) { 18 | e := event.UpdateEvent{ 19 | ObjectOld: &porterv1.Installation{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Generation: 1, 22 | }, 23 | }, 24 | ObjectNew: &porterv1.Installation{ 25 | ObjectMeta: metav1.ObjectMeta{ 26 | Generation: 2, 27 | }, 28 | }, 29 | } 30 | assert.True(t, predicate.Update(e), "expected changing the generation to trigger reconciliation") 31 | }) 32 | 33 | t.Run("finalizer added", func(t *testing.T) { 34 | e := event.UpdateEvent{ 35 | ObjectOld: &porterv1.Installation{ 36 | ObjectMeta: metav1.ObjectMeta{ 37 | Generation: 1, 38 | }, 39 | }, 40 | ObjectNew: &porterv1.Installation{ 41 | ObjectMeta: metav1.ObjectMeta{ 42 | Generation: 1, 43 | Finalizers: []string{porterv1.FinalizerName}, 44 | }, 45 | }, 46 | } 47 | assert.True(t, predicate.Update(e), "expected setting a finalizer to trigger reconciliation") 48 | }) 49 | 50 | t.Run("retry annotation changed", func(t *testing.T) { 51 | e := event.UpdateEvent{ 52 | ObjectOld: &porterv1.Installation{ 53 | ObjectMeta: metav1.ObjectMeta{ 54 | Generation: 1, 55 | }, 56 | }, 57 | ObjectNew: &porterv1.Installation{ 58 | ObjectMeta: metav1.ObjectMeta{ 59 | Generation: 1, 60 | Annotations: map[string]string{ 61 | porterv1.AnnotationRetry: "1", 62 | }, 63 | }, 64 | }, 65 | } 66 | assert.True(t, predicate.Update(e), "expected setting changing the retry annotation to trigger reconciliation") 67 | }) 68 | 69 | t.Run("status changed", func(t *testing.T) { 70 | e := event.UpdateEvent{ 71 | ObjectOld: &porterv1.Installation{ 72 | ObjectMeta: metav1.ObjectMeta{ 73 | Generation: 2, 74 | }, 75 | Status: porterv1.InstallationStatus{PorterResourceStatus: porterv1.PorterResourceStatus{ 76 | ObservedGeneration: 1, 77 | }}, 78 | }, 79 | ObjectNew: &porterv1.Installation{ 80 | ObjectMeta: metav1.ObjectMeta{ 81 | Generation: 2, 82 | }, 83 | Status: porterv1.InstallationStatus{PorterResourceStatus: porterv1.PorterResourceStatus{ 84 | ObservedGeneration: 2, 85 | }}, 86 | }, 87 | } 88 | assert.False(t, predicate.Update(e), "expected status changes to be ignored") 89 | }) 90 | 91 | t.Run("label added", func(t *testing.T) { 92 | e := event.UpdateEvent{ 93 | ObjectOld: &porterv1.Installation{ 94 | ObjectMeta: metav1.ObjectMeta{ 95 | Generation: 1, 96 | ResourceVersion: "1", 97 | }, 98 | }, 99 | ObjectNew: &porterv1.Installation{ 100 | ObjectMeta: metav1.ObjectMeta{ 101 | Generation: 1, 102 | ResourceVersion: "2", 103 | Labels: map[string]string{ 104 | "myLabel": "super useful", 105 | }, 106 | }, 107 | }, 108 | } 109 | assert.False(t, predicate.Update(e), "expected metadata changes to be ignored") 110 | }) 111 | } 112 | 113 | func Test_isFinalizerSet(t *testing.T) { 114 | inst := &porterv1.Installation{ 115 | ObjectMeta: metav1.ObjectMeta{}, 116 | } 117 | assert.False(t, isFinalizerSet(inst)) 118 | 119 | inst.Finalizers = append(inst.Finalizers, "something-else") 120 | assert.False(t, isFinalizerSet(inst)) 121 | 122 | inst.Finalizers = append(inst.Finalizers, porterv1.FinalizerName) 123 | assert.True(t, isFinalizerSet(inst)) 124 | } 125 | 126 | func TestRemoveFinalizer(t *testing.T) { 127 | ctx := context.Background() 128 | inst := &porterv1.Installation{ 129 | ObjectMeta: metav1.ObjectMeta{ 130 | Name: "fake-installation", 131 | Namespace: "default", 132 | }, 133 | } 134 | client := setupInstallationController(inst) 135 | 136 | err := removeFinalizer(ctx, logr.Discard(), client.Client, inst) 137 | assert.NoError(t, err) 138 | } 139 | -------------------------------------------------------------------------------- /controllers/types.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | installationv1 "get.porter.sh/porter/gen/proto/go/porterapis/installation/v1alpha1" 7 | "google.golang.org/grpc" 8 | ) 9 | 10 | type PorterClient interface { 11 | ListInstallations(ctx context.Context, in *installationv1.ListInstallationsRequest, opts ...grpc.CallOption) (*installationv1.ListInstallationsResponse, error) 12 | ListInstallationLatestOutputs(ctx context.Context, in *installationv1.ListInstallationLatestOutputRequest, opts ...grpc.CallOption) (*installationv1.ListInstallationLatestOutputResponse, error) 13 | } 14 | 15 | type ClientConn interface { 16 | Close() error 17 | } 18 | -------------------------------------------------------------------------------- /docker-bake.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "group": { 4 | "default": { 5 | "targets": ["porter"] 6 | } 7 | }, 8 | "target": { 9 | "porter": { 10 | "platforms": ["linux/amd64", "linux/arm64"], 11 | "dockerfile": "Dockerfile", 12 | "context": "./" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/content/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Porter Operator 3 | description: Automate Porter on Kubernetes with the Porter Operator 4 | layout: single 5 | --- 6 | 7 | With Porter Operator, you define installations, credential sets and parameter sets in custom resources on a cluster, and the operator handles executing Porter when the desired state of an installation changes. 8 | Learn more about how Porter manages desired state in our [Desired State QuickStart]. 9 | 10 | ![architectural diagram showing that an installation resource triggers the operator to run a porter agent job, which then runs the bundle, saving state in mongodb](operator.png) 11 | 12 | The initial prototype gave us a lot of feedback for how to improve Porter's support for desired state, resulting in the new [porter installation apply] command. 13 | We are currently rewriting the operator to make use of this new command and desired state patterns. 14 | 15 | You can watch the https://github.com/getporter/operator repository to know when new releases are ready, and participate in design discussions. 16 | 17 | ## Current State 18 | 19 | The operator is still under development, but it is ready for you to try out and provide feedback! 20 | 21 | [connect]: https://github.com/getporter/operator/blob/main/CONTRIBUTING.md#connect-to-the-in-cluster-mongo-database 22 | 23 | ## Next Steps 24 | 25 | * [Porter Operator Glossary](/docs/operator/glossary/) 26 | * [QuickStart: Using the Porter Operator](/docs/operator/quickstart/) 27 | * [Porter Operator File Formats](/docs/operator/file-formats/) 28 | * [Configure the Porter Agent] 29 | [porter installation apply]: https://porter.sh/cli/porter_installations_apply/ 30 | [Desired State QuickStart]: https://porter.sh/docs/introduction/concepts-and-components/intro-desired-state/ 31 | -------------------------------------------------------------------------------- /docs/content/administrators/configure-porter-agent.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Configure the Porter Agent 3 | description: Customize how Porter runs on Kubernetes 4 | --- 5 | 6 | The [Porter Agent] is a containerized version of the Porter CLI that is optimized for running Porter commands on a Kubernetes cluster. With the AgentConfig Custom Resource Definition (CRD), you can customize how the Porter Agent is run to meet your specific needs and requirements. For example, you can specify the version of Porter to use, install additional Porter plugins, or provide a custom Porter config file. 7 | 8 | This guide will show some ways to configure the Porter Agent through the [AgentConfig CRD](/docs/operator/file-formats/#agentconfig). 9 | 10 | First, let's create a new file with the AgentConfig CRD: 11 | 12 | ```yaml 13 | apiVersion: getporter.org/v1 14 | kind: AgentConfig 15 | metadata: 16 | name: customAgent 17 | spec: 18 | ``` 19 | 20 | ### Specifying a version 21 | 22 | By default, the Porter Agent uses the latest version from the official GitHub release. However, if you want to use a different version or a custom build hosted outside the official project, you can specify the repository and the version in the CRD. Here's an example: 23 | 24 | ```yaml 25 | apiVersion: getporter.org/v1 26 | kind: AgentConfig 27 | metadata: 28 | name: customAgent 29 | spec: 30 | porterRepository: 31 | porterVersion: v1.2.3 32 | ``` 33 | 34 | ### Configuring Access Permission 35 | 36 | In some cases, you may want to restrict access to the private registry that contains the images you need to install. With the AgentConfig CRD, you can specify two service accounts, one for the pod that runs the Agent job and another for the pod that runs the Porter installation. Here's an example: 37 | 38 | ```yaml 39 | apiVersion: getporter.org/v1 40 | kind: AgentConfig 41 | metadata: 42 | name: customAgent 43 | spec: 44 | porterRepository: 45 | porterVersion: v1.2.3 46 | serviceAccount: 47 | installationServiceAccount: 48 | ``` 49 | 50 | The porter operator ships two pre-defined ClusterRole, agentconfigs-editor-role and agentconfigs-viewer-role, for AgentConfig resources to help you to properly assign permissions to a custom service account. 51 | 52 | For more information on working with private registry images, see [this section of the Porter Operator Quickstart Guide](/docs/operator/quickstart/#private-bundle-registries). 53 | 54 | ## Configuring Porter Plugins 55 | 56 | You can also specify any required plugins necessary for your installation of Porter. For example, if you want to use the Kubernetes and Azure plugins, you can configure the AgentConfig like this: 57 | 58 | ```yaml 59 | apiVersion: getporter.org/v1 60 | kind: AgentConfig 61 | metadata: 62 | name: customAgent 63 | spec: 64 | pluginConfigFile: 65 | schemaVersion: 1.0.0 66 | plugins: 67 | kubernetes: 68 | version: v1.0.1 69 | azure: 70 | version: v1.0.1 71 | ``` 72 | 73 | The schema for the pluginConfigFile field is defined [in the Porter reference documentation](/docs/references/file-formats/plugins/). 74 | 75 | 🚨 WARNING: By default, the plugin version is set to `latest`. We recommend pinning to specific version of any plugins used to avoid undesired behavior caused by a stale plugin cache. Porter currently does not expire cached installations of plugins, so installing "latest" will not pick up new versions of plugins when they are released. 76 | 77 | ## Configuring Persistent Volume 78 | 79 | The AgentConfig can modify the VolumeSize that it creates as well as what StorageClassName it uses to create the volume. 80 | 81 | The VolumeSize can be specified using [CSIStorageCapacity Notation] and will result in the Persistent Volumes created by the operator having the requested capacity. When VolumeSize is not specified then a default of `64Mi` is used. 82 | 83 | The StorageClassName must resolve to the name of a StorageClass that is defined on the cluster. That StorageClass must have the following capabilities: 84 | 85 | - Supported AccessModes: ReadWriteOnce, ReadOnlyMany 86 | - Allow for running `chmod` 87 | 88 | When StorageClassName is not specified on the AgentConfig then the clusters default StorageClass is used. A custom StorageClass can be created by the cluster administrator and used by the operator if none of the clusters built-in StorageClasses fulfill the above requirements. 89 | 90 | You can configure the VolumeSize and StorageClassName in the AgentConfig like this: 91 | 92 | ```yaml 93 | apiVersion: getporter.org/v1 94 | kind: AgentConfig 95 | metadata: 96 | name: customAgent 97 | spec: 98 | storageClassName: customStorageClassName 99 | volumeSize: 128Mi 100 | ``` 101 | 102 | [csistoragecapacity notation]: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/csi-storage-capacity-v1/ 103 | 104 | ### StorageClassName Cluster Compatability Matrix 105 | 106 | This matrix will be updated as more clusters and CSI drivers are determined to be compatible 107 | 108 | | Cluster Type | Built In Compatible Driver | Cluster Version | 109 | | ------------ | -------------------------- | --------------- | 110 | | AKS | azureblob-nfs-premium | v1.25.4 | 111 | | KinD | default | v1.23.4 | 112 | 113 | [Porter Agent]: /docs/operator/glossary/#porteragent 114 | -------------------------------------------------------------------------------- /docs/content/operator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getporter/operator/7fad05bac94d461a7e8aaf2fddd5ce3be64473ad/docs/content/operator.png -------------------------------------------------------------------------------- /docs/content/quickstart/llama.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: Installation 3 | metadata: 4 | name: hello-llama 5 | namespace: quickstart 6 | spec: 7 | schemaVersion: 1.0.2 8 | namespace: quickstart 9 | name: mellama 10 | bundle: 11 | repository: getporter/hello-llama 12 | version: 0.1.1 13 | parameters: 14 | name: quickstart 15 | -------------------------------------------------------------------------------- /hack/arm-mongodb-params.yaml: -------------------------------------------------------------------------------- 1 | # You can change the arm64 image used for mongodb by setting PORTER_MONGODB_IMAGE 2 | schemaVersion: 1.0.1 3 | name: arm-mongodb-hack 4 | parameters: 5 | - name: mongodbVals 6 | source: 7 | path: hack/arm-mongodb-vals.yaml 8 | -------------------------------------------------------------------------------- /hack/arm-mongodb-vals.tmpl.yaml: -------------------------------------------------------------------------------- 1 | # This changes the image used for running mongodb to one that supports arm64 2 | # Bitnami helm charts do not support arm: https://github.com/bitnami/charts/issues/7305 3 | # This image was built using https://github.com/ZCube/bitnami-compat to create an 4 | # arm64 compatible image from the bitnami source. 5 | image: 6 | registry: ghcr.io 7 | repository: carolynvs/mongodb-bitnami-compat 8 | tag: 6.0.3-debian-11-r50 9 | digest: sha256:7397ffec8a5164deca5da0b52eb9f811acac04caaf1ecb215c2ef2ed33665191 10 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getporter/operator/7fad05bac94d461a7e8aaf2fddd5ce3be64473ad/hack/boilerplate.go.txt -------------------------------------------------------------------------------- /hack/creds.yaml: -------------------------------------------------------------------------------- 1 | # Use the local kind cluster created by mage EnsureTestCluster 2 | schemaVersion: 1.0.1 3 | name: kind 4 | credentials: 5 | - name: kubeconfig 6 | source: 7 | path: kind.config 8 | -------------------------------------------------------------------------------- /hack/dev-build-params.yaml: -------------------------------------------------------------------------------- 1 | # You can configure the mage bump command to use a local build of porter 2 | # build with mage XBuildAll LocalPorterAgentBuild 3 | # export PORTER_AGENT_REPOSITORY=localhost:5000/porter-agent 4 | # export PORTER_AGENT_VERSION=canary-dev 5 | schemaVersion: 1.0.1 6 | name: dev-build 7 | parameters: 8 | - name: porterRepository 9 | source: 10 | env: PORTER_AGENT_REPOSITORY 11 | - name: porterVersion 12 | source: 13 | env: PORTER_AGENT_VERSION 14 | - name: pullPolicy 15 | source: 16 | value: "Always" 17 | -------------------------------------------------------------------------------- /hack/website-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

The site has been deployed to getporter.org

7 | 8 | -------------------------------------------------------------------------------- /installer-olm/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | -------------------------------------------------------------------------------- /installer-olm/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .cnab/ 3 | -------------------------------------------------------------------------------- /installer-olm/README.md: -------------------------------------------------------------------------------- 1 | # Operator Lifecycle Manager (OLM) Bundle 2 | 3 | This bundle installs [Operator Lifecycle Manager][olm]. It downloads the appropriate 4 | manifests at runtime, so the same bundle can install any version of OLM. 5 | 6 | 7 | ## Generate credentials 8 | 9 | Before you can run the bundle, you need to generate credentials that point to 10 | the Kubernetes cluster where OLM should be installed. 11 | 12 | ``` 13 | porter credentials generate mycluster --reference ghcr.io/getporter/olm:v0.1.0 14 | ``` 15 | 16 | ## Install the latest version of OLM 17 | ``` 18 | porter install olm --reference ghcr.io/getporter/olm:v0.1.0 -c mycluster 19 | ``` 20 | 21 | ## Install a specific version of OLM 22 | ``` 23 | porter install olm --reference ghcr.io/getporter/olm:v0.1.0 -c mycluster --param version=v0.16.0 24 | ``` 25 | 26 | [olm]: https://github.com/operator-framework/operator-lifecycle-manager 27 | -------------------------------------------------------------------------------- /installer-olm/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | VERISON_FILE="/cnab/app/version.txt" 5 | DOWNLOAD_URL="https://github.com/operator-framework/operator-lifecycle-manager/releases/download" 6 | 7 | install() { 8 | setVersion 9 | downloadManifests 10 | applyManifests 11 | } 12 | 13 | upgrade() { 14 | setVersion 15 | downloadManifests 16 | applyManifests 17 | } 18 | 19 | uninstall() { 20 | setVersion 21 | downloadManifests 22 | deleteManifests 23 | } 24 | 25 | applyManifests() { 26 | echo "Running manifests for OLM ${OLM_VERSION}" 27 | 28 | echo "Apply CRDs..." 29 | kubectl apply -f crds.yaml 30 | echo "Waiting for CRDs to register..." 31 | kubectl wait --for condition="established" -f crds.yaml 32 | 33 | echo "Deploying OLM..." 34 | kubectl apply -f olm.yaml 35 | 36 | echo "Waiting for the OLM deployment to complete..." 37 | kubectl rollout status deployment/olm-operator --namespace olm 38 | } 39 | 40 | deleteManifests() { 41 | echo "Removing manifests for OLM ${OLM_VERSION}" 42 | kubectl delete -f olm.yaml --ignore-not-found=true --wait 43 | kubectl delete -f crds.yaml --ignore-not-found=true --wait 44 | } 45 | 46 | downloadManifests() { 47 | echo "Downloading OLM manifests" 48 | download ${DOWNLOAD_URL}/${OLM_VERSION}/crds.yaml 49 | download ${DOWNLOAD_URL}/${OLM_VERSION}/olm.yaml 50 | } 51 | 52 | download() { 53 | MANIFEST_URL=$1 54 | echo ${MANIFEST_URL} 55 | curl -sfLO ${MANIFEST_URL} 56 | if [[ "${PORTER_DEBUG}" == "true" ]]; then 57 | MANIFEST_FILE=$(basename -- "${MANIFEST_URL}") 58 | cat ${MANIFEST_FILE} 59 | fi 60 | } 61 | 62 | setVersion() { 63 | OLM_VERSION=`cat ${VERISON_FILE}` 64 | 65 | if [[ -z ${OLM_VERSION} || ${OLM_VERSION} == "latest" ]]; then 66 | echo "Determining the latest version of OLM..." 67 | OLM_VERSION=`curl -sf https://api.github.com/repos/operator-framework/operator-lifecycle-manager/releases/latest | jq -r .tag_name` 68 | echo "The latest OLM version is ${OLM_VERSION}" 69 | echo ${OLM_VERSION} > ${VERISON_FILE} 70 | fi 71 | } 72 | 73 | printContext() { 74 | echo "Kubernetes Version:" 75 | kubectl version 76 | NAME=`kubectl config current-context` 77 | echo "Using kubeconfig context ${NAME}" 78 | kubectl config get-contexts ${NAME} 79 | } 80 | 81 | # Call the requested function and pass the arguments as-is 82 | printContext 83 | "$@" 84 | -------------------------------------------------------------------------------- /installer-olm/porter.yaml: -------------------------------------------------------------------------------- 1 | name: olm 2 | version: 0.1.0 3 | description: "Installs the Operator Lifecycle Manager (OLM)" 4 | registry: ghcr.io/getporter 5 | 6 | credentials: 7 | - name: kubeconfig 8 | description: A Kubernetes config file with privileges to create new Namespaces, Deployments, ClusterRoles, ClusterRoleBindings and CustomResourceDefinitions. 9 | path: /root/.kube/config 10 | 11 | parameters: 12 | - name: version 13 | description: The version of OLM 14 | type: string 15 | env: OLM_VERSION 16 | path: /cnab/app/version.txt 17 | default: latest 18 | source: 19 | output: version 20 | 21 | outputs: 22 | - name: version 23 | description: Installed version of OLM 24 | type: string 25 | path: /cnab/app/version.txt 26 | applyTo: 27 | - install 28 | - upgrade 29 | 30 | mixins: 31 | - exec 32 | - jq 33 | - kubernetes: 34 | clientVersion: v1.20.2 35 | 36 | install: 37 | - exec: 38 | description: "Install Operator Lifecycle Manager (OLM)" 39 | command: ./helpers.sh 40 | arguments: 41 | - install 42 | 43 | upgrade: 44 | - exec: 45 | description: "Upgrade Operator Lifecycle Manager (OLM)" 46 | command: ./helpers.sh 47 | arguments: 48 | - upgrade 49 | 50 | uninstall: 51 | - exec: 52 | description: "Uninstall Operator Lifecycle Manager (OLM)" 53 | command: ./helpers.sh 54 | arguments: 55 | - uninstall 56 | -------------------------------------------------------------------------------- /installer/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Put files here that you don't want copied into your bundle's invocation image 3 | .gitignore 4 | Dockerfile.tmpl 5 | azure.porter.yaml 6 | vanilla.porter.yaml 7 | -------------------------------------------------------------------------------- /installer/.gitignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .cnab/ 3 | -------------------------------------------------------------------------------- /installer/Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | # syntax=docker.io/docker/dockerfile:1.4 2 | FROM debian:stable-slim 3 | 4 | # PORTER_INIT 5 | 6 | ARG KUSTOMIZE_VERSION="v3.8.7" 7 | 8 | RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache 9 | RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \ 10 | apt-get update && apt-get install -y ca-certificates curl 11 | 12 | # Install yq and kustomize 13 | RUN curl -sLo /usr/bin/yq https://github.com/mikefarah/yq/releases/download/v4.20.2/yq_linux_amd64 && \ 14 | chmod +x /usr/bin/yq &&\ 15 | curl -sLo /tmp/kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz &&\ 16 | tar -C /tmp -xzf /tmp/kustomize.tar.gz &&\ 17 | chmod +x /tmp/kustomize &&\ 18 | mv /tmp/kustomize /usr/bin/kustomize &&\ 19 | rm /tmp/kustomize.tar.gz 20 | 21 | # PORTER_MIXINS 22 | 23 | # Use the BUNDLE_DIR build argument to copy files into the bundle 24 | COPY --link . $BUNDLE_DIR 25 | 26 | -------------------------------------------------------------------------------- /installer/azure.porter.yaml: -------------------------------------------------------------------------------- 1 | name: porter-operator-azure 2 | description: "The Porter Operator on Azure. Execute bundles on an AKS cluster." 3 | registry: ghcr.io/getporter 4 | dockerfile: Dockerfile.tmpl 5 | 6 | parameters: 7 | - name: plugin 8 | type: string 9 | default: kubernetes 10 | description: The name of the plugin to use, can ether be azure or kubernetes . 11 | - name: namespace 12 | type: string 13 | applyTo: 14 | - configureNamespace 15 | - name: volumeSize 16 | description: Size of the volume shared between Porter and the bundles it executes. Defaults to 64Mi. 17 | type: string 18 | path: /cnab/app/manifests/namespace/agentconfig/volumeSize 19 | default: "" 20 | applyTo: 21 | - configureNamespace 22 | - name: porterRepository 23 | description: Docker image repository of the Porter agent. Defaults to ghcr.io/getporter/porter. 24 | type: string 25 | path: /cnab/app/manifests/namespace/agentconfig/porterVersion 26 | default: "" 27 | applyTo: 28 | - configureNamespace 29 | - name: porterVersion 30 | description: Version of the Porter agent, e.g. latest, canary, v0.33.0. Defaults to latest. 31 | type: string 32 | path: /cnab/app/manifests/namespace/agentconfig/porterVersion 33 | default: "" 34 | applyTo: 35 | - configureNamespace 36 | - name: pullPolicy 37 | description: Specifies how the Porter agent image should be pulled. Does not affect how bundles are pulled. Defaults to PullAlways for latest and canary, and PullIfNotPresent otherwise. 38 | type: string 39 | path: /cnab/app/manifests/namespace/agentconfig/pullPolicy 40 | default: "" 41 | applyTo: 42 | - configureNamespace 43 | - name: serviceAccount 44 | description: Name of the service account to run the Porter agent. If set, you are responsible for creating this service account and binding it to the porter-agent ClusterRole. Defaults to the porter-agent account created by the configureNamespace custom action. 45 | type: string 46 | path: /cnab/app/manifests/namespace/agentconfig/serviceAccount 47 | default: "porter-agent" 48 | applyTo: 49 | - configureNamespace 50 | - name: installationServiceAccount 51 | description: Name of the service account to run installation with. If set, you are responsible for creating this service account and giving it required permissions. 52 | type: string 53 | path: /cnab/app/manifests/namespace/agentconfig/installationServiceAccount 54 | default: "" 55 | applyTo: 56 | - configureNamespace 57 | 58 | credentials: 59 | - name: kubeconfig 60 | description: Kubeconfig file for cluster where the operator should be installed 61 | path: /root/.kube/config 62 | - name: config.toml 63 | default: "" 64 | description: Porter configuration file found in ~/.porter/config.toml. This is only required if you are not using the default kubernetes plugin 65 | path: /cnab/app/manifests/namespace/config.toml 66 | - name: azure-storage-connection-string 67 | default: "" 68 | description: Connection string for the azure storage plugin 69 | env: AZURE_STORAGE_CONNECTION_STRING 70 | - name: azure-tenant-id 71 | default: "" 72 | description: Tenant ID for the azure secrets plugin 73 | env: AZURE_TENANT_ID 74 | - name: azure-client-id 75 | default: "" 76 | description: Client ID for the azure secrets plugin 77 | env: AZURE_CLIENT_ID 78 | - name: azure-client-secret 79 | default: "" 80 | description: Password for the azure secrets plugin 81 | env: AZURE_CLIENT_SECRET 82 | 83 | mixins: 84 | - exec 85 | - kubernetes 86 | 87 | install: 88 | - kubernetes: 89 | description: "Apply manifests" 90 | manifests: 91 | - manifests/operator.yaml 92 | wait: true 93 | - exec: 94 | description: "Wait for deployment" 95 | command: kubectl 96 | arguments: 97 | - rollout 98 | - status 99 | - deploy/porter-operator-controller-manager 100 | flags: 101 | namespace: porter-operator-system 102 | timeout: 30s 103 | 104 | upgrade: 105 | - kubernetes: 106 | description: "Apply manifests" 107 | manifests: 108 | - manifests/operator.yaml 109 | wait: true 110 | - exec: 111 | description: "Restart operator deployment" 112 | command: kubectl 113 | arguments: 114 | - rollout 115 | - restart 116 | - deployment/porter-operator-controller-manager 117 | flags: 118 | namespace: porter-operator-system 119 | - exec: 120 | description: "Wait for deployment" 121 | command: kubectl 122 | arguments: 123 | - rollout 124 | - status 125 | - deploy/porter-operator-controller-manager 126 | flags: 127 | namespace: porter-operator-system 128 | timeout: 30s 129 | 130 | # TODO: Add a test action that runs a test bundle to check if everything is configured properly 131 | 132 | removeData: 133 | - exec: 134 | description: "Remove Porter Operator Data" 135 | command: ./helpers.sh 136 | arguments: 137 | - removeData 138 | 139 | uninstall: 140 | # using exec instead of kubernetes because of https://github.com/getporter/kubernetes-mixin/issues/25 141 | - kubernetes: 142 | description: "Uninstall manifests" 143 | manifests: 144 | - manifests/operator.yaml 145 | wait: true 146 | 147 | customActions: 148 | configureNamespace: 149 | description: Add necessary rbac, service account and configuration to use Porter Operator in a namespace. Creates the namespace if it does not already exist. 150 | removeData: 151 | description: Remove Porter Operator data, such as namespaces used with configureNamespace, configuration, jobs, etc. These are not removed during uninstall. 152 | 153 | configureNamespace: 154 | - exec: 155 | description: "Configure Porter Operator" 156 | command: ./helpers.sh 157 | arguments: 158 | - configureNamespace 159 | -------------------------------------------------------------------------------- /installer/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | setControllerImage() { 5 | # Replace the manager image with the image packaged with the bundle 6 | echo "Setting manager image to $1" 7 | cd manifests 8 | kustomize edit set image manager=$1 9 | kustomize build -o operator.yaml 10 | } 11 | 12 | configureNamespace() { 13 | cd manifests/namespace 14 | 15 | spec="/cnab/app/porter-config-spec.yaml" 16 | if [ -s $spec ]; then 17 | echo "Applying porter configuration..." 18 | else 19 | echo "Using the default porter configuration" 20 | cp defaults/porter-config-spec.yaml $spec 21 | fi 22 | yq eval-all 'select(fileIndex==0).spec = select(fileIndex==1) | select(fileIndex==0)' -i porter-config.yaml $spec 23 | 24 | # If settings were specified for the porter operator, create a AgentConfig with them included 25 | cfgFiles=`ls agentconfig` 26 | for cfg in $cfgFiles; do 27 | contents=`cat agentconfig/$cfg` 28 | if [[ $contents != "" ]]; then 29 | echo "Applying agent-config $cfg" 30 | yq eval ".spec.$cfg = \"$contents\"" -i porter-agentconfig.yaml 31 | fi 32 | done 33 | 34 | echo "Configuring porter-agent role binding..." 35 | yq eval ".subjects[].namespace=\"$NAMESPACE\"" -i porter-agent-binding.yaml 36 | 37 | echo "Setting namespace to $NAMESPACE..." 38 | yq eval ".metadata.name = \"$NAMESPACE\"" -i namespace.yaml 39 | yq eval-all ".metadata.namespace = \"$NAMESPACE\"" *.yaml > manifests.yaml 40 | 41 | echo "Applying manifests to cluster..." 42 | cat manifests.yaml 43 | kubectl apply -f manifests.yaml 44 | 45 | echo "Namespace $NAMESPACE is ready to use with the Porter Operator" 46 | } 47 | 48 | waitForDeployment() { 49 | set +e # allow this next command to fail 50 | kubectl rollout status deploy/porter-operator-controller-manager --namespace porter-operator-system --timeout 30s 51 | if [[ $? != 0 ]]; then 52 | echo "Deployment failed, retrieving logs to troubleshoot" 53 | kubectl logs deploy/porter-operator-controller-manager --namespace porter-operator-system -c manager 54 | fi 55 | } 56 | 57 | removeData() { 58 | filter="getporter.org/generator=porter-operator-bundle" 59 | # This should get anything made by the bundle 60 | kubectl delete namespace -l $filter --wait 61 | # Look for any stray data that wasn't in a porter managed namespace, or were missing labels 62 | kubectl delete jobs,pods,secrets,pvc,pv --all-namespaces $filter --wait 63 | kubectl delete installations.getporter.org,agentconfigs.getporter.org,porterconfigs.getporter.org --all-namespaces --wait 64 | } 65 | 66 | # Call the requested function and pass the arguments as-is 67 | "$@" 68 | -------------------------------------------------------------------------------- /installer/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - operator.yaml 6 | 7 | generatorOptions: 8 | disableNameSuffixHash: true 9 | 10 | images: 11 | - name: manager 12 | -------------------------------------------------------------------------------- /installer/manifests/namespace/agentconfig/.keep: -------------------------------------------------------------------------------- 1 | Files in this directory will be included to the porter configmap 2 | -------------------------------------------------------------------------------- /installer/manifests/namespace/defaults/porter-config-spec.yaml: -------------------------------------------------------------------------------- 1 | # These are the default values to use for the Porter configuration file 2 | # They are copied into manifests/namespace/porter-config.yaml when the porter-config 3 | # parameter is unset. 4 | # The key names are converted by the bundle from Porter's snake-case to Kubernetes camelCase. 5 | default-secrets-plugin: "kubernetes.secrets" 6 | default-storage: "in-cluster-mongodb" 7 | storage: 8 | - name: "in-cluster-mongodb" 9 | plugin: "mongodb" 10 | config: 11 | url: "mongodb://mongodb.porter-operator-system.svc.cluster.local" 12 | -------------------------------------------------------------------------------- /installer/manifests/namespace/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: default # The name is set by helpers.sh 5 | labels: 6 | getporter.org/generator: "porter-operator-bundle" 7 | -------------------------------------------------------------------------------- /installer/manifests/namespace/porter-agent-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: porter-agent 5 | labels: 6 | getporter.org/generator: "porter-operator-bundle" 7 | roleRef: 8 | apiGroup: rbac.authorization.k8s.io 9 | kind: ClusterRole 10 | name: porter-operator-agent-role 11 | subjects: 12 | - kind: ServiceAccount 13 | name: porter-agent 14 | namespace: REPLACE 15 | -------------------------------------------------------------------------------- /installer/manifests/namespace/porter-agent.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: porter-agent 5 | labels: 6 | getporter.org/generator: "porter-operator-bundle" 7 | -------------------------------------------------------------------------------- /installer/manifests/namespace/porter-agentconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: AgentConfig 3 | metadata: 4 | name: default 5 | labels: 6 | getporter.org/generator: "porter-operator-bundle" 7 | spec: 8 | pluginConfigFile: 9 | schemaVersion: 1.0.0 10 | plugins: 11 | kubernetes: {} 12 | # Values are set in helpers.sh based on the bundle parameters which are copied into agentconfig/* 13 | # where each file is a setting on the spec. -------------------------------------------------------------------------------- /installer/manifests/namespace/porter-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: getporter.org/v1 2 | kind: PorterConfig 3 | metadata: 4 | name: default 5 | labels: 6 | getporter.org/generator: "porter-operator-bundle" 7 | spec: 8 | # Values are supplied by the bundle parameter porter-config 9 | # If nothing is provided, defaults to the contents of defaults/porter-config-spec.yaml 10 | -------------------------------------------------------------------------------- /installer/vanilla.porter.yaml: -------------------------------------------------------------------------------- 1 | schemaVersion: 1.0.0 2 | name: porter-operator 3 | version: 0.0.0 4 | description: "The Porter Operator for Kubernetes. Execute bundles on a Kubernetes cluster." 5 | registry: ghcr.io/getporter 6 | dockerfile: Dockerfile.tmpl 7 | 8 | parameters: 9 | - name: operator_repository 10 | description: Porter Operator Manager Image Repository 11 | type: string 12 | default: "" 13 | - name: operator_digest 14 | description: Porter Operator Manager Image Digest (defaults to a stable version) 15 | type: string 16 | default: "" 17 | - name: namespace 18 | description: Setup Porter in this namespace 19 | type: string 20 | applyTo: 21 | - configureNamespace 22 | # Porter Configuration 23 | - name: porterConfig 24 | description: Porter config file, in yaml, same as ~/.porter/config.yaml 25 | type: file 26 | path: /cnab/app/porter-config-spec.yaml 27 | default: "" 28 | applyTo: 29 | - configureNamespace 30 | # Agent Configuration 31 | - name: volumeSize 32 | description: Size of the volume shared between Porter and the bundles it executes. Defaults to 64Mi. 33 | type: string 34 | path: /cnab/app/manifests/namespace/agentconfig/volumeSize 35 | default: "" 36 | applyTo: 37 | - configureNamespace 38 | - name: porterRepository 39 | description: Docker image repository of the Porter agent. Defaults to ghcr.io/getporter/porter. 40 | type: string 41 | path: /cnab/app/manifests/namespace/agentconfig/porterRepository 42 | default: "" 43 | applyTo: 44 | - configureNamespace 45 | - name: porterVersion 46 | description: Version of the Porter agent, e.g. latest, canary, v0.33.0. Defaults to latest. 47 | type: string 48 | path: /cnab/app/manifests/namespace/agentconfig/porterVersion 49 | default: "" 50 | applyTo: 51 | - configureNamespace 52 | - name: pullPolicy 53 | description: Specifies how the Porter agent image should be pulled. Does not affect how bundles are pulled. Defaults to PullAlways for latest and canary, and PullIfNotPresent otherwise. 54 | type: string 55 | path: /cnab/app/manifests/namespace/agentconfig/pullPolicy 56 | default: "" 57 | applyTo: 58 | - configureNamespace 59 | - name: serviceAccount 60 | description: Name of the service account to run the Porter agent. If set, you are responsible for creating this service account and binding it to the porter-agent ClusterRole. Defaults to the porter-agent account created by the configureNamespace custom action. 61 | type: string 62 | path: /cnab/app/manifests/namespace/agentconfig/serviceAccount 63 | default: "porter-agent" 64 | applyTo: 65 | - configureNamespace 66 | - name: installationServiceAccount 67 | description: Name of the service account to run installation with. If set, you are responsible for creating this service account and giving it required permissions. 68 | type: string 69 | path: /cnab/app/manifests/namespace/agentconfig/installationServiceAccount 70 | default: "" 71 | applyTo: 72 | - configureNamespace 73 | - name: mongodbChartVersion 74 | description: Version of the mongodb helm chart to install 75 | type: string 76 | default: "13.6.2" 77 | applyTo: 78 | - install 79 | - name: mongodbVals 80 | description: Helm values file to use when installing the mongodb chart 81 | type: file 82 | default: "" 83 | path: /cnab/app/mongodb-vals.yaml 84 | 85 | credentials: 86 | - name: kubeconfig 87 | description: Kubeconfig file for cluster where the operator should be installed 88 | path: /home/nonroot/.kube/config 89 | 90 | mixins: 91 | - exec 92 | - helm3: 93 | repositories: 94 | bitnami: 95 | url: "https://charts.bitnami.com/bitnami" 96 | - kubernetes 97 | 98 | install: 99 | - exec: 100 | description: "Set manager image reference" 101 | command: ./helpers.sh 102 | arguments: 103 | - setControllerImage 104 | - ${bundle.images.manager.repository}@${bundle.images.manager.digest} 105 | - kubernetes: 106 | description: "Apply operator manifests" 107 | manifests: 108 | - manifests/operator.yaml 109 | wait: true 110 | - helm3: 111 | description: "Install a mongo database for Porter" 112 | namespace: porter-operator-system 113 | name: mongodb 114 | chart: bitnami/mongodb 115 | version: ${bundle.parameters.mongodbChartVersion} 116 | atomic: false 117 | set: 118 | auth.enabled: false 119 | values: 120 | - /cnab/app/mongodb-vals.yaml 121 | - exec: 122 | description: "Wait for operator deployment to complete" 123 | command: ./helpers.sh 124 | arguments: 125 | - waitForDeployment 126 | 127 | upgrade: 128 | - exec: 129 | description: "Set manager image reference" 130 | command: ./helpers.sh 131 | arguments: 132 | - setControllerImage 133 | - ${bundle.images.manager.repository}@{bundle.images.manager.digest} 134 | - kubernetes: 135 | description: "Apply operator manifests" 136 | manifests: 137 | - manifests/operator.yaml 138 | wait: true 139 | - exec: 140 | description: "Wait for operator deployment to complete" 141 | command: ./helpers.sh 142 | arguments: 143 | - waitForDeployment 144 | 145 | # TODO: Add a test action that runs a test bundle to check if everything is configured properly 146 | 147 | removeData: 148 | - exec: 149 | description: "Remove ALL Porter Operator data from the cluster" 150 | command: ./helpers.sh 151 | arguments: 152 | - removeData 153 | 154 | uninstall: 155 | - kubernetes: 156 | description: "Uninstall operator" 157 | manifests: 158 | - manifests/operator.yaml 159 | wait: true 160 | - helm3: 161 | namespace: porter-operator-system 162 | releases: 163 | - mongodb 164 | wait: true 165 | 166 | customActions: 167 | configureNamespace: 168 | description: Add necessary rbac, service account and configuration to use Porter Operator in a namespace. Creates the namespace if it does not already exist. 169 | removeData: 170 | description: Remove Porter Operator data, such as namespaces used with configureNamespace, configuration, jobs, etc. These are not removed during uninstall. 171 | 172 | configureNamespace: 173 | - exec: 174 | description: "Configure Porter Operator" 175 | command: ./helpers.sh 176 | arguments: 177 | - configureNamespace 178 | 179 | images: 180 | manager: 181 | description: "The porter operator manager" 182 | imageType: "docker" 183 | repository: ${ bundle.parameters.operator_repository } 184 | digest: ${ bundle.parameters.operator_digest } 185 | -------------------------------------------------------------------------------- /mage.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/magefile/mage/mage" 10 | ) 11 | 12 | // This file allows someone to run mage commands without mage installed 13 | // by running `go run mage.go TARGET`. 14 | // See https://magefile.org/zeroinstall/ 15 | func main() { os.Exit(mage.Main()) } 16 | -------------------------------------------------------------------------------- /mage/README.md: -------------------------------------------------------------------------------- 1 | # Magefile Helpers 2 | 3 | This directory contains supporting files that extend our 4 | [magefile](https://magefile.org). This helps us separate the code and not have a 5 | mega-magefile. -------------------------------------------------------------------------------- /mage/docs/docs.go: -------------------------------------------------------------------------------- 1 | package docs 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/carolynvs/magex/mgx" 13 | "github.com/carolynvs/magex/shx" 14 | ) 15 | 16 | const ( 17 | // Triggers a build of the v1 website. 18 | porterV1Webhook = "https://api.netlify.com/build_hooks/60ca5ba254754934bce864b1" 19 | 20 | // LocalPorterRepositoryEnv is the environment variable used to store the path 21 | // to a local checkout of Porter. 22 | LocalPorterRepositoryEnv = "PORTER_REPOSITORY" 23 | 24 | // DefaultPorterSourceDir is the directory where the Porter repo 25 | // is cloned when LocalPorterRepositoryEnv was not specified. 26 | DefaultPorterSourceDir = "../porter" 27 | ) 28 | 29 | // DeployWebsite triggers a Netlify build for the website. 30 | func DeployWebsite() error { 31 | // Put up a page on the preview that redirects to the live site 32 | os.MkdirAll("docs/public", 0755) 33 | err := copy("hack/website-redirect.html", "docs/public/index.html") 34 | if err != nil { 35 | return err 36 | } 37 | 38 | return TriggerNetlifyDeployment(porterV1Webhook) 39 | } 40 | 41 | func copy(src string, dest string) error { 42 | srcF, err := os.Open(src) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | destF, err := os.Create(dest) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | _, err = io.Copy(destF, srcF) 53 | return err 54 | } 55 | 56 | // TriggerNetlifyDeployment builds a netlify site using the specified webhook 57 | func TriggerNetlifyDeployment(webhook string) error { 58 | emptyMsg := "{}" 59 | data := strings.NewReader(emptyMsg) 60 | fmt.Println("POST", webhook) 61 | fmt.Println(emptyMsg) 62 | 63 | r, err := http.Post(webhook, "application/json", data) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | if r.StatusCode >= 300 { 69 | defer r.Body.Close() 70 | msg, _ := io.ReadAll(r.Body) 71 | return fmt.Errorf("request failed (%d) %s: %s", r.StatusCode, r.Status, msg) 72 | } 73 | 74 | return nil 75 | } 76 | 77 | // DeployWebsitePreview builds the entire website for a preview on Netlify. 78 | func DeployWebsitePreview() error { 79 | pwd, _ := os.Getwd() 80 | websiteDir := ensurePorterRepository() 81 | os.Setenv("PORTER_OPERATOR_REPOSITORY", pwd) 82 | 83 | // Set BASE_URL so that huge understands where the site is hosted 84 | deployURL := os.Getenv("DEPLOY_PRIME_URL") + "/" 85 | 86 | // Build the website using our local changes 87 | err := shx.Command("go", "run", "mage.go", "-v", "docs"). 88 | In(websiteDir).Env("BASEURL=" + deployURL).RunV() 89 | if err != nil { 90 | return err 91 | } 92 | 93 | return shx.Copy(filepath.Join(websiteDir, "docs/public"), "docs/public", shx.CopyRecursive) 94 | } 95 | 96 | // Preview builds the entire website and previews it in the browser on your local machine. 97 | func Preview() error { 98 | pwd, _ := os.Getwd() 99 | websiteDir := ensurePorterRepository() 100 | os.Setenv("PORTER_OPERATOR_REPOSITORY", pwd) 101 | 102 | // Build the website using our local changes 103 | return shx.Command("go", "run", "mage.go", "docsPreview"). 104 | In(websiteDir).RunV() 105 | } 106 | 107 | // Ensures that we have an operator repository and returns its location 108 | func ensurePorterRepository() string { 109 | repoPath, err := ensurePorterRepositoryIn(os.Getenv(LocalPorterRepositoryEnv), DefaultPorterSourceDir) 110 | mgx.Must(err) 111 | return repoPath 112 | } 113 | 114 | // Checks if the repository in localRepo exists and return it 115 | // otherwise clone the repository into defaultRepo, updating with the latest changes if already cloned. 116 | func ensurePorterRepositoryIn(localRepo string, defaultRepo string) (string, error) { 117 | // Check if we are using a local repo 118 | if localRepo != "" { 119 | if _, err := os.Stat(localRepo); err != nil { 120 | log.Printf("%s %s does not exist, ignoring\n", LocalPorterRepositoryEnv, localRepo) 121 | os.Unsetenv(LocalPorterRepositoryEnv) 122 | } else { 123 | log.Printf("Using porter repository at %s\n", localRepo) 124 | return localRepo, nil 125 | } 126 | } 127 | 128 | // Clone the repo, and ensure it is up-to-date 129 | cloneDestination, _ := filepath.Abs(defaultRepo) 130 | _, err := os.Stat(filepath.Join(cloneDestination, ".git")) 131 | if err != nil && !os.IsNotExist(err) { 132 | return "", err 133 | } else if err == nil { 134 | log.Println("Porter repository already cloned, updating") 135 | if err = shx.Command("git", "fetch").In(cloneDestination).Run(); err != nil { 136 | return "", err 137 | } 138 | if err = shx.Command("git", "reset", "--hard", "FETCH_HEAD").In(cloneDestination).Run(); err != nil { 139 | return "", err 140 | } 141 | return cloneDestination, nil 142 | } 143 | 144 | log.Println("Cloning porter repository") 145 | os.RemoveAll(cloneDestination) // if the path existed but wasn't a git repo, we want to remove it and start fresh 146 | if err = shx.Run("git", "clone", "-b", "release/v1", "https://github.com/getporter/porter.git", cloneDestination); err != nil { 147 | return "", err 148 | } 149 | return cloneDestination, nil 150 | } 151 | -------------------------------------------------------------------------------- /mage/docs/docs_test.go: -------------------------------------------------------------------------------- 1 | package docs 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/carolynvs/magex/shx" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestEnsurePorterRepository(t *testing.T) { 13 | t.Run("has local repo", func(t *testing.T) { 14 | tmp := t.TempDir() 15 | 16 | resolvedPath, err := ensurePorterRepositoryIn(tmp, "") 17 | require.NoError(t, err) 18 | require.Equal(t, tmp, resolvedPath) 19 | }) 20 | 21 | t.Run("missing local repo", func(t *testing.T) { 22 | tmp := t.TempDir() 23 | 24 | resolvedPath, err := ensurePorterRepositoryIn("missing", tmp) 25 | require.NoError(t, err) 26 | require.Equal(t, tmp, resolvedPath) 27 | }) 28 | 29 | t.Run("local repo unset", func(t *testing.T) { 30 | tmp := t.TempDir() 31 | resolvedPath, err := ensurePorterRepositoryIn("", tmp) 32 | require.NoError(t, err) 33 | require.Equal(t, tmp, resolvedPath) 34 | }) 35 | 36 | t.Run("empty default path clones repo", func(t *testing.T) { 37 | tmp := t.TempDir() 38 | 39 | resolvedPath, err := ensurePorterRepositoryIn("", tmp) 40 | require.NoError(t, err) 41 | require.Equal(t, tmp, resolvedPath) 42 | 43 | err = shx.Command("git", "status").In(resolvedPath).RunE() 44 | require.NoError(t, err, "clone failed") 45 | }) 46 | 47 | t.Run("changes in default path are reset", func(t *testing.T) { 48 | tmp := t.TempDir() 49 | 50 | repoPath, err := ensurePorterRepositoryIn("", tmp) 51 | require.NoError(t, err) 52 | 53 | // make a change 54 | readme := filepath.Join(repoPath, "README.md") 55 | require.NoError(t, os.Remove(readme)) 56 | 57 | // Make sure rerunning resets the change 58 | _, err = ensurePorterRepositoryIn("", tmp) 59 | require.NoError(t, err) 60 | require.FileExists(t, readme) 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /mage/env.go: -------------------------------------------------------------------------------- 1 | package mage 2 | 3 | import ( 4 | "os" 5 | "path" 6 | ) 7 | 8 | const ( 9 | ProductionRegistry = "ghcr.io/getporter" 10 | TestRegistry = "localhost:5000" 11 | ) 12 | 13 | var ( 14 | Env = getAmbientEnvironment() 15 | ) 16 | 17 | type Environment struct { 18 | Name string 19 | Registry string 20 | ManagerImagePrefix string 21 | BundlePrefix string 22 | } 23 | 24 | func getAmbientEnvironment() Environment { 25 | name := os.Getenv("PORTER_ENV") 26 | switch name { 27 | case "prod", "production": 28 | return GetProductionEnvironment() 29 | case "test", "": 30 | return GetTestEnvironment() 31 | default: 32 | registry := os.Getenv("PORTER_OPERATOR_REGISTRY") 33 | if registry == "" { 34 | registry = "localhost:5000" 35 | } 36 | return buildEnvironment(name, registry) 37 | } 38 | } 39 | 40 | func UseTestEnvironment() { 41 | Env = GetTestEnvironment() 42 | } 43 | 44 | func UseProductionEnvironment() { 45 | Env = GetProductionEnvironment() 46 | } 47 | 48 | func GetTestEnvironment() Environment { 49 | return buildEnvironment("test", TestRegistry) 50 | } 51 | 52 | func GetProductionEnvironment() Environment { 53 | return buildEnvironment("production", ProductionRegistry) 54 | } 55 | 56 | func buildEnvironment(name string, registry string) Environment { 57 | return Environment{ 58 | Name: name, 59 | Registry: registry, 60 | ManagerImagePrefix: path.Join(registry, "porter-operator-manager:"), 61 | BundlePrefix: path.Join(registry, "porter-operator:"), 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /mage/tests/kind.config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "kind.x-k8s.io/v1alpha4" 2 | kind: "Cluster" 3 | networking: 4 | apiServerAddress: "{{.Address}}" 5 | containerdConfigPatches: 6 | - |- 7 | [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"] 8 | endpoint = ["http://registry:5000"] 9 | -------------------------------------------------------------------------------- /mage/tests/local-registry.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: local-registry-hosting 5 | namespace: kube-public 6 | data: 7 | localRegistryHosting.v1: | 8 | host: "localhost:5000" 9 | help: "https://kind.sigs.k8s.io/docs/user/local-registry/" -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | 7 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 8 | // to ensure that exec-entrypoint and run can make use of them. 9 | 10 | _ "k8s.io/client-go/plugin/pkg/client/auth" 11 | 12 | "k8s.io/apimachinery/pkg/runtime" 13 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 14 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 15 | ctrl "sigs.k8s.io/controller-runtime" 16 | "sigs.k8s.io/controller-runtime/pkg/healthz" 17 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 18 | "sigs.k8s.io/controller-runtime/pkg/metrics/server" 19 | 20 | v1 "get.porter.sh/operator/api/v1" 21 | "get.porter.sh/operator/controllers" 22 | // +kubebuilder:scaffold:imports 23 | ) 24 | 25 | var ( 26 | scheme = runtime.NewScheme() 27 | setupLog = ctrl.Log.WithName("setup") 28 | ) 29 | 30 | func init() { 31 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 32 | utilruntime.Must(v1.AddToScheme(scheme)) 33 | // +kubebuilder:scaffold:scheme 34 | } 35 | 36 | func main() { 37 | var metricsAddr string 38 | var enableLeaderElection bool 39 | var probeAddr string 40 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 41 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 42 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 43 | "Enable leader election for controller manager. "+ 44 | "Enabling this will ensure there is only one active controller manager.") 45 | opts := zap.Options{ 46 | Development: true, 47 | } 48 | opts.BindFlags(flag.CommandLine) 49 | flag.Parse() 50 | 51 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 52 | 53 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 54 | Scheme: scheme, 55 | Metrics: server.Options{ 56 | BindAddress: metricsAddr, 57 | }, 58 | HealthProbeBindAddress: probeAddr, 59 | LeaderElection: enableLeaderElection, 60 | LeaderElectionID: "c58eb551.getporter.org", 61 | }) 62 | if err != nil { 63 | setupLog.Error(err, "unable to start manager") 64 | os.Exit(1) 65 | } 66 | if err = (&controllers.InstallationReconciler{ 67 | Client: mgr.GetClient(), 68 | Recorder: mgr.GetEventRecorderFor("installation"), 69 | Log: ctrl.Log.WithName("controllers").WithName("Installation"), 70 | Scheme: mgr.GetScheme(), 71 | CreateGRPCClient: controllers.CreatePorterGRPCClient, 72 | }).SetupWithManager(mgr); err != nil { 73 | setupLog.Error(err, "unable to create controller", "controller", "Installation") 74 | os.Exit(1) 75 | } 76 | if err = (&controllers.AgentActionReconciler{ 77 | Client: mgr.GetClient(), 78 | Log: ctrl.Log.WithName("controllers").WithName("AgentAction"), 79 | Scheme: mgr.GetScheme(), 80 | }).SetupWithManager(mgr); err != nil { 81 | setupLog.Error(err, "unable to create controller", "controller", "AgentAction") 82 | os.Exit(1) 83 | } 84 | if err = (&controllers.CredentialSetReconciler{ 85 | Client: mgr.GetClient(), 86 | Log: ctrl.Log.WithName("controllers").WithName("CredentialSet"), 87 | Scheme: mgr.GetScheme(), 88 | }).SetupWithManager(mgr); err != nil { 89 | setupLog.Error(err, "unable to create controller", "controller", "CredentialSet") 90 | os.Exit(1) 91 | } 92 | if err = (&controllers.ParameterSetReconciler{ 93 | Client: mgr.GetClient(), 94 | Log: ctrl.Log.WithName("controllers").WithName("ParameterSet"), 95 | Scheme: mgr.GetScheme(), 96 | }).SetupWithManager(mgr); err != nil { 97 | setupLog.Error(err, "unable to create controller", "controller", "ParameterSet") 98 | os.Exit(1) 99 | } 100 | if err = (&controllers.AgentConfigReconciler{ 101 | Client: mgr.GetClient(), 102 | Log: ctrl.Log.WithName("controllers").WithName("AgentConfig"), 103 | Scheme: mgr.GetScheme(), 104 | }).SetupWithManager(mgr); err != nil { 105 | setupLog.Error(err, "unable to create controller", "controller", "AgentConfig") 106 | os.Exit(1) 107 | } 108 | // +kubebuilder:scaffold:builder 109 | 110 | if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { 111 | setupLog.Error(err, "unable to set up health check") 112 | os.Exit(1) 113 | } 114 | if err := mgr.AddReadyzCheck("check", healthz.Ping); err != nil { 115 | setupLog.Error(err, "unable to set up ready check") 116 | os.Exit(1) 117 | } 118 | 119 | setupLog.Info("starting manager") 120 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 121 | setupLog.Error(err, "problem running manager") 122 | os.Exit(1) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /mocks/grpc/clientconn_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.43.2. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // ClientConn is an autogenerated mock type for the ClientConn type 8 | type ClientConn struct { 9 | mock.Mock 10 | } 11 | 12 | // Close provides a mock function with given fields: 13 | func (_m *ClientConn) Close() error { 14 | ret := _m.Called() 15 | 16 | if len(ret) == 0 { 17 | panic("no return value specified for Close") 18 | } 19 | 20 | var r0 error 21 | if rf, ok := ret.Get(0).(func() error); ok { 22 | r0 = rf() 23 | } else { 24 | r0 = ret.Error(0) 25 | } 26 | 27 | return r0 28 | } 29 | 30 | // NewClientConn creates a new instance of ClientConn. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 31 | // The first argument is typically a *testing.T value. 32 | func NewClientConn(t interface { 33 | mock.TestingT 34 | Cleanup(func()) 35 | }) *ClientConn { 36 | mock := &ClientConn{} 37 | mock.Mock.Test(t) 38 | 39 | t.Cleanup(func() { mock.AssertExpectations(t) }) 40 | 41 | return mock 42 | } 43 | -------------------------------------------------------------------------------- /mocks/grpc/grpc_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.43.2. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | context "context" 7 | 8 | grpc "google.golang.org/grpc" 9 | 10 | installationv1alpha1 "get.porter.sh/porter/gen/proto/go/porterapis/installation/v1alpha1" 11 | 12 | mock "github.com/stretchr/testify/mock" 13 | ) 14 | 15 | // PorterClient is an autogenerated mock type for the PorterClient type 16 | type PorterClient struct { 17 | mock.Mock 18 | } 19 | 20 | // ListInstallationLatestOutputs provides a mock function with given fields: ctx, in, opts 21 | func (_m *PorterClient) ListInstallationLatestOutputs(ctx context.Context, in *installationv1alpha1.ListInstallationLatestOutputRequest, opts ...grpc.CallOption) (*installationv1alpha1.ListInstallationLatestOutputResponse, error) { 22 | _va := make([]interface{}, len(opts)) 23 | for _i := range opts { 24 | _va[_i] = opts[_i] 25 | } 26 | var _ca []interface{} 27 | _ca = append(_ca, ctx, in) 28 | _ca = append(_ca, _va...) 29 | ret := _m.Called(_ca...) 30 | 31 | if len(ret) == 0 { 32 | panic("no return value specified for ListInstallationLatestOutputs") 33 | } 34 | 35 | var r0 *installationv1alpha1.ListInstallationLatestOutputResponse 36 | var r1 error 37 | if rf, ok := ret.Get(0).(func(context.Context, *installationv1alpha1.ListInstallationLatestOutputRequest, ...grpc.CallOption) (*installationv1alpha1.ListInstallationLatestOutputResponse, error)); ok { 38 | return rf(ctx, in, opts...) 39 | } 40 | if rf, ok := ret.Get(0).(func(context.Context, *installationv1alpha1.ListInstallationLatestOutputRequest, ...grpc.CallOption) *installationv1alpha1.ListInstallationLatestOutputResponse); ok { 41 | r0 = rf(ctx, in, opts...) 42 | } else { 43 | if ret.Get(0) != nil { 44 | r0 = ret.Get(0).(*installationv1alpha1.ListInstallationLatestOutputResponse) 45 | } 46 | } 47 | 48 | if rf, ok := ret.Get(1).(func(context.Context, *installationv1alpha1.ListInstallationLatestOutputRequest, ...grpc.CallOption) error); ok { 49 | r1 = rf(ctx, in, opts...) 50 | } else { 51 | r1 = ret.Error(1) 52 | } 53 | 54 | return r0, r1 55 | } 56 | 57 | // ListInstallations provides a mock function with given fields: ctx, in, opts 58 | func (_m *PorterClient) ListInstallations(ctx context.Context, in *installationv1alpha1.ListInstallationsRequest, opts ...grpc.CallOption) (*installationv1alpha1.ListInstallationsResponse, error) { 59 | _va := make([]interface{}, len(opts)) 60 | for _i := range opts { 61 | _va[_i] = opts[_i] 62 | } 63 | var _ca []interface{} 64 | _ca = append(_ca, ctx, in) 65 | _ca = append(_ca, _va...) 66 | ret := _m.Called(_ca...) 67 | 68 | if len(ret) == 0 { 69 | panic("no return value specified for ListInstallations") 70 | } 71 | 72 | var r0 *installationv1alpha1.ListInstallationsResponse 73 | var r1 error 74 | if rf, ok := ret.Get(0).(func(context.Context, *installationv1alpha1.ListInstallationsRequest, ...grpc.CallOption) (*installationv1alpha1.ListInstallationsResponse, error)); ok { 75 | return rf(ctx, in, opts...) 76 | } 77 | if rf, ok := ret.Get(0).(func(context.Context, *installationv1alpha1.ListInstallationsRequest, ...grpc.CallOption) *installationv1alpha1.ListInstallationsResponse); ok { 78 | r0 = rf(ctx, in, opts...) 79 | } else { 80 | if ret.Get(0) != nil { 81 | r0 = ret.Get(0).(*installationv1alpha1.ListInstallationsResponse) 82 | } 83 | } 84 | 85 | if rf, ok := ret.Get(1).(func(context.Context, *installationv1alpha1.ListInstallationsRequest, ...grpc.CallOption) error); ok { 86 | r1 = rf(ctx, in, opts...) 87 | } else { 88 | r1 = ret.Error(1) 89 | } 90 | 91 | return r0, r1 92 | } 93 | 94 | // NewPorterClient creates a new instance of PorterClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 95 | // The first argument is typically a *testing.T value. 96 | func NewPorterClient(t interface { 97 | mock.TestingT 98 | Cleanup(func()) 99 | }) *PorterClient { 100 | mock := &PorterClient{} 101 | mock.Mock.Test(t) 102 | 103 | t.Cleanup(func() { mock.AssertExpectations(t) }) 104 | 105 | return mock 106 | } 107 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [settings] 2 | ID = "porter-operator" 3 | 4 | [build] 5 | base = "/" 6 | publish = "docs/public" 7 | command = "go run mage.go -v DocsDeploy" 8 | 9 | [build.environment] 10 | GO_VERSION = "1.21.9" 11 | 12 | [context.branch-deploy] 13 | command = "go run mage.go -v DocsDeployPreview" 14 | 15 | [context.deploy-preview] 16 | command = "go run mage.go -v DocsDeployPreview" 17 | -------------------------------------------------------------------------------- /staticcheck.conf: -------------------------------------------------------------------------------- 1 | # Default Config 2 | checks = ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1023", "-ST1005"] 3 | initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"] 4 | http_status_code_whitelist = ["200", "400", "404", "500"] -------------------------------------------------------------------------------- /tests/integration/installation_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | 3 | package integration_test 4 | 5 | import ( 6 | "context" 7 | "time" 8 | 9 | porterv1 "get.porter.sh/operator/api/v1" 10 | "get.porter.sh/operator/controllers" 11 | "get.porter.sh/porter/pkg/storage" 12 | "github.com/go-logr/logr" 13 | . "github.com/onsi/ginkgo/v2" 14 | . "github.com/onsi/gomega" 15 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 | "k8s.io/apimachinery/pkg/runtime" 17 | "sigs.k8s.io/controller-runtime/pkg/client" 18 | ) 19 | 20 | // The default amount of time to wait while a test action is processed. 21 | const defaultWaitTimeout = 120 * time.Second 22 | 23 | var _ = Describe("Installation Lifecycle", func() { 24 | Context("When an installation is changed", func() { 25 | It("Should run porter", func() { 26 | By("By creating an agent action") 27 | ctx := context.Background() 28 | ns := createTestNamespace(ctx) 29 | 30 | Log("create an installation") 31 | inst := &porterv1.Installation{ 32 | TypeMeta: metav1.TypeMeta{ 33 | APIVersion: "getporter.org/v1", 34 | Kind: "Installation", 35 | }, 36 | ObjectMeta: metav1.ObjectMeta{ 37 | Name: "porter-hello", 38 | Namespace: ns, 39 | }, 40 | Spec: porterv1.InstallationSpec{ 41 | SchemaVersion: string(storage.DefaultInstallationSchemaVersion), 42 | Name: "hello", 43 | Namespace: "operator-tests", 44 | Bundle: porterv1.OCIReferenceParts{ 45 | Repository: "carolynvs/porter-hello-nonroot", 46 | Version: "0.1.0", 47 | }, 48 | }, 49 | } 50 | Expect(k8sClient.Create(ctx, inst)).Should(Succeed()) 51 | Expect(waitForPorter(ctx, inst, 1, "waiting for the bundle to install")).Should(Succeed()) 52 | validateResourceConditions(inst) 53 | 54 | patchInstallation := func(inst *porterv1.Installation) { 55 | controllers.PatchObjectWithRetry(ctx, logr.Discard(), k8sClient, k8sClient.Patch, inst, func() client.Object { 56 | return &porterv1.Installation{} 57 | }) 58 | } 59 | 60 | Log("upgrade the installation") 61 | inst.Spec.Parameters = runtime.RawExtension{Raw: []byte(`{"name": "operator"}`)} 62 | patchInstallation(inst) 63 | Expect(waitForPorter(ctx, inst, 2, "waiting for the bundle to upgrade")).Should(Succeed()) 64 | validateResourceConditions(inst) 65 | 66 | Log("uninstall the installation") 67 | inst.Spec.Uninstalled = true 68 | patchInstallation(inst) 69 | Expect(waitForPorter(ctx, inst, 3, "waiting for the bundle to uninstall")).Should(Succeed()) 70 | validateResourceConditions(inst) 71 | 72 | Log("delete the installation") 73 | Expect(k8sClient.Delete(ctx, inst)).Should(Succeed()) 74 | Expect(waitForResourceDeleted(ctx, inst)).Should(Succeed()) 75 | }) 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /tests/integration/paramset_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | 3 | package integration_test 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "get.porter.sh/operator/controllers" 10 | "get.porter.sh/porter/pkg/storage" 11 | "github.com/go-logr/logr" 12 | . "github.com/onsi/ginkgo/v2" 13 | "github.com/tidwall/gjson" 14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | apitypes "k8s.io/apimachinery/pkg/types" 16 | "sigs.k8s.io/controller-runtime/pkg/client" 17 | 18 | porterv1 "get.porter.sh/operator/api/v1" 19 | . "github.com/onsi/gomega" 20 | ) 21 | 22 | var _ = Describe("ParameterSet lifecycle", func() { 23 | It("should run porter apply", func() { 24 | By("creating an agent action", func() { 25 | ctx := context.Background() 26 | ns := createTestNamespace(ctx) 27 | delayValue := "1" 28 | resourceName := "ps-lifecycle-" + ns 29 | pSetName := "ps-lifecycle-test" 30 | createTestSecret(ctx, resourceName, delayValue, ns) 31 | 32 | Log(fmt.Sprintf("create parameter set '%s'", resourceName)) 33 | paramName := "delay" 34 | ps := NewTestParamSet(resourceName) 35 | ps.Spec.Name = pSetName 36 | ps.ObjectMeta.Namespace = ns 37 | param := porterv1.Parameter{ 38 | Name: paramName, 39 | Source: porterv1.ParameterSource{ 40 | Secret: resourceName, 41 | }, 42 | } 43 | ps.Spec.Parameters = append(ps.Spec.Parameters, param) 44 | ps.Spec.Namespace = ns 45 | 46 | Expect(k8sClient.Create(ctx, ps)).Should(Succeed()) 47 | Expect(waitForPorter(ctx, ps, 1, "waiting for parameter set to apply")).Should(Succeed()) 48 | validateResourceConditions(ps) 49 | 50 | Log("verify it's created") 51 | jsonOut := runAgentActionWithDefaultAgentCfg(ctx, "create-check-parameters-list", ns, []string{"parameters", "list", "-n", ns, "-o", "json"}) 52 | firstName := gjson.Get(jsonOut, "0.name").String() 53 | numParamSets := gjson.Get(jsonOut, "#").Int() 54 | numParams := gjson.Get(jsonOut, "0.parameters.#").Int() 55 | firstParamName := gjson.Get(jsonOut, "0.parameters.0.name").String() 56 | Expect(int64(1)).To(Equal(numParamSets)) 57 | Expect(int64(1)).To(Equal(numParams)) 58 | Expect(ps.Spec.Name).To(Equal(firstName)) 59 | Expect(paramName).To(Equal(firstParamName)) 60 | 61 | Log("install porter-test-me bundle with new param set") 62 | inst := NewTestInstallation("ps-with-secret") 63 | inst.ObjectMeta.Namespace = ns 64 | inst.Spec.Namespace = ns 65 | inst.Spec.ParameterSets = append(inst.Spec.ParameterSets, pSetName) 66 | Expect(k8sClient.Create(ctx, inst)).Should(Succeed()) 67 | Expect(waitForPorter(ctx, inst, 1, "waiting for porter-test-me to install")).Should(Succeed()) 68 | validateResourceConditions(inst) 69 | 70 | // Validate that the correct parameter set was used by the installation 71 | instJsonOut := runAgentActionWithDefaultAgentCfg(ctx, "show-outputs", ns, []string{"installation", "outputs", "list", "-n", ns, "-i", inst.Spec.Name, "-o", "json"}) 72 | outDelayValue := gjson.Get(instJsonOut, `#(name=="outDelay").value`).String() 73 | Expect(outDelayValue).To(Equal(delayValue)) 74 | 75 | Log("update a parameter set") 76 | updateParamName := "update-paramset" 77 | updateParamValue := "update-value" 78 | newParam := porterv1.Parameter{ 79 | Name: updateParamName, 80 | Source: porterv1.ParameterSource{ 81 | Value: updateParamValue, 82 | }, 83 | } 84 | ps.Spec.Parameters = append(ps.Spec.Parameters, newParam) 85 | patchPS := func(ps *porterv1.ParameterSet) { 86 | controllers.PatchObjectWithRetry(ctx, logr.Discard(), k8sClient, k8sClient.Patch, ps, func() client.Object { 87 | return &porterv1.ParameterSet{} 88 | }) 89 | // Wait for the patch to apply, this can cause race conditions 90 | } 91 | patchPS(ps) 92 | Expect(waitForPorter(ctx, ps, 2, "waiting for parameters update to apply")).Should(Succeed()) 93 | Log("verify it's updated") 94 | jsonOut = runAgentActionWithDefaultAgentCfg(ctx, "update-check-parameters-list", ns, []string{"parameters", "list", "-n", ns, "-o", "json"}) 95 | updatedFirstName := gjson.Get(jsonOut, "0.name").String() 96 | updatedNumParamSets := gjson.Get(jsonOut, "#").Int() 97 | updatedNumParams := gjson.Get(jsonOut, "0.parameters.#").Int() 98 | updatedFirstParamName := gjson.Get(jsonOut, "0.parameters.0.name").String() 99 | updatedSecondParamName := gjson.Get(jsonOut, "0.parameters.1.name").String() 100 | Expect(int64(1)).To(Equal(updatedNumParamSets)) 101 | Expect(int64(2)).To(Equal(updatedNumParams)) 102 | Expect(ps.Spec.Name).To(Equal(updatedFirstName)) 103 | Expect(paramName).To(Equal(updatedFirstParamName)) 104 | Expect(updateParamName).To(Equal(updatedSecondParamName)) 105 | 106 | Log("delete a parameter set") 107 | Expect(k8sClient.Delete(ctx, ps)).Should(Succeed()) 108 | Expect(waitForResourceDeleted(ctx, ps)).Should(Succeed()) 109 | 110 | Log("verify parameter set is gone from porter data store") 111 | delJsonOut := runAgentActionWithDefaultAgentCfg(ctx, "delete-check-parameters-list", ns, []string{"parameters", "list", "-n", ns, "-o", "json"}) 112 | delNumParams := gjson.Get(delJsonOut, "#").Int() 113 | Expect(int64(0)).To(Equal(delNumParams)) 114 | 115 | Log("verify parameter set CRD is gone from k8s cluster") 116 | nsName := apitypes.NamespacedName{Namespace: ps.Namespace, Name: ps.Name} 117 | getPS := &porterv1.ParameterSet{} 118 | Expect(k8sClient.Get(ctx, nsName, getPS)).ShouldNot(Succeed()) 119 | 120 | }) 121 | }) 122 | }) 123 | 124 | // NewTestParamSet minimal ParameterSet CRD for tests 125 | func NewTestParamSet(psName string) *porterv1.ParameterSet { 126 | ps := &porterv1.ParameterSet{ 127 | TypeMeta: metav1.TypeMeta{ 128 | APIVersion: "getporter.org/v1", 129 | Kind: "ParameterSet", 130 | }, 131 | ObjectMeta: metav1.ObjectMeta{ 132 | GenerateName: "porter-test-me-", 133 | }, 134 | Spec: porterv1.ParameterSetSpec{ 135 | SchemaVersion: string(storage.DefaultParameterSetSchemaVersion), 136 | Name: psName, 137 | }, 138 | } 139 | return ps 140 | } 141 | -------------------------------------------------------------------------------- /tests/integration/suite_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | 3 | package integration_test 4 | 5 | import ( 6 | "context" 7 | "os" 8 | "path/filepath" 9 | "testing" 10 | 11 | . "github.com/onsi/ginkgo/v2" 12 | . "github.com/onsi/gomega" 13 | corev1 "k8s.io/api/core/v1" 14 | rbacv1 "k8s.io/api/rbac/v1" 15 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 | "k8s.io/client-go/kubernetes/scheme" 17 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 18 | "k8s.io/client-go/rest" 19 | "k8s.io/utils/ptr" 20 | ctrl "sigs.k8s.io/controller-runtime" 21 | "sigs.k8s.io/controller-runtime/pkg/client" 22 | "sigs.k8s.io/controller-runtime/pkg/envtest" 23 | logf "sigs.k8s.io/controller-runtime/pkg/log" 24 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 25 | 26 | "get.porter.sh/operator/controllers" 27 | 28 | v1 "get.porter.sh/operator/api/v1" 29 | // +kubebuilder:scaffold:imports 30 | ) 31 | 32 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 33 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 34 | 35 | var cfg *rest.Config 36 | var k8sClient client.Client 37 | var testEnv *envtest.Environment 38 | 39 | func TestAPIs(t *testing.T) { 40 | RegisterFailHandler(Fail) 41 | 42 | RunSpecs(t, "Controller Suite") 43 | } 44 | 45 | var _ = BeforeSuite(func(done Done) { 46 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 47 | 48 | By("bootstrapping test environment") 49 | testEnv = &envtest.Environment{ 50 | UseExistingCluster: ptr.To(true), 51 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 52 | } 53 | 54 | cfg, err := testEnv.Start() 55 | Expect(err).NotTo(HaveOccurred()) 56 | Expect(cfg).NotTo(BeNil()) 57 | 58 | Expect(clientgoscheme.AddToScheme(scheme.Scheme)).To(Succeed()) 59 | Expect(v1.AddToScheme(scheme.Scheme)).To(Succeed()) 60 | 61 | // +kubebuilder:scaffold:scheme 62 | 63 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 64 | Expect(err).NotTo(HaveOccurred()) 65 | Expect(k8sClient).NotTo(BeNil()) 66 | 67 | k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ 68 | Scheme: scheme.Scheme, 69 | }) 70 | Expect(err).ToNot(HaveOccurred()) 71 | 72 | err = (&controllers.InstallationReconciler{ 73 | Client: k8sManager.GetClient(), 74 | Scheme: scheme.Scheme, 75 | Recorder: k8sManager.GetEventRecorderFor("installation"), 76 | Log: ctrl.Log.WithName("controllers").WithName("Installation"), 77 | CreateGRPCClient: controllers.CreatePorterGRPCClient, 78 | }).SetupWithManager(k8sManager) 79 | Expect(err).ToNot(HaveOccurred()) 80 | 81 | err = (&controllers.CredentialSetReconciler{ 82 | Client: k8sManager.GetClient(), 83 | Scheme: scheme.Scheme, 84 | Log: ctrl.Log.WithName("controllers").WithName("CredentialSet"), 85 | }).SetupWithManager(k8sManager) 86 | Expect(err).ToNot(HaveOccurred()) 87 | 88 | err = (&controllers.ParameterSetReconciler{ 89 | Client: k8sManager.GetClient(), 90 | Scheme: scheme.Scheme, 91 | Log: ctrl.Log.WithName("controllers").WithName("ParameterSet"), 92 | }).SetupWithManager(k8sManager) 93 | Expect(err).ToNot(HaveOccurred()) 94 | 95 | err = (&controllers.AgentActionReconciler{ 96 | Client: k8sManager.GetClient(), 97 | Scheme: scheme.Scheme, 98 | Log: ctrl.Log.WithName("controllers").WithName("AgentAction"), 99 | }).SetupWithManager(k8sManager) 100 | Expect(err).ToNot(HaveOccurred()) 101 | 102 | err = (&controllers.AgentConfigReconciler{ 103 | Client: k8sManager.GetClient(), 104 | Scheme: scheme.Scheme, 105 | Log: ctrl.Log.WithName("controllers").WithName("AgentConfig"), 106 | }).SetupWithManager(k8sManager) 107 | Expect(err).ToNot(HaveOccurred()) 108 | 109 | go func() { 110 | err = k8sManager.Start(ctrl.SetupSignalHandler()) 111 | Expect(err).ToNot(HaveOccurred()) 112 | }() 113 | 114 | k8sClient = k8sManager.GetClient() 115 | Expect(k8sClient).ToNot(BeNil()) 116 | 117 | close(done) 118 | }) 119 | 120 | var _ = AfterSuite(func() { 121 | By("tearing down the test environment") 122 | err := testEnv.Stop() 123 | Expect(err).NotTo(HaveOccurred()) 124 | }) 125 | 126 | func createTestNamespace(ctx context.Context) string { 127 | ns := &corev1.Namespace{ 128 | ObjectMeta: metav1.ObjectMeta{ 129 | GenerateName: "ginkgo-tests-", 130 | Labels: map[string]string{ 131 | "getporter.org/testdata": "true", 132 | }, 133 | }, 134 | } 135 | Expect(k8sClient.Create(ctx, ns)).To(Succeed()) 136 | 137 | // porter-agent service account 138 | svc := &corev1.ServiceAccount{ 139 | ObjectMeta: metav1.ObjectMeta{ 140 | Name: "porter-agent", 141 | Namespace: ns.Name, 142 | }, 143 | } 144 | Expect(k8sClient.Create(ctx, svc)).To(Succeed()) 145 | 146 | // Configure rbac for porter-agent 147 | svcRole := &rbacv1.RoleBinding{ 148 | ObjectMeta: metav1.ObjectMeta{ 149 | Name: "porter-agent-rolebinding", 150 | Namespace: ns.Name, 151 | }, 152 | Subjects: []rbacv1.Subject{ 153 | { 154 | Kind: "ServiceAccount", 155 | Name: svc.Name, 156 | Namespace: svc.Namespace, 157 | }, 158 | }, 159 | RoleRef: rbacv1.RoleRef{ 160 | APIGroup: "rbac.authorization.k8s.io", 161 | Kind: "ClusterRole", 162 | Name: "porter-operator-agent-role", 163 | }, 164 | } 165 | Expect(k8sClient.Create(ctx, svcRole)).To(Succeed()) 166 | 167 | // installation image service account 168 | instsa := &corev1.ServiceAccount{ 169 | ObjectMeta: metav1.ObjectMeta{ 170 | Name: "installation-agent", 171 | Namespace: ns.Name, 172 | }, 173 | } 174 | Expect(k8sClient.Create(ctx, instsa)).To(Succeed()) 175 | 176 | agentRepo := os.Getenv("PORTER_AGENT_REPOSITORY") 177 | if agentRepo == "" { 178 | agentRepo = "ghcr.io/getporter/porter-agent" 179 | } 180 | agentVersion := os.Getenv("PORTER_AGENT_VERSION") 181 | if agentVersion == "" { 182 | // We can switch this back to latest when 1.0.0 of porter releases 183 | agentVersion = v1.DefaultPorterAgentVersion 184 | } 185 | var retryLimit int32 = 2 186 | // Tweak porter agent config for testing 187 | agentCfg := &v1.AgentConfig{ 188 | ObjectMeta: metav1.ObjectMeta{ 189 | Name: "default", 190 | Namespace: ns.Name, 191 | }, 192 | Spec: v1.AgentConfigSpec{ 193 | PullPolicy: corev1.PullAlways, 194 | PorterRepository: agentRepo, 195 | PorterVersion: agentVersion, 196 | RetryLimit: &retryLimit, 197 | ServiceAccount: svc.Name, 198 | InstallationServiceAccount: "installation-agent", 199 | PluginConfigFile: &v1.PluginFileSpec{SchemaVersion: "1.0.0", Plugins: map[string]v1.Plugin{"kubernetes": {FeedURL: "https://cdn.porter.sh/plugins/atom.xml", Version: "v1.0.1"}}}, 200 | }, 201 | } 202 | Expect(k8sClient.Create(ctx, agentCfg)).To(Succeed()) 203 | 204 | return ns.Name 205 | } 206 | -------------------------------------------------------------------------------- /tests/integration/testdata/operator_porter_config.yaml: -------------------------------------------------------------------------------- 1 | default-secrets: "kubernetes-secrets" 2 | verbosity: "debug" 3 | default-storage: "in-cluster-mongodb" 4 | storage: 5 | - name: "in-cluster-mongodb" 6 | plugin: "mongodb" 7 | config: 8 | url: "mongodb://mongodb.porter-operator-system.svc.cluster.local" 9 | secrets: 10 | - name: "kubernetes-secrets" 11 | plugin: "kubernetes.secrets" 12 | --------------------------------------------------------------------------------