From df425d911b64ba201ba3f7b96fc3685c191b67a7 Mon Sep 17 00:00:00 2001 From: Simon Beck Date: Wed, 13 Sep 2023 15:06:40 +0200 Subject: [PATCH 1/2] Add apiserver --- .github/ISSUE_TEMPLATE/bug_report.yml | 49 + .github/ISSUE_TEMPLATE/config.yml | 7 + .github/ISSUE_TEMPLATE/feature_request.yml | 67 + .github/PULL_REQUEST_TEMPLATE.md | 19 + .github/changelog-configuration.json | 42 + .github/workflows/build.yml | 30 + .github/workflows/docs.yml | 82 + .github/workflows/lint.yml | 29 + .github/workflows/release.yml | 65 + .github/workflows/test.yml | 30 + .gitignore | 22 + .goreleaser.yml | 52 + Dockerfile | 18 + LICENSE | 28 + Makefile | 196 + apis/appcat/v1/appcat_types.go | 182 + apis/appcat/v1/appcat_types_test.go | 167 + apis/appcat/v1/generated.pb.go | 3251 +++++++++++++++++ apis/appcat/v1/register.go | 41 + apis/appcat/v1/vshn_postgres_backup_types.go | 139 + .../v1/vshn_postgres_backup_types_test.go | 113 + apis/appcat/v1/vshn_redis_backup_types.go | 72 + apis/appcat/v1/zz_generated.deepcopy.go | 324 ++ apis/v1/conditions.go | 48 + apis/v1/groupversion_info.go | 25 + apis/v1/objectreference.go | 13 + apis/v1/objectstorage_types.go | 77 + apis/v1/zz_generated.deepcopy.go | 188 + apis/vshn/v1/common_types.go | 84 + apis/vshn/v1/dbaas_vshn_postgresql.go | 327 ++ apis/vshn/v1/dbaas_vshn_redis.go | 130 + apis/vshn/v1/groupversion_info.go | 32 + apis/vshn/v1/zz_generated.deepcopy.go | 731 ++++ cmd/apiserver.go | 65 + config/apiserver/aggregated-apiserver.yaml | 51 + config/apiserver/apiserver-cert.yaml | 12 + config/apiserver/apiserver-envs.yaml | 11 + config/apiserver/apiservice.yaml | 16 + config/apiserver/cluster-role-binding.yaml | 12 + config/apiserver/namespace.yaml | 4 + config/apiserver/role.yaml | 80 + config/apiserver/service-account.yaml | 5 + config/apiserver/service.yaml | 16 + docs/antora-build.mk | 24 + docs/antora-playbook.yml | 18 + docs/antora-preview.mk | 7 + docs/antora.yml | 6 + docs/modules/ROOT/assets/images/.gitkeep | 0 .../ROOT/assets/images/quotas.excalidraw.png | Bin 0 -> 91288 bytes docs/modules/ROOT/examples/.gitkeep | 0 docs/modules/ROOT/nav.adoc | 21 + docs/modules/ROOT/pages/examples/.gitkeep | 0 docs/modules/ROOT/pages/examples/vscode.adoc | 115 + docs/modules/ROOT/pages/explanations/.gitkeep | 0 .../explanations/apiserver/boostrap.adoc | 13 + docs/modules/ROOT/pages/how-tos/.gitkeep | 0 docs/modules/ROOT/pages/index.adoc | 22 + docs/modules/ROOT/pages/references/.gitkeep | 0 .../references/apiserver/env-variables.adoc | 8 + docs/modules/ROOT/pages/tutorials/.gitkeep | 0 docs/modules/ROOT/partials/.gitkeep | 0 docs/package-lock.json | 727 ++++ docs/package.json | 8 + go.mod | 159 + go.sum | 1011 +++++ log.go | 54 + login.json | 4 + main.go | 46 + pkg/apiserver/common.go | 69 + pkg/apiserver/hack/boilerplate.txt | 0 pkg/apiserver/vshn/k8up/k8up.go | 99 + pkg/apiserver/vshn/postgres/backup.go | 59 + pkg/apiserver/vshn/postgres/backup_test.go | 138 + pkg/apiserver/vshn/postgres/create.go | 15 + pkg/apiserver/vshn/postgres/delete.go | 29 + pkg/apiserver/vshn/postgres/get.go | 49 + pkg/apiserver/vshn/postgres/get_test.go | 108 + pkg/apiserver/vshn/postgres/list.go | 105 + pkg/apiserver/vshn/postgres/list_test.go | 278 ++ pkg/apiserver/vshn/postgres/sgbackups.go | 125 + pkg/apiserver/vshn/postgres/sgbackups_test.go | 142 + pkg/apiserver/vshn/postgres/table.go | 92 + pkg/apiserver/vshn/postgres/table_test.go | 65 + pkg/apiserver/vshn/postgres/update.go | 21 + pkg/apiserver/vshn/postgres/vshnpostgresql.go | 39 + .../vshn/postgres/vshnpostgresql_test.go | 131 + pkg/apiserver/vshn/redis/backup.go | 73 + pkg/apiserver/vshn/redis/common_test.go | 64 + pkg/apiserver/vshn/redis/create.go | 16 + pkg/apiserver/vshn/redis/delete.go | 29 + pkg/apiserver/vshn/redis/get.go | 55 + pkg/apiserver/vshn/redis/get_test.go | 110 + pkg/apiserver/vshn/redis/list.go | 61 + pkg/apiserver/vshn/redis/list_test.go | 154 + pkg/apiserver/vshn/redis/table.go | 59 + pkg/apiserver/vshn/redis/table_test.go | 109 + pkg/apiserver/vshn/redis/update.go | 18 + pkg/apiserver/vshn/redis/vshnredis.go | 28 + pkg/apiserver/vshn/redis/watch.go | 76 + test/mocks/mock_client.go | 259 ++ test/mocks/mock_composition.go | 84 + test/mocks/mock_sgbackups.go | 83 + test/mocks/mock_vshnpostgresqls.go | 51 + tools.go | 24 + 104 files changed, 12112 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/changelog-configuration.json create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .goreleaser.yml create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 apis/appcat/v1/appcat_types.go create mode 100644 apis/appcat/v1/appcat_types_test.go create mode 100644 apis/appcat/v1/generated.pb.go create mode 100644 apis/appcat/v1/register.go create mode 100644 apis/appcat/v1/vshn_postgres_backup_types.go create mode 100644 apis/appcat/v1/vshn_postgres_backup_types_test.go create mode 100644 apis/appcat/v1/vshn_redis_backup_types.go create mode 100644 apis/appcat/v1/zz_generated.deepcopy.go create mode 100644 apis/v1/conditions.go create mode 100644 apis/v1/groupversion_info.go create mode 100644 apis/v1/objectreference.go create mode 100644 apis/v1/objectstorage_types.go create mode 100644 apis/v1/zz_generated.deepcopy.go create mode 100644 apis/vshn/v1/common_types.go create mode 100644 apis/vshn/v1/dbaas_vshn_postgresql.go create mode 100644 apis/vshn/v1/dbaas_vshn_redis.go create mode 100644 apis/vshn/v1/groupversion_info.go create mode 100644 apis/vshn/v1/zz_generated.deepcopy.go create mode 100644 cmd/apiserver.go create mode 100644 config/apiserver/aggregated-apiserver.yaml create mode 100644 config/apiserver/apiserver-cert.yaml create mode 100644 config/apiserver/apiserver-envs.yaml create mode 100644 config/apiserver/apiservice.yaml create mode 100644 config/apiserver/cluster-role-binding.yaml create mode 100644 config/apiserver/namespace.yaml create mode 100644 config/apiserver/role.yaml create mode 100644 config/apiserver/service-account.yaml create mode 100644 config/apiserver/service.yaml create mode 100644 docs/antora-build.mk create mode 100644 docs/antora-playbook.yml create mode 100644 docs/antora-preview.mk create mode 100644 docs/antora.yml create mode 100644 docs/modules/ROOT/assets/images/.gitkeep create mode 100644 docs/modules/ROOT/assets/images/quotas.excalidraw.png create mode 100644 docs/modules/ROOT/examples/.gitkeep create mode 100644 docs/modules/ROOT/nav.adoc create mode 100644 docs/modules/ROOT/pages/examples/.gitkeep create mode 100644 docs/modules/ROOT/pages/examples/vscode.adoc create mode 100644 docs/modules/ROOT/pages/explanations/.gitkeep create mode 100644 docs/modules/ROOT/pages/explanations/apiserver/boostrap.adoc create mode 100644 docs/modules/ROOT/pages/how-tos/.gitkeep create mode 100644 docs/modules/ROOT/pages/index.adoc create mode 100644 docs/modules/ROOT/pages/references/.gitkeep create mode 100644 docs/modules/ROOT/pages/references/apiserver/env-variables.adoc create mode 100644 docs/modules/ROOT/pages/tutorials/.gitkeep create mode 100644 docs/modules/ROOT/partials/.gitkeep create mode 100644 docs/package-lock.json create mode 100644 docs/package.json create mode 100644 go.mod create mode 100644 go.sum create mode 100644 log.go create mode 100644 login.json create mode 100644 main.go create mode 100644 pkg/apiserver/common.go create mode 100644 pkg/apiserver/hack/boilerplate.txt create mode 100644 pkg/apiserver/vshn/k8up/k8up.go create mode 100644 pkg/apiserver/vshn/postgres/backup.go create mode 100644 pkg/apiserver/vshn/postgres/backup_test.go create mode 100644 pkg/apiserver/vshn/postgres/create.go create mode 100644 pkg/apiserver/vshn/postgres/delete.go create mode 100644 pkg/apiserver/vshn/postgres/get.go create mode 100644 pkg/apiserver/vshn/postgres/get_test.go create mode 100644 pkg/apiserver/vshn/postgres/list.go create mode 100644 pkg/apiserver/vshn/postgres/list_test.go create mode 100644 pkg/apiserver/vshn/postgres/sgbackups.go create mode 100644 pkg/apiserver/vshn/postgres/sgbackups_test.go create mode 100644 pkg/apiserver/vshn/postgres/table.go create mode 100644 pkg/apiserver/vshn/postgres/table_test.go create mode 100644 pkg/apiserver/vshn/postgres/update.go create mode 100644 pkg/apiserver/vshn/postgres/vshnpostgresql.go create mode 100644 pkg/apiserver/vshn/postgres/vshnpostgresql_test.go create mode 100644 pkg/apiserver/vshn/redis/backup.go create mode 100644 pkg/apiserver/vshn/redis/common_test.go create mode 100644 pkg/apiserver/vshn/redis/create.go create mode 100644 pkg/apiserver/vshn/redis/delete.go create mode 100644 pkg/apiserver/vshn/redis/get.go create mode 100644 pkg/apiserver/vshn/redis/get_test.go create mode 100644 pkg/apiserver/vshn/redis/list.go create mode 100644 pkg/apiserver/vshn/redis/list_test.go create mode 100644 pkg/apiserver/vshn/redis/table.go create mode 100644 pkg/apiserver/vshn/redis/table_test.go create mode 100644 pkg/apiserver/vshn/redis/update.go create mode 100644 pkg/apiserver/vshn/redis/vshnredis.go create mode 100644 pkg/apiserver/vshn/redis/watch.go create mode 100644 test/mocks/mock_client.go create mode 100644 test/mocks/mock_composition.go create mode 100644 test/mocks/mock_sgbackups.go create mode 100644 test/mocks/mock_vshnpostgresqls.go create mode 100644 tools.go diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..e866d86 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,49 @@ +name: 🐛 Bug report +description: Create a report to help us improve 🎉 +labels: + - bug + +body: + - type: textarea + id: description + attributes: + label: Description + description: A clear and concise description of what the bug is. + validations: + required: true + - type: textarea + id: context + attributes: + label: Additional Context + description: Add any other context about the problem here. + validations: + required: false + - type: textarea + id: logs + attributes: + label: Logs + description: If applicable, add logs to help explain the bug. + render: shell + validations: + required: false + - type: textarea + id: expected_behavior + attributes: + label: Expected Behavior + description: A clear and concise description of what you expected to happen. + validations: + required: true + - type: textarea + id: reproduction_steps + attributes: + label: Steps To Reproduce + description: Describe steps to reproduce the behavior + validations: + required: false + - type: textarea + id: version + attributes: + label: Versions + placeholder: v1.2.3 [, Kubernetes 1.21] + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c07048c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +blank_issues_enabled: false + +# TODO: Redirect support questions +# contact_links: +# - name: ❓ Question +# url: https://github.com///discussions +# about: Ask or discuss with us, we're happy to help 🙋 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..401c7aa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,67 @@ +name: 🚀 Feature request +description: Suggest an idea for this project 💡 +labels: + - enhancement + +body: + - type: textarea + id: summary + attributes: + label: Summary + value: | + **As** role name\ + **I want** a feature or functionality\ + **So that** I get certain business value + description: This user story helps us to quickly understand what this idea is about. + validations: + required: true + - type: textarea + id: context + attributes: + label: Context + description: Add more information here. You are completely free regarding form and length. + validations: + required: true + - type: textarea + id: out_of_scope + attributes: + label: Out of Scope + description: List aspects that are explicitly not part of this feature + placeholder: | + - ... + - ... + - ... + validations: + required: false + - type: textarea + id: links + attributes: + label: Further links + description: URLs of relevant Git repositories, PRs, Issues, etc. + placeholder: | + - #567 + - https://kubernetes.io/docs/reference/ + validations: + required: false + - type: textarea + id: acceptance_criteria + attributes: + label: Acceptance Criteria + description: If you already have ideas what the detailed requirements are, please list them below in given-when-then expressions. + placeholder: | + - Given a precondition, when an action happens, then expect a result + + ```gherkin + Given a precondition + When an action happens + Then expect a result + ``` + validations: + required: false + - type: textarea + id: implementation_idea + attributes: + label: Implementation Ideas + description: If applicable, shortly list possible implementation ideas + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c6d8d79 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +## Summary + +* Short summary of what's included in the PR +* Give special note to breaking changes + +## Checklist + +- [ ] Categorize the PR by setting a good title and adding one of the labels: + `bug`, `enhancement`, `documentation`, `change`, `breaking`, `dependency` + as they show up in the changelog +- [ ] Update tests. +- [ ] Link this PR to related issues. + + diff --git a/.github/changelog-configuration.json b/.github/changelog-configuration.json new file mode 100644 index 0000000..8c93e7b --- /dev/null +++ b/.github/changelog-configuration.json @@ -0,0 +1,42 @@ +{ + "pr_template": "- ${{TITLE}} (#${{NUMBER}})", + "categories": [ + { + "title": "## 🚀 Features", + "labels": [ + "enhancement" + ] + }, + { + "title": "## 🛠️ Minor Changes", + "labels": [ + "change" + ] + }, + { + "title": "## 🔎 Breaking Changes", + "labels": [ + "breaking" + ] + }, + { + "title": "## 🐛 Fixes", + "labels": [ + "bug" + ] + }, + { + "title": "## 📄 Documentation", + "labels": [ + "documentation" + ] + }, + { + "title": "## 🔗 Dependency Updates", + "labels": [ + "dependency" + ] + } + ], + "template": "${{CATEGORIZED_COUNT}} changes since ${{FROM_TAG}}\n\n${{CHANGELOG}}" +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..9b1a128 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,30 @@ +name: Build + +on: + pull_request: {} + push: + branches: + - master + +jobs: + go-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Determine Go version from go.mod + run: echo "GO_VERSION=$(go mod edit -json | jq -r .Go)" >> $GITHUB_ENV + + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.GO_VERSION }} + + - uses: actions/cache@v3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Run build + run: make build diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..9441b63 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,82 @@ +name: Docs + +on: + push: + branches: + - master + tags: + - "*" + +jobs: + antora: + runs-on: ubuntu-latest + if: ${{ contains(github.ref, 'tags') }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Configure Git + run: | + git config user.name "Antora via GitHub Actions" + git config user.email "actions@github.com" + + - name: Parse semver string + id: semver + uses: booxmedialtd/ws-action-parse-semver@v1 + with: + input_string: ${{ github.ref }} + version_extractor_regex: '\/v(.*)$' + - name: Set variables + run: | + echo "MINOR_VERSION=${{ steps.semver.outputs.major }}.${{ steps.semver.outputs.minor }}" >> $GITHUB_ENV + echo "BRANCH_NAME=docs/v${{ steps.semver.outputs.major }}.${{ steps.semver.outputs.minor }}" >> $GITHUB_ENV + - name: Set branch name for Prerelease + if: ${{ steps.semver.outputs.prerelease != '' }} + run: echo "BRANCH_NAME=${{ env.BRANCH_NAME }}-rc" >> $GITHUB_ENV + + - name: Checkout remote branch if exists + run: git checkout ${{ env.BRANCH_NAME }} + continue-on-error: true + - name: Rebase if possible + run: git rebase ${GITHUB_REF##*/} ${{ env.BRANCH_NAME }} + continue-on-error: true + - name: Create new branch if not existing + run: git switch --create ${{ env.BRANCH_NAME }} + continue-on-error: true + + - name: Patch Antora file for Release + run: yq eval 'del(.prerelease) | del (.display_version) | .version = "${{ env.MINOR_VERSION }}"' -i docs/antora.yml + if: ${{ steps.semver.outputs.prerelease == '' }} + - name: Patch Antora file for Prerelease + run: yq eval 'del (.display_version) | .version = "${{ env.MINOR_VERSION }}", .prerelease = "-${{ steps.semver.outputs.prerelease }}"' -i docs/antora.yml + if: ${{ steps.semver.outputs.prerelease != '' }} + + - name: Commit + run: git commit --all --message "Update version for Antora" + continue-on-error: true + - name: Push + run: git push --atomic --force --set-upstream origin ${{ env.BRANCH_NAME }} + + - name: Cleanup prerelease branch if existing + if: ${{ steps.semver.outputs.prerelease == '' }} + run: git push origin --delete ${{ env.BRANCH_NAME }}-rc + continue-on-error: true + + gh-pages: + runs-on: ubuntu-latest + # The needs+if combo will cause this job to wait until Antora versioning is done for tags, but still run on master branch + needs: antora + if: always() + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}" + git config user.name "Antora via GitHub Actions" + git config user.email "actions@github.com" + + - name: Publish documentation + run: make docs-publish diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..c82fb46 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,29 @@ +name: Lint + +on: + pull_request: {} + push: + branches: + - master +jobs: + go-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Determine Go version from go.mod + run: echo "GO_VERSION=$(go mod edit -json | jq -r .Go)" >> $GITHUB_ENV + + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.GO_VERSION }} + + - uses: actions/cache@v3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Run linters + run: make lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a87687e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,65 @@ +name: Release + +on: + push: + tags: + - "*" + +jobs: + dist: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Determine Go version from go.mod + run: echo "GO_VERSION=$(grep "go 1." go.mod | cut -d " " -f 2)" >> $GITHUB_ENV + + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - uses: actions/cache@v3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Login to GHCR + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push docker image + run: make docker-push -e IMG_TAG=${GITHUB_REF##*/} + + - name: Build changelog from PRs with labels + id: build_changelog + uses: mikepenz/release-changelog-builder-action@v3 + with: + configuration: ".github/changelog-configuration.json" + # PreReleases still get a changelog, but the next full release gets a diff since the last full release, + # combining possible changelogs of all previous PreReleases in between. PreReleases show a partial changelog + # since last PreRelease. + ignorePreReleases: "${{ !contains(github.ref, '-rc') }}" + outputFile: .github/release-notes.md + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish releases + uses: goreleaser/goreleaser-action@v4 + with: + args: release --release-notes .github/release-notes.md + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..3f1f422 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +name: Test + +on: + push: + branches: + - master + pull_request: {} + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Determine Go version from go.mod + run: echo "GO_VERSION=$(grep "go 1." go.mod | cut -d " " -f 2)" >> $GITHUB_ENV + + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.GO_VERSION }} + + - uses: actions/cache@v3 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Run tests + run: make test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19d5607 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Goreleaser +dist/ +.github/release-notes.md + +# Binaries for programs and plugins +appcat +# But don't ignore the appcat APIS! +!apis/appcat + +# temp file, editor and IDE paraphernalia +.idea +.kind +.work +.cache +.public + +# debug +apiserver.local.config +__debug_bin* + +# Kubebuilder +/apis/generated/ diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..29638e8 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,52 @@ +builds: + - binary: appcat + env: + - CGO_ENABLED=0 # this is needed otherwise the Docker image build is faulty + goarch: + - amd64 + - arm64 + goos: + - linux + goarm: + - "8" + +archives: + - format: binary + name_template: "{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" + +checksum: + name_template: 'checksums.txt' + +snapshot: + name_template: "{{ incpatch .Version }}-snapshot" + +dockers: + - goarch: amd64 + use: buildx + build_flag_templates: + - "--platform=linux/amd64" + image_templates: + - "ghcr.io/vshn/appcat:v{{ .Version }}-amd64" + + - goarch: arm64 + use: buildx + build_flag_templates: + - "--platform=linux/arm64/v8" + image_templates: + - "ghcr.io/vshn/appcat:v{{ .Version }}-arm64" + +docker_manifests: + # For prereleases, updating `latest` does not make sense. + # Only the image for the exact version should be pushed. + - name_template: "{{ if not .Prerelease }}ghcr.io/vshn/appcat:latest{{ end }}" + image_templates: + - "ghcr.io/vshn/appcat:v{{ .Version }}-amd64" + - "ghcr.io/vshn/appcat:v{{ .Version }}-arm64" + + - name_template: "ghcr.io/vshn/appcat:v{{ .Version }}" + image_templates: + - "ghcr.io/vshn/appcat:v{{ .Version }}-amd64" + - "ghcr.io/vshn/appcat:v{{ .Version }}-arm64" + +release: + prerelease: auto diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e662e17 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM docker.io/library/alpine:3.15 as runtime + +ENTRYPOINT ["appcat"] + +RUN \ + apk add --update --no-cache \ + bash \ + ca-certificates \ + curl + +RUN \ + mkdir /.cache && chmod -R g=u /.cache + +COPY appcat /usr/local/bin/ + +RUN chmod a+x /usr/local/bin/appcat + +USER 65532:0 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5e98580 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +BSD 3-Clause License + +Copyright (c) 2023, VSHN AG + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f7fdb58 --- /dev/null +++ b/Makefile @@ -0,0 +1,196 @@ + +# Image URL to use all building/pushing image targets +IMG_TAG ?= latest +GHCR_IMG ?= ghcr.io/vshn/appcat:$(IMG_TAG) +DOCKER_CMD ?= docker + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +OS := $(shell uname) +ifeq ($(OS), Darwin) + sed ?= gsed +else + sed ?= sed +endif + +# For alpine image it is required the following env before building the application +DOCKER_IMAGE_GOOS = linux +DOCKER_IMAGE_GOARCH = amd64 + +PROJECT_ROOT_DIR = . +PROJECT_NAME ?= appcat +PROJECT_OWNER ?= vshn + +PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) +BIN_FILENAME ?= $(PROJECT_DIR)/appcat + +## Stackgres CRDs +STACKGRES_VERSION ?= 1.4.3 +STACKGRES_CRD_URL ?= https://gitlab.com/ongresinc/stackgres/-/raw/${STACKGRES_VERSION}/stackgres-k8s/src/common/src/main/resources/crds + +## BUILD:go +go_bin ?= $(PWD)/.work/bin +$(go_bin): + @mkdir -p $@ + +uname_s := $(shell uname -s) +ifeq ($(uname_s),Linux) + distr_protoc := linux-x86_64 +else + distr_protoc := osx-universal_binary +endif + +protoc_bin = $(go_bin)/protoc +$(protoc_bin): export GOBIN = $(go_bin) +$(protoc_bin): | $(go_bin) + @echo "installing protocol buffers with dependencies" + @git clone -q --depth 1 https://github.com/kubernetes/kubernetes.git .work/kubernetes + @go install github.com/gogo/protobuf/protoc-gen-gogo@latest + @go install golang.org/x/tools/cmd/goimports@latest + @wget -q -O $(go_bin)/protoc.zip https://github.com/protocolbuffers/protobuf/releases/download/v22.1/protoc-22.1-$(distr_protoc).zip + @unzip $(go_bin)/protoc.zip -d .work + @rm $(go_bin)/protoc.zip + +-include docs/antora-preview.mk docs/antora-build.mk + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +.PHONY: generate +generate: export PATH := $(go_bin):$(PATH) +generate: $(protoc_bin) generate-stackgres-crds ## Generate code with controller-gen and protobuf. + go version + rm -rf apis/generated + go run sigs.k8s.io/controller-tools/cmd/controller-gen paths=./apis/... object crd:crdVersions=v1,allowDangerousTypes=true output:artifacts:config=./apis/generated + go generate ./... + # Because yaml is such a fun and easy specification, we need to hack some things here. + # Depending on the yaml parser implementation the equal sign (=) has special meaning, or not... + # So we make it explicitly a string. + $(sed) -i ':a;N;$$!ba;s/- =\n/- "="\n/g' apis/generated/vshn.appcat.vshn.io_vshnpostgresqls.yaml + rm -rf crds && cp -r apis/generated crds + go run sigs.k8s.io/controller-tools/cmd/controller-gen rbac:roleName=appcat paths="{./apis/...,./pkg/apiserver/...}" output:artifacts:config=config/apiserver + go run sigs.k8s.io/controller-tools/cmd/controller-gen rbac:roleName=appcat-sli-exporter paths="{./pkg/sliexporter/...}" output:artifacts:config=config/sliexporter/rbac + go run k8s.io/code-generator/cmd/go-to-protobuf \ + --packages=github.com/vshn/appcat-apiserver/apis/appcat/v1 \ + --output-base=./.work/tmp \ + --go-header-file=./pkg/apiserver/hack/boilerplate.txt \ + --apimachinery-packages='-k8s.io/apimachinery/pkg/util/intstr,-k8s.io/apimachinery/pkg/api/resource,-k8s.io/apimachinery/pkg/runtime/schema,-k8s.io/apimachinery/pkg/runtime,-k8s.io/apimachinery/pkg/apis/meta/v1,-k8s.io/apimachinery/pkg/apis/meta/v1beta1,-k8s.io/api/core/v1,-k8s.io/api/rbac/v1' \ + --proto-import=./.work/kubernetes/vendor/ && \ + mv ./.work/tmp/github.com/vshn/appcat-apiserver/apis/appcat/v1/generated.pb.go ./apis/appcat/v1/ && \ + rm -rf ./.work/tmp + +.PHONY: generate-stackgres-crds +generate-stackgres-crds: + curl ${STACKGRES_CRD_URL}/SGDbOps.yaml?inline=false -o apis/stackgres/v1/sgdbops_crd.yaml + yq -i e apis/stackgres/v1/sgdbops.yaml --expression ".components.schemas.SGDbOpsSpec=load(\"apis/stackgres/v1/sgdbops_crd.yaml\").spec.versions[0].schema.openAPIV3Schema.properties.spec" + yq -i e apis/stackgres/v1/sgdbops.yaml --expression ".components.schemas.SGDbOpsStatus=load(\"apis/stackgres/v1/sgdbops_crd.yaml\").spec.versions[0].schema.openAPIV3Schema.properties.status" + go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=v1 -generate=types -o apis/stackgres/v1/sgdbops.gen.go apis/stackgres/v1/sgdbops.yaml + perl -i -0pe 's/\*struct\s\{\n\s\sAdditionalProperties\smap\[string\]string\s`json:"-"`\n\s}/map\[string\]string/gms' apis/stackgres/v1/sgdbops.gen.go + + curl ${STACKGRES_CRD_URL}/SGCluster.yaml?inline=false -o apis/stackgres/v1/sgcluster_crd.yaml + yq -i e apis/stackgres/v1/sgcluster.yaml --expression ".components.schemas.SGClusterSpec=load(\"apis/stackgres/v1/sgcluster_crd.yaml\").spec.versions[0].schema.openAPIV3Schema.properties.spec" + yq -i e apis/stackgres/v1/sgcluster.yaml --expression ".components.schemas.SGClusterStatus=load(\"apis/stackgres/v1/sgcluster_crd.yaml\").spec.versions[0].schema.openAPIV3Schema.properties.status" + go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=v1 -generate=types -o apis/stackgres/v1/sgcluster.gen.go apis/stackgres/v1/sgcluster.yaml + perl -i -0pe 's/\*struct\s\{\n\s\sAdditionalProperties\smap\[string\]string\s`json:"-"`\n\s}/map\[string\]string/gms' apis/stackgres/v1/sgcluster.gen.go + + go run sigs.k8s.io/controller-tools/cmd/controller-gen object paths=./apis/stackgres/v1/... + rm apis/stackgres/v1/*_crd.yaml + +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... + +.PHONY: vet +vet: ## Run go vet against code. + go vet ./... + +.PHONY: lint +lint: fmt vet ## All-in-one linting + @echo 'Check for uncommitted changes ...' + git diff --exit-code + +##@ Build + +.PHONY: build +build: export CGO_ENABLED = 0 +build: generate fmt vet ## Build manager binary. +build: + @echo "GOOS=$$(go env GOOS) GOARCH=$$(go env GOARCH)" + go build -o $(BIN_FILENAME) + +.PHONY: test +test: ## Run tests + go test ./... + +.PHONY: docker-build +docker-build: + env CGO_ENABLED=0 GOOS=$(DOCKER_IMAGE_GOOS) GOARCH=$(DOCKER_IMAGE_GOARCH) \ + go build -o ${BIN_FILENAME} + docker build --platform $(DOCKER_IMAGE_GOOS)/$(DOCKER_IMAGE_GOARCH) -t ${GHCR_IMG} . + +.PHONY: docker-build-branchtag +docker-build-branchtag: docker-build ## Build docker image with current branch name + tag=$$(git rev-parse --abbrev-ref HEAD) && \ + docker tag ${GHCR_IMG} ghcr.io/vshn/appcat:"$${tag////_}" + +.PHONY: kind-load-branch-tag +kind-load-branch-tag: ## load docker image with current branch tag into kind + tag=$$(git rev-parse --abbrev-ref HEAD) && \ + kind load docker-image --name kindev ghcr.io/vshn/appcat:"$${tag////_}" + +.PHONY: docker-push +docker-push: docker-build ## Push docker image with the manager. + docker push ${GHCR_IMG} + +# Generate webhook certificates. +# This is only relevant when debugging. +# Component-appcat installs a proper certificate for this. +.PHONY: webhook-cert +webhook_key = .work/webhook/tls.key +webhook_cert = .work/webhook/tls.crt +webhook-cert: $(webhook_cert) ## Generate webhook certificates for out-of-cluster debugging in an IDE + +$(webhook_key): + mkdir -p .work/webhook + ipsan="" && \ + if [[ $(webhook_service_name) =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$$ ]]; then \ + ipsan=", IP:$(webhook_service_name)"; \ + fi; \ + openssl req -x509 -newkey rsa:4096 -nodes -keyout $@ --noout -days 3650 -subj "/CN=$(webhook_service_name)" -addext "subjectAltName = DNS:$(webhook_service_name)$$ipsan" + +$(webhook_cert): $(webhook_key) + ipsan="" && \ + if [[ $(webhook_service_name) =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$$ ]]; then \ + ipsan=", IP:$(webhook_service_name)"; \ + fi; \ + openssl req -x509 -key $(webhook_key) -nodes -out $@ -days 3650 -subj "/CN=$(webhook_service_name)" -addext "subjectAltName = DNS:$(webhook_service_name)$$ipsan" + + +.PHONY: webhook-debug +webhook_service_name = host.docker.internal + +webhook-debug: $(webhook_cert) ## Creates certificates, patches the webhook registrations and applies everything to the given kube cluster +webhook-debug: + kubectl -n syn-appcat scale deployment appcat-controller --replicas 0 + cabundle=$$(cat .work/webhook/tls.crt | base64) && \ + HOSTIP=$(webhook_service_name) && \ + kubectl annotate validatingwebhookconfigurations.admissionregistration.k8s.io appcat-pg-validation cert-manager.io/inject-ca-from- && \ + kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io appcat-pg-validation -oyaml | \ + yq e "del(.webhooks[0].clientConfig.service) | .webhooks[0].clientConfig.caBundle |= \"$$cabundle\" | .webhooks[0].clientConfig.url |= \"https://$$HOSTIP:9443/validate-vshn-appcat-vshn-io-v1-vshnpostgresql\"" - | \ + kubectl apply -f - && \ + kubectl annotate validatingwebhookconfigurations.admissionregistration.k8s.io appcat-redis-validation cert-manager.io/inject-ca-from- && \ + kubectl annotate validatingwebhookconfigurations.admissionregistration.k8s.io appcat-pg-validation kubectl.kubernetes.io/last-applied-configuration- && \ + kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io appcat-redis-validation -oyaml | \ + yq e "del(.webhooks[0].clientConfig.service) | .webhooks[0].clientConfig.caBundle |= \"$$cabundle\" | .webhooks[0].clientConfig.url |= \"https://$$HOSTIP:9443/validate-vshn-appcat-vshn-io-v1-vshnredis\"" - | \ + kubectl apply -f - && \ + kubectl annotate validatingwebhookconfigurations.admissionregistration.k8s.io appcat-redis-validation kubectl.kubernetes.io/last-applied-configuration- + +.PHONY: clean +clean: + rm -rf bin/ appcat .work/ docs/node_modules $docs_out_dir .public .cache apiserver.local.config apis/generated default.sock diff --git a/apis/appcat/v1/appcat_types.go b/apis/appcat/v1/appcat_types.go new file mode 100644 index 0000000..d1a6397 --- /dev/null +++ b/apis/appcat/v1/appcat_types.go @@ -0,0 +1,182 @@ +package v1 + +import ( + "encoding/json" + "regexp" + "strings" + + v1 "github.com/crossplane/crossplane/apis/apiextensions/v1" + "golang.org/x/text/cases" + "golang.org/x/text/language" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/apiserver-runtime/pkg/builder/resource" +) + +// +kubebuilder:rbac:groups="apiextensions.crossplane.io",resources=compositions,verbs=get;list;watch +// +kubebuilder:rbac:groups="stackgres.io",resources=sgbackups,verbs=get;list;watch +// +kubebuilder:rbac:groups="k8up.io",resources=snapshots,verbs=get;list;watch +// +kubebuilder:rbac:groups="vshn.appcat.vshn.io",resources=vshnredis;xvshnpostgresqls,verbs=get;list;watch + +var ( + // OfferedValue is the label value to identify AppCat services + OfferedValue = "true" + + // PrefixAppCatKey is the label and annotation prefix for AppCat services in compositions. + PrefixAppCatKey = "metadata.appcat.vshn.io" + + // OfferedKey is the label key to identify AppCat services + OfferedKey = PrefixAppCatKey + "/offered" +) + +// Resource is the name of this resource in plural form +var Resource = "appcats" + +// +kubebuilder:object:root=true + +// AppCat defines the main object for this API Server +type AppCat struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Details `json:"details,omitempty"` + Plans map[string]VSHNPlan `json:"plans,omitempty"` + Status AppCatStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// AppCatList defines a list of AppCat +type AppCatList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []AppCat `json:"items"` +} + +// Details are fields that are dynamically parsed from the annotations on a composition. +type Details map[string]string + +// VSHNPlan represents a plan for a VSHN service. +// It ignores the scheduling labels and other internal fields. +type VSHNPlan struct { + Note string `json:"note,omitempty"` + // JSize is called JSize because protobuf creates a method Size() + JSize VSHNSize `json:"size,omitempty"` +} + +// VSHNSize describes the aspects of the actual plan. +// This needs to be a separate struct as the protobuf generator can't handle +// embedded struct apparently. +type VSHNSize struct { + CPU string `json:"cpu,omitempty"` + Disk string `json:"disk,omitempty"` + Memory string `json:"memory,omitempty"` +} + +// AppCat needs to implement the builder resource interface +var _ resource.Object = &AppCat{} + +func (in *AppCat) GetObjectMeta() *metav1.ObjectMeta { + return &in.ObjectMeta +} + +func (in *AppCat) NamespaceScoped() bool { + return false +} + +func (in *AppCat) New() runtime.Object { + return &AppCat{} +} + +func (in *AppCat) NewList() runtime.Object { + return &AppCatList{} +} + +// GetGroupVersionResource returns the GroupVersionResource for this resource. +// The resource should be the all lowercase and pluralized kind +func (in *AppCat) GetGroupVersionResource() schema.GroupVersionResource { + return schema.GroupVersionResource{ + Group: GroupVersion.Group, + Version: GroupVersion.Version, + Resource: Resource, + } +} + +// IsStorageVersion returns true if the object is also the internal version -- i.e. is the type defined for the API group or an alias to this object. +// If false, the resource is expected to implement MultiVersionObject interface. +func (in *AppCat) IsStorageVersion() bool { + return true +} + +var _ resource.ObjectList = &AppCatList{} + +func (in *AppCatList) GetListMeta() *metav1.ListMeta { + return &in.ListMeta +} + +// AppCatStatus defines the observed state of AppCat +type AppCatStatus struct { + // CompositionName is the name of the composition + CompositionName string `json:"compositionName,omitempty"` +} + +// NewAppCatFromComposition returns an AppCat based on the given composition +// If the composition does not satisfy one of its rules, the func will return nil +func NewAppCatFromComposition(comp *v1.Composition) *AppCat { + if comp == nil || comp.Labels == nil || comp.Labels[OfferedKey] != OfferedValue { + return nil + } + + appCat := &AppCat{ + ObjectMeta: *comp.ObjectMeta.DeepCopy(), + Status: AppCatStatus{ + CompositionName: comp.Name, + }, + Details: Details{}, + } + appCat.Annotations = nil + appCat.Labels = nil + if comp.Annotations != nil { + for k, v := range comp.Annotations { + if k == PrefixAppCatKey+"/plans" { + parsePlansJSON(v, appCat) + continue + } + if strings.HasPrefix(k, PrefixAppCatKey) { + index := strings.LastIndex(k, "/") + appCat.Details[makeCamelCase(k[index+1:])] = v + } + } + } + + return appCat +} + +// makeCamelCase transforms any string in camel case string +func makeCamelCase(s string) string { + reg, _ := regexp.Compile("[^a-zA-Z0-9-]+") + s = reg.ReplaceAllString(s, "") + s = strings.ToLower(s) + slices := strings.FieldsFunc(s, func(c rune) bool { return c == '-' }) + strCamel := slices[0] + for _, v := range slices[1:] { + strCamel += cases.Title(language.English).String(v) + } + return strCamel +} + +func parsePlansJSON(jsonPlans string, spec *AppCat) { + plans := map[string]VSHNPlan{} + + err := json.Unmarshal([]byte(jsonPlans), &plans) + if err != nil { + spec.Plans = map[string]VSHNPlan{ + "Plans are currently not available": {}, + } + return + } + spec.Plans = plans +} diff --git a/apis/appcat/v1/appcat_types_test.go b/apis/appcat/v1/appcat_types_test.go new file mode 100644 index 0000000..70beec6 --- /dev/null +++ b/apis/appcat/v1/appcat_types_test.go @@ -0,0 +1,167 @@ +package v1 + +import ( + "testing" + + crossplanev1 "github.com/crossplane/crossplane/apis/apiextensions/v1" + "gotest.tools/v3/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestNewAppCatFromComposition(t *testing.T) { + tests := map[string]struct { + composition *crossplanev1.Composition + appCat *AppCat + }{ + "GivenNil_ThenNil": {}, + "GivenNoLabels_ThenNil": { + composition: &crossplanev1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Labels: nil, + }, + }, + }, + "GivenNonOfferedLabel_ThenNil": { + composition: &crossplanev1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + OfferedKey: "false", + }, + }, + }, + }, + "GivenMissingOfferedLabel_ThenNil": { + composition: &crossplanev1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "labelname": "labelvalue", + }, + }, + }, + }, + "GivenOfferedLabelWithAppCatAnnotations_ThenReturnAppCat": { + composition: &crossplanev1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + OfferedKey: OfferedValue, + }, + Annotations: map[string]string{ + PrefixAppCatKey + "/zone": "rma1", + "non-appcat-prefix" + "/displayname": "comp-1", + "non-appcat-prefix" + "/pippo": "value-23", + }, + Name: "comp-1", + }, + }, + appCat: &AppCat{ + ObjectMeta: metav1.ObjectMeta{ + Name: "comp-1", + }, + + Details: map[string]string{ + "zone": "rma1", + }, + + Status: AppCatStatus{ + CompositionName: "comp-1", + }, + }, + }, + "GivenWithPlans_ThenReturnAppCatWithPlans": { + composition: &crossplanev1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + OfferedKey: OfferedValue, + }, + Annotations: map[string]string{ + PrefixAppCatKey + "/plans": `{"standard-4": { "note": "test", "size": { "cpu": "900m", "disk": "40Gi", "enabled": true, "memory": "3776Mi" } } }`, + }, + Name: "comp-1", + }, + }, + appCat: &AppCat{ + ObjectMeta: metav1.ObjectMeta{ + Name: "comp-1", + }, + Details: Details{}, + Plans: map[string]VSHNPlan{ + "standard-4": { + Note: "test", + JSize: VSHNSize{ + CPU: "900m", + Disk: "40Gi", + Memory: "3776Mi", + }, + }, + }, + + Status: AppCatStatus{ + CompositionName: "comp-1", + }, + }, + }, + "GivenInvalidPlans_ThenReturnAppCatWithMessage": { + composition: &crossplanev1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + OfferedKey: OfferedValue, + }, + Annotations: map[string]string{ + PrefixAppCatKey + "/plans": "imnotajson", + }, + Name: "comp-1", + }, + }, + appCat: &AppCat{ + ObjectMeta: metav1.ObjectMeta{ + Name: "comp-1", + }, + + Details: Details{}, + + Plans: map[string]VSHNPlan{ + "Plans are currently not available": {}, + }, + + Status: AppCatStatus{ + CompositionName: "comp-1", + }, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + actualAppCat := NewAppCatFromComposition(tt.composition) + assert.DeepEqual(t, tt.appCat, actualAppCat) + }) + } +} + +func TestMakeCamelCase(t *testing.T) { + tests := map[string]struct { + input, output string + }{ + "GivenWrongK8sStrCase1_ThenCamelCaseStr": { + input: "-k8s-name-type-", + output: "k8sNameType", + }, + "GivenWrongK8sStrCase2_ThenCamelCaseStr": { + input: "::-k8s-name-.type/", + output: "k8sNameType", + }, + "GivenWrongK8sStrCase3_ThenCamelCaseStr": { + input: "-k8S-nA%me-tyPe%", + output: "k8sNameType", + }, + "GivenCorrectK8sStr_ThenCamelCaseStr": { + input: "k8s-name-type", + output: "k8sNameType", + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + actualStr := makeCamelCase(tt.input) + assert.Equal(t, tt.output, actualStr) + }) + } +} diff --git a/apis/appcat/v1/generated.pb.go b/apis/appcat/v1/generated.pb.go new file mode 100644 index 0000000..131e5ec --- /dev/null +++ b/apis/appcat/v1/generated.pb.go @@ -0,0 +1,3251 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: .work/tmp/github.com/vshn/appcat-apiserver/apis/appcat/v1/generated.proto + +package v1 + +import ( + fmt "fmt" + + io "io" + + proto "github.com/gogo/protobuf/proto" + github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys" + runtime "k8s.io/apimachinery/pkg/runtime" + + math "math" + math_bits "math/bits" + reflect "reflect" + strings "strings" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +func (m *AppCat) Reset() { *m = AppCat{} } +func (*AppCat) ProtoMessage() {} +func (*AppCat) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{0} +} +func (m *AppCat) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AppCat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *AppCat) XXX_Merge(src proto.Message) { + xxx_messageInfo_AppCat.Merge(m, src) +} +func (m *AppCat) XXX_Size() int { + return m.Size() +} +func (m *AppCat) XXX_DiscardUnknown() { + xxx_messageInfo_AppCat.DiscardUnknown(m) +} + +var xxx_messageInfo_AppCat proto.InternalMessageInfo + +func (m *AppCatList) Reset() { *m = AppCatList{} } +func (*AppCatList) ProtoMessage() {} +func (*AppCatList) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{1} +} +func (m *AppCatList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AppCatList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *AppCatList) XXX_Merge(src proto.Message) { + xxx_messageInfo_AppCatList.Merge(m, src) +} +func (m *AppCatList) XXX_Size() int { + return m.Size() +} +func (m *AppCatList) XXX_DiscardUnknown() { + xxx_messageInfo_AppCatList.DiscardUnknown(m) +} + +var xxx_messageInfo_AppCatList proto.InternalMessageInfo + +func (m *AppCatStatus) Reset() { *m = AppCatStatus{} } +func (*AppCatStatus) ProtoMessage() {} +func (*AppCatStatus) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{2} +} +func (m *AppCatStatus) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AppCatStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *AppCatStatus) XXX_Merge(src proto.Message) { + xxx_messageInfo_AppCatStatus.Merge(m, src) +} +func (m *AppCatStatus) XXX_Size() int { + return m.Size() +} +func (m *AppCatStatus) XXX_DiscardUnknown() { + xxx_messageInfo_AppCatStatus.DiscardUnknown(m) +} + +var xxx_messageInfo_AppCatStatus proto.InternalMessageInfo + +func (m *SGBackupInfo) Reset() { *m = SGBackupInfo{} } +func (*SGBackupInfo) ProtoMessage() {} +func (*SGBackupInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{3} +} +func (m *SGBackupInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SGBackupInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *SGBackupInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_SGBackupInfo.Merge(m, src) +} +func (m *SGBackupInfo) XXX_Size() int { + return m.Size() +} +func (m *SGBackupInfo) XXX_DiscardUnknown() { + xxx_messageInfo_SGBackupInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_SGBackupInfo proto.InternalMessageInfo + +func (m *VSHNPlan) Reset() { *m = VSHNPlan{} } +func (*VSHNPlan) ProtoMessage() {} +func (*VSHNPlan) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{4} +} +func (m *VSHNPlan) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *VSHNPlan) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *VSHNPlan) XXX_Merge(src proto.Message) { + xxx_messageInfo_VSHNPlan.Merge(m, src) +} +func (m *VSHNPlan) XXX_Size() int { + return m.Size() +} +func (m *VSHNPlan) XXX_DiscardUnknown() { + xxx_messageInfo_VSHNPlan.DiscardUnknown(m) +} + +var xxx_messageInfo_VSHNPlan proto.InternalMessageInfo + +func (m *VSHNPostgresBackup) Reset() { *m = VSHNPostgresBackup{} } +func (*VSHNPostgresBackup) ProtoMessage() {} +func (*VSHNPostgresBackup) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{5} +} +func (m *VSHNPostgresBackup) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *VSHNPostgresBackup) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *VSHNPostgresBackup) XXX_Merge(src proto.Message) { + xxx_messageInfo_VSHNPostgresBackup.Merge(m, src) +} +func (m *VSHNPostgresBackup) XXX_Size() int { + return m.Size() +} +func (m *VSHNPostgresBackup) XXX_DiscardUnknown() { + xxx_messageInfo_VSHNPostgresBackup.DiscardUnknown(m) +} + +var xxx_messageInfo_VSHNPostgresBackup proto.InternalMessageInfo + +func (m *VSHNPostgresBackupList) Reset() { *m = VSHNPostgresBackupList{} } +func (*VSHNPostgresBackupList) ProtoMessage() {} +func (*VSHNPostgresBackupList) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{6} +} +func (m *VSHNPostgresBackupList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *VSHNPostgresBackupList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *VSHNPostgresBackupList) XXX_Merge(src proto.Message) { + xxx_messageInfo_VSHNPostgresBackupList.Merge(m, src) +} +func (m *VSHNPostgresBackupList) XXX_Size() int { + return m.Size() +} +func (m *VSHNPostgresBackupList) XXX_DiscardUnknown() { + xxx_messageInfo_VSHNPostgresBackupList.DiscardUnknown(m) +} + +var xxx_messageInfo_VSHNPostgresBackupList proto.InternalMessageInfo + +func (m *VSHNPostgresBackupStatus) Reset() { *m = VSHNPostgresBackupStatus{} } +func (*VSHNPostgresBackupStatus) ProtoMessage() {} +func (*VSHNPostgresBackupStatus) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{7} +} +func (m *VSHNPostgresBackupStatus) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *VSHNPostgresBackupStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *VSHNPostgresBackupStatus) XXX_Merge(src proto.Message) { + xxx_messageInfo_VSHNPostgresBackupStatus.Merge(m, src) +} +func (m *VSHNPostgresBackupStatus) XXX_Size() int { + return m.Size() +} +func (m *VSHNPostgresBackupStatus) XXX_DiscardUnknown() { + xxx_messageInfo_VSHNPostgresBackupStatus.DiscardUnknown(m) +} + +var xxx_messageInfo_VSHNPostgresBackupStatus proto.InternalMessageInfo + +func (m *VSHNRedisBackup) Reset() { *m = VSHNRedisBackup{} } +func (*VSHNRedisBackup) ProtoMessage() {} +func (*VSHNRedisBackup) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{8} +} +func (m *VSHNRedisBackup) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *VSHNRedisBackup) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *VSHNRedisBackup) XXX_Merge(src proto.Message) { + xxx_messageInfo_VSHNRedisBackup.Merge(m, src) +} +func (m *VSHNRedisBackup) XXX_Size() int { + return m.Size() +} +func (m *VSHNRedisBackup) XXX_DiscardUnknown() { + xxx_messageInfo_VSHNRedisBackup.DiscardUnknown(m) +} + +var xxx_messageInfo_VSHNRedisBackup proto.InternalMessageInfo + +func (m *VSHNRedisBackupList) Reset() { *m = VSHNRedisBackupList{} } +func (*VSHNRedisBackupList) ProtoMessage() {} +func (*VSHNRedisBackupList) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{9} +} +func (m *VSHNRedisBackupList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *VSHNRedisBackupList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *VSHNRedisBackupList) XXX_Merge(src proto.Message) { + xxx_messageInfo_VSHNRedisBackupList.Merge(m, src) +} +func (m *VSHNRedisBackupList) XXX_Size() int { + return m.Size() +} +func (m *VSHNRedisBackupList) XXX_DiscardUnknown() { + xxx_messageInfo_VSHNRedisBackupList.DiscardUnknown(m) +} + +var xxx_messageInfo_VSHNRedisBackupList proto.InternalMessageInfo + +func (m *VSHNRedisBackupStatus) Reset() { *m = VSHNRedisBackupStatus{} } +func (*VSHNRedisBackupStatus) ProtoMessage() {} +func (*VSHNRedisBackupStatus) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{10} +} +func (m *VSHNRedisBackupStatus) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *VSHNRedisBackupStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *VSHNRedisBackupStatus) XXX_Merge(src proto.Message) { + xxx_messageInfo_VSHNRedisBackupStatus.Merge(m, src) +} +func (m *VSHNRedisBackupStatus) XXX_Size() int { + return m.Size() +} +func (m *VSHNRedisBackupStatus) XXX_DiscardUnknown() { + xxx_messageInfo_VSHNRedisBackupStatus.DiscardUnknown(m) +} + +var xxx_messageInfo_VSHNRedisBackupStatus proto.InternalMessageInfo + +func (m *VSHNSize) Reset() { *m = VSHNSize{} } +func (*VSHNSize) ProtoMessage() {} +func (*VSHNSize) Descriptor() ([]byte, []int) { + return fileDescriptor_7745f0f2a1a993fe, []int{11} +} +func (m *VSHNSize) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *VSHNSize) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *VSHNSize) XXX_Merge(src proto.Message) { + xxx_messageInfo_VSHNSize.Merge(m, src) +} +func (m *VSHNSize) XXX_Size() int { + return m.Size() +} +func (m *VSHNSize) XXX_DiscardUnknown() { + xxx_messageInfo_VSHNSize.DiscardUnknown(m) +} + +var xxx_messageInfo_VSHNSize proto.InternalMessageInfo + +func init() { + proto.RegisterType((*AppCat)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.AppCat") + proto.RegisterMapType((Details)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.AppCat.DetailsEntry") + proto.RegisterMapType((map[string]VSHNPlan)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.AppCat.PlansEntry") + proto.RegisterType((*AppCatList)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.AppCatList") + proto.RegisterType((*AppCatStatus)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.AppCatStatus") + proto.RegisterType((*SGBackupInfo)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.SGBackupInfo") + proto.RegisterType((*VSHNPlan)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNPlan") + proto.RegisterType((*VSHNPostgresBackup)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNPostgresBackup") + proto.RegisterType((*VSHNPostgresBackupList)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNPostgresBackupList") + proto.RegisterType((*VSHNPostgresBackupStatus)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNPostgresBackupStatus") + proto.RegisterType((*VSHNRedisBackup)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNRedisBackup") + proto.RegisterType((*VSHNRedisBackupList)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNRedisBackupList") + proto.RegisterType((*VSHNRedisBackupStatus)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNRedisBackupStatus") + proto.RegisterType((*VSHNSize)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNSize") +} + +func init() { + proto.RegisterFile(".work/tmp/github.com/vshn/appcat-apiserver/apis/appcat/v1/generated.proto", fileDescriptor_7745f0f2a1a993fe) +} + +var fileDescriptor_7745f0f2a1a993fe = []byte{ + // 951 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xdd, 0x6e, 0x1b, 0x45, + 0x14, 0xf6, 0xda, 0xce, 0x4f, 0x8f, 0x03, 0x49, 0x07, 0x0a, 0xc6, 0x12, 0x6e, 0xe4, 0x0b, 0x14, + 0x21, 0xba, 0xdb, 0x44, 0x51, 0x55, 0x0a, 0x08, 0x75, 0xe3, 0x8a, 0x06, 0xb5, 0x21, 0x4c, 0x1a, + 0x84, 0x10, 0xaa, 0x3a, 0xde, 0x9d, 0x3a, 0x53, 0x67, 0x77, 0x56, 0x3b, 0x63, 0x97, 0xf4, 0x0a, + 0x89, 0x17, 0xe0, 0x39, 0xb8, 0xe1, 0x19, 0xb8, 0x41, 0x91, 0xb8, 0xa9, 0xc4, 0x4d, 0x91, 0x50, + 0x45, 0xcc, 0x5b, 0x70, 0x85, 0x66, 0x67, 0xbc, 0x5e, 0xff, 0x11, 0xdb, 0x54, 0xb9, 0xdb, 0xf9, + 0xf9, 0xbe, 0xef, 0x9c, 0x33, 0xdf, 0x39, 0xb2, 0xc1, 0xb5, 0x9f, 0xf2, 0xb8, 0xe5, 0xc8, 0x20, + 0x72, 0x9a, 0x4c, 0x1e, 0xb5, 0x1b, 0xb6, 0xc7, 0x03, 0xa7, 0x23, 0x8e, 0x42, 0x87, 0x44, 0x91, + 0x47, 0xa4, 0xd3, 0xd9, 0x76, 0x48, 0xc4, 0x44, 0xba, 0xdc, 0x74, 0x9a, 0x34, 0xa4, 0x31, 0x91, + 0xd4, 0xb7, 0xa3, 0x98, 0x4b, 0x8e, 0x36, 0xfa, 0x48, 0x5b, 0x21, 0x6d, 0x7d, 0xd5, 0xee, 0x6c, + 0xdb, 0x0a, 0x99, 0x2e, 0x37, 0x2b, 0xd7, 0x32, 0x1a, 0x4d, 0xde, 0xe4, 0x4e, 0x42, 0xd0, 0x68, + 0x3f, 0x4e, 0x56, 0xc9, 0x22, 0xf9, 0xd2, 0xc4, 0x95, 0xed, 0xd6, 0x4d, 0x61, 0x33, 0xae, 0xf4, + 0x03, 0xe2, 0x1d, 0xb1, 0x90, 0xc6, 0x27, 0x4e, 0xd4, 0x6a, 0xea, 0x80, 0x02, 0x2a, 0xc9, 0x98, + 0x70, 0x2a, 0xce, 0x24, 0x54, 0xdc, 0x0e, 0x25, 0x0b, 0xe8, 0x08, 0xe0, 0xc6, 0x79, 0x00, 0xe1, + 0x1d, 0xd1, 0x80, 0x0c, 0xe3, 0x6a, 0xbf, 0x15, 0x61, 0xf1, 0x76, 0x14, 0xed, 0x10, 0x89, 0x1e, + 0xc1, 0xb2, 0x0a, 0xc7, 0x27, 0x92, 0x94, 0xad, 0x75, 0x6b, 0xa3, 0xb4, 0x75, 0xdd, 0xd6, 0xac, + 0x76, 0x96, 0xd5, 0x8e, 0x5a, 0x4d, 0x5d, 0x13, 0x75, 0xdb, 0xee, 0x6c, 0xda, 0x5f, 0x34, 0x9e, + 0x50, 0x4f, 0xde, 0xa7, 0x92, 0xb8, 0xe8, 0xf4, 0xe5, 0xd5, 0x5c, 0xf7, 0xe5, 0x55, 0xe8, 0xef, + 0xe1, 0x94, 0x15, 0x09, 0x58, 0xf2, 0xa9, 0x24, 0xec, 0x58, 0x94, 0xf3, 0xeb, 0x85, 0x8d, 0xd2, + 0xd6, 0x27, 0xf6, 0xb4, 0x65, 0xb7, 0x75, 0x90, 0x76, 0x5d, 0xe3, 0xef, 0x84, 0x32, 0x3e, 0x71, + 0x2b, 0x46, 0x6d, 0xc9, 0xec, 0xfe, 0xd3, 0xff, 0xc4, 0x3d, 0x25, 0xf4, 0x08, 0x16, 0xa2, 0x63, + 0x12, 0x8a, 0x72, 0x21, 0x91, 0xfc, 0x68, 0x66, 0xc9, 0x7d, 0x85, 0xd6, 0x82, 0xaf, 0x19, 0xc1, + 0x85, 0x64, 0x0f, 0x6b, 0x62, 0xf4, 0x10, 0x16, 0x85, 0x24, 0xb2, 0x2d, 0xca, 0xc5, 0xa4, 0x6c, + 0x37, 0x66, 0x95, 0x38, 0x48, 0xd0, 0xee, 0xeb, 0x86, 0x7d, 0x51, 0xaf, 0xb1, 0x61, 0xad, 0xdc, + 0x82, 0x95, 0x6c, 0xda, 0x68, 0x0d, 0x0a, 0x2d, 0x7a, 0x92, 0xbc, 0xd1, 0x25, 0xac, 0x3e, 0xd1, + 0x9b, 0xb0, 0xd0, 0x21, 0xc7, 0x6d, 0x5a, 0xce, 0x27, 0x7b, 0x7a, 0x71, 0x2b, 0x7f, 0xd3, 0xaa, + 0x1c, 0x03, 0xf4, 0xe3, 0x1f, 0x83, 0xbc, 0x9b, 0x45, 0x96, 0xb6, 0xb6, 0xa6, 0x0f, 0xfd, 0xab, + 0x83, 0xbb, 0x7b, 0x8a, 0x3a, 0xa3, 0x56, 0xfb, 0xc5, 0x02, 0xd0, 0x29, 0xdd, 0x63, 0x42, 0xa2, + 0x6f, 0x47, 0x1c, 0x65, 0x4f, 0xe7, 0x28, 0x85, 0x4e, 0xfc, 0xb4, 0x66, 0x4a, 0xb2, 0xdc, 0xdb, + 0xc9, 0xb8, 0xe9, 0x10, 0x16, 0x98, 0xa4, 0x41, 0xcf, 0x4b, 0xd7, 0x67, 0xad, 0x7a, 0xff, 0x35, + 0x77, 0x15, 0x0d, 0xd6, 0x6c, 0xb5, 0x2f, 0x61, 0x25, 0xfb, 0x2a, 0xe8, 0x36, 0xac, 0x7a, 0x3c, + 0x88, 0xb8, 0x60, 0x92, 0xf1, 0x70, 0x8f, 0x04, 0x54, 0xd7, 0xcf, 0x7d, 0xdb, 0xc0, 0x57, 0x77, + 0x06, 0x8f, 0xf1, 0xf0, 0xfd, 0xda, 0xaf, 0x79, 0x58, 0x39, 0xf8, 0xcc, 0x25, 0x5e, 0xab, 0x1d, + 0xed, 0x86, 0x8f, 0x39, 0xf2, 0x01, 0x78, 0xda, 0x20, 0xaf, 0xb4, 0xd9, 0x32, 0xbc, 0xe8, 0x6b, + 0x58, 0x8a, 0x62, 0xee, 0x51, 0x21, 0xcc, 0xeb, 0x5e, 0x9b, 0x28, 0x61, 0xa6, 0x84, 0x8d, 0xc9, + 0xd3, 0x3b, 0xdf, 0x49, 0x1a, 0x0a, 0xc6, 0x43, 0x77, 0xb5, 0xd7, 0x5e, 0xfb, 0x9a, 0x05, 0xf7, + 0xe8, 0x50, 0x07, 0x2e, 0x37, 0xd2, 0x6c, 0xe2, 0x80, 0xa8, 0x4c, 0xcb, 0x85, 0x79, 0x34, 0xde, + 0x31, 0x1a, 0x97, 0xdd, 0x61, 0x3e, 0x3c, 0x2a, 0x51, 0xfb, 0xc1, 0x82, 0xe5, 0x9e, 0xef, 0xd0, + 0x3a, 0x14, 0x43, 0x2e, 0x7b, 0xaf, 0xb1, 0x62, 0x88, 0x8a, 0x7b, 0x5c, 0x52, 0x9c, 0x9c, 0xa0, + 0x43, 0x28, 0x0a, 0xf6, 0x6c, 0x4e, 0x6f, 0x1f, 0xb0, 0x67, 0xb4, 0x6f, 0x91, 0xcf, 0xd5, 0x12, + 0x27, 0x74, 0xb5, 0xae, 0x05, 0x28, 0x89, 0x82, 0x0b, 0xd9, 0x8c, 0xa9, 0xd0, 0xa1, 0x5f, 0xc0, + 0xfc, 0x7c, 0x92, 0x0e, 0x1a, 0x9d, 0x91, 0x3b, 0x63, 0xb7, 0x0e, 0xc4, 0xfb, 0xdf, 0x43, 0xa7, + 0xf6, 0x87, 0x05, 0x6f, 0x8d, 0x82, 0x2e, 0xa0, 0xad, 0xc9, 0x60, 0x5b, 0x7f, 0xfc, 0x7f, 0x72, + 0x9c, 0xd0, 0xe2, 0x3f, 0xe5, 0xa1, 0x3c, 0xa9, 0x20, 0xe8, 0x41, 0xbf, 0x6b, 0xac, 0x79, 0x1c, + 0x5d, 0x1a, 0xdb, 0x31, 0xf1, 0xb8, 0x8e, 0x99, 0xab, 0x2b, 0xaf, 0x4c, 0xdb, 0x2d, 0xa8, 0x0e, + 0x6b, 0xaa, 0xa2, 0x0d, 0x22, 0xe8, 0x6e, 0x28, 0x24, 0x09, 0x3d, 0x9a, 0x34, 0xe9, 0x25, 0xb7, + 0x6c, 0xca, 0xb2, 0x56, 0x1f, 0x3a, 0xc7, 0x23, 0x88, 0xda, 0x9f, 0x16, 0xac, 0xaa, 0x62, 0x61, + 0xea, 0xb3, 0x8b, 0xb3, 0x7a, 0x73, 0xc8, 0xea, 0x9f, 0xce, 0x66, 0x83, 0x4c, 0xb0, 0xe7, 0xf8, + 0xfc, 0x77, 0x0b, 0xde, 0x18, 0x42, 0x5c, 0x80, 0xc9, 0x1f, 0x0e, 0x9a, 0xfc, 0xc3, 0xb9, 0xb3, + 0x9b, 0xe0, 0xf0, 0x9f, 0x2d, 0xb8, 0x32, 0xb6, 0x0e, 0xa8, 0x02, 0x79, 0xe6, 0x9b, 0x99, 0x09, + 0x06, 0x9b, 0xdf, 0xad, 0xe3, 0x3c, 0xf3, 0xd1, 0x3d, 0x28, 0xfa, 0x44, 0xf6, 0xe6, 0xe5, 0xfb, + 0xd3, 0xe5, 0xfb, 0x80, 0x05, 0xb4, 0x3f, 0x7d, 0xeb, 0x44, 0x4d, 0x5f, 0xc5, 0x82, 0x3e, 0x80, + 0x65, 0x36, 0x68, 0xbb, 0xb4, 0x22, 0xa9, 0xdd, 0xd2, 0x1b, 0x35, 0xa1, 0x27, 0xbb, 0x1a, 0xb3, + 0xe8, 0x5d, 0x28, 0x78, 0x51, 0xdb, 0x04, 0x59, 0x32, 0xa0, 0xc2, 0xce, 0xfe, 0x21, 0x56, 0xfb, + 0x6a, 0xf0, 0xfb, 0x4c, 0xb4, 0xf4, 0x8f, 0x9d, 0x8c, 0x34, 0x13, 0x2d, 0x9c, 0x9c, 0xa0, 0xf7, + 0x60, 0x31, 0xa0, 0x01, 0x8f, 0x4f, 0x8c, 0x70, 0xfa, 0xf8, 0xf7, 0x93, 0x5d, 0x6c, 0x4e, 0xdd, + 0xbd, 0xd3, 0xb3, 0x6a, 0xee, 0xf9, 0x59, 0x35, 0xf7, 0xe2, 0xac, 0x9a, 0xfb, 0xbe, 0x5b, 0xb5, + 0x4e, 0xbb, 0x55, 0xeb, 0x79, 0xb7, 0x6a, 0xbd, 0xe8, 0x56, 0xad, 0xbf, 0xba, 0x55, 0xeb, 0xc7, + 0xbf, 0xab, 0xb9, 0x6f, 0x36, 0xa6, 0xfd, 0x57, 0xf1, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x48, + 0x35, 0x98, 0x68, 0x8a, 0x0c, 0x00, 0x00, +} + +func (m *AppCat) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AppCat) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AppCat) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.Plans) > 0 { + keysForPlans := make([]string, 0, len(m.Plans)) + for k := range m.Plans { + keysForPlans = append(keysForPlans, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForPlans) + for iNdEx := len(keysForPlans) - 1; iNdEx >= 0; iNdEx-- { + v := m.Plans[string(keysForPlans[iNdEx])] + baseI := i + { + size, err := (&v).MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(keysForPlans[iNdEx]) + copy(dAtA[i:], keysForPlans[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForPlans[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x1a + } + } + if len(m.Details) > 0 { + keysForDetails := make([]string, 0, len(m.Details)) + for k := range m.Details { + keysForDetails = append(keysForDetails, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForDetails) + for iNdEx := len(keysForDetails) - 1; iNdEx >= 0; iNdEx-- { + v := m.Details[string(keysForDetails[iNdEx])] + baseI := i + i -= len(v) + copy(dAtA[i:], v) + i = encodeVarintGenerated(dAtA, i, uint64(len(v))) + i-- + dAtA[i] = 0x12 + i -= len(keysForDetails[iNdEx]) + copy(dAtA[i:], keysForDetails[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForDetails[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *AppCatList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AppCatList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AppCatList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *AppCatStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AppCatStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AppCatStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.CompositionName) + copy(dAtA[i:], m.CompositionName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.CompositionName))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *SGBackupInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SGBackupInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SGBackupInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.BackupInformation.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size, err := m.Process.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *VSHNPlan) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VSHNPlan) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *VSHNPlan) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.JSize.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(m.Note) + copy(dAtA[i:], m.Note) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Note))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *VSHNPostgresBackup) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VSHNPostgresBackup) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *VSHNPostgresBackup) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *VSHNPostgresBackupList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VSHNPostgresBackupList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *VSHNPostgresBackupList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *VSHNPostgresBackupStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VSHNPostgresBackupStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *VSHNPostgresBackupStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.DatabaseInstance) + copy(dAtA[i:], m.DatabaseInstance) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DatabaseInstance))) + i-- + dAtA[i] = 0x1a + if m.BackupInformation != nil { + { + size, err := m.BackupInformation.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.Process != nil { + { + size, err := m.Process.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *VSHNRedisBackup) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VSHNRedisBackup) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *VSHNRedisBackup) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Status.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ObjectMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *VSHNRedisBackupList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VSHNRedisBackupList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *VSHNRedisBackupList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Items) > 0 { + for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.ListMeta.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *VSHNRedisBackupStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VSHNRedisBackupStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *VSHNRedisBackupStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Instance) + copy(dAtA[i:], m.Instance) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Instance))) + i-- + dAtA[i] = 0x1a + { + size, err := m.Date.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(m.ID) + copy(dAtA[i:], m.ID) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.ID))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *VSHNSize) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *VSHNSize) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *VSHNSize) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Memory) + copy(dAtA[i:], m.Memory) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Memory))) + i-- + dAtA[i] = 0x1a + i -= len(m.Disk) + copy(dAtA[i:], m.Disk) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Disk))) + i-- + dAtA[i] = 0x12 + i -= len(m.CPU) + copy(dAtA[i:], m.CPU) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.CPU))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + offset -= sovGenerated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *AppCat) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Details) > 0 { + for k, v := range m.Details { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + len(v) + sovGenerated(uint64(len(v))) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + if len(m.Plans) > 0 { + for k, v := range m.Plans { + _ = k + _ = v + l = v.Size() + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *AppCatList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *AppCatStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.CompositionName) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *SGBackupInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Process.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.BackupInformation.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *VSHNPlan) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Note) + n += 1 + l + sovGenerated(uint64(l)) + l = m.JSize.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *VSHNPostgresBackup) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *VSHNPostgresBackupList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *VSHNPostgresBackupStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Process != nil { + l = m.Process.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.BackupInformation != nil { + l = m.BackupInformation.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.DatabaseInstance) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *VSHNRedisBackup) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *VSHNRedisBackupList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *VSHNRedisBackupStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ID) + n += 1 + l + sovGenerated(uint64(l)) + l = m.Date.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Instance) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *VSHNSize) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.CPU) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Disk) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Memory) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func sovGenerated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *AppCat) String() string { + if this == nil { + return "nil" + } + keysForDetails := make([]string, 0, len(this.Details)) + for k := range this.Details { + keysForDetails = append(keysForDetails, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForDetails) + mapStringForDetails := "Details{" + for _, k := range keysForDetails { + mapStringForDetails += fmt.Sprintf("%v: %v,", k, this.Details[k]) + } + mapStringForDetails += "}" + keysForPlans := make([]string, 0, len(this.Plans)) + for k := range this.Plans { + keysForPlans = append(keysForPlans, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForPlans) + mapStringForPlans := "map[string]VSHNPlan{" + for _, k := range keysForPlans { + mapStringForPlans += fmt.Sprintf("%v: %v,", k, this.Plans[k]) + } + mapStringForPlans += "}" + s := strings.Join([]string{`&AppCat{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Details:` + mapStringForDetails + `,`, + `Plans:` + mapStringForPlans + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "AppCatStatus", "AppCatStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *AppCatList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]AppCat{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "AppCat", "AppCat", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&AppCatList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *AppCatStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&AppCatStatus{`, + `CompositionName:` + fmt.Sprintf("%v", this.CompositionName) + `,`, + `}`, + }, "") + return s +} +func (this *SGBackupInfo) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&SGBackupInfo{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Process:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Process), "RawExtension", "runtime.RawExtension", 1), `&`, ``, 1) + `,`, + `BackupInformation:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.BackupInformation), "RawExtension", "runtime.RawExtension", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *VSHNPlan) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VSHNPlan{`, + `Note:` + fmt.Sprintf("%v", this.Note) + `,`, + `JSize:` + strings.Replace(strings.Replace(this.JSize.String(), "VSHNSize", "VSHNSize", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *VSHNPostgresBackup) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VSHNPostgresBackup{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "VSHNPostgresBackupStatus", "VSHNPostgresBackupStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *VSHNPostgresBackupList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]VSHNPostgresBackup{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "VSHNPostgresBackup", "VSHNPostgresBackup", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&VSHNPostgresBackupList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *VSHNPostgresBackupStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VSHNPostgresBackupStatus{`, + `Process:` + strings.Replace(fmt.Sprintf("%v", this.Process), "RawExtension", "runtime.RawExtension", 1) + `,`, + `BackupInformation:` + strings.Replace(fmt.Sprintf("%v", this.BackupInformation), "RawExtension", "runtime.RawExtension", 1) + `,`, + `DatabaseInstance:` + fmt.Sprintf("%v", this.DatabaseInstance) + `,`, + `}`, + }, "") + return s +} +func (this *VSHNRedisBackup) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VSHNRedisBackup{`, + `ObjectMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ObjectMeta), "ObjectMeta", "v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "VSHNRedisBackupStatus", "VSHNRedisBackupStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *VSHNRedisBackupList) String() string { + if this == nil { + return "nil" + } + repeatedStringForItems := "[]VSHNRedisBackup{" + for _, f := range this.Items { + repeatedStringForItems += strings.Replace(strings.Replace(f.String(), "VSHNRedisBackup", "VSHNRedisBackup", 1), `&`, ``, 1) + "," + } + repeatedStringForItems += "}" + s := strings.Join([]string{`&VSHNRedisBackupList{`, + `ListMeta:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.ListMeta), "ListMeta", "v1.ListMeta", 1), `&`, ``, 1) + `,`, + `Items:` + repeatedStringForItems + `,`, + `}`, + }, "") + return s +} +func (this *VSHNRedisBackupStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VSHNRedisBackupStatus{`, + `ID:` + fmt.Sprintf("%v", this.ID) + `,`, + `Date:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Date), "Time", "v1.Time", 1), `&`, ``, 1) + `,`, + `Instance:` + fmt.Sprintf("%v", this.Instance) + `,`, + `}`, + }, "") + return s +} +func (this *VSHNSize) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&VSHNSize{`, + `CPU:` + fmt.Sprintf("%v", this.CPU) + `,`, + `Disk:` + fmt.Sprintf("%v", this.Disk) + `,`, + `Memory:` + fmt.Sprintf("%v", this.Memory) + `,`, + `}`, + }, "") + return s +} +func valueToStringGenerated(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *AppCat) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AppCat: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AppCat: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Details", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Details == nil { + m.Details = make(Details) + } + var mapkey string + var mapvalue string + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Details[mapkey] = mapvalue + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Plans", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Plans == nil { + m.Plans = make(map[string]VSHNPlan) + } + var mapkey string + mapvalue := &VSHNPlan{} + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthGenerated + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &VSHNPlan{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Plans[mapkey] = *mapvalue + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AppCatList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AppCatList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AppCatList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, AppCat{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AppCatStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AppCatStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AppCatStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CompositionName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CompositionName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SGBackupInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SGBackupInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SGBackupInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Process", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Process.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BackupInformation", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.BackupInformation.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VSHNPlan) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VSHNPlan: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VSHNPlan: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Note", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Note = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field JSize", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.JSize.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VSHNPostgresBackup) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VSHNPostgresBackup: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VSHNPostgresBackup: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VSHNPostgresBackupList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VSHNPostgresBackupList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VSHNPostgresBackupList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, VSHNPostgresBackup{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VSHNPostgresBackupStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VSHNPostgresBackupStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VSHNPostgresBackupStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Process", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Process == nil { + m.Process = &runtime.RawExtension{} + } + if err := m.Process.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BackupInformation", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BackupInformation == nil { + m.BackupInformation = &runtime.RawExtension{} + } + if err := m.BackupInformation.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DatabaseInstance", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DatabaseInstance = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VSHNRedisBackup) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VSHNRedisBackup: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VSHNRedisBackup: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VSHNRedisBackupList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VSHNRedisBackupList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VSHNRedisBackupList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, VSHNRedisBackup{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VSHNRedisBackupStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VSHNRedisBackupStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VSHNRedisBackupStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ID = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Date", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Date.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Instance", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Instance = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VSHNSize) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: VSHNSize: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VSHNSize: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CPU", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CPU = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Disk", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Disk = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Memory", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Memory = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenerated(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenerated + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenerated + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenerated = fmt.Errorf("proto: unexpected end of group") +) diff --git a/apis/appcat/v1/register.go b/apis/appcat/v1/register.go new file mode 100644 index 0000000..0dabe4c --- /dev/null +++ b/apis/appcat/v1/register.go @@ -0,0 +1,41 @@ +// Package v1 contains API Schema definitions for the appcat-server v1 API group +// +kubebuilder:object:generate=true +// +kubebuilder:skip +// +groupName=api.appcat.vshn.io +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "api.appcat.vshn.io", Version: "v1"} + + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + SchemeBuilder.Register(addKnownTypes) +} + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(GroupVersion, + &AppCat{}, + &AppCatList{}, + &VSHNPostgresBackup{}, + &VSHNPostgresBackupList{}, + &VSHNRedisBackup{}, + &VSHNRedisBackupList{}, + ) + metav1.AddToGroupVersion(scheme, GroupVersion) + return nil +} + +func GetGroupResource(resource string) schema.GroupResource { + return schema.GroupResource{Group: GroupVersion.Group, Resource: resource} +} diff --git a/apis/appcat/v1/vshn_postgres_backup_types.go b/apis/appcat/v1/vshn_postgres_backup_types.go new file mode 100644 index 0000000..153c005 --- /dev/null +++ b/apis/appcat/v1/vshn_postgres_backup_types.go @@ -0,0 +1,139 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/apiserver-runtime/pkg/builder/resource" +) + +var ( + // ResourceBackup is the name of this backup resource in plural form + ResourceBackup = "vshnpostgresbackups" + + // Metadata holds field path name metadata + Metadata = "metadata" + // Status holds field path name status + Status = "status" + // Process holds field path name process + Process = "process" + // BackupInformation holds field path name backupInformation + BackupInformation = "backupInformation" + // Timing holds field path name timing + Timing = "timing" + // End holds field path name end + End = "end" +) + +// VSHNPostgreSQLName represents the name of a VSHNPostgreSQL +type VSHNPostgreSQLName string + +// VSHNPostgreSQLNamespace represents the namespace of a VSHNPostgreSQL +type VSHNPostgreSQLNamespace string + +// +kubebuilder:object:root=true + +// VSHNPostgresBackup defines VSHN managed PostgreSQL backups +type VSHNPostgresBackup struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Status holds the backup specific metadata. + Status VSHNPostgresBackupStatus `json:"status,omitempty"` +} + +// VSHNPostgresBackupStatus defines the desired state of VSHNPostgresBackup +type VSHNPostgresBackupStatus struct { + // Process holds status information of the backup process + Process *runtime.RawExtension `json:"process,omitempty"` + // BackupInformation holds specific backup information + BackupInformation *runtime.RawExtension `json:"backupInformation,omitempty"` + // DatabaseInstance is the database from which the backup has been done + DatabaseInstance string `json:"databaseInstance"` +} + +// VSHNPostgresBackup needs to implement the builder resource interface +var _ resource.Object = &VSHNPostgresBackup{} + +func (in *VSHNPostgresBackup) GetObjectMeta() *metav1.ObjectMeta { + return &in.ObjectMeta +} + +func (in *VSHNPostgresBackup) NamespaceScoped() bool { + return true +} + +func (in *VSHNPostgresBackup) New() runtime.Object { + return &VSHNPostgresBackup{} +} + +func (in *VSHNPostgresBackup) NewList() runtime.Object { + return &VSHNPostgresBackupList{} +} + +// GetGroupVersionResource returns the GroupVersionResource for this resource. +// The resource should be the all lowercase and pluralized kind +func (in *VSHNPostgresBackup) GetGroupVersionResource() schema.GroupVersionResource { + return schema.GroupVersionResource{ + Group: GroupVersion.Group, + Version: GroupVersion.Version, + Resource: "vshnpostgresbackups", + } +} + +// IsStorageVersion returns true if the object is also the internal version -- i.e. is the type defined for the API group or an alias to this object. +// If false, the resource is expected to implement MultiVersionObject interface. +func (in *VSHNPostgresBackup) IsStorageVersion() bool { + return true +} + +// +kubebuilder:object:root=true + +// VSHNPostgresBackupList defines a list of VSHNPostgresBackup +type VSHNPostgresBackupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []VSHNPostgresBackup `json:"items"` +} + +var _ resource.ObjectList = &VSHNPostgresBackupList{} + +func (in *VSHNPostgresBackupList) GetListMeta() *metav1.ListMeta { + return &in.ListMeta +} + +func New() *VSHNPostgresBackup { + return &VSHNPostgresBackup{} +} + +// SGBackupInfo holds necessary data for VSHNPostgresBackup +type SGBackupInfo struct { + metav1.ObjectMeta + Process runtime.RawExtension + BackupInformation runtime.RawExtension +} + +// NewVSHNPostgresBackup creates a new VSHNPostgresBackup out of a SGBackupInfo and a database instance +func NewVSHNPostgresBackup(backup *SGBackupInfo, db, originalNamespace string) *VSHNPostgresBackup { + if backup == nil || originalNamespace == "" || db == "" { + return nil + } + + vshnPostgresBackup := &VSHNPostgresBackup{ + ObjectMeta: backup.ObjectMeta, + } + + vshnPostgresBackup.Status.DatabaseInstance = db + vshnPostgresBackup.Namespace = originalNamespace + + if backup.Process.Object != nil { + vshnPostgresBackup.Status.Process = &backup.Process + } + + if backup.BackupInformation.Object != nil { + vshnPostgresBackup.Status.BackupInformation = &backup.BackupInformation + } + + return vshnPostgresBackup +} diff --git a/apis/appcat/v1/vshn_postgres_backup_types_test.go b/apis/appcat/v1/vshn_postgres_backup_types_test.go new file mode 100644 index 0000000..1147972 --- /dev/null +++ b/apis/appcat/v1/vshn_postgres_backup_types_test.go @@ -0,0 +1,113 @@ +package v1 + +import ( + "gotest.tools/v3/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "testing" +) + +func TestNewVSHNBackupFromBackInfo(t *testing.T) { + tests := map[string]struct { + db string + namespace string + backupInfo *SGBackupInfo + vshnPostgresBackup *VSHNPostgresBackup + }{ + "GivenNoBackupInfo_ThenNil": { + db: "db1", + namespace: "namespace", + }, + "GivenNoDB_ThenNil": { + backupInfo: &SGBackupInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "backup"}, + Process: *rawFromObject(getTestProcess()), + BackupInformation: *rawFromObject(getTestBI()), + }, + namespace: "namespace", + }, + "GivenNoNamespace_ThenNil": { + backupInfo: &SGBackupInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "backup"}, + Process: *rawFromObject(getTestProcess()), + BackupInformation: *rawFromObject(getTestBI()), + }, + db: "db1", + }, + "GivenBackupInfo_ThenVSHNBackup": { + db: "db1", + namespace: "namespace", + backupInfo: &SGBackupInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "backup"}, + Process: *rawFromObject(getTestProcess()), + BackupInformation: *rawFromObject(getTestBI()), + }, + vshnPostgresBackup: &VSHNPostgresBackup{ + ObjectMeta: metav1.ObjectMeta{Name: "backup", Namespace: "namespace"}, + Status: VSHNPostgresBackupStatus{ + DatabaseInstance: "db1", + Process: rawFromObject(getTestProcess()), + BackupInformation: rawFromObject(getTestBI()), + }, + }, + }, + "GivenBackupInfoWithNoProcess_ThenVSHNBackup": { + db: "db1", + namespace: "namespace", + backupInfo: &SGBackupInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "backup"}, + BackupInformation: *rawFromObject(getTestBI()), + }, + vshnPostgresBackup: &VSHNPostgresBackup{ + ObjectMeta: metav1.ObjectMeta{Name: "backup", Namespace: "namespace"}, + Status: VSHNPostgresBackupStatus{ + DatabaseInstance: "db1", + BackupInformation: rawFromObject(getTestBI()), + }, + }, + }, + "GivenBackupInfoWithNoBI_ThenVSHNBackup": { + db: "db1", + namespace: "namespace", + backupInfo: &SGBackupInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "backup"}, + Process: *rawFromObject(getTestProcess()), + }, + vshnPostgresBackup: &VSHNPostgresBackup{ + ObjectMeta: metav1.ObjectMeta{Name: "backup", Namespace: "namespace"}, + Status: VSHNPostgresBackupStatus{ + DatabaseInstance: "db1", + Process: rawFromObject(getTestProcess()), + }, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + actualBackup := NewVSHNPostgresBackup(tt.backupInfo, tt.db, tt.namespace) + assert.DeepEqual(t, tt.vshnPostgresBackup, actualBackup) + }) + } +} + +func getTestProcess() unstructured.Unstructured { + return unstructured.Unstructured{ + Object: map[string]interface{}{ + "status": "Completed", + }, + } +} + +func getTestBI() unstructured.Unstructured { + return unstructured.Unstructured{ + Object: map[string]interface{}{ + "memory": 1024, + "time": "45s", + }, + } +} + +func rawFromObject(object unstructured.Unstructured) *runtime.RawExtension { + return &runtime.RawExtension{Object: &object} +} diff --git a/apis/appcat/v1/vshn_redis_backup_types.go b/apis/appcat/v1/vshn_redis_backup_types.go new file mode 100644 index 0000000..a482156 --- /dev/null +++ b/apis/appcat/v1/vshn_redis_backup_types.go @@ -0,0 +1,72 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/apiserver-runtime/pkg/builder/resource" +) + +// VSHNRedisBackup needs to implement the builder resource interface +var _ resource.Object = &VSHNRedisBackup{} +var _ resource.ObjectList = &VSHNRedisBackupList{} + +// +kubebuilder:object:root=true + +type VSHNRedisBackup struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Status VSHNRedisBackupStatus `json:"status,omitempty"` +} + +type VSHNRedisBackupStatus struct { + ID string `json:"id,omitempty"` + Date metav1.Time `json:"date,omitempty"` + Instance string `json:"instance,omitempty"` +} + +// +kubebuilder:object:root=true + +type VSHNRedisBackupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []VSHNRedisBackup `json:"items,omitempty"` +} + +// GetGroupVersionResource returns the GroupVersionResource for this resource. +// The resource should be the all lowercase and pluralized kind +func (in *VSHNRedisBackup) GetGroupVersionResource() schema.GroupVersionResource { + return schema.GroupVersionResource{ + Group: GroupVersion.Group, + Version: GroupVersion.Version, + Resource: "vshnredisbackups", + } +} + +func (in *VSHNRedisBackup) GetObjectMeta() *metav1.ObjectMeta { + return &in.ObjectMeta +} + +// IsStorageVersion returns true if the object is also the internal version -- i.e. is the type defined for the API group or an alias to this object. +// If false, the resource is expected to implement MultiVersionObject interface. +func (in *VSHNRedisBackup) IsStorageVersion() bool { + return true +} + +func (in *VSHNRedisBackup) NamespaceScoped() bool { + return true +} + +func (in *VSHNRedisBackup) New() runtime.Object { + return &VSHNRedisBackup{} +} + +func (in *VSHNRedisBackup) NewList() runtime.Object { + return &VSHNRedisBackupList{} +} + +func (in *VSHNRedisBackupList) GetListMeta() *metav1.ListMeta { + return &in.ListMeta +} diff --git a/apis/appcat/v1/zz_generated.deepcopy.go b/apis/appcat/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000..3f57d98 --- /dev/null +++ b/apis/appcat/v1/zz_generated.deepcopy.go @@ -0,0 +1,324 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppCat) DeepCopyInto(out *AppCat) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Details != nil { + in, out := &in.Details, &out.Details + *out = make(Details, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Plans != nil { + in, out := &in.Plans, &out.Plans + *out = make(map[string]VSHNPlan, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppCat. +func (in *AppCat) DeepCopy() *AppCat { + if in == nil { + return nil + } + out := new(AppCat) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AppCat) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppCatList) DeepCopyInto(out *AppCatList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AppCat, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppCatList. +func (in *AppCatList) DeepCopy() *AppCatList { + if in == nil { + return nil + } + out := new(AppCatList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AppCatList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppCatStatus) DeepCopyInto(out *AppCatStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppCatStatus. +func (in *AppCatStatus) DeepCopy() *AppCatStatus { + if in == nil { + return nil + } + out := new(AppCatStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in Details) DeepCopyInto(out *Details) { + { + in := &in + *out = make(Details, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Details. +func (in Details) DeepCopy() Details { + if in == nil { + return nil + } + out := new(Details) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGBackupInfo) DeepCopyInto(out *SGBackupInfo) { + *out = *in + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Process.DeepCopyInto(&out.Process) + in.BackupInformation.DeepCopyInto(&out.BackupInformation) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGBackupInfo. +func (in *SGBackupInfo) DeepCopy() *SGBackupInfo { + if in == nil { + return nil + } + out := new(SGBackupInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPlan) DeepCopyInto(out *VSHNPlan) { + *out = *in + out.JSize = in.JSize +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPlan. +func (in *VSHNPlan) DeepCopy() *VSHNPlan { + if in == nil { + return nil + } + out := new(VSHNPlan) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgresBackup) DeepCopyInto(out *VSHNPostgresBackup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgresBackup. +func (in *VSHNPostgresBackup) DeepCopy() *VSHNPostgresBackup { + if in == nil { + return nil + } + out := new(VSHNPostgresBackup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VSHNPostgresBackup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgresBackupList) DeepCopyInto(out *VSHNPostgresBackupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VSHNPostgresBackup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgresBackupList. +func (in *VSHNPostgresBackupList) DeepCopy() *VSHNPostgresBackupList { + if in == nil { + return nil + } + out := new(VSHNPostgresBackupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VSHNPostgresBackupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgresBackupStatus) DeepCopyInto(out *VSHNPostgresBackupStatus) { + *out = *in + if in.Process != nil { + in, out := &in.Process, &out.Process + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } + if in.BackupInformation != nil { + in, out := &in.BackupInformation, &out.BackupInformation + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgresBackupStatus. +func (in *VSHNPostgresBackupStatus) DeepCopy() *VSHNPostgresBackupStatus { + if in == nil { + return nil + } + out := new(VSHNPostgresBackupStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNRedisBackup) DeepCopyInto(out *VSHNRedisBackup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNRedisBackup. +func (in *VSHNRedisBackup) DeepCopy() *VSHNRedisBackup { + if in == nil { + return nil + } + out := new(VSHNRedisBackup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VSHNRedisBackup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNRedisBackupList) DeepCopyInto(out *VSHNRedisBackupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VSHNRedisBackup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNRedisBackupList. +func (in *VSHNRedisBackupList) DeepCopy() *VSHNRedisBackupList { + if in == nil { + return nil + } + out := new(VSHNRedisBackupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VSHNRedisBackupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNRedisBackupStatus) DeepCopyInto(out *VSHNRedisBackupStatus) { + *out = *in + in.Date.DeepCopyInto(&out.Date) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNRedisBackupStatus. +func (in *VSHNRedisBackupStatus) DeepCopy() *VSHNRedisBackupStatus { + if in == nil { + return nil + } + out := new(VSHNRedisBackupStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNSize) DeepCopyInto(out *VSHNSize) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNSize. +func (in *VSHNSize) DeepCopy() *VSHNSize { + if in == nil { + return nil + } + out := new(VSHNSize) + in.DeepCopyInto(out) + return out +} diff --git a/apis/v1/conditions.go b/apis/v1/conditions.go new file mode 100644 index 0000000..9e913a7 --- /dev/null +++ b/apis/v1/conditions.go @@ -0,0 +1,48 @@ +package v1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// The reason we need a custom condition type is that metav1.Condition declares all fields as required. +// However, we have seen issues where Crossplane's properties in Conditions aren't all required and lead to Crossplane not being able to copy conditions. + +type Condition struct { + // +kubebuilder:validation:Optional + // +kubebuilder:validation: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])$` + // +kubebuilder:validation:MaxLength=316 + + // Type of condition. + Type string `json:"type,omitempty"` + + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Enum=True;False;Unknown + + // Status of the condition, one of True, False, Unknown. + Status metav1.ConditionStatus `json:"status,omitempty"` + + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Minimum=0 + + // ObservedGeneration represents the .metadata.generation that the condition was set based upon. + // For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=date-time + + // LastTransitionTime is the last time the condition transitioned from one status to another. + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` + + // +kubebuilder:validation:Optional + // +kubebuilder:validation:MaxLength=1024 + // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$` + + // Reason contains a programmatic identifier indicating the reason for the condition's last transition. + Reason string `json:"reason,omitempty"` + + // +kubebuilder:validation:Optional + // +kubebuilder:validation:MaxLength=32768 + + // Message is a human-readable message indicating details about the transition. + Message string `json:"message,omitempty"` +} diff --git a/apis/v1/groupversion_info.go b/apis/v1/groupversion_info.go new file mode 100644 index 0000000..f71dd72 --- /dev/null +++ b/apis/v1/groupversion_info.go @@ -0,0 +1,25 @@ +// +kubebuilder:object:generate=true +// +groupName=appcat.vshn.io +// +versionName=v1 + +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "appcat.vshn.io", Version: "v1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) + +func init() { + SchemeBuilder.Register(&ObjectBucket{}, &XObjectBucket{}) +} diff --git a/apis/v1/objectreference.go b/apis/v1/objectreference.go new file mode 100644 index 0000000..939097f --- /dev/null +++ b/apis/v1/objectreference.go @@ -0,0 +1,13 @@ +package v1 + +// We use a custom LocalObjectReference as the description of corev1.LocalObjectReference contains a todo + +// LocalObjectReference contains enough information to let you locate the +// referenced object inside the same namespace. +// +structType=atomic +type LocalObjectReference struct { + // Name of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` + Namespace string `json:"namespace,omitempty" protobuf:"bytes,2,opt,name=namespace"` +} diff --git a/apis/v1/objectstorage_types.go b/apis/v1/objectstorage_types.go new file mode 100644 index 0000000..2f5b2ff --- /dev/null +++ b/apis/v1/objectstorage_types.go @@ -0,0 +1,77 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +kubebuilder:object:root=true +// +kubebuilder:printcolumn:name="Bucket Name",type="string",JSONPath=".spec.parameters.bucketName" +// +kubebuilder:printcolumn:name="Region",type="string",JSONPath=".spec.parameters.region" + +// ObjectBucket is the API for creating S3 buckets. +type ObjectBucket struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ObjectBucketSpec `json:"spec"` + Status ObjectBucketStatus `json:"status,omitempty"` +} + +// ObjectBucketSpec defines the desired state of a ObjectBucket. +type ObjectBucketSpec struct { + Parameters ObjectBucketParameters `json:"parameters,omitempty"` + + // WriteConnectionSecretToRef references a secret to which the connection details will be written. + WriteConnectionSecretToRef LocalObjectReference `json:"writeConnectionSecretToRef,omitempty"` +} + +// ObjectBucketParameters are the configurable fields of a ObjectBucket. +type ObjectBucketParameters struct { + // +kubebuilder:validation:Required + + // BucketName is the name of the bucket to create. + // Cannot be changed after bucket is created. + // Name must be acceptable by the S3 protocol, which follows RFC 1123. + // Be aware that S3 providers may require a unique name across the platform or region. + BucketName string `json:"bucketName"` + + // +kubebuilder:validation:Required + + // Region is the name of the region where the bucket shall be created. + // The region must be available in the S3 endpoint. + Region string `json:"region"` +} + +// ObjectBucketStatus reflects the observed state of a ObjectBucket. +type ObjectBucketStatus struct { + // AccessUserConditions contains a copy of the claim's underlying user account conditions. + AccessUserConditions []Condition `json:"accessUserConditions,omitempty"` + // BucketConditions contains a copy of the claim's underlying bucket conditions. + BucketConditions []Condition `json:"bucketConditions,omitempty"` +} + +// +kubebuilder:object:generate=true +// +kubebuilder:object:root=true + +// XObjectBucket represents the internal composite of this claim +type XObjectBucket struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec XObjectBucketSpec `json:"spec"` + Status ObjectBucketStatus `json:"status,omitempty"` +} + +// XObjectBucketSpec defines the desired state of a ObjectBucket. +type XObjectBucketSpec struct { + Parameters ObjectBucketParameters `json:"parameters,omitempty"` + + // WriteConnectionSecretToRef references a secret to which the connection details will be written. + WriteConnectionSecretToRef NamespacedName `json:"writeConnectionSecretToRef,omitempty"` +} + +// NamespacedName describes an object reference by its name and namespace +type NamespacedName struct { + Namespace string `json:"namespace,omitempty"` + Name string `json:"name,omitempty"` +} diff --git a/apis/v1/zz_generated.deepcopy.go b/apis/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000..dbe78c3 --- /dev/null +++ b/apis/v1/zz_generated.deepcopy.go @@ -0,0 +1,188 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Condition) DeepCopyInto(out *Condition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. +func (in *Condition) DeepCopy() *Condition { + if in == nil { + return nil + } + out := new(Condition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalObjectReference. +func (in *LocalObjectReference) DeepCopy() *LocalObjectReference { + if in == nil { + return nil + } + out := new(LocalObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedName) DeepCopyInto(out *NamespacedName) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedName. +func (in *NamespacedName) DeepCopy() *NamespacedName { + if in == nil { + return nil + } + out := new(NamespacedName) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectBucket) DeepCopyInto(out *ObjectBucket) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectBucket. +func (in *ObjectBucket) DeepCopy() *ObjectBucket { + if in == nil { + return nil + } + out := new(ObjectBucket) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ObjectBucket) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectBucketParameters) DeepCopyInto(out *ObjectBucketParameters) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectBucketParameters. +func (in *ObjectBucketParameters) DeepCopy() *ObjectBucketParameters { + if in == nil { + return nil + } + out := new(ObjectBucketParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectBucketSpec) DeepCopyInto(out *ObjectBucketSpec) { + *out = *in + out.Parameters = in.Parameters + out.WriteConnectionSecretToRef = in.WriteConnectionSecretToRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectBucketSpec. +func (in *ObjectBucketSpec) DeepCopy() *ObjectBucketSpec { + if in == nil { + return nil + } + out := new(ObjectBucketSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectBucketStatus) DeepCopyInto(out *ObjectBucketStatus) { + *out = *in + if in.AccessUserConditions != nil { + in, out := &in.AccessUserConditions, &out.AccessUserConditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.BucketConditions != nil { + in, out := &in.BucketConditions, &out.BucketConditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectBucketStatus. +func (in *ObjectBucketStatus) DeepCopy() *ObjectBucketStatus { + if in == nil { + return nil + } + out := new(ObjectBucketStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *XObjectBucket) DeepCopyInto(out *XObjectBucket) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XObjectBucket. +func (in *XObjectBucket) DeepCopy() *XObjectBucket { + if in == nil { + return nil + } + out := new(XObjectBucket) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *XObjectBucket) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *XObjectBucketSpec) DeepCopyInto(out *XObjectBucketSpec) { + *out = *in + out.Parameters = in.Parameters + out.WriteConnectionSecretToRef = in.WriteConnectionSecretToRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XObjectBucketSpec. +func (in *XObjectBucketSpec) DeepCopy() *XObjectBucketSpec { + if in == nil { + return nil + } + out := new(XObjectBucketSpec) + in.DeepCopyInto(out) + return out +} diff --git a/apis/vshn/v1/common_types.go b/apis/vshn/v1/common_types.go new file mode 100644 index 0000000..b151edb --- /dev/null +++ b/apis/vshn/v1/common_types.go @@ -0,0 +1,84 @@ +package v1 + +// K8upBackupSpec specifies when a backup for redis should be triggered. +// It also contains the retention policy for the backup. +type K8upBackupSpec struct { + // +kubebuilder:validation:Pattern=^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))$ + Schedule string `json:"schedule,omitempty"` + + Retention K8upRetentionPolicy `json:"retention,omitempty"` +} + +// GetBackupSchedule returns the currently set schedule for this backup config +func (k *K8upBackupSpec) GetBackupSchedule() string { + return k.Schedule +} + +// SetBackupSchedule sets the schedule to the given value +func (k *K8upBackupSpec) SetBackupSchedule(schedule string) { + k.Schedule = schedule +} + +// K8upRetentionPolicy describes the retention configuration for a K8up backup. +type K8upRetentionPolicy struct { + KeepLast int `json:"keepLast,omitempty"` + KeepHourly int `json:"keepHourly,omitempty"` + // +kubebuilder:default=6 + KeepDaily int `json:"keepDaily,omitempty"` + KeepWeekly int `json:"keepWeekly,omitempty"` + KeepMonthly int `json:"keepMonthly,omitempty"` + KeepYearly int `json:"keepYearly,omitempty"` +} + +// K8upRestoreSpec contains restore specific parameters. +type K8upRestoreSpec struct { + + // ClaimName specifies the name of the instance you want to restore from. + // The claim has to be in the same namespace as this new instance. + ClaimName string `json:"claimName,omitempty"` + + // BackupName is the name of the specific backup you want to restore. + BackupName string `json:"backupName,omitempty"` +} + +type VSHNDBaaSServiceLevel string + +const ( + BestEffort VSHNDBaaSServiceLevel = "besteffort" + Guaranteed VSHNDBaaSServiceLevel = "guaranteed" +) + +// VSHNDBaaSMaintenanceScheduleSpec contains settings to control the maintenance of an instance. +type VSHNDBaaSMaintenanceScheduleSpec struct { + // +kubebuilder:validation:Enum=monday;tuesday;wednesday;thursday;friday;saturday;sunday + + // DayOfWeek specifies at which weekday the maintenance is held place. + // Allowed values are [monday, tuesday, wednesday, thursday, friday, saturday, sunday] + DayOfWeek string `json:"dayOfWeek,omitempty"` + + // +kubebuilder:validation:Pattern="^([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$" + + // TimeOfDay for installing updates in UTC. + // Format: "hh:mm:ss". + TimeOfDay string `json:"timeOfDay,omitempty"` +} + +// GetMaintenanceDayOfWeek returns the currently set day of week +func (n *VSHNDBaaSMaintenanceScheduleSpec) GetMaintenanceDayOfWeek() string { + return n.DayOfWeek +} + +// GetMaintenanceTimeOfDay returns the currently set time of day +func (n *VSHNDBaaSMaintenanceScheduleSpec) GetMaintenanceTimeOfDay() string { + return n.TimeOfDay +} + +// SetMaintenanceDayOfWeek sets the day of week to the given value +func (n *VSHNDBaaSMaintenanceScheduleSpec) SetMaintenanceDayOfWeek(dow string) { + n.DayOfWeek = dow +} + +// SetMaintenanceTimeOfDay sets the time of day to the given value +func (n *VSHNDBaaSMaintenanceScheduleSpec) SetMaintenanceTimeOfDay(tod string) { + n.TimeOfDay = tod +} diff --git a/apis/vshn/v1/dbaas_vshn_postgresql.go b/apis/vshn/v1/dbaas_vshn_postgresql.go new file mode 100644 index 0000000..1953372 --- /dev/null +++ b/apis/vshn/v1/dbaas_vshn_postgresql.go @@ -0,0 +1,327 @@ +package v1 + +import ( + alertmanagerv1alpha1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1alpha1" + apisv1 "github.com/vshn/appcat-apiserver/apis/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// Workaround to make nested defaulting work. +// kubebuilder is unable to set a {} default +//go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnpostgresqls.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.default={})" +//go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnpostgresqls.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.size.default={})" +//go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnpostgresqls.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.service.default={})" +//go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnpostgresqls.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.backup.default={})" +//go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnpostgresqls.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.maintenance.default={})" + +// +kubebuilder:object:root=true + +// VSHNPostgreSQL is the API for creating Postgresql clusters. +type VSHNPostgreSQL struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of a VSHNPostgreSQL. + Spec VSHNPostgreSQLSpec `json:"spec"` + + // Status reflects the observed state of a VSHNPostgreSQL. + Status VSHNPostgreSQLStatus `json:"status,omitempty"` +} + +// VSHNPostgreSQLSpec defines the desired state of a VSHNPostgreSQL. +type VSHNPostgreSQLSpec struct { + // Parameters are the configurable fields of a VSHNPostgreSQL. + Parameters VSHNPostgreSQLParameters `json:"parameters,omitempty"` + // WriteConnectionSecretToRef references a secret to which the connection details will be written. + WriteConnectionSecretToRef apisv1.LocalObjectReference `json:"writeConnectionSecretToRef,omitempty"` + + // ResourceRef contains a reference to the composite. + ResourceRef corev1.ObjectReference `json:"resourceRef,omitempty"` + + // CompositeDeletePolicy defines how the claim should behave if it's deleted. + // This field definition will be overwritten by crossplane again, once the XRD is applied to a cluster. + // It's added here so it can be marshalled correctly in third party operators or composition functions. + CompositeDeletePolicy string `json:"compositeDeletePolicy,omitempty"` +} + +// VSHNPostgreSQLParameters are the configurable fields of a VSHNPostgreSQL. +type VSHNPostgreSQLParameters struct { + // Service contains PostgreSQL DBaaS specific properties + Service VSHNPostgreSQLServiceSpec `json:"service,omitempty"` + + // Maintenance contains settings to control the maintenance of an instance. + Maintenance VSHNDBaaSMaintenanceScheduleSpec `json:"maintenance,omitempty"` + + // Size contains settings to control the sizing of a service. + Size VSHNDBaaSSizeSpec `json:"size,omitempty"` + + // Scheduling contains settings to control the scheduling of an instance. + Scheduling VSHNDBaaSSchedulingSpec `json:"scheduling,omitempty"` + + // Network contains any network related settings. + Network VSHNDBaaSNetworkSpec `json:"network,omitempty"` + + // Backup contains settings to control the backups of an instance. + Backup VSHNPostgreSQLBackup `json:"backup,omitempty"` + + // Restore contains settings to control the restore of an instance. + Restore VSHNPostgreSQLRestore `json:"restore,omitempty"` + + // Monitoring contains settings to control monitoring. + Monitoring VSHNPostgreSQLMonitoring `json:"monitoring,omitempty"` + + // Encryption contains settings to control the storage encryption of an instance. + Encryption VSHNPostgreSQLEncryption `json:"encryption,omitempty"` + + // UpdateStrategy indicates when updates to the instance spec will be applied. + UpdateStrategy VSHNPostgreSQLUpdateStrategy `json:"updateStrategy,omitempty"` + + // +kubebuilder:default=1 + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=3 + + // Instances configures the number of PostgreSQL instances for the cluster. + // Each instance contains one Postgres server. + // Out of all Postgres servers, one is elected as the primary, the rest remain as read-only replicas. + Instances int `json:"instances,omitempty"` + + // This section allows to configure Postgres replication mode and HA roles groups. + // + // The main replication group is implicit and contains the total number of instances less the sum of all instances in other replication groups. + Replication VSHNPostgreSQLReplicationStrategy `json:"replication,omitempty"` +} + +type VSHNPostgreSQLReplicationStrategy struct { + // +kubebuilder:validation:Enum="async";"sync";"strict-sync" + + // Mode defines the replication mode applied to the whole cluster. Possible values are: "async"(default), "sync", and "strict-sync" + // + // "async": When in asynchronous mode the cluster is allowed to lose some committed transactions. + // When the primary server fails or becomes unavailable for any other reason a sufficiently healthy standby will automatically be promoted to primary. + // Any transactions that have not been replicated to that standby remain in a “forked timeline” on the primary, and are effectively unrecoverable + // + // "sync": When in synchronous mode a standby will not be promoted unless it is certain that the standby contains all transactions that may have returned a successful commit status to client. + // This means that the system may be unavailable for writes even though some servers are available. + // + // "strict-sync": When it is absolutely necessary to guarantee that each write is stored durably on at least two nodes, use the strict synchronous mode. + // This mode prevents synchronous replication to be switched off on the primary when no synchronous standby candidates are available. + // As a downside, the primary will not be available for writes, blocking all client write requests until at least one synchronous replica comes up. + // + // NOTE: We recommend to always use three intances when setting the mode to "strict-sync". + Mode string `json:"mode,omitempty"` +} + +const VSHNPostgreSQLUpdateStrategyTypeImmediate = "Immediate" +const VSHNPostgreSQLUpdateStrategyTypeOnRestart = "OnRestart" + +// VSHNPostgreSQLUpdateStrategy indicates how and when updates to the instance spec will be applied. +type VSHNPostgreSQLUpdateStrategy struct { + // +kubebuilder:validation:Enum="Immediate";"OnRestart" + // +kubebuilder:default="Immediate" + + // Type indicates the type of the UpdateStrategy. Default is OnRestart. + // Possible enum values: + // - `"OnRestart"` indicates that the changes to the spec will only be applied once the instance is restarted by other means, most likely during maintenance. + // - `"Immediate"` indicates that update will be applied to the instance as soon as the spec changes. Please be aware that this might lead to short downtime. + Type string `json:"type,omitempty"` +} + +// VSHNPostgreSQLServiceSpec contains PostgreSQL DBaaS specific properties +type VSHNPostgreSQLServiceSpec struct { + // +kubebuilder:validation:Enum="12";"13";"14";"15" + // +kubebuilder:default="15" + + // MajorVersion contains supported version of PostgreSQL. + // Multiple versions are supported. The latest version "15" is the default version. + MajorVersion string `json:"majorVersion,omitempty"` + + // PGSettings contains additional PostgreSQL settings. + PostgreSQLSettings runtime.RawExtension `json:"pgSettings,omitempty"` + + // Extensions allow to enable/disable any of the supported + Extensions []VSHNDBaaSPostgresExtension `json:"extensions,omitempty"` + + // +kubebuilder:validation:Enum="besteffort";"guaranteed" + // +kubebuilder:default="besteffort" + + // ServiceLevel defines the service level of this service. Either Best Effort or Guaranteed Availability is allowed. + ServiceLevel VSHNDBaaSServiceLevel `json:"serviceLevel,omitempty"` +} + +// VSHNDBaaSPostgresExtension contains the name of a single extension. +type VSHNDBaaSPostgresExtension struct { + // Name is the name of the extension to enable. + // For an extensive list, please consult https://stackgres.io/doc/latest/intro/extensions/ + Name string `json:"name,omitempty"` +} + +// VSHNDBaaSSchedulingSpec contains settings to control the scheduling of an instance. +type VSHNDBaaSSchedulingSpec struct { + // NodeSelector is a selector which must match a node’s labels for the pod to be scheduled on that node + NodeSelector map[string]string `json:"nodeSelector,omitempty"` +} + +// VSHNDBaaSSizeSpec contains settings to control the sizing of a service. +type VSHNDBaaSSizeSpec struct { + // CPU defines the amount of Kubernetes CPUs for an instance. + CPU string `json:"cpu,omitempty"` + + // Memory defines the amount of memory in units of bytes for an instance. + Memory string `json:"memory,omitempty"` + + // Requests defines CPU and memory requests for an instance + Requests VSHNDBaaSSizeRequestsSpec `json:"requests,omitempty"` + + // Disk defines the amount of disk space for an instance. + Disk string `json:"disk,omitempty"` + + // Plan is the name of the resource plan that defines the compute resources. + Plan string `json:"plan,omitempty"` +} + +// VSHNDBaaSSizeRequestsSpec contains settings to control the resoure requests of a service. +type VSHNDBaaSSizeRequestsSpec struct { + // CPU defines the amount of Kubernetes CPUs for an instance. + CPU string `json:"cpu,omitempty"` + + // Memory defines the amount of memory in units of bytes for an instance. + Memory string `json:"memory,omitempty"` +} + +// VSHNDBaaSNetworkSpec contains any network related settings. +type VSHNDBaaSNetworkSpec struct { + // +kubebuilder:default={"0.0.0.0/0"} + + // IPFilter is a list of allowed IPv4 CIDR ranges that can access the service. + // If no IP Filter is set, you may not be able to reach the service. + // A value of `0.0.0.0/0` will open the service to all addresses on the public internet. + IPFilter []string `json:"ipFilter,omitempty"` + + // ServiceType defines the type of the service. + // Possible enum values: + // - `"ClusterIP"` indicates that the service is only reachable from within the cluster. + // - `"LoadBalancer"` indicates that the service is reachable from the public internet via dedicated Ipv4 address. + // +kubebuilder:default="ClusterIP" + // +kubebuilder:validation:Enum="ClusterIP";"LoadBalancer" + ServiceType string `json:"serviceType,omitempty"` +} + +type VSHNPostgreSQLBackup struct { + // +kubebuilder:validation:Pattern=^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))$ + Schedule string `json:"schedule,omitempty"` + + // +kubebuilder:validation:Pattern="^[1-9][0-9]*$" + // +kubebuilder:default=6 + // +kubebuilder:validation:XIntOrString + Retention int `json:"retention,omitempty"` + + // DeletionProtection will protect the instance from being deleted for the given retention time. + // This is enabled by default. + // +kubebuilder:default=true + DeletionProtection bool `json:"deletionProtection,omitempty"` + + // DeletionRetention specifies in days how long the instance should be kept after deletion. + // The default is keeping it one week. + // +kubebuilder:default=7 + DeletionRetention int `json:"deletionRetention,omitempty"` +} + +// GetBackupSchedule gets the currently set schedule +func (p *VSHNPostgreSQLBackup) GetBackupSchedule() string { + return p.Schedule +} + +// SetBackupSchedule sets the schedule to the given value +func (p *VSHNPostgreSQLBackup) SetBackupSchedule(schedule string) { + p.Schedule = schedule +} + +// VSHNPostgreSQLRestore contains restore specific parameters. +type VSHNPostgreSQLRestore struct { + + // ClaimName specifies the name of the instance you want to restore from. + // The claim has to be in the same namespace as this new instance. + ClaimName string `json:"claimName,omitempty"` + + // BackupName is the name of the specific backup you want to restore. + BackupName string `json:"backupName,omitempty"` + + // RecoveryTimeStamp an ISO 8601 date, that holds UTC date indicating at which point-in-time the database has to be restored. + // This is optional and if no PIT recovery is required, it can be left empty. + // +kubebuilder:validation:Pattern=`^(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)$` + RecoveryTimeStamp string `json:"recoveryTimeStamp,omitempty"` +} + +// VSHNPostgreSQLMonitoring contains settings to configure monitoring aspects of PostgreSQL +type VSHNPostgreSQLMonitoring struct { + // AlertmanagerConfigRef contains the name of the AlertmanagerConfig that should be copied over to the + // namespace of the PostgreSQL instance. + AlertmanagerConfigRef string `json:"alertmanagerConfigRef,omitempty"` + + // AlertmanagerConfigSecretRef contains the name of the secret that is used + // in the referenced AlertmanagerConfig + AlertmanagerConfigSecretRef string `json:"alertmanagerConfigSecretRef,omitempty"` + + // AlertmanagerConfigSpecTemplate takes an AlertmanagerConfigSpec object. + // This takes precedence over the AlertmanagerConfigRef. + AlertmanagerConfigSpecTemplate *alertmanagerv1alpha1.AlertmanagerConfigSpec `json:"alertmanagerConfigTemplate,omitempty"` + + // Email necessary to send alerts via email + Email string `json:"email,omitempty"` +} + +// VSHNPostgreSQLEncryption contains storage encryption specific parameters +type VSHNPostgreSQLEncryption struct { + + // Enabled specifies if the instance should use encrypted storage for the instance. + Enabled bool `json:"enabled,omitempty"` +} + +// VSHNPostgreSQLStatus reflects the observed state of a VSHNPostgreSQL. +type VSHNPostgreSQLStatus struct { + // InstanceNamespace contains the name of the namespace where the instance resides + InstanceNamespace string `json:"instanceNamespace,omitempty"` + // PostgreSQLConditions contains the status conditions of the backing object. + PostgreSQLConditions []apisv1.Condition `json:"postgresqlConditions,omitempty"` + NamespaceConditions []apisv1.Condition `json:"namespaceConditions,omitempty"` + ProfileConditions []apisv1.Condition `json:"profileConditions,omitempty"` + PGConfigConditions []apisv1.Condition `json:"pgconfigConditions,omitempty"` + PGClusterConditions []apisv1.Condition `json:"pgclusterConditions,omitempty"` + SecretsConditions []apisv1.Condition `json:"secretConditions,omitempty"` + ObjectBucketConditions []apisv1.Condition `json:"ObjectBucketConditions,omitempty"` + ObjectBackupConfigConditions []apisv1.Condition `json:"ObjectBackupConfigConditions,omitempty"` + NetworkPolicyConditions []apisv1.Condition `json:"networkPolicyConditions,omitempty"` + LocalCAConditions []apisv1.Condition `json:"localCAConditions,omitempty"` + CertificateConditions []apisv1.Condition `json:"certificateConditions,omitempty"` + // IsEOL indicates if this instance is using an EOL version of PostgreSQL. + IsEOL bool `json:"isEOL,omitempty"` +} + +// +kubebuilder:object:root=true + +// VSHNPostgreSQLList defines a list of VSHNPostgreSQL +type VSHNPostgreSQLList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []VSHNPostgreSQL `json:"items"` +} + +// +kubebuilder:object:generate=true +// +kubebuilder:object:root=true + +// XVSHNPostgreSQL represents the internal composite of this claim +type XVSHNPostgreSQL VSHNPostgreSQL + +// +kubebuilder:object:generate=true +// +kubebuilder:object:root=true + +// XVSHNPostgreSQLList represents a list of composites +type XVSHNPostgreSQLList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []XVSHNPostgreSQL `json:"items"` +} diff --git a/apis/vshn/v1/dbaas_vshn_redis.go b/apis/vshn/v1/dbaas_vshn_redis.go new file mode 100644 index 0000000..e9f6029 --- /dev/null +++ b/apis/vshn/v1/dbaas_vshn_redis.go @@ -0,0 +1,130 @@ +package v1 + +import ( + v1 "github.com/vshn/appcat-apiserver/apis/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Workaround to make nested defaulting work. +// kubebuilder is unable to set a {} default +//go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnredis.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.default={})" +//go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnredis.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.size.default={})" +//go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnredis.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.service.default={})" +//go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnredis.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.tls.default={})" +//go:generate yq -i e ../../generated/vshn.appcat.vshn.io_vshnredis.yaml --expression "with(.spec.versions[]; .schema.openAPIV3Schema.properties.spec.properties.parameters.properties.backup.default={})" + +// +kubebuilder:object:root=true + +// VSHNRedis is the API for creating Redis clusters. +type VSHNRedis struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of a VSHNRedis. + Spec VSHNRedisSpec `json:"spec"` + + // Status reflects the observed state of a VSHNRedis. + Status VSHNRedisStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:generate=true +// +kubebuilder:object:root=true +type VSHNRedisList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []VSHNRedis `json:"items,omitempty"` +} + +// VSHNRedisSpec defines the desired state of a VSHNRedis. +type VSHNRedisSpec struct { + // Parameters are the configurable fields of a VSHNRedis. + Parameters VSHNRedisParameters `json:"parameters,omitempty"` + + // WriteConnectionSecretToRef references a secret to which the connection details will be written. + WriteConnectionSecretToRef v1.LocalObjectReference `json:"writeConnectionSecretToRef,omitempty"` +} + +// VSHNRedisParameters are the configurable fields of a VSHNRedis. +type VSHNRedisParameters struct { + // Service contains Redis DBaaS specific properties + Service VSHNRedisServiceSpec `json:"service,omitempty"` + + // Size contains settings to control the sizing of a service. + Size VSHNRedisSizeSpec `json:"size,omitempty"` + + // Scheduling contains settings to control the scheduling of an instance. + Scheduling VSHNDBaaSSchedulingSpec `json:"scheduling,omitempty"` + + // TLS contains settings to control tls traffic of a service. + TLS VSHNRedisTLSSpec `json:"tls,omitempty"` + + // Backup contains settings to control how the instance should get backed up. + Backup K8upBackupSpec `json:"backup,omitempty"` + + // Restore contains settings to control the restore of an instance. + Restore K8upRestoreSpec `json:"restore,omitempty"` + + // Maintenance contains settings to control the maintenance of an instance. + Maintenance VSHNDBaaSMaintenanceScheduleSpec `json:"maintenance,omitempty"` +} + +// VSHNRedisServiceSpec contains Redis DBaaS specific properties +type VSHNRedisServiceSpec struct { + // +kubebuilder:validation:Enum="6.2";"7.0" + // +kubebuilder:default="7.0" + + // Version contains supported version of Redis. + // Multiple versions are supported. The latest version "7.0" is the default version. + Version string `json:"version,omitempty"` + + // RedisSettings contains additional Redis settings. + RedisSettings string `json:"redisSettings,omitempty"` +} + +// VSHNRedisSizeSpec contains settings to control the sizing of a service. +type VSHNRedisSizeSpec struct { + + // CPURequests defines the requests amount of Kubernetes CPUs for an instance. + CPURequests string `json:"cpuRequests,omitempty"` + + // CPULimits defines the limits amount of Kubernetes CPUs for an instance. + CPULimits string `json:"cpuLimits,omitempty"` + + // MemoryRequests defines the requests amount of memory in units of bytes for an instance. + MemoryRequests string `json:"memoryRequests,omitempty"` + + // MemoryLimits defines the limits amount of memory in units of bytes for an instance. + MemoryLimits string `json:"memoryLimits,omitempty"` + + // Disk defines the amount of disk space for an instance. + Disk string `json:"disk,omitempty"` + + // Plan is the name of the resource plan that defines the compute resources. + Plan string `json:"plan,omitempty"` +} + +// VSHNRedisTLSSpec contains settings to control tls traffic of a service. +type VSHNRedisTLSSpec struct { + // +kubebuilder:default=true + + // TLSEnabled enables TLS traffic for the service + TLSEnabled bool `json:"enabled,omitempty"` + + // +kubebuilder:default=true + // TLSAuthClients enables client authentication requirement + TLSAuthClients bool `json:"authClients,omitempty"` +} + +// VSHNRedisStatus reflects the observed state of a VSHNRedis. +type VSHNRedisStatus struct { + // RedisConditions contains the status conditions of the backing object. + NamespaceConditions []v1.Condition `json:"namespaceConditions,omitempty"` + SelfSignedIssuerConditions []v1.Condition `json:"selfSignedIssuerConditions,omitempty"` + LocalCAConditions []v1.Condition `json:"localCAConditions,omitempty"` + CaCertificateConditions []v1.Condition `json:"caCertificateConditions,omitempty"` + ServerCertificateConditions []v1.Condition `json:"serverCertificateConditions,omitempty"` + ClientCertificateConditions []v1.Condition `json:"clientCertificateConditions,omitempty"` + // InstanceNamespace contains the name of the namespace where the instance resides + InstanceNamespace string `json:"instanceNamespace,omitempty"` +} diff --git a/apis/vshn/v1/groupversion_info.go b/apis/vshn/v1/groupversion_info.go new file mode 100644 index 0000000..a574426 --- /dev/null +++ b/apis/vshn/v1/groupversion_info.go @@ -0,0 +1,32 @@ +// +kubebuilder:object:generate=true +// +groupName=vshn.appcat.vshn.io +// +versionName=v1 + +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "vshn.appcat.vshn.io", Version: "v1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) + +func init() { + SchemeBuilder.Register( + &VSHNPostgreSQL{}, + &VSHNPostgreSQLList{}, + &XVSHNPostgreSQL{}, + &XVSHNPostgreSQLList{}, + &VSHNRedis{}, + &VSHNRedisList{}, + ) +} diff --git a/apis/vshn/v1/zz_generated.deepcopy.go b/apis/vshn/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000..d1e51f0 --- /dev/null +++ b/apis/vshn/v1/zz_generated.deepcopy.go @@ -0,0 +1,731 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1alpha1" + apisv1 "github.com/vshn/appcat-apiserver/apis/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *K8upBackupSpec) DeepCopyInto(out *K8upBackupSpec) { + *out = *in + out.Retention = in.Retention +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8upBackupSpec. +func (in *K8upBackupSpec) DeepCopy() *K8upBackupSpec { + if in == nil { + return nil + } + out := new(K8upBackupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *K8upRestoreSpec) DeepCopyInto(out *K8upRestoreSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8upRestoreSpec. +func (in *K8upRestoreSpec) DeepCopy() *K8upRestoreSpec { + if in == nil { + return nil + } + out := new(K8upRestoreSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *K8upRetentionPolicy) DeepCopyInto(out *K8upRetentionPolicy) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8upRetentionPolicy. +func (in *K8upRetentionPolicy) DeepCopy() *K8upRetentionPolicy { + if in == nil { + return nil + } + out := new(K8upRetentionPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNDBaaSMaintenanceScheduleSpec) DeepCopyInto(out *VSHNDBaaSMaintenanceScheduleSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNDBaaSMaintenanceScheduleSpec. +func (in *VSHNDBaaSMaintenanceScheduleSpec) DeepCopy() *VSHNDBaaSMaintenanceScheduleSpec { + if in == nil { + return nil + } + out := new(VSHNDBaaSMaintenanceScheduleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNDBaaSNetworkSpec) DeepCopyInto(out *VSHNDBaaSNetworkSpec) { + *out = *in + if in.IPFilter != nil { + in, out := &in.IPFilter, &out.IPFilter + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNDBaaSNetworkSpec. +func (in *VSHNDBaaSNetworkSpec) DeepCopy() *VSHNDBaaSNetworkSpec { + if in == nil { + return nil + } + out := new(VSHNDBaaSNetworkSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNDBaaSPostgresExtension) DeepCopyInto(out *VSHNDBaaSPostgresExtension) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNDBaaSPostgresExtension. +func (in *VSHNDBaaSPostgresExtension) DeepCopy() *VSHNDBaaSPostgresExtension { + if in == nil { + return nil + } + out := new(VSHNDBaaSPostgresExtension) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNDBaaSSchedulingSpec) DeepCopyInto(out *VSHNDBaaSSchedulingSpec) { + *out = *in + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNDBaaSSchedulingSpec. +func (in *VSHNDBaaSSchedulingSpec) DeepCopy() *VSHNDBaaSSchedulingSpec { + if in == nil { + return nil + } + out := new(VSHNDBaaSSchedulingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNDBaaSSizeRequestsSpec) DeepCopyInto(out *VSHNDBaaSSizeRequestsSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNDBaaSSizeRequestsSpec. +func (in *VSHNDBaaSSizeRequestsSpec) DeepCopy() *VSHNDBaaSSizeRequestsSpec { + if in == nil { + return nil + } + out := new(VSHNDBaaSSizeRequestsSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNDBaaSSizeSpec) DeepCopyInto(out *VSHNDBaaSSizeSpec) { + *out = *in + out.Requests = in.Requests +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNDBaaSSizeSpec. +func (in *VSHNDBaaSSizeSpec) DeepCopy() *VSHNDBaaSSizeSpec { + if in == nil { + return nil + } + out := new(VSHNDBaaSSizeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQL) DeepCopyInto(out *VSHNPostgreSQL) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQL. +func (in *VSHNPostgreSQL) DeepCopy() *VSHNPostgreSQL { + if in == nil { + return nil + } + out := new(VSHNPostgreSQL) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VSHNPostgreSQL) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQLBackup) DeepCopyInto(out *VSHNPostgreSQLBackup) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQLBackup. +func (in *VSHNPostgreSQLBackup) DeepCopy() *VSHNPostgreSQLBackup { + if in == nil { + return nil + } + out := new(VSHNPostgreSQLBackup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQLEncryption) DeepCopyInto(out *VSHNPostgreSQLEncryption) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQLEncryption. +func (in *VSHNPostgreSQLEncryption) DeepCopy() *VSHNPostgreSQLEncryption { + if in == nil { + return nil + } + out := new(VSHNPostgreSQLEncryption) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQLList) DeepCopyInto(out *VSHNPostgreSQLList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VSHNPostgreSQL, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQLList. +func (in *VSHNPostgreSQLList) DeepCopy() *VSHNPostgreSQLList { + if in == nil { + return nil + } + out := new(VSHNPostgreSQLList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VSHNPostgreSQLList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQLMonitoring) DeepCopyInto(out *VSHNPostgreSQLMonitoring) { + *out = *in + if in.AlertmanagerConfigSpecTemplate != nil { + in, out := &in.AlertmanagerConfigSpecTemplate, &out.AlertmanagerConfigSpecTemplate + *out = new(v1alpha1.AlertmanagerConfigSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQLMonitoring. +func (in *VSHNPostgreSQLMonitoring) DeepCopy() *VSHNPostgreSQLMonitoring { + if in == nil { + return nil + } + out := new(VSHNPostgreSQLMonitoring) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQLParameters) DeepCopyInto(out *VSHNPostgreSQLParameters) { + *out = *in + in.Service.DeepCopyInto(&out.Service) + out.Maintenance = in.Maintenance + out.Size = in.Size + in.Scheduling.DeepCopyInto(&out.Scheduling) + in.Network.DeepCopyInto(&out.Network) + out.Backup = in.Backup + out.Restore = in.Restore + in.Monitoring.DeepCopyInto(&out.Monitoring) + out.Encryption = in.Encryption + out.UpdateStrategy = in.UpdateStrategy + out.Replication = in.Replication +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQLParameters. +func (in *VSHNPostgreSQLParameters) DeepCopy() *VSHNPostgreSQLParameters { + if in == nil { + return nil + } + out := new(VSHNPostgreSQLParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQLReplicationStrategy) DeepCopyInto(out *VSHNPostgreSQLReplicationStrategy) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQLReplicationStrategy. +func (in *VSHNPostgreSQLReplicationStrategy) DeepCopy() *VSHNPostgreSQLReplicationStrategy { + if in == nil { + return nil + } + out := new(VSHNPostgreSQLReplicationStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQLRestore) DeepCopyInto(out *VSHNPostgreSQLRestore) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQLRestore. +func (in *VSHNPostgreSQLRestore) DeepCopy() *VSHNPostgreSQLRestore { + if in == nil { + return nil + } + out := new(VSHNPostgreSQLRestore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQLServiceSpec) DeepCopyInto(out *VSHNPostgreSQLServiceSpec) { + *out = *in + in.PostgreSQLSettings.DeepCopyInto(&out.PostgreSQLSettings) + if in.Extensions != nil { + in, out := &in.Extensions, &out.Extensions + *out = make([]VSHNDBaaSPostgresExtension, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQLServiceSpec. +func (in *VSHNPostgreSQLServiceSpec) DeepCopy() *VSHNPostgreSQLServiceSpec { + if in == nil { + return nil + } + out := new(VSHNPostgreSQLServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQLSpec) DeepCopyInto(out *VSHNPostgreSQLSpec) { + *out = *in + in.Parameters.DeepCopyInto(&out.Parameters) + out.WriteConnectionSecretToRef = in.WriteConnectionSecretToRef + out.ResourceRef = in.ResourceRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQLSpec. +func (in *VSHNPostgreSQLSpec) DeepCopy() *VSHNPostgreSQLSpec { + if in == nil { + return nil + } + out := new(VSHNPostgreSQLSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQLStatus) DeepCopyInto(out *VSHNPostgreSQLStatus) { + *out = *in + if in.PostgreSQLConditions != nil { + in, out := &in.PostgreSQLConditions, &out.PostgreSQLConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NamespaceConditions != nil { + in, out := &in.NamespaceConditions, &out.NamespaceConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ProfileConditions != nil { + in, out := &in.ProfileConditions, &out.ProfileConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.PGConfigConditions != nil { + in, out := &in.PGConfigConditions, &out.PGConfigConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.PGClusterConditions != nil { + in, out := &in.PGClusterConditions, &out.PGClusterConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SecretsConditions != nil { + in, out := &in.SecretsConditions, &out.SecretsConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ObjectBucketConditions != nil { + in, out := &in.ObjectBucketConditions, &out.ObjectBucketConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ObjectBackupConfigConditions != nil { + in, out := &in.ObjectBackupConfigConditions, &out.ObjectBackupConfigConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NetworkPolicyConditions != nil { + in, out := &in.NetworkPolicyConditions, &out.NetworkPolicyConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.LocalCAConditions != nil { + in, out := &in.LocalCAConditions, &out.LocalCAConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.CertificateConditions != nil { + in, out := &in.CertificateConditions, &out.CertificateConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQLStatus. +func (in *VSHNPostgreSQLStatus) DeepCopy() *VSHNPostgreSQLStatus { + if in == nil { + return nil + } + out := new(VSHNPostgreSQLStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNPostgreSQLUpdateStrategy) DeepCopyInto(out *VSHNPostgreSQLUpdateStrategy) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNPostgreSQLUpdateStrategy. +func (in *VSHNPostgreSQLUpdateStrategy) DeepCopy() *VSHNPostgreSQLUpdateStrategy { + if in == nil { + return nil + } + out := new(VSHNPostgreSQLUpdateStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNRedis) DeepCopyInto(out *VSHNRedis) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNRedis. +func (in *VSHNRedis) DeepCopy() *VSHNRedis { + if in == nil { + return nil + } + out := new(VSHNRedis) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VSHNRedis) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNRedisList) DeepCopyInto(out *VSHNRedisList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VSHNRedis, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNRedisList. +func (in *VSHNRedisList) DeepCopy() *VSHNRedisList { + if in == nil { + return nil + } + out := new(VSHNRedisList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VSHNRedisList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNRedisParameters) DeepCopyInto(out *VSHNRedisParameters) { + *out = *in + out.Service = in.Service + out.Size = in.Size + in.Scheduling.DeepCopyInto(&out.Scheduling) + out.TLS = in.TLS + out.Backup = in.Backup + out.Restore = in.Restore + out.Maintenance = in.Maintenance +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNRedisParameters. +func (in *VSHNRedisParameters) DeepCopy() *VSHNRedisParameters { + if in == nil { + return nil + } + out := new(VSHNRedisParameters) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNRedisServiceSpec) DeepCopyInto(out *VSHNRedisServiceSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNRedisServiceSpec. +func (in *VSHNRedisServiceSpec) DeepCopy() *VSHNRedisServiceSpec { + if in == nil { + return nil + } + out := new(VSHNRedisServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNRedisSizeSpec) DeepCopyInto(out *VSHNRedisSizeSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNRedisSizeSpec. +func (in *VSHNRedisSizeSpec) DeepCopy() *VSHNRedisSizeSpec { + if in == nil { + return nil + } + out := new(VSHNRedisSizeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNRedisSpec) DeepCopyInto(out *VSHNRedisSpec) { + *out = *in + in.Parameters.DeepCopyInto(&out.Parameters) + out.WriteConnectionSecretToRef = in.WriteConnectionSecretToRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNRedisSpec. +func (in *VSHNRedisSpec) DeepCopy() *VSHNRedisSpec { + if in == nil { + return nil + } + out := new(VSHNRedisSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNRedisStatus) DeepCopyInto(out *VSHNRedisStatus) { + *out = *in + if in.NamespaceConditions != nil { + in, out := &in.NamespaceConditions, &out.NamespaceConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SelfSignedIssuerConditions != nil { + in, out := &in.SelfSignedIssuerConditions, &out.SelfSignedIssuerConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.LocalCAConditions != nil { + in, out := &in.LocalCAConditions, &out.LocalCAConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.CaCertificateConditions != nil { + in, out := &in.CaCertificateConditions, &out.CaCertificateConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ServerCertificateConditions != nil { + in, out := &in.ServerCertificateConditions, &out.ServerCertificateConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ClientCertificateConditions != nil { + in, out := &in.ClientCertificateConditions, &out.ClientCertificateConditions + *out = make([]apisv1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNRedisStatus. +func (in *VSHNRedisStatus) DeepCopy() *VSHNRedisStatus { + if in == nil { + return nil + } + out := new(VSHNRedisStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNRedisTLSSpec) DeepCopyInto(out *VSHNRedisTLSSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNRedisTLSSpec. +func (in *VSHNRedisTLSSpec) DeepCopy() *VSHNRedisTLSSpec { + if in == nil { + return nil + } + out := new(VSHNRedisTLSSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *XVSHNPostgreSQL) DeepCopyInto(out *XVSHNPostgreSQL) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XVSHNPostgreSQL. +func (in *XVSHNPostgreSQL) DeepCopy() *XVSHNPostgreSQL { + if in == nil { + return nil + } + out := new(XVSHNPostgreSQL) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *XVSHNPostgreSQL) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *XVSHNPostgreSQLList) DeepCopyInto(out *XVSHNPostgreSQLList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]XVSHNPostgreSQL, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new XVSHNPostgreSQLList. +func (in *XVSHNPostgreSQLList) DeepCopy() *XVSHNPostgreSQLList { + if in == nil { + return nil + } + out := new(XVSHNPostgreSQLList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *XVSHNPostgreSQLList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/cmd/apiserver.go b/cmd/apiserver.go new file mode 100644 index 0000000..c93aabd --- /dev/null +++ b/cmd/apiserver.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "log" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + appcatv1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "github.com/vshn/appcat-apiserver/pkg/apiserver/appcat" + vshnpostgres "github.com/vshn/appcat-apiserver/pkg/apiserver/vshn/postgres" + vshnredis "github.com/vshn/appcat-apiserver/pkg/apiserver/vshn/redis" + "sigs.k8s.io/apiserver-runtime/pkg/builder" +) + +var apiServerCMDStr = "apiserver" + +var APIServerCMD = newAPIServerCMD() + +func newAPIServerCMD() *cobra.Command { + + viper.AutomaticEnv() + + var appcatEnabled, vshnPGBackupsEnabled, vshnRedisBackupsEnabled bool + + if len(os.Args) < 2 { + return &cobra.Command{} + } + + if os.Args[1] == apiServerCMDStr { + appcatEnabled = viper.GetBool("APPCAT_HANDLER_ENABLED") + vshnPGBackupsEnabled = viper.GetBool("VSHN_POSTGRES_BACKUP_HANDLER_ENABLED") + vshnRedisBackupsEnabled = viper.GetBool("VSHN_REDIS_BACKUP_HANDLER_ENABLED") + if !appcatEnabled && !vshnPGBackupsEnabled && !vshnRedisBackupsEnabled { + log.Fatal("Handlers are not enabled, please set at least one of APPCAT_HANDLER_ENABLED | VSHN_POSTGRES_BACKUP_HANDLER_ENABLED | VSHN_REDIS_BACKUP_HANDLER_ENABLED env variables to True") + } + } + + b := builder.APIServer + + if appcatEnabled { + b.WithResourceAndHandler(&appcatv1.AppCat{}, appcat.New()) + } + + if vshnPGBackupsEnabled { + b.WithResourceAndHandler(&appcatv1.VSHNPostgresBackup{}, vshnpostgres.New()) + } + + if vshnRedisBackupsEnabled { + b.WithResourceAndHandler(&appcatv1.VSHNRedisBackup{}, vshnredis.New()) + } + + b.WithoutEtcd(). + ExposeLoopbackAuthorizer(). + ExposeLoopbackMasterClientConfig() + + cmd, err := b.Build() + cmd.Use = "apiserver" + cmd.Short = "AppCat API Server" + cmd.Long = "Run the AppCat API Server" + if err != nil { + log.Fatal(err, "Unable to load build API Server") + } + return cmd +} diff --git a/config/apiserver/aggregated-apiserver.yaml b/config/apiserver/aggregated-apiserver.yaml new file mode 100644 index 0000000..d3f7ad3 --- /dev/null +++ b/config/apiserver/aggregated-apiserver.yaml @@ -0,0 +1,51 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: appcat-apiserver + namespace: appcat + labels: + api: appcat + apiserver: "true" +spec: + selector: + matchLabels: + api: appcat + apiserver: "true" + replicas: 1 + template: + metadata: + labels: + api: appcat + apiserver: "true" + spec: + containers: + - name: apiserver + image: "ghcr.io/vshn/appcat:v0.0.1" #patched + volumeMounts: + - name: apiserver-certs + mountPath: /apiserver.local.config/certificates + readOnly: true + args: + - "apiserver" + - "--secure-port=9443" + - "--tls-cert-file=/apiserver.local.config/certificates/tls.crt" + - "--tls-private-key-file=/apiserver.local.config/certificates/tls.key" + - "--audit-log-path=-" + - "--feature-gates=APIPriorityAndFairness=false" + - "--audit-log-maxage=0" + - "--audit-log-maxbackup=0" + envFrom: + - configMapRef: + name: apiserver-envs + resources: + requests: + cpu: 100m + memory: 50Mi + limits: + cpu: 200m + memory: 200Mi + serviceAccountName: appcat-apiserver + volumes: + - name: apiserver-certs + secret: + secretName: appcat diff --git a/config/apiserver/apiserver-cert.yaml b/config/apiserver/apiserver-cert.yaml new file mode 100644 index 0000000..9e0eb36 --- /dev/null +++ b/config/apiserver/apiserver-cert.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: appcat + namespace: appcat + labels: + api: appcat + apiserver: "true" +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURpVENDQW5HZ0F3SUJBZ0lJZXh2Rkd0dGkzRjR3RFFZSktvWklodmNOQVFFTEJRQXdaakVMTUFrR0ExVUUKQmhNQ2RXNHhDekFKQmdOVkJBZ01Bbk4wTVFvd0NBWURWUVFIREFGc01Rb3dDQVlEVlFRS0RBRnZNUXN3Q1FZRApWUVFMREFKdmRURWxNQ01HQTFVRUF3d2NZWEJ3WTJGMExXTmxjblJwWm1sallYUmxMV0YxZEdodmNtbDBlVEFlCkZ3MHlNekF4TWpReE1EQXhOVFZhRncwek16QXhNakl3T0RVNU5EaGFNQjB4R3pBWkJnTlZCQU1URW1Gd2NHTmgKZEM1a1pXWmhkV3gwTG5OMll6Q0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUx6VQpiQWR4d3J3YkdkTzVCSDA1dUtuNnYrQjd0ODFFYUhUUUtEY2srZFFockpxNTM0U2x2UElZV25UYTM0eVM2eWZjCjk4aFlLcml2ZVhjYmlxcFVFZVNMWWxpOEJhNUhPMXNVdUU3VEpURFFIM2Z6amtOM2p1Uy9Nd2ZFQU9vU0VJQjUKR2tNMG1LblJaaTJaQ3V4dEtqc25CT0VJRmJobWlMQUxCVDNFdi9KaERma2cxY0s0dWpPTkJWQ0ZGOWxHdzFUdApoa2xWOFdURFIwb1RYeUt0cXRudm5LOFNQZmFET2RVUW1DdDRLOEhsZWtqdDkraThUOFRzN0o5cXRDQXdJNXphCk9xa2ZKUXNOQnRRdDMvY1FFWVdyb0lVWDBYbzg3MzV2enJoK0QwaXh4NjJpdzJWWStBTUdZbkJFdGQ4dFlnKzEKaFhDOUdQYkdFaHdqNzJMdkgyY0NBd0VBQWFPQmd6Q0JnREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdIUVlEVlIwbApCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQndNQ01COEdBMVVkSXdRWU1CYUFGRGxSWDJWQ3hDUVoxTmFPCjFjMWlHYmk3VWZ1M01DNEdBMVVkRVFRbk1DV0NDV3h2WTJGc2FHOXpkSUlTWVhCd1kyRjBMbVJsWm1GMWJIUXUKYzNaamh3Ui9BQUFCTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDaXJrQmVuSlQxQXMwVnNFd0RVbWZNcyt1MQpKUUJycDZIVU9LNXVGMHY4WlhqNzdCM3pub1ZTMG1lSDhzR1B0L1BkZjBCbkJYTWZEMEVWQjZ4UjNZV3Q4RHZSCmdEbG9Wa0tsYlp4dHJpMG45K2lsRHZjRWJObHlRK2lDbGFlVWtEVWUzSDlLa1ZkNkdMLzJtSWtMWWh4ajcvZGsKaFNieS9oVHd4Rkd1aVBNKzZtZ25jc3AzdWNZT0tBdTFyRmt4aHpQTGpEYzlyRUlIVFhHNjNvNVFVMFFPWEZLRApJeVFwR0RQeUpTOVhnMEZ0R0Y0eHNWTk9uTEZuazlHTmJxUlZvUEFxVll6bXF1ZnVaS250S1I3KzBweGk3K3orCkN2QjVHZ3lxKzdmWVhadzgwbTZQQWtaUkZ3QzdCMGljRytVZFdhK0ZaazMwT1o0R0pzdlM4aEdqRjl2bwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdk5Sc0IzSEN2QnNaMDdrRWZUbTRxZnEvNEh1M3pVUm9kTkFvTnlUNTFDR3Ntcm5mCmhLVzg4aGhhZE5yZmpKTHJKOXozeUZncXVLOTVkeHVLcWxRUjVJdGlXTHdGcmtjN1d4UzRUdE1sTU5BZmQvT08KUTNlTzVMOHpCOFFBNmhJUWdIa2FRelNZcWRGbUxaa0s3RzBxT3ljRTRRZ1Z1R2FJc0FzRlBjUy84bUVOK1NEVgp3cmk2TTQwRlVJVVgyVWJEVk8yR1NWWHhaTU5IU2hOZklxMnEyZStjcnhJOTlvTTUxUkNZSzNncndlVjZTTzMzCjZMeFB4T3pzbjJxMElEQWpuTm82cVI4bEN3MEcxQzNmOXhBUmhhdWdoUmZSZWp6dmZtL091SDRQU0xISHJhTEQKWlZqNEF3WmljRVMxM3kxaUQ3V0ZjTDBZOXNZU0hDUHZZdThmWndJREFRQUJBb0lCQUMzUExzSURsQ1dFUVppKwppdHRDVkkxUUdwcUFDUkFRSjNNblNJcFFPeGQrYjl3OWVYODVvZ1B0YW94c3lNeldtNXZaSEhlTEJCbzMzN1RJCmhyUlpudG1lQXViWi9sclFSeE1Lak1mNnEwd1RRWDhkSkFjMEN3TnRheGpZTVF5WUEwN1hra3A1aVF1eGxDTTkKdkl1czFCRHpuMWhrV0xpYWoxQjk2L0NYT2FXNEFPZ1VWTFY0WTI4cDFPd0Vza0FzYzFmQWdhUzMvQ202aHk2bQpvaXB3bG1SM28xL01PR0V4Z1BWVHl2cU5jZGZpUDRnR0phekdHM3ZWamg5WldraUd6d0JrSjlQSGlhMytraFhkCmhrcE5CYkRMN1A3cW9TVVA2SWV1ZjRIOUZYZGFVS0s5bEJDSkdVclZIbFJKYXgxZjB2VldacU9rVk55am1zcXAKNUdIa25Ea0NnWUVBeTQ1dEIxblhFQXhKejFtcXJGLzdTY3YzREV5Qzh1dlBXeVQ5V1JYeWFoVkpISXBheksvNQpQeU9Rd052UmpHbHY2QmVGZTUzUDN2YUNmbFZtYlRKWjd1Uk5IT3ErZk1rU0hBbUNGRmROMzJRdHF3eUN6V0hUCkZZVHNxbTVTbkp5N0V2T0t4NUFVRTMxTDBiQ1pVZVhoZFFocjBLNjArbnljNENHZU05em5HT1VDZ1lFQTdYcTAKSFpuTDloQlFQRk9yVVhTbGNVRm8xeEZMblFPTGJKblRiaUVySFNKa3hyVzhCMUh3MmFmUGFSTGdYMmFRRlhMKwp6SmZEbm14WEM0NHU4OHhZQm1TczNYZUtZcVJLOE9jOTNmTldVS0ZZWVlnY01EUGw2OUptbzRoOVdZT1JkWUxRCk5LdUg4Vk1Fb1dBTHdKd2lLMEk2OFBZV3V6SG54RzlRRElTdHpsc0NnWUVBeHpoc3NzNFZzd29qendEMkRtQ2QKNHRyeHZpSy80cG94eGdXTXd2eTgxV0JodnNJV0hkQjRnM2YrZXhKb295Q3FPcCtTcjZxRFFMZElmZlo5R1pBMgovcHlJY0MyN0l3dnpZbWRYM1NxWTkzTG5VMG9hVm9KUWdmWW5YcUk4empTQ3p3aDJvZHY4R2hyOThvc0JjMnNtClhsRlFtOXZ4R2xxTmVyck1SL3dDWFpVQ2dZRUFoa0xsS2djVnBSdzhBSjlkTUVRK2RQV0NESFdleGpxR3pQTjYKOGJ5VzMzWGVlK21yVUlnbkh5Y2N3RThIdzJmODllTjU0cm02d3dxRmUrYjY3S01POFM1aC9TUGtmVGhkbGkxbQpjT0Z1WDArWTVDdExwSVR5N0l5YnNzRXIvVHZCTlNxc0E5c21sRzFYSk5Wa1Q1VDJUSGE1VkJvTlovQWpGSS9LCk5nbWRLaGtDZ1lBb1A4dW04azZ0TFZxM0lMU1JINm9oRm40a3d6cFpGTDd5K1h6S2xkMTVtUGdsc3BTQnFnTEIKelhrMFBQc2QwV3NvTVVieWhocjB5ZzQxUnlJQnA1dUhWR1F5MSt5MFdPaWZaM2dveHNDTllnVkJEdU45QjUzTQpzWFJrWCtwaGFwK0dUQkx2VDVsRnFWRTYvME5JZmE2eFB4V3NMUjJCWmwyMzlVVnMvWFlrWVE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/config/apiserver/apiserver-envs.yaml b/config/apiserver/apiserver-envs.yaml new file mode 100644 index 0000000..13641d6 --- /dev/null +++ b/config/apiserver/apiserver-envs.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: apiserver-envs + namespace: appcat + labels: + api: appcat + apiserver: "true" +data: + APPCAT_HANDLER_ENABLED: "true" + VSHN_POSTGRES_BACKUP_HANDLER_ENABLED: "true" diff --git a/config/apiserver/apiservice.yaml b/config/apiserver/apiservice.yaml new file mode 100644 index 0000000..6190965 --- /dev/null +++ b/config/apiserver/apiservice.yaml @@ -0,0 +1,16 @@ +apiVersion: apiregistration.k8s.io/v1 +kind: APIService +metadata: + name: v1.api.appcat.vshn.io + labels: + api: appcat + apiserver: "true" +spec: + version: v1 + group: api.appcat.vshn.io + groupPriorityMinimum: 2000 + service: + name: appcat + namespace: appcat + port: 443 + versionPriority: 10 diff --git a/config/apiserver/cluster-role-binding.yaml b/config/apiserver/cluster-role-binding.yaml new file mode 100644 index 0000000..9fff5b0 --- /dev/null +++ b/config/apiserver/cluster-role-binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: appcat-apiserver +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: appcat-apiserver +subjects: + - kind: ServiceAccount + name: appcat-apiserver + namespace: appcat diff --git a/config/apiserver/namespace.yaml b/config/apiserver/namespace.yaml new file mode 100644 index 0000000..947da61 --- /dev/null +++ b/config/apiserver/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: appcat diff --git a/config/apiserver/role.yaml b/config/apiserver/role.yaml new file mode 100644 index 0000000..1f9a9bb --- /dev/null +++ b/config/apiserver/role.yaml @@ -0,0 +1,80 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: appcat +rules: +- apiGroups: + - "" + resourceNames: + - extension-apiserver-authentication + resources: + - configmaps + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - create + - delete + - get + - list + - update + - watch +- apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + - validatingwebhookconfigurations + verbs: + - get + - list + - watch +- apiGroups: + - apiextensions.crossplane.io + resources: + - compositions + verbs: + - get + - list + - watch +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create + - delete + - get + - list + - update + - watch +- apiGroups: + - k8up.io + resources: + - snapshots + verbs: + - get + - list + - watch +- apiGroups: + - stackgres.io + resources: + - sgbackups + verbs: + - get + - list + - watch +- apiGroups: + - vshn.appcat.vshn.io + resources: + - vshnredis + - xvshnpostgresqls + verbs: + - get + - list + - watch diff --git a/config/apiserver/service-account.yaml b/config/apiserver/service-account.yaml new file mode 100644 index 0000000..ad7cc8d --- /dev/null +++ b/config/apiserver/service-account.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: appcat-apiserver + namespace: appcat diff --git a/config/apiserver/service.yaml b/config/apiserver/service.yaml new file mode 100644 index 0000000..2374c9f --- /dev/null +++ b/config/apiserver/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: appcat + namespace: appcat + labels: + api: appcat + apiserver: "true" +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + api: appcat + apiserver: "true" diff --git a/docs/antora-build.mk b/docs/antora-build.mk new file mode 100644 index 0000000..8341f3c --- /dev/null +++ b/docs/antora-build.mk @@ -0,0 +1,24 @@ +docs_out_dir := ./.public + +docker_opts ?= --rm --tty --user "$$(id -u)" + +antora_build_version ?= latest +antora_cmd ?= $(DOCKER_CMD) run $(docker_opts) --volume "$${PWD}":/antora ghcr.io/vshn/antora:$(antora_build_version) +antora_opts ?= --cache-dir=.cache/antora + +.PHONY: docs +docs: docs-html ## All-in-one docs build + +.PHONY: docs-html +docs-html: $(docs_out_dir)/index.html ## Generate HTML version of documentation with Antora, output at ./.public + @touch $(docs_out_dir)/.nojekyll + +$(docs_out_dir)/index.html: + $(antora_cmd) $(antora_opts) docs/antora-playbook.yml + +.PHONY: docs-publish +docs-publish: docs/node_modules docs-html ## Publishes the Antora documentation on Github Pages + npm --prefix ./docs run deploy + +docs/node_modules: + npm --prefix ./docs install diff --git a/docs/antora-playbook.yml b/docs/antora-playbook.yml new file mode 100644 index 0000000..20b96d6 --- /dev/null +++ b/docs/antora-playbook.yml @@ -0,0 +1,18 @@ +site: + title: Application Catalog + start_page: appcat-apiserver::index.adoc + url: https://vshn.github.io/appcat-apiserver +content: + sources: + - url: ../antora + branches: + - HEAD + - docs/v* + start_path: docs +ui: + bundle: + url: https://github.com/vshn/antora-ui-default/releases/download/2.0.15/ui-bundle.zip + snapshot: true +output: + dir: .public/ + clean: true diff --git a/docs/antora-preview.mk b/docs/antora-preview.mk new file mode 100644 index 0000000..f5fc94c --- /dev/null +++ b/docs/antora-preview.mk @@ -0,0 +1,7 @@ + +antora_preview_version ?= 3.1.4 +antora_preview_cmd ?= $(DOCKER_CMD) run --rm --publish 35729:35729 --publish 2020:2020 --volume "${PWD}":/preview/antora ghcr.io/vshn/antora-preview:$(antora_preview_version) --style=vshn + +.PHONY: docs-preview +docs-preview: ## Preview the documentation + $(antora_preview_cmd) diff --git a/docs/antora.yml b/docs/antora.yml new file mode 100644 index 0000000..7aa95d4 --- /dev/null +++ b/docs/antora.yml @@ -0,0 +1,6 @@ +name: appcat +title: Application Catalog +version: master +start_page: ROOT:index.adoc +nav: + - modules/ROOT/nav.adoc diff --git a/docs/modules/ROOT/assets/images/.gitkeep b/docs/modules/ROOT/assets/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/modules/ROOT/assets/images/quotas.excalidraw.png b/docs/modules/ROOT/assets/images/quotas.excalidraw.png new file mode 100644 index 0000000000000000000000000000000000000000..b819539af6209fbdd3ecf4ffb1561df9da874472 GIT binary patch literal 91288 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.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/gh-pages": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-4.0.0.tgz", + "integrity": "sha512-p8S0T3aGJc68MtwOcZusul5qPSNZCalap3NWbhRUZYu1YOdp+EjZ+4kPmRM8h3NNRdqw00yuevRjlkuSzCn7iQ==", + "dependencies": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "requires": { + "lodash": "^4.17.14" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=" + }, + "filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "gh-pages": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-4.0.0.tgz", + "integrity": "sha512-p8S0T3aGJc68MtwOcZusul5qPSNZCalap3NWbhRUZYu1YOdp+EjZ+4kPmRM8h3NNRdqw00yuevRjlkuSzCn7iQ==", + "requires": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + } + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..d90338c --- /dev/null +++ b/docs/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "gh-pages": "5.0.0" + }, + "scripts": { + "deploy": "gh-pages -d ../.public --dotfiles -m \"Update documentation rev $(git rev-parse --short HEAD), $(date --utc '+%Y-%m-%d %H:%M')\"" + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2c4d895 --- /dev/null +++ b/go.mod @@ -0,0 +1,159 @@ +module github.com/vshn/appcat-apiserver + +go 1.20 + +replace github.com/deepmap/oapi-codegen => github.com/sljeff/oapi-codegen v1.5.1-0.20211207091501-bc20f55d338a + +require ( + github.com/crossplane/crossplane v1.11.5 + github.com/deepmap/oapi-codegen v0.0.0-00010101000000-000000000000 + github.com/go-logr/logr v1.2.3 + github.com/go-logr/zapr v1.2.3 + github.com/gogo/protobuf v1.3.2 + github.com/golang/mock v1.6.0 + github.com/golang/protobuf v1.5.3 + github.com/k8up-io/k8up/v2 v2.7.1 + github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.63.0 + github.com/spf13/cobra v1.7.0 + github.com/spf13/viper v1.15.0 + github.com/stretchr/testify v1.8.2 + github.com/vektra/mockery/v2 v2.26.1 + go.uber.org/zap v1.24.0 + golang.org/x/text v0.9.0 + gotest.tools/v3 v3.0.3 + k8s.io/api v0.26.3 + k8s.io/apimachinery v0.26.3 + k8s.io/apiserver v0.26.3 + k8s.io/client-go v0.26.3 + k8s.io/code-generator v0.26.3 + k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 + sigs.k8s.io/apiserver-runtime v1.1.2-0.20221226021050-33c901856927 + sigs.k8s.io/controller-runtime v0.14.5 + sigs.k8s.io/controller-tools v0.11.3 + sigs.k8s.io/kind v0.17.0 +) + +require ( + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/crossplane/crossplane-runtime v0.19.2 // indirect + github.com/onsi/ginkgo/v2 v2.8.3 // indirect + github.com/onsi/gomega v1.27.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/common v0.40.0 // indirect + google.golang.org/grpc v1.55.0 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) + +require ( + github.com/BurntSushi/toml v1.2.1 // indirect + github.com/NYTimes/gziphandler v1.1.1 // indirect + github.com/alessio/shellescape v1.4.1 // indirect + github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chigopher/pathlib v0.13.0 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/getkin/kin-openapi v0.107.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.21.1 // indirect + github.com/gobuffalo/flect v0.3.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/cel-go v0.12.6 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/iancoleman/strcase v0.2.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/yaml v0.1.0 // indirect + github.com/jinzhu/copier v0.3.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/labstack/echo/v4 v4.10.2 // indirect + github.com/labstack/gommon v0.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/rs/zerolog v1.29.0 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect + github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + go.etcd.io/etcd/api/v3 v3.5.6 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.6 // indirect + go.etcd.io/etcd/client/v3 v3.5.6 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 // indirect + go.opentelemetry.io/otel v1.11.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 // indirect + go.opentelemetry.io/otel/metric v0.33.0 // indirect + go.opentelemetry.io/otel/sdk v1.10.0 // indirect + go.opentelemetry.io/otel/trace v1.11.1 // indirect + go.opentelemetry.io/proto/otlp v0.19.0 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.8.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.9.1 // indirect + gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.26.3 // indirect + k8s.io/component-base v0.26.3 // indirect + k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect + k8s.io/klog/v2 v2.90.1 // indirect + k8s.io/kms v0.26.3 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..60d0c7b --- /dev/null +++ b/go.sum @@ -0,0 +1,1011 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chigopher/pathlib v0.13.0 h1:9AYqYGR+JaYJtZfTSsC+Wvz7CBd2jcZFb4fva7Z4r+4= +github.com/chigopher/pathlib v0.13.0/go.mod h1:EJ5UtJ/sK8Nt6q3VWN+EwZLZ3g0afJiG8NegYiQQ/gQ= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/crossplane/crossplane v1.11.5 h1:It/kq9VeO2dLy9kU95RJDJup9N0WEqiATmVMqcOci6Y= +github.com/crossplane/crossplane v1.11.5/go.mod h1:HhJ/EVMdbbZD5NuwJ32F1TSjaaiD6jPveP/qNghZiyk= +github.com/crossplane/crossplane-runtime v0.19.2 h1:9qBnhpqKN4x6apF2siaQ6PvgxqBXbGcKmgeD8mSIDO8= +github.com/crossplane/crossplane-runtime v0.19.2/go.mod h1:OJQ1NxtQK2ZTRmvtnQPoy8LsXsARTnVydRVDQEgIuz4= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/getkin/kin-openapi v0.80.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getkin/kin-openapi v0.107.0 h1:bxhL6QArW7BXQj8NjXfIJQy680NsMKd25nwhvpCXchg= +github.com/getkin/kin-openapi v0.107.0/go.mod h1:9Dhr+FasATJZjS4iOLvB0hkaxgYdulrNYm2e9epLWOo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= +github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/gobuffalo/flect v0.3.0 h1:erfPWM+K1rFNIQeRPdeEXxo8yFr/PO17lhRnS8FUrtk= +github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= +github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M= +github.com/google/cel-go v0.12.6/go.mod h1:Jk7ljRzLBhkmiAwBoUxB1sZSCVBAzkqPF25olK/iRDw= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20221102093814-76f304f74e5e h1:F1LLQqQ8WoIbyoxLUY+JUZe1kuHdxThM6CPUATzE6Io= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 h1:SJ+NtwL6QaZ21U+IrK7d0gGgpjGGvd2kz+FzTHVzdqI= +github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= +github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= +github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/k8up-io/k8up/v2 v2.7.1 h1:Ejbkb2bAfOKWAxmKOo3mGZKRx3Oz1oEahNfWTmiaUJA= +github.com/k8up-io/k8up/v2 v2.7.1/go.mod h1:xr0QtV800Zd55/XAQhDTamj1ulkENao1FpFDvjKYRT8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= +github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/codegen v1.0.2/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM= +github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.2.7/go.mod h1:bw24IXWbavc0R2RsOtpXL7RtMyP589yZ1+L7kd09ZGA= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo/v2 v2.8.3 h1:RpbK1G8nWPNaCVFBWsOGnEQQGgASi6b8fxcWBvDYjxQ= +github.com/onsi/ginkgo/v2 v2.8.3/go.mod h1:6OaUA8BCi0aZfmzYT/q9AacwTzDpNbxILUT+TlBq6MY= +github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= +github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.63.0 h1:efsW3CfymG5bZUpeIsYfdihB33YItCn7uHBOEbnHQG8= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.63.0/go.mod h1:/UtstAaWVaS3Z9GK9jo8+4SN9T+RMSq7VlOcQMmiEsc= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.40.0 h1:Afz7EVRqGg2Mqqf4JuF9vdvp1pi220m55Pi9T2JnO4Q= +github.com/prometheus/common v0.40.0/go.mod h1:L65ZJPSmfn/UBWLQIHV7dBrKFidB/wPlF1y5TlSt9OE= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= +github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sljeff/oapi-codegen v1.5.1-0.20211207091501-bc20f55d338a h1:bLe3hVqcJrse1CGVS2MtkPK139RaGVdhIGY3wNb0eFQ= +github.com/sljeff/oapi-codegen v1.5.1-0.20211207091501-bc20f55d338a/go.mod h1:Vid3nS1BbbSPSqaGOuMCEU1Ml8R/8VLjcyZZGDCKkkY= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.4.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/vektra/mockery/v2 v2.26.1 h1:Y8mlLkWHWjuUpsJBwhFb1LeG1ZnWFvo+prsIuiABJ88= +github.com/vektra/mockery/v2 v2.26.1/go.mod h1:BOVUIv65DB6wuTYzoPtyMoBYce3n2C1IcsOdWu6Rpu4= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c h1:/RwRVN9EdXAVtdHxP7Ndn/tfmM9/goiwU0QTnLBgS4w= +go.etcd.io/etcd/api/v3 v3.5.6 h1:Cy2qx3npLcYqTKqGJzMypnMv2tiRyifZJ17BlWIWA7A= +go.etcd.io/etcd/api/v3 v3.5.6/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= +go.etcd.io/etcd/client/pkg/v3 v3.5.6 h1:TXQWYceBKqLp4sa87rcPs11SXxUA/mHwH975v+BDvLU= +go.etcd.io/etcd/client/pkg/v3 v3.5.6/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= +go.etcd.io/etcd/client/v2 v2.305.6 h1:fIDR0p4KMjw01MJMfUIDWdQbjo06PD6CeYM5z4EHLi0= +go.etcd.io/etcd/client/v3 v3.5.6 h1:coLs69PWCXE9G4FKquzNaSHrRyMCAXwF+IX1tAPVO8E= +go.etcd.io/etcd/client/v3 v3.5.6/go.mod h1:f6GRinRMCsFVv9Ht42EyY7nfsVGwrNO0WEoS2pRKzQk= +go.etcd.io/etcd/pkg/v3 v3.5.5 h1:Ablg7T7OkR+AeeeU32kdVhw/AGDsitkKPl7aW73ssjU= +go.etcd.io/etcd/raft/v3 v3.5.5 h1:Ibz6XyZ60OYyRopu73lLM/P+qco3YtlZMOhnXNS051I= +go.etcd.io/etcd/server/v3 v3.5.5 h1:jNjYm/9s+f9A9r6+SC4RvNaz6AqixpOvhrFdT0PvIj0= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4 h1:PRXhsszxTt5bbPriTjmaweWUsAnJYeWBhUMLRetUgBU= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4/go.mod h1:05eWWy6ZWzmpeImD3UowLTB3VjDMU1yxQ+ENuVWDM3c= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0 h1:Ajldaqhxqw/gNzQA45IKFWLdG7jZuXX/wBW1d5qvbUI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.0/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= +go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= +go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXoY1pmMpFcdyhXOmL5drCrI3vU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 h1:KtiUEhQmj/Pa874bVYKGNVdq8NPKiacPbaRRtgXi+t4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0= +go.opentelemetry.io/otel/metric v0.33.0 h1:xQAyl7uGEYvrLAiV/09iTJlp1pZnQ9Wl793qbVvED1E= +go.opentelemetry.io/otel/metric v0.33.0/go.mod h1:QlTYc+EnYNq/M2mNk1qDDMRLpqCOj2f/r5c7Fd5FYaI= +go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= +go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= +go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= +go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU= +k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE= +k8s.io/apiextensions-apiserver v0.26.3 h1:5PGMm3oEzdB1W/FTMgGIDmm100vn7IaUP5er36dB+YE= +k8s.io/apiextensions-apiserver v0.26.3/go.mod h1:jdA5MdjNWGP+njw1EKMZc64xAT5fIhN6VJrElV3sfpQ= +k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= +k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/apiserver v0.26.3 h1:blBpv+yOiozkPH2aqClhJmJY+rp53Tgfac4SKPDJnU4= +k8s.io/apiserver v0.26.3/go.mod h1:CJe/VoQNcXdhm67EvaVjYXxR3QyfwpceKPuPaeLibTA= +k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s= +k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= +k8s.io/code-generator v0.26.3 h1:DNYPsWoeFwmg4qFg97Z1cHSSv7KSG10mAEIFoZGTQM8= +k8s.io/code-generator v0.26.3/go.mod h1:ryaiIKwfxEJEaywEzx3dhWOydpVctKYbqLajJf0O8dI= +k8s.io/component-base v0.26.3 h1:oC0WMK/ggcbGDTkdcqefI4wIZRYdK3JySx9/HADpV0g= +k8s.io/component-base v0.26.3/go.mod h1:5kj1kZYwSC6ZstHJN7oHBqcJC6yyn41eR+Sqa/mQc8E= +k8s.io/gengo v0.0.0-20220902162205-c0856e24416d h1:U9tB195lKdzwqicbJvyJeOXV7Klv+wNAWENRnXEGi08= +k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= +k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kms v0.26.3 h1:+rC4BMeMBkH5hrfZt9WFMRrs2m3vY2rXymisNactcTY= +k8s.io/kms v0.26.3/go.mod h1:69qGnf1NsFOQP07fBYqNLZklqEHSJF024JqYCaeVxHg= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= +k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 h1:xMMXJlJbsU8w3V5N2FLDQ8YgU8s1EoULdbQBcAeNJkY= +k8s.io/utils v0.0.0-20230313181309-38a27ef9d749/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36 h1:PUuX1qIFv309AT8hF/CdPKDmsG/hn/L8zRX7VvISM3A= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.36/go.mod h1:WxjusMwXlKzfAs4p9km6XJRndVt2FROgMVCE4cdohFo= +sigs.k8s.io/apiserver-runtime v1.1.2-0.20221226021050-33c901856927 h1:YLqdUEPYZ5jzmORSZeRs5AZWFRJbZ0jrLivh3/X145M= +sigs.k8s.io/apiserver-runtime v1.1.2-0.20221226021050-33c901856927/go.mod h1:4MJyV8pBRl+X7ml5z/D5H0AVJyz9qb49MIt0295HWNA= +sigs.k8s.io/controller-runtime v0.14.5 h1:6xaWFqzT5KuAQ9ufgUaj1G/+C4Y1GRkhrxl+BJ9i+5s= +sigs.k8s.io/controller-runtime v0.14.5/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/controller-tools v0.11.3 h1:T1xzLkog9saiyQSLz1XOImu4OcbdXWytc5cmYsBeBiE= +sigs.k8s.io/controller-tools v0.11.3/go.mod h1:qcfX7jfcfYD/b7lAhvqAyTbt/px4GpvN88WKLFFv7p8= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/kind v0.17.0 h1:CScmGz/wX66puA06Gj8OZb76Wmk7JIjgWf5JDvY7msM= +sigs.k8s.io/kind v0.17.0/go.mod h1:Qqp8AiwOlMZmJWs37Hgs31xcbiYXjtXlRBSftcnZXQk= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/log.go b/log.go new file mode 100644 index 0000000..94461b9 --- /dev/null +++ b/log.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "github.com/go-logr/logr" + "github.com/go-logr/zapr" + "github.com/spf13/cobra" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "os" + "runtime" + "strings" + "time" +) + +// LogMetadata prints various metadata to the root logger. +// It prints version, architecture and current user ID and returns nil. +func LogMetadata(cmd *cobra.Command) error { + log := logr.FromContextOrDiscard(cmd.Context()) + log.WithValues( + "date", time.Now(), + "go_os", runtime.GOOS, + "go_arch", runtime.GOARCH, + "go_version", runtime.Version(), + "uid", os.Getuid(), + "gid", os.Getgid(), + ).Info("Starting up " + cmd.Short) + return nil +} + +func SetupLogging(cmd *cobra.Command, logLevel int, logFormat string) error { + isJson := strings.EqualFold("JSON", logFormat) + log, err := newZapLogger(strings.ToUpper(cmd.Use), logLevel, isJson) + cmd.SetContext(logr.NewContext(cmd.Context(), log)) + return err +} + +func newZapLogger(name string, verbosityLevel int, useProductionConfig bool) (logr.Logger, error) { + cfg := zap.NewDevelopmentConfig() + cfg.EncoderConfig.ConsoleSeparator = " | " + if useProductionConfig { + cfg = zap.NewProductionConfig() + } + // Zap's levels get more verbose as the number gets smaller, + // bug logr's level increases with greater numbers. + cfg.Level = zap.NewAtomicLevelAt(zapcore.Level(verbosityLevel * -1)) + z, err := cfg.Build() + if err != nil { + return logr.Discard(), fmt.Errorf("error configuring the logging stack: %w", err) + } + zap.ReplaceGlobals(z) + zlog := zapr.NewLogger(z).WithName(name) + return zlog, nil +} diff --git a/login.json b/login.json new file mode 100644 index 0000000..2133869 --- /dev/null +++ b/login.json @@ -0,0 +1,4 @@ +{ + "password": "f0QOZutJ8aRUWiy9pww0WHpHfYk5OiImSieflB1b", + "username": "admin" +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..4ed30ba --- /dev/null +++ b/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/vshn/appcat-apiserver/cmd" +) + +func init() { + rootCmd.AddCommand(cmd.APIServerCMD) +} + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +var ( + logLevel int + logFormat string + rootCmd = &cobra.Command{ + Short: "AppCat", + Long: "AppCat controllers, api servers, grpc servers and probers", + PersistentPreRunE: setupLogging, + } +) + +func init() { + rootCmd.PersistentFlags().IntVarP(&logLevel, "log-level", "v", 0, "Number of the log level verbosity.") + rootCmd.PersistentFlags().StringVar(&logFormat, "log-format", "console", "Sets the log format (values: [json, console]).") + viper.AutomaticEnv() +} + +func setupLogging(cmd *cobra.Command, _ []string) error { + err := SetupLogging(cmd, logLevel, logFormat) + if err != nil { + return err + } + + return LogMetadata(cmd) +} diff --git a/pkg/apiserver/common.go b/pkg/apiserver/common.go new file mode 100644 index 0000000..c258d85 --- /dev/null +++ b/pkg/apiserver/common.go @@ -0,0 +1,69 @@ +package apiserver + +import ( + "errors" + "sync" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" +) + +func ResolveError(groupResource schema.GroupResource, err error) error { + statusErr := &apierrors.StatusError{} + + if errors.As(err, &statusErr) { + switch { + case apierrors.IsNotFound(err): + return apierrors.NewNotFound(groupResource, statusErr.ErrStatus.Details.Name) + case apierrors.IsAlreadyExists(err): + return apierrors.NewAlreadyExists(groupResource, statusErr.ErrStatus.Details.Name) + } + } + return err +} + +// MultiWatch is wrapper of multiple source watches which implements the same methods as a normal watch.Watch +var _ watch.Interface = &MultiWatcher{} + +type MultiWatcher struct { + watchers []watch.Interface + eventChan chan watch.Event + wg sync.WaitGroup +} + +// NewEmptyMultiWatch creates an empty watch +func NewEmptyMultiWatch() *MultiWatcher { + return &MultiWatcher{ + eventChan: make(chan watch.Event), + } +} + +// AddWatcher adds a watch to this MultiWatcher +func (m *MultiWatcher) AddWatcher(w watch.Interface) { + m.watchers = append(m.watchers, w) +} + +// Stop stops all watches of this MultiWatch +func (m *MultiWatcher) Stop() { + for _, watcher := range m.watchers { + watcher.Stop() + } + m.wg.Wait() + close(m.eventChan) +} + +// ResultChan aggregates all channels from all watches of this MultiWatch +func (m *MultiWatcher) ResultChan() <-chan watch.Event { + for _, w := range m.watchers { + m.wg.Add(1) + watcher := w + go func() { + defer m.wg.Done() + for c := range watcher.ResultChan() { + m.eventChan <- c + } + }() + } + return m.eventChan +} diff --git a/pkg/apiserver/hack/boilerplate.txt b/pkg/apiserver/hack/boilerplate.txt new file mode 100644 index 0000000..e69de29 diff --git a/pkg/apiserver/vshn/k8up/k8up.go b/pkg/apiserver/vshn/k8up/k8up.go new file mode 100644 index 0000000..d166b08 --- /dev/null +++ b/pkg/apiserver/vshn/k8up/k8up.go @@ -0,0 +1,99 @@ +package k8up + +import ( + "context" + "fmt" + + k8upv1 "github.com/k8up-io/k8up/v2/api/v1" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ( + K8upGVR = schema.GroupVersionResource{ + Group: k8upv1.GroupVersion.Group, + Version: k8upv1.GroupVersion.Version, + Resource: "snapshots", + } +) + +var _ Snapshothandler = &ConcreteSnapshotHandler{} + +// Snapshothandler is an interface that handles listing and getting of k8up snapsthots. +type Snapshothandler interface { + Get(ctx context.Context, id, instanceNamespace string) (*k8upv1.Snapshot, error) + List(ctx context.Context, instanceNamespace string) (*k8upv1.SnapshotList, error) + Watch(ctx context.Context, namespace string, options *metainternalversion.ListOptions) (watch.Interface, error) + GetFromEvent(in watch.Event) (*k8upv1.Snapshot, error) +} + +// ConcreteSnapshotHandler implements Snapshothandler. +type ConcreteSnapshotHandler struct { + client client.WithWatch +} + +// Get returns the snapshot with the given ID. +func (c *ConcreteSnapshotHandler) Get(ctx context.Context, id, instanceNamespace string) (*k8upv1.Snapshot, error) { + + snapshot := &k8upv1.Snapshot{} + + err := c.client.Get(ctx, client.ObjectKey{Namespace: instanceNamespace, Name: id}, snapshot) + if err != nil { + return snapshot, err + } + + return snapshot, nil +} + +// List returns all snapshots in the given namespace. +func (c *ConcreteSnapshotHandler) List(ctx context.Context, instanceNamespace string) (*k8upv1.SnapshotList, error) { + + snapshots := &k8upv1.SnapshotList{} + + err := c.client.List(ctx, snapshots, &client.ListOptions{Namespace: instanceNamespace}) + if err != nil { + return nil, err + } + + return snapshots, nil +} + +// Watch returns a watcher for the given objects +func (c *ConcreteSnapshotHandler) Watch(ctx context.Context, namespace string, options *metainternalversion.ListOptions) (watch.Interface, error) { + + snapshots, err := c.List(ctx, namespace) + if err != nil { + return nil, err + } + + return c.client.Watch(ctx, snapshots) + +} + +// GetFromEvent resolves watch.Event into k8upv1.Snapshot +func (c *ConcreteSnapshotHandler) GetFromEvent(in watch.Event) (*k8upv1.Snapshot, error) { + snap, ok := in.Object.(*unstructured.Unstructured) + if !ok { + return nil, fmt.Errorf("cannot parse snapshot from watch event") + } + + snapshot := &k8upv1.Snapshot{} + + err := runtime.DefaultUnstructuredConverter.FromUnstructured(snap.Object, snapshot) + if err != nil { + return nil, err + } + + return snapshot, nil +} + +// New returns a new Snapshothandler implemented by ConcreteSnapshotHandler. +func New(client client.WithWatch) Snapshothandler { + return &ConcreteSnapshotHandler{ + client: client, + } +} diff --git a/pkg/apiserver/vshn/postgres/backup.go b/pkg/apiserver/vshn/postgres/backup.go new file mode 100644 index 0000000..c138267 --- /dev/null +++ b/pkg/apiserver/vshn/postgres/backup.go @@ -0,0 +1,59 @@ +package postgres + +import ( + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + vshnv1 "github.com/vshn/appcat-apiserver/apis/vshn/v1" + "k8s.io/apimachinery/pkg/runtime" + genericregistry "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/client-go/dynamic" + restbuilder "sigs.k8s.io/apiserver-runtime/pkg/builder/rest" + "sigs.k8s.io/apiserver-runtime/pkg/util/loopback" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// New returns a new storage provider for VSHNPostgresBackup +func New() restbuilder.ResourceHandlerProvider { + return func(s *runtime.Scheme, gasdf genericregistry.RESTOptionsGetter) (rest.Storage, error) { + c, err := client.New(loopback.GetLoopbackMasterClientConfig(), client.Options{}) + if err != nil { + return nil, err + } + + err = vshnv1.AddToScheme(c.Scheme()) + if err != nil { + return nil, err + } + + dc, err := dynamic.NewForConfig(loopback.GetLoopbackMasterClientConfig()) + if err != nil { + return nil, err + } + return &vshnPostgresBackupStorage{ + sgbackups: &kubeSGBackupProvider{ + DynamicClient: dc.Resource(sgbackupGroupVersionResource), + }, + vshnpostgresql: &kubeXVSHNPostgresqlProvider{ + Client: c, + }, + }, nil + } +} + +type vshnPostgresBackupStorage struct { + sgbackups sgbackupProvider + vshnpostgresql vshnPostgresqlProvider +} + +func (v vshnPostgresBackupStorage) New() runtime.Object { + return &v1.VSHNPostgresBackup{} +} + +func (v vshnPostgresBackupStorage) Destroy() {} + +var _ rest.Scoper = &vshnPostgresBackupStorage{} +var _ rest.Storage = &vshnPostgresBackupStorage{} + +func (v *vshnPostgresBackupStorage) NamespaceScoped() bool { + return true +} diff --git a/pkg/apiserver/vshn/postgres/backup_test.go b/pkg/apiserver/vshn/postgres/backup_test.go new file mode 100644 index 0000000..68d6115 --- /dev/null +++ b/pkg/apiserver/vshn/postgres/backup_test.go @@ -0,0 +1,138 @@ +package postgres + +import ( + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "github.com/vshn/appcat-apiserver/test/mocks" + + "testing" + + vshnv1 "github.com/vshn/appcat-apiserver/apis/vshn/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" + + "github.com/golang/mock/gomock" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// newMockedVSHNPostgresBackupStorage is a mocked instance of vshnPostgresBackup +func newMockedVSHNPostgresBackupStorage(t *testing.T, ctrl *gomock.Controller) (rest.StandardStorage, *mocks.MocksgbackupProvider, *mocks.MockvshnPostgresqlProvider) { + t.Helper() + sgbackup := mocks.NewMocksgbackupProvider(ctrl) + vshnpostgres := mocks.NewMockvshnPostgresqlProvider(ctrl) + stor := &vshnPostgresBackupStorage{ + sgbackups: sgbackup, + vshnpostgresql: vshnpostgres, + } + return rest.Storage(stor).(rest.StandardStorage), sgbackup, vshnpostgres +} + +// Test AppCat instances +var ( + vshnBackupOne = &v1.VSHNPostgresBackup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "one", + Namespace: "namespace", + }, + Status: v1.VSHNPostgresBackupStatus{ + DatabaseInstance: "postgres-one", + Process: &runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{"status": "Failed"}}}, + BackupInformation: &runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{"disk": "1GB", "cpu": "1"}}}, + }, + } + + backupInfoOne = &v1.SGBackupInfo{ + ObjectMeta: metav1.ObjectMeta{ + Name: "one", + Namespace: "namespace-one", + }, + Process: runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{"status": "Failed"}}}, + BackupInformation: runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{"disk": "1GB", "cpu": "1"}}}, + } + + unstructuredBackupOne = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "one", + "namespace": "namespace-one", + }, + "status": map[string]interface{}{ + "process": map[string]interface{}{ + "status": "Failed", + }, + "backupInformation": map[string]interface{}{ + "disk": "1GB", + "cpu": "1", + }, + }, + }, + } + + vshnBackupTwo = &v1.VSHNPostgresBackup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "two", + Namespace: "namespace", + }, + Status: v1.VSHNPostgresBackupStatus{ + DatabaseInstance: "postgres-two", + Process: &runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{"status": "Completed"}}}, + BackupInformation: &runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{"disk": "2GB", "cpu": "2"}}}, + }, + } + + backupInfoTwo = &v1.SGBackupInfo{ + ObjectMeta: metav1.ObjectMeta{ + Name: "two", + Namespace: "namespace-two", + }, + Process: runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{"status": "Completed"}}}, + BackupInformation: runtime.RawExtension{Object: &unstructured.Unstructured{Object: map[string]interface{}{"disk": "2GB", "cpu": "2"}}}, + } + + unstructuredBackupTwo = &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "two", + "namespace": "namespace-two", + }, + "status": map[string]interface{}{ + "process": map[string]interface{}{ + "status": "Completed", + }, + "backupInformation": map[string]interface{}{ + "disk": "2GB", + "cpu": "2", + }, + }, + }, + } + + vshnPostgreSQLInstances = &vshnv1.XVSHNPostgreSQLList{ + Items: []vshnv1.XVSHNPostgreSQL{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "postgres-one-tty", + Labels: map[string]string{ + claimNameLabel: "postgres-one", + claimNamespaceLabel: "namespace-claim", + }, + }, + Status: vshnv1.VSHNPostgreSQLStatus{ + InstanceNamespace: "namespace-one", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "postgres-two-bbf", + Labels: map[string]string{ + claimNameLabel: "postgres-two", + claimNamespaceLabel: "namespace-claim", + }, + }, + Status: vshnv1.VSHNPostgreSQLStatus{ + InstanceNamespace: "namespace-two", + }, + }, + }, + } +) diff --git a/pkg/apiserver/vshn/postgres/create.go b/pkg/apiserver/vshn/postgres/create.go new file mode 100644 index 0000000..5ceeaf7 --- /dev/null +++ b/pkg/apiserver/vshn/postgres/create.go @@ -0,0 +1,15 @@ +package postgres + +import ( + "context" + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Creater = &vshnPostgresBackupStorage{} + +func (v vshnPostgresBackupStorage) Create(_ context.Context, _ runtime.Object, _ rest.ValidateObjectFunc, _ *metav1.CreateOptions) (runtime.Object, error) { + return nil, fmt.Errorf("method not implemented") +} diff --git a/pkg/apiserver/vshn/postgres/delete.go b/pkg/apiserver/vshn/postgres/delete.go new file mode 100644 index 0000000..0256adc --- /dev/null +++ b/pkg/apiserver/vshn/postgres/delete.go @@ -0,0 +1,29 @@ +package postgres + +import ( + "context" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.GracefulDeleter = &vshnPostgresBackupStorage{} +var _ rest.CollectionDeleter = &vshnPostgresBackupStorage{} + +func (v vshnPostgresBackupStorage) Delete(_ context.Context, name string, _ rest.ValidateObjectFunc, _ *metav1.DeleteOptions) (runtime.Object, bool, error) { + return &v1.VSHNPostgresBackup{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + }, false, nil +} + +func (v *vshnPostgresBackupStorage) DeleteCollection(ctx context.Context, _ rest.ValidateObjectFunc, _ *metav1.DeleteOptions, _ *metainternalversion.ListOptions) (runtime.Object, error) { + return &v1.VSHNPostgresBackupList{ + Items: []v1.VSHNPostgresBackup{}, + }, nil +} diff --git a/pkg/apiserver/vshn/postgres/get.go b/pkg/apiserver/vshn/postgres/get.go new file mode 100644 index 0000000..396815f --- /dev/null +++ b/pkg/apiserver/vshn/postgres/get.go @@ -0,0 +1,49 @@ +package postgres + +import ( + "context" + "fmt" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "github.com/vshn/appcat-apiserver/pkg/apiserver" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Getter = &vshnPostgresBackupStorage{} + +// Get returns a VSHNPostgresBackupStorage service based on stackgres SGBackup resource +func (v *vshnPostgresBackupStorage) Get(ctx context.Context, name string, _ *metav1.GetOptions) (runtime.Object, error) { + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, fmt.Errorf("cannot get namespace from resource") + } + + instances, err := v.vshnpostgresql.ListXVSHNPostgreSQL(ctx, namespace) + if err != nil { + return nil, fmt.Errorf("cannot list VSHNPostgreSQL instances") + } + + var vshnBackup *v1.VSHNPostgresBackup + for _, value := range instances.Items { + backupInfo, err := v.sgbackups.GetSGBackup(ctx, name, value.Status.InstanceNamespace) + if err != nil { + resolvedErr := apiserver.ResolveError(sgbackupGroupVersionResource.GroupResource(), err) + if apierrors.IsNotFound(resolvedErr) { + continue + } + return nil, err + } + + vshnBackup = v1.NewVSHNPostgresBackup(backupInfo, value.Labels[claimNameLabel], namespace) + } + + if vshnBackup == nil { + return nil, apierrors.NewNotFound(v1.New().GetGroupVersionResource().GroupResource(), name) + } + + return vshnBackup, nil +} diff --git a/pkg/apiserver/vshn/postgres/get_test.go b/pkg/apiserver/vshn/postgres/get_test.go new file mode 100644 index 0000000..7e47170 --- /dev/null +++ b/pkg/apiserver/vshn/postgres/get_test.go @@ -0,0 +1,108 @@ +package postgres + +import ( + "testing" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + vshnv1 "github.com/vshn/appcat-apiserver/apis/vshn/v1" + "github.com/vshn/appcat-apiserver/test/mocks" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "k8s.io/apiserver/pkg/endpoints/request" +) + +func TestVSHNPostgresBackupStorage_Get(t *testing.T) { + tests := map[string]struct { + name string + postgresqls *vshnv1.XVSHNPostgreSQLList + backupInfo *v1.SGBackupInfo + backupInfoCalls func(mocks.MocksgbackupProvider, string) + vshnPostgresBackup *v1.VSHNPostgresBackup + err error + }{ + "GivenAListOfPostgresAndBackups_ThenVSHNPostgresBackup": { + name: "one", + postgresqls: vshnPostgreSQLInstances, + backupInfo: backupInfoOne, + backupInfoCalls: func(provider mocks.MocksgbackupProvider, name string) { + provider.EXPECT(). + GetSGBackup(gomock.Any(), name, "namespace-one"). + Return(backupInfoOne, nil). + Times(1) + + provider.EXPECT(). + GetSGBackup(gomock.Any(), name, "namespace-two"). + Return(nil, apierrors.NewNotFound(v1.GetGroupResource(v1.ResourceBackup), name)). + Times(1) + }, + vshnPostgresBackup: vshnBackupOne, + err: nil, + }, + "GivenErrNotFound_ThenErrNotFound": { + name: "one", + postgresqls: vshnPostgreSQLInstances, + backupInfo: nil, + backupInfoCalls: func(provider mocks.MocksgbackupProvider, name string) { + provider.EXPECT(). + GetSGBackup(gomock.Any(), name, "namespace-one"). + Return(nil, apierrors.NewNotFound(v1.GetGroupResource(v1.ResourceBackup), name)). + Times(1) + + provider.EXPECT(). + GetSGBackup(gomock.Any(), name, "namespace-two"). + Return(nil, apierrors.NewNotFound(v1.GetGroupResource(v1.ResourceBackup), name)). + Times(1) + }, + vshnPostgresBackup: nil, + err: apierrors.NewNotFound(v1.GetGroupResource(v1.ResourceBackup), "one"), + }, + "GivenNoPostgresInstances_ThenErrNotFound": { + name: "one", + postgresqls: &vshnv1.XVSHNPostgreSQLList{}, + backupInfo: nil, + backupInfoCalls: func(provider mocks.MocksgbackupProvider, name string) { + provider.EXPECT(). + GetSGBackup(gomock.Any(), gomock.Any(), gomock.Any()). + Times(0) + }, + vshnPostgresBackup: nil, + err: apierrors.NewNotFound(v1.GetGroupResource(v1.ResourceBackup), "one"), + }, + } + + for n, tc := range tests { + + t.Run(n, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + s, backupProvider, postgresProvider := newMockedVSHNPostgresBackupStorage(t, ctrl) + + postgresProvider.EXPECT(). + ListXVSHNPostgreSQL(gomock.Any(), gomock.Any()). + Return(tc.postgresqls, nil). + Times(1) + + tc.backupInfoCalls(*backupProvider, tc.name) + + actual, err := s.Get(request.WithRequestInfo( + request.WithNamespace(request.NewContext(), "namespace"), + &request.RequestInfo{ + Verb: "get", + APIGroup: v1.GroupVersion.Group, + Resource: v1.ResourceBackup, + Name: tc.name, + }), + tc.name, nil) + if tc.err != nil { + assert.EqualError(t, err, tc.err.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, tc.vshnPostgresBackup, actual) + }) + } +} diff --git a/pkg/apiserver/vshn/postgres/list.go b/pkg/apiserver/vshn/postgres/list.go new file mode 100644 index 0000000..4b9a08f --- /dev/null +++ b/pkg/apiserver/vshn/postgres/list.go @@ -0,0 +1,105 @@ +package postgres + +import ( + "context" + "fmt" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "github.com/vshn/appcat-apiserver/pkg/apiserver" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Lister = &vshnPostgresBackupStorage{} + +func (v *vshnPostgresBackupStorage) NewList() runtime.Object { + return &v1.VSHNPostgresBackupList{} +} + +// List returns a list of VSHNPostgresBackup services based on stackgres SGBackup resources +func (v *vshnPostgresBackupStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, fmt.Errorf("cannot get namespace from resource") + } + + instances, err := v.vshnpostgresql.ListXVSHNPostgreSQL(ctx, namespace) + if err != nil { + return nil, fmt.Errorf("cannot list VSHNPostgreSQL instances %v", err) + } + + // Aggregate all backups from all postgres clusters from the same namespace + backups := v1.VSHNPostgresBackupList{} + for _, value := range instances.Items { + bis, err := v.sgbackups.ListSGBackup(ctx, value.Status.InstanceNamespace, options) + if err != nil { + return nil, apiserver.ResolveError(v1.GetGroupResource(v1.ResourceBackup), err) + } + for _, b := range *bis { + vb := v1.NewVSHNPostgresBackup(&b, value.Labels[claimNameLabel], namespace) + if vb != nil { + backups.Items = append(backups.Items, *vb) + } + } + } + + return &backups, nil +} + +var _ rest.Watcher = &vshnPostgresBackupStorage{} + +// Watch returns a watched list of VSHNPostgresBackup services based on stackgres SGBackup resources +func (v *vshnPostgresBackupStorage) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, fmt.Errorf("cannot get namespace from resource") + } + + instances, err := v.vshnpostgresql.ListXVSHNPostgreSQL(ctx, namespace) + if err != nil { + return nil, fmt.Errorf("cannot list VSHNPostgreSQL instances") + } + + mw := apiserver.NewEmptyMultiWatch() + for _, value := range instances.Items { + backupWatcher, err := v.sgbackups.WatchSGBackup(ctx, value.Status.InstanceNamespace, options) + if err != nil { + return nil, apiserver.ResolveError(v1.GetGroupResource(v1.ResourceBackup), err) + } + mw.AddWatcher(backupWatcher) + } + + return watch.Filter(mw, func(in watch.Event) (out watch.Event, keep bool) { + if in.Object == nil { + // This should never happen, let downstream deal with it + return in, true + } + + sgbackupInfo := GetFromEvent(in) + if sgbackupInfo == nil { + return in, true + } + + db := "" + for _, value := range instances.Items { + if value.Status.InstanceNamespace == sgbackupInfo.Namespace { + db = value.Labels[claimNameLabel] + } + } + + if db == "" { + return in, false + } + + in.Object = v1.NewVSHNPostgresBackup(sgbackupInfo, db, namespace) + + if in.Object.(*v1.VSHNPostgresBackup) == nil { + return in, false + } + + return in, true + }), nil +} diff --git a/pkg/apiserver/vshn/postgres/list_test.go b/pkg/apiserver/vshn/postgres/list_test.go new file mode 100644 index 0000000..9b4e17f --- /dev/null +++ b/pkg/apiserver/vshn/postgres/list_test.go @@ -0,0 +1,278 @@ +package postgres + +import ( + "fmt" + "testing" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + vshnv1 "github.com/vshn/appcat-apiserver/apis/vshn/v1" + "github.com/vshn/appcat-apiserver/test/mocks" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "k8s.io/apiserver/pkg/endpoints/request" +) + +func TestVSHNPostgresBackupStorage_List(t *testing.T) { + tests := map[string]struct { + postgresqls *vshnv1.XVSHNPostgreSQLList + postgresqlsErr error + + backupInfoCalls func(mocks.MocksgbackupProvider, error) + backupInfosErr error + + vshnBackups *v1.VSHNPostgresBackupList + err error + }{ + "GivenPostgresDataAndListOfBackupInfos_ThenReturnVshnBackups": { + postgresqls: vshnPostgreSQLInstances, + backupInfoCalls: func(provider mocks.MocksgbackupProvider, err error) { + provider.EXPECT(). + ListSGBackup(gomock.Any(), "namespace-one", gomock.Any()). + Return(&[]v1.SGBackupInfo{*backupInfoOne}, nil). + Times(1) + + provider.EXPECT(). + ListSGBackup(gomock.Any(), "namespace-two", gomock.Any()). + Return(&[]v1.SGBackupInfo{*backupInfoTwo}, err). + Times(1) + }, + vshnBackups: &v1.VSHNPostgresBackupList{ + Items: []v1.VSHNPostgresBackup{ + *vshnBackupOne, + *vshnBackupTwo, + }, + }, + }, + "GivenNoPostgresData_ThenReturnEmpty": { + postgresqls: &vshnv1.XVSHNPostgreSQLList{}, + backupInfoCalls: func(provider mocks.MocksgbackupProvider, err error) {}, + vshnBackups: &v1.VSHNPostgresBackupList{ + Items: []v1.VSHNPostgresBackup(nil), + }, + }, + "GivenNoBackups_ThenReturnEmpty": { + postgresqls: vshnPostgreSQLInstances, + backupInfoCalls: func(provider mocks.MocksgbackupProvider, err error) { + provider.EXPECT(). + ListSGBackup(gomock.Any(), "namespace-one", gomock.Any()). + Return(&[]v1.SGBackupInfo{}, nil). + Times(1) + + provider.EXPECT(). + ListSGBackup(gomock.Any(), "namespace-two", gomock.Any()). + Return(&[]v1.SGBackupInfo{}, err). + Times(1) + }, + vshnBackups: &v1.VSHNPostgresBackupList{ + Items: []v1.VSHNPostgresBackup(nil), + }, + }, + "GivenBackupErrList_ThenReturnError": { + postgresqls: &vshnv1.XVSHNPostgreSQLList{ + Items: []vshnv1.XVSHNPostgreSQL{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "postgres-two", + }, + Status: vshnv1.VSHNPostgreSQLStatus{ + InstanceNamespace: "namespace-two", + }, + }, + }, + }, + backupInfoCalls: func(provider mocks.MocksgbackupProvider, err error) { + provider.EXPECT(). + ListSGBackup(gomock.Any(), "namespace-two", gomock.Any()). + Return(&[]v1.SGBackupInfo{}, err). + Times(1) + }, + backupInfosErr: fmt.Errorf("cannot get list"), + err: fmt.Errorf("cannot get list"), + }, + } + for n, tc := range tests { + t.Run(n, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + stor, backupsProvider, vshnPostgresProvider := newMockedVSHNPostgresBackupStorage(t, ctrl) + + vshnPostgresProvider.EXPECT(). + ListXVSHNPostgreSQL(gomock.Any(), gomock.Any()). + Return(tc.postgresqls, nil). + Times(1) + + tc.backupInfoCalls(*backupsProvider, tc.backupInfosErr) + + actualList, err := stor.List(request.WithRequestInfo( + request.WithNamespace(request.NewContext(), "namespace"), + &request.RequestInfo{ + Verb: "list", + APIGroup: v1.GroupVersion.Group, + Resource: v1.ResourceBackup, + }), nil) + if tc.err != nil { + assert.EqualError(t, err, tc.err.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, tc.vshnBackups, actualList) + }) + } +} + +type testWatcher struct { + events chan watch.Event + countStops int +} + +func (w *testWatcher) Stop() { + w.countStops++ +} + +func (w *testWatcher) ResultChan() <-chan watch.Event { + return w.events +} + +func TestVSHNPostgresBackupStorage_Watch(t *testing.T) { + tests := map[string]struct { + postgresqls *vshnv1.XVSHNPostgreSQLList + postgresqlsErr error + + unstructuredEvents []watch.Event + unstructuredErr error + + backupInfoCalls func(mocks.MocksgbackupProvider, error, []watch.Interface) + + stopWatchCounterOne int + stopWatchCounterTwo int + + vshnBackupEvents []watch.Event + err error + }{ + "GivenSGBackupsUnstructured_ThenVSHNBackupsEvents": { + postgresqls: vshnPostgreSQLInstances, + stopWatchCounterOne: 1, + stopWatchCounterTwo: 1, + backupInfoCalls: func(provider mocks.MocksgbackupProvider, _ error, watches []watch.Interface) { + provider.EXPECT(). + WatchSGBackup(gomock.Any(), "namespace-one", gomock.Any()). + Return(watches[0], nil). + AnyTimes() + + provider.EXPECT(). + WatchSGBackup(gomock.Any(), "namespace-two", gomock.Any()). + Return(watches[1], nil). + AnyTimes() + }, + unstructuredEvents: []watch.Event{ + { + Type: watch.Added, + Object: unstructuredBackupOne, + }, + { + Type: watch.Modified, + Object: unstructuredBackupTwo, + }, + }, + vshnBackupEvents: []watch.Event{ + { + Type: watch.Added, + Object: vshnBackupOne, + }, + { + Type: watch.Modified, + Object: vshnBackupTwo, + }, + }, + }, + "GivenErrNotFound_ThenErrNotFound": { + postgresqls: &vshnv1.XVSHNPostgreSQLList{ + Items: []vshnv1.XVSHNPostgreSQL{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "postgres-one", + }, + Status: vshnv1.VSHNPostgreSQLStatus{ + InstanceNamespace: "namespace-one", + }, + }, + }, + }, + unstructuredErr: apierrors.NewNotFound(schema.GroupResource{ + Resource: "sgbackups", + }, "not-found"), + backupInfoCalls: func(provider mocks.MocksgbackupProvider, err error, _ []watch.Interface) { + provider.EXPECT(). + WatchSGBackup(gomock.Any(), "namespace-one", gomock.Any()). + Return(nil, err). + AnyTimes() + }, + err: apierrors.NewNotFound(schema.GroupResource{ + Group: v1.GroupVersion.Group, + Resource: v1.ResourceBackup, + }, "not-found"), + }, + "GivenPostgresInstancesError_ThenError": { + postgresqlsErr: fmt.Errorf("cannot get postgres instances"), + err: fmt.Errorf("cannot list VSHNPostgreSQL instances"), + backupInfoCalls: func(provider mocks.MocksgbackupProvider, err error, _ []watch.Interface) {}, + }, + } + + for n, tc := range tests { + t.Run(n, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + stor, backupProvider, vshnPostgresProvider := newMockedVSHNPostgresBackupStorage(t, ctrl) + + backupWatcherOne := &testWatcher{ + events: make(chan watch.Event, 1), + } + backupWatcherTwo := &testWatcher{ + events: make(chan watch.Event, 1), + } + if tc.unstructuredEvents != nil { + backupWatcherOne.events <- tc.unstructuredEvents[0] + backupWatcherTwo.events <- tc.unstructuredEvents[1] + + close(backupWatcherOne.events) + close(backupWatcherTwo.events) + } + + vshnPostgresProvider.EXPECT(). + ListXVSHNPostgreSQL(gomock.Any(), gomock.Any()). + Return(tc.postgresqls, tc.postgresqlsErr). + Times(1) + + tc.backupInfoCalls(*backupProvider, tc.unstructuredErr, []watch.Interface{backupWatcherOne, backupWatcherTwo}) + + vshnBackupWatch, err := stor.Watch(request.WithRequestInfo( + request.WithNamespace(request.NewContext(), "namespace"), + &request.RequestInfo{ + Verb: "watch", + APIGroup: v1.GroupVersion.Group, + Resource: v1.ResourceBackup, + }), nil) + if tc.err != nil { + assert.EqualError(t, err, tc.err.Error()) + return + } + require.NoError(t, err) + e1 := <-vshnBackupWatch.ResultChan() + e2 := <-vshnBackupWatch.ResultChan() + vshnBackupWatch.Stop() + vshnBackupEvents := []watch.Event{e1, e2} + assert.Equal(t, tc.stopWatchCounterOne, backupWatcherOne.countStops) + assert.Equal(t, tc.stopWatchCounterTwo, backupWatcherTwo.countStops) + assert.ElementsMatch(t, tc.vshnBackupEvents, vshnBackupEvents) + }) + } + +} diff --git a/pkg/apiserver/vshn/postgres/sgbackups.go b/pkg/apiserver/vshn/postgres/sgbackups.go new file mode 100644 index 0000000..385c4b7 --- /dev/null +++ b/pkg/apiserver/vshn/postgres/sgbackups.go @@ -0,0 +1,125 @@ +package postgres + +import ( + "context" + "fmt" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + client "k8s.io/client-go/dynamic" +) + +var ( + sgbackupGroupVersionResource = schema.GroupVersionResource{ + Group: "stackgres.io", + Version: "v1", + Resource: "sgbackups", + } +) + +// sgbackupProvider is an abstraction to interact with the K8s API +type sgbackupProvider interface { + GetSGBackup(ctx context.Context, name, namespace string) (*v1.SGBackupInfo, error) + ListSGBackup(ctx context.Context, namespace string, options *metainternalversion.ListOptions) (*[]v1.SGBackupInfo, error) + WatchSGBackup(ctx context.Context, namespace string, options *metainternalversion.ListOptions) (watch.Interface, error) +} + +type kubeSGBackupProvider struct { + DynamicClient client.NamespaceableResourceInterface +} + +// GetSGBackup fetches SGBackup resource into unstructured.Unstructured. Relevant data is saved to v1.SGBackupInfo +func (k *kubeSGBackupProvider) GetSGBackup(ctx context.Context, name, namespace string) (*v1.SGBackupInfo, error) { + unstructuredObject, err := k.DynamicClient.Namespace(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return convertToSGBackupInfo(unstructuredObject) +} + +// ListSGBackup fetches SGBackup resources into unstructured.UnstructuredList. Relevant data is saved to v1.SGBackupInfo +func (k *kubeSGBackupProvider) ListSGBackup(ctx context.Context, namespace string, options *metainternalversion.ListOptions) (*[]v1.SGBackupInfo, error) { + unstructuredList, err := k.DynamicClient.Namespace(namespace).List(ctx, metav1.ListOptions{ + Limit: options.Limit, + Continue: options.Continue, + }) + if err != nil { + return nil, err + } + + sgbackupsInfos := make([]v1.SGBackupInfo, 0, len(unstructuredList.Items)) + for _, v := range unstructuredList.Items { + backupsInfo, err := convertToSGBackupInfo(&v) + if err != nil { + continue + } + sgbackupsInfos = append(sgbackupsInfos, *backupsInfo) + } + return &sgbackupsInfos, nil +} + +// WatchSGBackup watches SGBackup resources. +func (k *kubeSGBackupProvider) WatchSGBackup(ctx context.Context, namespace string, options *metainternalversion.ListOptions) (watch.Interface, error) { + return k.DynamicClient.Namespace(namespace).Watch(ctx, metav1.ListOptions{ + TypeMeta: options.TypeMeta, + LabelSelector: options.LabelSelector.String(), + FieldSelector: options.FieldSelector.String(), + Limit: options.Limit, + Continue: options.Continue, + }) +} + +// GetFromEvent resolves watch.Event into v1.SGBackupInfo +func GetFromEvent(in watch.Event) *v1.SGBackupInfo { + u, ok := in.Object.(*unstructured.Unstructured) + if !ok { + return nil + } + + backup, err := convertToSGBackupInfo(u) + if err != nil { + return nil + } + return backup +} + +func convertToSGBackupInfo(object *unstructured.Unstructured) (*v1.SGBackupInfo, error) { + content := object.UnstructuredContent() + objectMeta, exists, err := unstructured.NestedMap(content, v1.Metadata) + if err != nil || exists == false { + return nil, fmt.Errorf("cannot parse metadata from object %s", object) + } + + name := object.GetName() + + o := &metav1.ObjectMeta{} + err = runtime.DefaultUnstructuredConverter.FromUnstructured(objectMeta, o) + if err != nil { + return nil, err + } + + p, _, err := unstructured.NestedMap(content, v1.Status, v1.Process) + if err != nil { + return nil, fmt.Errorf("cannot parse status.process field from object name %s", name) + } + + bi, _, err := unstructured.NestedMap(content, v1.Status, v1.BackupInformation) + if err != nil { + return nil, fmt.Errorf("cannot parse status.backupInformation field from object name %s", name) + } + + b := &v1.SGBackupInfo{ObjectMeta: *o} + if p != nil { + b.Process = runtime.RawExtension{Object: &unstructured.Unstructured{Object: p}} + } + if bi != nil { + b.BackupInformation = runtime.RawExtension{Object: &unstructured.Unstructured{Object: bi}} + } + + return b, nil +} diff --git a/pkg/apiserver/vshn/postgres/sgbackups_test.go b/pkg/apiserver/vshn/postgres/sgbackups_test.go new file mode 100644 index 0000000..e3d6737 --- /dev/null +++ b/pkg/apiserver/vshn/postgres/sgbackups_test.go @@ -0,0 +1,142 @@ +package postgres + +import ( + "fmt" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestConvertToBackupInfo(t *testing.T) { + tests := map[string]struct { + unstr *unstructured.Unstructured + backupInfo *v1.SGBackupInfo + err error + }{ + "GivenAnUnstructuredObject_ThenBackupInfo": { + unstr: getUnstructuredObject("one"), + backupInfo: getBackupInfo("one"), + err: nil, + }, + "GivenAnUnstructuredObjectWithoutMeta_ThenError": { + unstr: getUnstructuredObjectWithoutMeta("two"), + backupInfo: nil, + err: fmt.Errorf("cannot parse metadata from object %s", getUnstructuredObjectWithoutMeta("two")), + }, + "GivenAnUnstructuredObjectWithoutProcess_ThenBackupInfo": { + unstr: getUnstructuredObjectWithoutProcess("three"), + backupInfo: getBackupInfoWithoutProcess("three"), + err: nil, + }, + "GivenAnUnstructuredObjectWithoutBI_ThenBackupInfo": { + unstr: getUnstructuredObjectWithoutBI("four"), + backupInfo: getBackupInfoWithoutBI("four"), + err: nil, + }, + } + + for n, tc := range tests { + + t.Run(n, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + actual, err := convertToSGBackupInfo(tc.unstr) + + if tc.err != nil { + assert.EqualError(t, err, err.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tc.backupInfo, actual) + }) + } +} + +func getBackupInfoWithoutProcess(name string) *v1.SGBackupInfo { + b := getBackupInfo(name) + b.Process = runtime.RawExtension{} + return b +} + +func getBackupInfoWithoutBI(name string) *v1.SGBackupInfo { + b := getBackupInfo(name) + b.BackupInformation = runtime.RawExtension{} + return b +} + +func getUnstructuredObjectWithoutMeta(name string) *unstructured.Unstructured { + unstr := getUnstructuredObject(name) + unstructured.RemoveNestedField(unstr.Object, "metadata") + return unstr +} + +func getUnstructuredObjectWithoutProcess(name string) *unstructured.Unstructured { + unstr := getUnstructuredObject(name) + unstructured.RemoveNestedField(unstr.Object, "status", "process") + return unstr +} + +func getUnstructuredObjectWithoutBI(name string) *unstructured.Unstructured { + unstr := getUnstructuredObject(name) + unstructured.RemoveNestedField(unstr.Object, "status", "backupInformation") + return unstr +} + +func getBackupInfo(name string) *v1.SGBackupInfo { + return &v1.SGBackupInfo{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "namespaceOne", + }, + Process: runtime.RawExtension{Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "status": "Success", + "err": "", + }, + }}, + BackupInformation: runtime.RawExtension{Object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "memoryUsed": "1Gb", + "cpuUsed": "1", + "finished": true, + "paths": map[string]interface{}{ + "pathOne": "/path/to/one", + "pathTwo": "/path/to/two", + }, + }, + }}, + } +} + +func getUnstructuredObject(name string) *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": name, + "namespace": "namespaceOne", + }, + "status": map[string]interface{}{ + "process": map[string]interface{}{ + "status": "Success", + "err": "", + }, + "nonRelevantField": true, + "backupInformation": map[string]interface{}{ + "memoryUsed": "1Gb", + "cpuUsed": "1", + "finished": true, + "paths": map[string]interface{}{ + "pathOne": "/path/to/one", + "pathTwo": "/path/to/two", + }, + }, + }, + }, + } +} diff --git a/pkg/apiserver/vshn/postgres/table.go b/pkg/apiserver/vshn/postgres/table.go new file mode 100644 index 0000000..9d359ab --- /dev/null +++ b/pkg/apiserver/vshn/postgres/table.go @@ -0,0 +1,92 @@ +package postgres + +import ( + "context" + "fmt" + "time" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/duration" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.TableConvertor = &vshnPostgresBackupStorage{} + +// ConvertToTable translates the given object to a table for kubectl printing +func (v *vshnPostgresBackupStorage) ConvertToTable(_ context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + var table metav1.Table + + backups := []v1.VSHNPostgresBackup{} + if meta.IsListType(obj) { + backupList, ok := obj.(*v1.VSHNPostgresBackupList) + if !ok { + return nil, fmt.Errorf("not a vshn postgres backup: %#v", obj) + } + backups = backupList.Items + } else { + backup, ok := obj.(*v1.VSHNPostgresBackup) + if !ok { + return nil, fmt.Errorf("not a vshn postgres backup: %#v", obj) + } + backups = append(backups, *backup) + } + + for _, backup := range backups { + table.Rows = append(table.Rows, backupToTableRow(&backup)) + } + + if opt, ok := tableOptions.(*metav1.TableOptions); !ok || !opt.NoHeaders { + desc := metav1.ObjectMeta{}.SwaggerDoc() + table.ColumnDefinitions = []metav1.TableColumnDefinition{ + {Name: "Backup Name", Type: "string", Format: "name", Description: desc["name"]}, + {Name: "Database Instance", Type: "string", Description: "The database instance"}, + {Name: "Finished On", Type: "string", Description: "The data is available up to this time"}, + {Name: "Status", Type: "string", Description: "The state of this backup"}, + {Name: "Age", Type: "date", Description: desc["creationTimestamp"]}, + } + } + return &table, nil +} + +func backupToTableRow(backup *v1.VSHNPostgresBackup) metav1.TableRow { + return metav1.TableRow{ + Cells: []interface{}{ + backup.GetName(), + backup.Status.DatabaseInstance, + getEndTime(backup.Status.Process), + getProcessStatus(backup.Status.Process), + duration.HumanDuration(time.Since(backup.GetCreationTimestamp().Time))}, + Object: runtime.RawExtension{Object: backup}, + } +} + +func getEndTime(process *runtime.RawExtension) string { + if process != nil && process.Object != nil { + if v, err := runtime.DefaultUnstructuredConverter.ToUnstructured(process.Object); err == nil { + if endTime, exists, _ := unstructured.NestedString(v, v1.Timing, v1.End); exists { + return endTime + } + } + } + return "" +} + +func getProcessStatus(process *runtime.RawExtension) string { + if process != nil && process.Object != nil { + unstructuredProcess, err := runtime.DefaultUnstructuredConverter.ToUnstructured(process.Object) + if err != nil { + return "" + } + + status, ok, err := unstructured.NestedString(unstructuredProcess, v1.Status) + if err != nil || !ok { + return "" + } + return status + } + return "" +} diff --git a/pkg/apiserver/vshn/postgres/table_test.go b/pkg/apiserver/vshn/postgres/table_test.go new file mode 100644 index 0000000..f0ac8f0 --- /dev/null +++ b/pkg/apiserver/vshn/postgres/table_test.go @@ -0,0 +1,65 @@ +package postgres + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestVSHNPostgresBackupStorage_ConvertToTable(t *testing.T) { + tests := map[string]struct { + obj runtime.Object + tableOptions runtime.Object + fail bool + nrRows int + }{ + "GivenEmptyBackup_ThenSingleRow": { + obj: &v1.VSHNPostgresBackup{}, + nrRows: 1, + }, + "GivenBackup_ThenSingleRow": { + obj: vshnBackupOne, + nrRows: 1, + }, + "GivenBackupList_ThenMultipleRow": { + obj: &v1.VSHNPostgresBackupList{ + Items: []v1.VSHNPostgresBackup{ + {}, + {}, + {}, + }, + }, + nrRows: 3, + }, + "GivenNil_ThenFail": { + obj: nil, + fail: true, + }, + "GivenNonBackup_ThenFail": { + obj: &corev1.Pod{}, + fail: true, + }, + "GivenNonBackupList_ThenFail": { + obj: &corev1.PodList{}, + fail: true, + }, + } + backupStore := &vshnPostgresBackupStorage{} + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + table, err := backupStore.ConvertToTable(context.TODO(), tc.obj, tc.tableOptions) + if tc.fail { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Len(t, table.Rows, tc.nrRows) + }) + } +} diff --git a/pkg/apiserver/vshn/postgres/update.go b/pkg/apiserver/vshn/postgres/update.go new file mode 100644 index 0000000..108f609 --- /dev/null +++ b/pkg/apiserver/vshn/postgres/update.go @@ -0,0 +1,21 @@ +package postgres + +import ( + "context" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Updater = &vshnPostgresBackupStorage{} +var _ rest.CreaterUpdater = &vshnPostgresBackupStorage{} + +func (v vshnPostgresBackupStorage) Update(_ context.Context, name string, _ rest.UpdatedObjectInfo, _ rest.ValidateObjectFunc, _ rest.ValidateObjectUpdateFunc, _ bool, _ *metav1.UpdateOptions) (runtime.Object, bool, error) { + return &v1.VSHNPostgresBackup{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + }, false, nil +} diff --git a/pkg/apiserver/vshn/postgres/vshnpostgresql.go b/pkg/apiserver/vshn/postgres/vshnpostgresql.go new file mode 100644 index 0000000..4a09da6 --- /dev/null +++ b/pkg/apiserver/vshn/postgres/vshnpostgresql.go @@ -0,0 +1,39 @@ +package postgres + +import ( + "context" + + vshnv1 "github.com/vshn/appcat-apiserver/apis/vshn/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ( + claimNamespaceLabel = "crossplane.io/claim-namespace" + claimNameLabel = "crossplane.io/claim-name" +) + +// vshnPostgresqlProvider is an abstraction to interact with the K8s API +type vshnPostgresqlProvider interface { + ListXVSHNPostgreSQL(ctx context.Context, namespace string) (*vshnv1.XVSHNPostgreSQLList, error) +} + +type kubeXVSHNPostgresqlProvider struct { + client.Client +} + +// ListXVSHNPostgreSQL fetches a list of XVSHNPostgreSQL. +func (k *kubeXVSHNPostgresqlProvider) ListXVSHNPostgreSQL(ctx context.Context, namespace string) (*vshnv1.XVSHNPostgreSQLList, error) { + list := &vshnv1.XVSHNPostgreSQLList{} + err := k.Client.List(ctx, list) + cleanedList := make([]vshnv1.XVSHNPostgreSQL, 0) + for _, p := range list.Items { + if p.Labels[claimNamespaceLabel] == "" || p.Labels[claimNameLabel] == "" { + continue + } + if p.Labels[claimNamespaceLabel] == namespace { + cleanedList = append(cleanedList, p) + } + } + list.Items = cleanedList + return list, err +} diff --git a/pkg/apiserver/vshn/postgres/vshnpostgresql_test.go b/pkg/apiserver/vshn/postgres/vshnpostgresql_test.go new file mode 100644 index 0000000..65f73a1 --- /dev/null +++ b/pkg/apiserver/vshn/postgres/vshnpostgresql_test.go @@ -0,0 +1,131 @@ +package postgres + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + vshnv1 "github.com/vshn/appcat-apiserver/apis/vshn/v1" + "github.com/vshn/appcat-apiserver/test/mocks" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_ListXVSHNPostgreSQL(t *testing.T) { + tests := map[string]struct { + namespace string + postgresqls *vshnv1.XVSHNPostgreSQLList + expectedPostgresqls *vshnv1.XVSHNPostgreSQLList + }{ + "GivenAListOfPostgreSQLs_ThenFilter": { + namespace: "namespace-prod", + postgresqls: &vshnv1.XVSHNPostgreSQLList{ + Items: []vshnv1.XVSHNPostgreSQL{ + getInstance("prod", "namespace-prod"), + getInstance("prod-2", "namespace-prod-2"), + getInstanceWithoutLabels("prod-3"), + getInstanceWithoutLabels("prod"), + getInstanceWithoutClaimName("prod", "namespace-prod"), + getInstanceWithoutClaimName("prod-3", "namespace-prod-2"), + getInstanceWithoutClaimNamespace("prod"), + getInstanceWithoutClaimNamespace("prod-3"), + getInstance("test", "namespace-test-2"), + getInstance("test", "namespace-prod"), + }, + }, + expectedPostgresqls: &vshnv1.XVSHNPostgreSQLList{ + Items: []vshnv1.XVSHNPostgreSQL{ + getInstance("prod", "namespace-prod"), + getInstance("test", "namespace-prod"), + }, + }, + }, + "GivenAListOfPostgreSQLs_ThenFilter_2": { + namespace: "namespace-not-match", + postgresqls: &vshnv1.XVSHNPostgreSQLList{ + Items: []vshnv1.XVSHNPostgreSQL{ + getInstance("prod", "namespace-prod"), + getInstance("prod-2", "namespace-prod-2"), + getInstanceWithoutLabels("prod-3"), + getInstanceWithoutLabels("prod"), + getInstanceWithoutClaimName("prod", "namespace-prod"), + getInstanceWithoutClaimName("prod-3", "namespace-prod-2"), + getInstanceWithoutClaimNamespace("prod"), + getInstanceWithoutClaimNamespace("prod-3"), + getInstance("test", "namespace-test-2"), + getInstance("test", "namespace-prod"), + }, + }, + expectedPostgresqls: &vshnv1.XVSHNPostgreSQLList{ + Items: []vshnv1.XVSHNPostgreSQL{}, + }, + }, + } + + for n, tc := range tests { + t.Run(n, func(t *testing.T) { + + // GIVEN + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := mocks.NewMockClient(ctrl) + provider := kubeXVSHNPostgresqlProvider{ + client, + } + + client.EXPECT(). + List(gomock.Any(), gomock.Any()). + SetArg(1, *tc.postgresqls). + Times(1) + + // WHEN + instances, err := provider.ListXVSHNPostgreSQL(context.Background(), tc.namespace) + + // THEN + assert.NoError(t, err) + assert.Equal(t, tc.expectedPostgresqls, instances) + }) + } +} + +func getInstanceWithoutClaimName(name, namespace string) vshnv1.XVSHNPostgreSQL { + return vshnv1.XVSHNPostgreSQL{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-tty", + Labels: map[string]string{ + claimNamespaceLabel: namespace, + }, + }, + } +} + +func getInstanceWithoutClaimNamespace(name string) vshnv1.XVSHNPostgreSQL { + return vshnv1.XVSHNPostgreSQL{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-tty", + Labels: map[string]string{ + claimNameLabel: name, + }, + }, + } +} + +func getInstanceWithoutLabels(name string) vshnv1.XVSHNPostgreSQL { + return vshnv1.XVSHNPostgreSQL{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-tty", + }, + } +} + +func getInstance(name, namespace string) vshnv1.XVSHNPostgreSQL { + return vshnv1.XVSHNPostgreSQL{ + ObjectMeta: metav1.ObjectMeta{ + Name: name + "-tty", + Labels: map[string]string{ + claimNameLabel: name, + claimNamespaceLabel: namespace, + }, + }, + } +} diff --git a/pkg/apiserver/vshn/redis/backup.go b/pkg/apiserver/vshn/redis/backup.go new file mode 100644 index 0000000..cd90bfc --- /dev/null +++ b/pkg/apiserver/vshn/redis/backup.go @@ -0,0 +1,73 @@ +package redis + +import ( + k8upv1 "github.com/k8up-io/k8up/v2/api/v1" + appcatv1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "github.com/vshn/appcat-apiserver/pkg/apiserver/vshn/k8up" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + genericregistry "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" + restbuilder "sigs.k8s.io/apiserver-runtime/pkg/builder/rest" + "sigs.k8s.io/apiserver-runtime/pkg/util/loopback" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ rest.Scoper = &vshnRedisBackupStorage{} +var _ rest.Storage = &vshnRedisBackupStorage{} + +type vshnRedisBackupStorage struct { + snapshothandler k8up.Snapshothandler + vshnRedis vshnRedisProvider +} + +// New returns a new resthandler for Redis backups. +func New() restbuilder.ResourceHandlerProvider { + return func(s *runtime.Scheme, gasdf genericregistry.RESTOptionsGetter) (rest.Storage, error) { + c, err := client.NewWithWatch(loopback.GetLoopbackMasterClientConfig(), client.Options{}) + if err != nil { + return nil, err + } + + _ = k8upv1.AddToScheme(c.Scheme()) + + return &vshnRedisBackupStorage{ + snapshothandler: k8up.New(c), + vshnRedis: &concreteRedisProvider{ + client: c, + }, + }, nil + } +} + +func (v vshnRedisBackupStorage) New() runtime.Object { + return &appcatv1.VSHNRedisBackup{} +} + +func (v vshnRedisBackupStorage) Destroy() {} + +func (v *vshnRedisBackupStorage) NamespaceScoped() bool { + return true +} + +func trimStringLength(in string) string { + length := len(in) + if length > 8 { + length = 8 + } + return in[:length] +} + +func deRefString(in *string) string { + if in == nil { + return "" + } + return *in +} + +func deRefMetaTime(in *metav1.Time) metav1.Time { + if in == nil { + return metav1.Now() + } + return *in +} diff --git a/pkg/apiserver/vshn/redis/common_test.go b/pkg/apiserver/vshn/redis/common_test.go new file mode 100644 index 0000000..39ce918 --- /dev/null +++ b/pkg/apiserver/vshn/redis/common_test.go @@ -0,0 +1,64 @@ +package redis + +import ( + "context" + + k8upv1 "github.com/k8up-io/k8up/v2/api/v1" + vshnv1 "github.com/vshn/appcat-apiserver/apis/vshn/v1" + "github.com/vshn/appcat-apiserver/pkg/apiserver/vshn/k8up" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + "k8s.io/apimachinery/pkg/watch" +) + +var _ vshnRedisProvider = &mockprovider{} +var _ k8up.Snapshothandler = &mockhandler{} + +type mockprovider struct { + err error + instances *vshnv1.VSHNRedisList +} + +func (m *mockprovider) ListVSHNRedis(ctx context.Context, namespace string) (*vshnv1.VSHNRedisList, error) { + + instances := &vshnv1.VSHNRedisList{ + Items: []vshnv1.VSHNRedis{}, + } + + for _, instance := range m.instances.Items { + if instance.GetNamespace() == namespace { + instances.Items = append(instances.Items, instance) + } + } + + return instances, m.err +} + +type mockhandler struct { + snapshot *k8upv1.Snapshot + snapshots *k8upv1.SnapshotList +} + +func (m *mockhandler) Get(ctx context.Context, id, instanceNamespace string) (*k8upv1.Snapshot, error) { + return m.snapshot, nil +} + +func (m *mockhandler) List(ctx context.Context, instanceNamespace string) (*k8upv1.SnapshotList, error) { + + snapshots := &k8upv1.SnapshotList{ + Items: []k8upv1.Snapshot{}, + } + + for _, snap := range m.snapshots.Items { + if snap.GetNamespace() == instanceNamespace { + snapshots.Items = append(snapshots.Items, snap) + } + } + + return snapshots, nil +} +func (m *mockhandler) Watch(ctx context.Context, namespace string, options *metainternalversion.ListOptions) (watch.Interface, error) { + return nil, nil +} +func (m *mockhandler) GetFromEvent(in watch.Event) (*k8upv1.Snapshot, error) { + return nil, nil +} diff --git a/pkg/apiserver/vshn/redis/create.go b/pkg/apiserver/vshn/redis/create.go new file mode 100644 index 0000000..b64d8d0 --- /dev/null +++ b/pkg/apiserver/vshn/redis/create.go @@ -0,0 +1,16 @@ +package redis + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Creater = &vshnRedisBackupStorage{} + +func (v vshnRedisBackupStorage) Create(_ context.Context, _ runtime.Object, _ rest.ValidateObjectFunc, _ *metav1.CreateOptions) (runtime.Object, error) { + return nil, fmt.Errorf("method not implemented") +} diff --git a/pkg/apiserver/vshn/redis/delete.go b/pkg/apiserver/vshn/redis/delete.go new file mode 100644 index 0000000..5a6d603 --- /dev/null +++ b/pkg/apiserver/vshn/redis/delete.go @@ -0,0 +1,29 @@ +package redis + +import ( + "context" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.GracefulDeleter = &vshnRedisBackupStorage{} +var _ rest.CollectionDeleter = &vshnRedisBackupStorage{} + +func (v vshnRedisBackupStorage) Delete(_ context.Context, name string, _ rest.ValidateObjectFunc, _ *metav1.DeleteOptions) (runtime.Object, bool, error) { + return &v1.VSHNRedisBackup{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + }, false, nil +} + +func (v *vshnRedisBackupStorage) DeleteCollection(ctx context.Context, _ rest.ValidateObjectFunc, _ *metav1.DeleteOptions, _ *metainternalversion.ListOptions) (runtime.Object, error) { + return &v1.VSHNRedisBackupList{ + Items: []v1.VSHNRedisBackup{}, + }, nil +} diff --git a/pkg/apiserver/vshn/redis/get.go b/pkg/apiserver/vshn/redis/get.go new file mode 100644 index 0000000..5751b16 --- /dev/null +++ b/pkg/apiserver/vshn/redis/get.go @@ -0,0 +1,55 @@ +package redis + +import ( + "context" + "fmt" + + appcatv1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Getter = &vshnRedisBackupStorage{} + +func (v *vshnRedisBackupStorage) Get(ctx context.Context, name string, opts *metav1.GetOptions) (runtime.Object, error) { + + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, fmt.Errorf("cannot get namespace from context") + } + + instances, err := v.vshnRedis.ListVSHNRedis(ctx, namespace) + if err != nil { + return nil, err + } + + redisSnap := &appcatv1.VSHNRedisBackup{} + + for _, instance := range instances.Items { + ins := instance.Status.InstanceNamespace + snap, err := v.snapshothandler.Get(ctx, name, ins) + if err != nil { + if apierrors.IsNotFound(err) { + continue + } + return nil, err + } + + backupMeta := snap.ObjectMeta + backupMeta.Namespace = instance.GetNamespace() + + redisSnap = &appcatv1.VSHNRedisBackup{ + ObjectMeta: backupMeta, + Status: appcatv1.VSHNRedisBackupStatus{ + ID: deRefString(snap.Spec.ID), + Date: deRefMetaTime(snap.Spec.Date), + Instance: instance.GetName(), + }, + } + } + + return redisSnap, nil +} diff --git a/pkg/apiserver/vshn/redis/get_test.go b/pkg/apiserver/vshn/redis/get_test.go new file mode 100644 index 0000000..174ef8e --- /dev/null +++ b/pkg/apiserver/vshn/redis/get_test.go @@ -0,0 +1,110 @@ +package redis + +import ( + "context" + "testing" + "time" + + k8upv1 "github.com/k8up-io/k8up/v2/api/v1" + "github.com/stretchr/testify/assert" + appcatv1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + vshnv1 "github.com/vshn/appcat-apiserver/apis/vshn/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/utils/pointer" +) + +func Test_vshnRedisBackupStorage_Get(t *testing.T) { + tests := []struct { + name string + instanceName string + instances *vshnv1.VSHNRedisList + snapshot *k8upv1.Snapshot + wantDate metav1.Time + wantNs string + want runtime.Object + wantErr bool + }{ + { + name: "GivenExistingBackup_ThenExpectBackup", + instances: &vshnv1.VSHNRedisList{ + Items: []vshnv1.VSHNRedis{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "myinstance", + Namespace: "ns1", + }, + Status: vshnv1.VSHNRedisStatus{ + InstanceNamespace: "ins1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "myinstance", + Namespace: "ns2", + }, + Status: vshnv1.VSHNRedisStatus{ + InstanceNamespace: "ins2", + }, + }, + }, + }, + snapshot: &k8upv1.Snapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myid", + Namespace: "ns1", + }, + Spec: k8upv1.SnapshotSpec{ + ID: pointer.String("myid"), + }, + }, + wantDate: metav1.Date(2023, time.April, 3, 13, 37, 0, 0, time.UTC), + wantNs: "ns1", + want: &appcatv1.VSHNRedisBackup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myid", + Namespace: "ns1", + }, + Status: appcatv1.VSHNRedisBackupStatus{ + ID: "myid", + Instance: "myinstance", + Date: metav1.Date(2023, time.April, 3, 13, 37, 0, 0, time.UTC), + }, + }, + }, + { + name: "GivenNoBackup_ThenExpectEmptyObjects", + instances: &vshnv1.VSHNRedisList{}, + snapshot: nil, + want: &appcatv1.VSHNRedisBackup{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + ctx := request.WithNamespace(context.TODO(), tt.wantNs) + + if tt.snapshot != nil { + tt.snapshot.Spec.Date = &tt.wantDate + } + + v := &vshnRedisBackupStorage{ + vshnRedis: &mockprovider{ + instances: tt.instances, + }, + snapshothandler: &mockhandler{ + snapshot: tt.snapshot, + }, + } + got, err := v.Get(ctx, tt.instanceName, &metav1.GetOptions{}) + if (err != nil) != tt.wantErr { + t.Errorf("vshnRedisBackupStorage.Get() error = %v, wantErr %v", err, tt.wantErr) + return + } + + assert.Equal(t, tt.want, got) + + }) + } +} diff --git a/pkg/apiserver/vshn/redis/list.go b/pkg/apiserver/vshn/redis/list.go new file mode 100644 index 0000000..91b7203 --- /dev/null +++ b/pkg/apiserver/vshn/redis/list.go @@ -0,0 +1,61 @@ +package redis + +import ( + "context" + "fmt" + + appcatv1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Lister = &vshnRedisBackupStorage{} + +func (v *vshnRedisBackupStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { + + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, fmt.Errorf("cannot get namespace from resource") + } + + instances, err := v.vshnRedis.ListVSHNRedis(ctx, namespace) + if err != nil { + return nil, err + } + + redisSnapshots := &appcatv1.VSHNRedisBackupList{ + Items: []appcatv1.VSHNRedisBackup{}, + } + + for _, instance := range instances.Items { + snapshots, err := v.snapshothandler.List(ctx, instance.Status.InstanceNamespace) + if err != nil && !apierrors.IsNotFound(err) { + return nil, err + } + + for _, snap := range snapshots.Items { + + backupMeta := snap.ObjectMeta + backupMeta.Namespace = instance.GetNamespace() + + redisSnapshots.Items = append(redisSnapshots.Items, appcatv1.VSHNRedisBackup{ + ObjectMeta: backupMeta, + Status: appcatv1.VSHNRedisBackupStatus{ + ID: deRefString(snap.Spec.ID), + Date: deRefMetaTime(snap.Spec.Date), + Instance: instance.GetName(), + }, + }) + } + + } + + return redisSnapshots, nil +} + +func (v *vshnRedisBackupStorage) NewList() runtime.Object { + return &appcatv1.VSHNRedisBackupList{} +} diff --git a/pkg/apiserver/vshn/redis/list_test.go b/pkg/apiserver/vshn/redis/list_test.go new file mode 100644 index 0000000..6113c7c --- /dev/null +++ b/pkg/apiserver/vshn/redis/list_test.go @@ -0,0 +1,154 @@ +package redis + +import ( + "context" + "testing" + "time" + + k8upv1 "github.com/k8up-io/k8up/v2/api/v1" + "github.com/stretchr/testify/assert" + appcatv1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + vshnv1 "github.com/vshn/appcat-apiserver/apis/vshn/v1" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/utils/pointer" +) + +func Test_vshnRedisBackupStorage_List(t *testing.T) { + tests := []struct { + name string + instances *vshnv1.VSHNRedisList + snapshots *k8upv1.SnapshotList + wantNs string + want runtime.Object + wantDate metav1.Time + wantErr bool + }{ + { + name: "GivenAvailableBackups_ThenExpectBackupList", + instances: &vshnv1.VSHNRedisList{ + Items: []vshnv1.VSHNRedis{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "myinstance", + Namespace: "ns1", + }, + Status: vshnv1.VSHNRedisStatus{ + InstanceNamespace: "ins1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "mysecondinstance", + Namespace: "ns1", + }, + Status: vshnv1.VSHNRedisStatus{ + InstanceNamespace: "ins2", + }, + }, + }, + }, + snapshots: &k8upv1.SnapshotList{ + Items: []k8upv1.Snapshot{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "snap1", + Namespace: "ins1", + }, + Spec: k8upv1.SnapshotSpec{ + ID: pointer.String("snap1"), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "snap2", + Namespace: "ins2", + }, + Spec: k8upv1.SnapshotSpec{ + ID: pointer.String("snap2"), + }, + }, + }, + }, + wantNs: "ns1", + wantDate: metav1.Date(2023, time.April, 3, 13, 37, 0, 0, time.UTC), + want: &appcatv1.VSHNRedisBackupList{ + Items: []appcatv1.VSHNRedisBackup{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "snap1", + Namespace: "ns1", + }, + Status: appcatv1.VSHNRedisBackupStatus{ + ID: "snap1", + Instance: "myinstance", + Date: metav1.Date(2023, time.April, 3, 13, 37, 0, 0, time.UTC), + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "snap2", + Namespace: "ns1", + }, + Status: appcatv1.VSHNRedisBackupStatus{ + ID: "snap2", + Instance: "mysecondinstance", + Date: metav1.Date(2023, time.April, 3, 13, 37, 0, 0, time.UTC), + }, + }, + }, + }, + }, + { + name: "GivenNobackups_ThenExpectEmptyList", + instances: &vshnv1.VSHNRedisList{ + Items: []vshnv1.VSHNRedis{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "myinstance", + Namespace: "ns1", + }, + Status: vshnv1.VSHNRedisStatus{ + InstanceNamespace: "ins1", + }, + }, + }, + }, + snapshots: &k8upv1.SnapshotList{ + Items: []k8upv1.Snapshot{}, + }, + wantNs: "ns1", + want: &appcatv1.VSHNRedisBackupList{ + Items: []appcatv1.VSHNRedisBackup{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + ctx := request.WithNamespace(context.TODO(), tt.wantNs) + + for i := range tt.snapshots.Items { + tt.snapshots.Items[i].Spec.Date = &tt.wantDate + } + + v := &vshnRedisBackupStorage{ + snapshothandler: &mockhandler{ + snapshots: tt.snapshots, + }, + vshnRedis: &mockprovider{ + instances: tt.instances, + }, + } + got, err := v.List(ctx, &internalversion.ListOptions{}) + if (err != nil) != tt.wantErr { + t.Errorf("vshnRedisBackupStorage.List() error = %v, wantErr %v", err, tt.wantErr) + return + } + + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/apiserver/vshn/redis/table.go b/pkg/apiserver/vshn/redis/table.go new file mode 100644 index 0000000..9d6c1e7 --- /dev/null +++ b/pkg/apiserver/vshn/redis/table.go @@ -0,0 +1,59 @@ +package redis + +import ( + "context" + "fmt" + + appcatv1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.TableConvertor = &vshnRedisBackupStorage{} + +func (v *vshnRedisBackupStorage) ConvertToTable(_ context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + + table := &metav1.Table{} + + backups := []appcatv1.VSHNRedisBackup{} + if meta.IsListType(obj) { + backupList, ok := obj.(*appcatv1.VSHNRedisBackupList) + if !ok { + return nil, fmt.Errorf("not a vshn redis backup: %#v", obj) + } + backups = backupList.Items + } else { + backup, ok := obj.(*appcatv1.VSHNRedisBackup) + if !ok { + return nil, fmt.Errorf("not a vshn redis backup: %#v", obj) + } + backups = append(backups, *backup) + } + + for i := range backups { + table.Rows = append(table.Rows, backupToTableRow(&backups[i])) + } + + if opt, ok := tableOptions.(*metav1.TableOptions); !ok || !opt.NoHeaders { + table.ColumnDefinitions = []metav1.TableColumnDefinition{ + {Name: "Backup ID", Type: "string", Format: "name", Description: "ID of the snapshot"}, + {Name: "Database Instance", Type: "string", Description: "The redis instance"}, + {Name: "Backup Time", Type: "string", Description: "When backup was made"}, + } + } + + return table, nil +} + +func backupToTableRow(backup *appcatv1.VSHNRedisBackup) metav1.TableRow { + + return metav1.TableRow{ + Cells: []interface{}{ + trimStringLength(backup.Status.ID), + backup.Status.Instance, + backup.Status.Date}, + Object: runtime.RawExtension{Object: backup}, + } +} diff --git a/pkg/apiserver/vshn/redis/table_test.go b/pkg/apiserver/vshn/redis/table_test.go new file mode 100644 index 0000000..58fba4e --- /dev/null +++ b/pkg/apiserver/vshn/redis/table_test.go @@ -0,0 +1,109 @@ +package redis + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + vshnv1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func Test_vshnRedisBackupStorage_ConvertToTable(t *testing.T) { + tests := []struct { + name string + obj runtime.Object + tableOptions runtime.Object + wantRows int + wantErr bool + }{ + { + name: "GivenOneBackup_ThenExpectOneRow", + obj: &vshnv1.VSHNRedisBackup{}, + wantRows: 1, + }, + { + name: "GivenMiltipleBackups_ThenExpectMultipleRows", + obj: &vshnv1.VSHNRedisBackupList{ + Items: []vshnv1.VSHNRedisBackup{ + {}, + {}, + {}, + }, + }, + wantRows: 3, + }, + { + name: "GivenNoBackupObject_ThenExpectError", + obj: &vshnv1.AppCat{}, + wantErr: true, + }, + { + name: "GivenNil_ThenExpectError", + obj: nil, + wantErr: true, + }, + { + name: "GivenNonBackupList_THenExpectError", + obj: &vshnv1.VSHNPostgresBackupList{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := &vshnRedisBackupStorage{} + got, err := v.ConvertToTable(context.TODO(), tt.obj, tt.tableOptions) + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + + assert.Len(t, got.Rows, tt.wantRows) + }) + } +} + +func Test_vshnRedisBackupStorage_ConvertToTable_noduplicate(t *testing.T) { + v := &vshnRedisBackupStorage{} + obj := vshnv1.VSHNRedisBackupList{ + Items: []vshnv1.VSHNRedisBackup{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "foo1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "foo2", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "foo3", + }, + }, + }, + } + got, err := v.ConvertToTable(context.TODO(), &obj, nil) + assert.NoError(t, err) + assert.Len(t, got.Rows, 3) + + foo1, ok := got.Rows[0].Object.Object.(*vshnv1.VSHNRedisBackup) + if assert.True(t, ok, "unexpected type for foo1") { + assert.Equal(t, "foo1", foo1.Namespace) + } + foo2, ok := got.Rows[1].Object.Object.(*vshnv1.VSHNRedisBackup) + if assert.True(t, ok, "unexpected type for foo1") { + assert.Equal(t, "foo2", foo2.Namespace) + } + foo3, ok := got.Rows[2].Object.Object.(*vshnv1.VSHNRedisBackup) + if assert.True(t, ok, "unexpected type for foo1") { + assert.Equal(t, "foo3", foo3.Namespace) + } + +} diff --git a/pkg/apiserver/vshn/redis/update.go b/pkg/apiserver/vshn/redis/update.go new file mode 100644 index 0000000..baa52f1 --- /dev/null +++ b/pkg/apiserver/vshn/redis/update.go @@ -0,0 +1,18 @@ +package redis + +import ( + "context" + "fmt" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Updater = &vshnRedisBackupStorage{} +var _ rest.CreaterUpdater = &vshnRedisBackupStorage{} + +func (v vshnRedisBackupStorage) Update(_ context.Context, name string, _ rest.UpdatedObjectInfo, _ rest.ValidateObjectFunc, _ rest.ValidateObjectUpdateFunc, _ bool, _ *metav1.UpdateOptions) (runtime.Object, bool, error) { + return &v1.VSHNPostgresBackup{}, false, fmt.Errorf("method not implemented") +} diff --git a/pkg/apiserver/vshn/redis/vshnredis.go b/pkg/apiserver/vshn/redis/vshnredis.go new file mode 100644 index 0000000..7d73369 --- /dev/null +++ b/pkg/apiserver/vshn/redis/vshnredis.go @@ -0,0 +1,28 @@ +package redis + +import ( + "context" + + vshnv1 "github.com/vshn/appcat-apiserver/apis/vshn/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type vshnRedisProvider interface { + ListVSHNRedis(ctx context.Context, namespace string) (*vshnv1.VSHNRedisList, error) +} + +type concreteRedisProvider struct { + client client.Client +} + +func (c *concreteRedisProvider) ListVSHNRedis(ctx context.Context, namespace string) (*vshnv1.VSHNRedisList, error) { + + instances := &vshnv1.VSHNRedisList{} + + err := c.client.List(ctx, instances, &client.ListOptions{Namespace: namespace}) + if err != nil { + return nil, err + } + + return instances, nil +} diff --git a/pkg/apiserver/vshn/redis/watch.go b/pkg/apiserver/vshn/redis/watch.go new file mode 100644 index 0000000..5ce2a0f --- /dev/null +++ b/pkg/apiserver/vshn/redis/watch.go @@ -0,0 +1,76 @@ +package redis + +import ( + "context" + "fmt" + + appcatv1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "github.com/vshn/appcat-apiserver/pkg/apiserver" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Watcher = &vshnRedisBackupStorage{} + +func (v *vshnRedisBackupStorage) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { + namespace, ok := request.NamespaceFrom(ctx) + if !ok { + return nil, fmt.Errorf("cannot get namespace from resource") + } + + instances, err := v.vshnRedis.ListVSHNRedis(ctx, namespace) + if err != nil { + return nil, fmt.Errorf("cannot list VSHNPostgreSQL instances") + } + + mw := apiserver.NewEmptyMultiWatch() + for _, value := range instances.Items { + backupWatcher, err := v.snapshothandler.Watch(ctx, value.Status.InstanceNamespace, options) + if err != nil { + return nil, apiserver.ResolveError(v1.GetGroupResource(v1.ResourceBackup), err) + } + mw.AddWatcher(backupWatcher) + } + + return watch.Filter(mw, func(in watch.Event) (out watch.Event, keep bool) { + if in.Object == nil { + // This should never happen, let downstream deal with it + return in, true + } + + backupInfo, err := v.snapshothandler.GetFromEvent(in) + if err != nil { + return in, false + } + + db := "" + namespace := "" + for _, value := range instances.Items { + if value.Status.InstanceNamespace == backupInfo.GetNamespace() { + db = value.GetName() + namespace = value.GetNamespace() + } + } + + if db == "" { + return in, false + } + + backupMeta := backupInfo.ObjectMeta + backupMeta.Namespace = namespace + + in.Object = &appcatv1.VSHNRedisBackup{ + ObjectMeta: backupMeta, + Status: v1.VSHNRedisBackupStatus{ + ID: deRefString(backupInfo.Spec.ID), + Date: deRefMetaTime(backupInfo.Spec.Date), + Instance: db, + }, + } + + return in, true + }), nil +} diff --git a/test/mocks/mock_client.go b/test/mocks/mock_client.go new file mode 100644 index 0000000..eaf344a --- /dev/null +++ b/test/mocks/mock_client.go @@ -0,0 +1,259 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: sigs.k8s.io/controller-runtime/pkg/client (interfaces: Client) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + meta "k8s.io/apimachinery/pkg/api/meta" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MockClient is a mock of Client interface. +type MockClient struct { + ctrl *gomock.Controller + recorder *MockClientMockRecorder +} + +// MockClientMockRecorder is the mock recorder for MockClient. +type MockClientMockRecorder struct { + mock *MockClient +} + +// NewMockClient creates a new mock instance. +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &MockClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClient) EXPECT() *MockClientMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockClient) Create(arg0 context.Context, arg1 client.Object, arg2 ...client.CreateOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockClientMockRecorder) Create(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockClient)(nil).Create), varargs...) +} + +// Delete mocks base method. +func (m *MockClient) Delete(arg0 context.Context, arg1 client.Object, arg2 ...client.DeleteOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockClientMockRecorder) Delete(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClient)(nil).Delete), varargs...) +} + +// DeleteAllOf mocks base method. +func (m *MockClient) DeleteAllOf(arg0 context.Context, arg1 client.Object, arg2 ...client.DeleteAllOfOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteAllOf", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAllOf indicates an expected call of DeleteAllOf. +func (mr *MockClientMockRecorder) DeleteAllOf(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockClient)(nil).DeleteAllOf), varargs...) +} + +// Get mocks base method. +func (m *MockClient) Get(arg0 context.Context, arg1 types.NamespacedName, arg2 client.Object, arg3 ...client.GetOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Get", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Get indicates an expected call of Get. +func (mr *MockClientMockRecorder) Get(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), varargs...) +} + +// GroupVersionKindFor mocks base method. +func (m *MockClient) GroupVersionKindFor(arg0 runtime.Object) (schema.GroupVersionKind, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GroupVersionKindFor", arg0) + ret0, _ := ret[0].(schema.GroupVersionKind) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GroupVersionKindFor indicates an expected call of GroupVersionKindFor. +func (mr *MockClientMockRecorder) GroupVersionKindFor(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupVersionKindFor", reflect.TypeOf((*MockClient)(nil).GroupVersionKindFor), arg0) +} + +// IsObjectNamespaced mocks base method. +func (m *MockClient) IsObjectNamespaced(arg0 runtime.Object) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsObjectNamespaced", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsObjectNamespaced indicates an expected call of IsObjectNamespaced. +func (mr *MockClientMockRecorder) IsObjectNamespaced(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsObjectNamespaced", reflect.TypeOf((*MockClient)(nil).IsObjectNamespaced), arg0) +} + +// List mocks base method. +func (m *MockClient) List(arg0 context.Context, arg1 client.ObjectList, arg2 ...client.ListOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// List indicates an expected call of List. +func (mr *MockClientMockRecorder) List(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockClient)(nil).List), varargs...) +} + +// Patch mocks base method. +func (m *MockClient) Patch(arg0 context.Context, arg1 client.Object, arg2 client.Patch, arg3 ...client.PatchOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2} + for _, a := range arg3 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Patch", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Patch indicates an expected call of Patch. +func (mr *MockClientMockRecorder) Patch(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2}, arg3...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockClient)(nil).Patch), varargs...) +} + +// RESTMapper mocks base method. +func (m *MockClient) RESTMapper() meta.RESTMapper { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RESTMapper") + ret0, _ := ret[0].(meta.RESTMapper) + return ret0 +} + +// RESTMapper indicates an expected call of RESTMapper. +func (mr *MockClientMockRecorder) RESTMapper() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTMapper", reflect.TypeOf((*MockClient)(nil).RESTMapper)) +} + +// Scheme mocks base method. +func (m *MockClient) Scheme() *runtime.Scheme { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Scheme") + ret0, _ := ret[0].(*runtime.Scheme) + return ret0 +} + +// Scheme indicates an expected call of Scheme. +func (mr *MockClientMockRecorder) Scheme() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scheme", reflect.TypeOf((*MockClient)(nil).Scheme)) +} + +// Status mocks base method. +func (m *MockClient) Status() client.SubResourceWriter { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Status") + ret0, _ := ret[0].(client.SubResourceWriter) + return ret0 +} + +// Status indicates an expected call of Status. +func (mr *MockClientMockRecorder) Status() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status)) +} + +// SubResource mocks base method. +func (m *MockClient) SubResource(arg0 string) client.SubResourceClient { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubResource", arg0) + ret0, _ := ret[0].(client.SubResourceClient) + return ret0 +} + +// SubResource indicates an expected call of SubResource. +func (mr *MockClientMockRecorder) SubResource(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubResource", reflect.TypeOf((*MockClient)(nil).SubResource), arg0) +} + +// Update mocks base method. +func (m *MockClient) Update(arg0 context.Context, arg1 client.Object, arg2 ...client.UpdateOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockClientMockRecorder) Update(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockClient)(nil).Update), varargs...) +} diff --git a/test/mocks/mock_composition.go b/test/mocks/mock_composition.go new file mode 100644 index 0000000..5280ff4 --- /dev/null +++ b/test/mocks/mock_composition.go @@ -0,0 +1,84 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ../pkg/apiserver/appcat/composition.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + v1 "github.com/crossplane/crossplane/apis/apiextensions/v1" + gomock "github.com/golang/mock/gomock" + internalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + v10 "k8s.io/apimachinery/pkg/apis/meta/v1" + watch "k8s.io/apimachinery/pkg/watch" +) + +// MockcompositionProvider is a mock of compositionProvider interface. +type MockcompositionProvider struct { + ctrl *gomock.Controller + recorder *MockcompositionProviderMockRecorder +} + +// MockcompositionProviderMockRecorder is the mock recorder for MockcompositionProvider. +type MockcompositionProviderMockRecorder struct { + mock *MockcompositionProvider +} + +// NewMockcompositionProvider creates a new mock instance. +func NewMockcompositionProvider(ctrl *gomock.Controller) *MockcompositionProvider { + mock := &MockcompositionProvider{ctrl: ctrl} + mock.recorder = &MockcompositionProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockcompositionProvider) EXPECT() *MockcompositionProviderMockRecorder { + return m.recorder +} + +// GetComposition mocks base method. +func (m *MockcompositionProvider) GetComposition(ctx context.Context, name string, options *v10.GetOptions) (*v1.Composition, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetComposition", ctx, name, options) + ret0, _ := ret[0].(*v1.Composition) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetComposition indicates an expected call of GetComposition. +func (mr *MockcompositionProviderMockRecorder) GetComposition(ctx, name, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetComposition", reflect.TypeOf((*MockcompositionProvider)(nil).GetComposition), ctx, name, options) +} + +// ListCompositions mocks base method. +func (m *MockcompositionProvider) ListCompositions(ctx context.Context, options *internalversion.ListOptions) (*v1.CompositionList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListCompositions", ctx, options) + ret0, _ := ret[0].(*v1.CompositionList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListCompositions indicates an expected call of ListCompositions. +func (mr *MockcompositionProviderMockRecorder) ListCompositions(ctx, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListCompositions", reflect.TypeOf((*MockcompositionProvider)(nil).ListCompositions), ctx, options) +} + +// WatchCompositions mocks base method. +func (m *MockcompositionProvider) WatchCompositions(ctx context.Context, options *internalversion.ListOptions) (watch.Interface, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WatchCompositions", ctx, options) + ret0, _ := ret[0].(watch.Interface) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WatchCompositions indicates an expected call of WatchCompositions. +func (mr *MockcompositionProviderMockRecorder) WatchCompositions(ctx, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WatchCompositions", reflect.TypeOf((*MockcompositionProvider)(nil).WatchCompositions), ctx, options) +} diff --git a/test/mocks/mock_sgbackups.go b/test/mocks/mock_sgbackups.go new file mode 100644 index 0000000..3c9e103 --- /dev/null +++ b/test/mocks/mock_sgbackups.go @@ -0,0 +1,83 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ../pkg/apiserver/vshn/postgres/sgbackups.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + internalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + watch "k8s.io/apimachinery/pkg/watch" +) + +// MocksgbackupProvider is a mock of sgbackupProvider interface. +type MocksgbackupProvider struct { + ctrl *gomock.Controller + recorder *MocksgbackupProviderMockRecorder +} + +// MocksgbackupProviderMockRecorder is the mock recorder for MocksgbackupProvider. +type MocksgbackupProviderMockRecorder struct { + mock *MocksgbackupProvider +} + +// NewMocksgbackupProvider creates a new mock instance. +func NewMocksgbackupProvider(ctrl *gomock.Controller) *MocksgbackupProvider { + mock := &MocksgbackupProvider{ctrl: ctrl} + mock.recorder = &MocksgbackupProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MocksgbackupProvider) EXPECT() *MocksgbackupProviderMockRecorder { + return m.recorder +} + +// GetSGBackup mocks base method. +func (m *MocksgbackupProvider) GetSGBackup(ctx context.Context, name, namespace string) (*v1.SGBackupInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSGBackup", ctx, name, namespace) + ret0, _ := ret[0].(*v1.SGBackupInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSGBackup indicates an expected call of GetSGBackup. +func (mr *MocksgbackupProviderMockRecorder) GetSGBackup(ctx, name, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSGBackup", reflect.TypeOf((*MocksgbackupProvider)(nil).GetSGBackup), ctx, name, namespace) +} + +// ListSGBackup mocks base method. +func (m *MocksgbackupProvider) ListSGBackup(ctx context.Context, namespace string, options *internalversion.ListOptions) (*[]v1.SGBackupInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListSGBackup", ctx, namespace, options) + ret0, _ := ret[0].(*[]v1.SGBackupInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListSGBackup indicates an expected call of ListSGBackup. +func (mr *MocksgbackupProviderMockRecorder) ListSGBackup(ctx, namespace, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSGBackup", reflect.TypeOf((*MocksgbackupProvider)(nil).ListSGBackup), ctx, namespace, options) +} + +// WatchSGBackup mocks base method. +func (m *MocksgbackupProvider) WatchSGBackup(ctx context.Context, namespace string, options *internalversion.ListOptions) (watch.Interface, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WatchSGBackup", ctx, namespace, options) + ret0, _ := ret[0].(watch.Interface) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WatchSGBackup indicates an expected call of WatchSGBackup. +func (mr *MocksgbackupProviderMockRecorder) WatchSGBackup(ctx, namespace, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WatchSGBackup", reflect.TypeOf((*MocksgbackupProvider)(nil).WatchSGBackup), ctx, namespace, options) +} diff --git a/test/mocks/mock_vshnpostgresqls.go b/test/mocks/mock_vshnpostgresqls.go new file mode 100644 index 0000000..416a62d --- /dev/null +++ b/test/mocks/mock_vshnpostgresqls.go @@ -0,0 +1,51 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ../pkg/apiserver/vshn/postgres/vshnpostgresql.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + vshnv1 "github.com/vshn/appcat-apiserver/apis/vshn/v1" +) + +// MockvshnPostgresqlProvider is a mock of vshnPostgresqlProvider interface. +type MockvshnPostgresqlProvider struct { + ctrl *gomock.Controller + recorder *MockvshnPostgresqlProviderMockRecorder +} + +// MockvshnPostgresqlProviderMockRecorder is the mock recorder for MockvshnPostgresqlProvider. +type MockvshnPostgresqlProviderMockRecorder struct { + mock *MockvshnPostgresqlProvider +} + +// NewMockvshnPostgresqlProvider creates a new mock instance. +func NewMockvshnPostgresqlProvider(ctrl *gomock.Controller) *MockvshnPostgresqlProvider { + mock := &MockvshnPostgresqlProvider{ctrl: ctrl} + mock.recorder = &MockvshnPostgresqlProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockvshnPostgresqlProvider) EXPECT() *MockvshnPostgresqlProviderMockRecorder { + return m.recorder +} + +// ListXVSHNPostgreSQL mocks base method. +func (m *MockvshnPostgresqlProvider) ListXVSHNPostgreSQL(ctx context.Context, namespace string) (*vshnv1.XVSHNPostgreSQLList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListXVSHNPostgreSQL", ctx, namespace) + ret0, _ := ret[0].(*vshnv1.XVSHNPostgreSQLList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListXVSHNPostgreSQL indicates an expected call of ListXVSHNPostgreSQL. +func (mr *MockvshnPostgresqlProviderMockRecorder) ListXVSHNPostgreSQL(ctx, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListXVSHNPostgreSQL", reflect.TypeOf((*MockvshnPostgresqlProvider)(nil).ListXVSHNPostgreSQL), ctx, namespace) +} diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..45cb969 --- /dev/null +++ b/tools.go @@ -0,0 +1,24 @@ +//go:build tools +// +build tools + +// Package tools is a place to put any tooling dependencies as imports. +// Go modules will be forced to download and install them. +package tools + +import ( + // This is basically KubeBuilder + _ "sigs.k8s.io/controller-tools/cmd/controller-gen" + // To generate mocks + _ "github.com/golang/mock/mockgen" + // To have protoc generator + _ "github.com/golang/protobuf/protoc-gen-go" + // To have Kind updated via Renovate + _ "sigs.k8s.io/kind" + // To have protobuf generator + _ "k8s.io/code-generator" + // mock tool + _ "github.com/vektra/mockery/v2" + // Add any build-time dependencies here with blank imports like `_ "package"` + _ "github.com/deepmap/oapi-codegen/cmd/oapi-codegen" + _ "sigs.k8s.io/controller-tools/cmd/controller-gen" +) From fd670b12f45ddc785f4657ca12327871b8f749c2 Mon Sep 17 00:00:00 2001 From: Simon Beck Date: Wed, 13 Sep 2023 15:15:24 +0200 Subject: [PATCH 2/2] Makefile fixes --- .gitignore | 2 +- Dockerfile | 12 +- Makefile | 78 +---------- apis/appcat/v1/generated.pb.go | 182 ++++++++++++------------ config/apiserver/role.yaml | 1 + pkg/apiserver/appcat/appcat.go | 57 ++++++++ pkg/apiserver/appcat/appcat_test.go | 92 +++++++++++++ pkg/apiserver/appcat/composition.go | 51 +++++++ pkg/apiserver/appcat/create.go | 15 ++ pkg/apiserver/appcat/delete.go | 29 ++++ pkg/apiserver/appcat/get.go | 30 ++++ pkg/apiserver/appcat/get_test.go | 86 ++++++++++++ pkg/apiserver/appcat/list.go | 89 ++++++++++++ pkg/apiserver/appcat/list_test.go | 205 ++++++++++++++++++++++++++++ pkg/apiserver/appcat/table.go | 70 ++++++++++ pkg/apiserver/appcat/table_test.go | 74 ++++++++++ pkg/apiserver/appcat/update.go | 21 +++ 17 files changed, 924 insertions(+), 170 deletions(-) create mode 100644 pkg/apiserver/appcat/appcat.go create mode 100644 pkg/apiserver/appcat/appcat_test.go create mode 100644 pkg/apiserver/appcat/composition.go create mode 100644 pkg/apiserver/appcat/create.go create mode 100644 pkg/apiserver/appcat/delete.go create mode 100644 pkg/apiserver/appcat/get.go create mode 100644 pkg/apiserver/appcat/get_test.go create mode 100644 pkg/apiserver/appcat/list.go create mode 100644 pkg/apiserver/appcat/list_test.go create mode 100644 pkg/apiserver/appcat/table.go create mode 100644 pkg/apiserver/appcat/table_test.go create mode 100644 pkg/apiserver/appcat/update.go diff --git a/.gitignore b/.gitignore index 19d5607..8978e19 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ dist/ .github/release-notes.md # Binaries for programs and plugins -appcat +appcat-apiserver # But don't ignore the appcat APIS! !apis/appcat diff --git a/Dockerfile b/Dockerfile index e662e17..ca7bdd1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,18 @@ FROM docker.io/library/alpine:3.15 as runtime -ENTRYPOINT ["appcat"] +ENTRYPOINT ["appcat-apiserver"] RUN \ apk add --update --no-cache \ - bash \ - ca-certificates \ - curl + bash \ + ca-certificates \ + curl RUN \ mkdir /.cache && chmod -R g=u /.cache -COPY appcat /usr/local/bin/ +COPY appcat-apiserver /usr/local/bin/ -RUN chmod a+x /usr/local/bin/appcat +RUN chmod a+x /usr/local/bin/appcat-apiserver USER 65532:0 diff --git a/Makefile b/Makefile index f7fdb58..b7a5065 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Image URL to use all building/pushing image targets IMG_TAG ?= latest -GHCR_IMG ?= ghcr.io/vshn/appcat:$(IMG_TAG) +GHCR_IMG ?= ghcr.io/vshn/appcat-apiserver:$(IMG_TAG) DOCKER_CMD ?= docker # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) @@ -23,11 +23,11 @@ DOCKER_IMAGE_GOOS = linux DOCKER_IMAGE_GOARCH = amd64 PROJECT_ROOT_DIR = . -PROJECT_NAME ?= appcat +PROJECT_NAME ?= appcat-apiserver PROJECT_OWNER ?= vshn PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) -BIN_FILENAME ?= $(PROJECT_DIR)/appcat +BIN_FILENAME ?= $(PROJECT_DIR)/appcat-apiserver ## Stackgres CRDs STACKGRES_VERSION ?= 1.4.3 @@ -64,18 +64,12 @@ help: ## Display this help. .PHONY: generate generate: export PATH := $(go_bin):$(PATH) -generate: $(protoc_bin) generate-stackgres-crds ## Generate code with controller-gen and protobuf. +generate: $(protoc_bin) ## Generate code with controller-gen and protobuf. go version rm -rf apis/generated go run sigs.k8s.io/controller-tools/cmd/controller-gen paths=./apis/... object crd:crdVersions=v1,allowDangerousTypes=true output:artifacts:config=./apis/generated - go generate ./... - # Because yaml is such a fun and easy specification, we need to hack some things here. - # Depending on the yaml parser implementation the equal sign (=) has special meaning, or not... - # So we make it explicitly a string. - $(sed) -i ':a;N;$$!ba;s/- =\n/- "="\n/g' apis/generated/vshn.appcat.vshn.io_vshnpostgresqls.yaml - rm -rf crds && cp -r apis/generated crds go run sigs.k8s.io/controller-tools/cmd/controller-gen rbac:roleName=appcat paths="{./apis/...,./pkg/apiserver/...}" output:artifacts:config=config/apiserver - go run sigs.k8s.io/controller-tools/cmd/controller-gen rbac:roleName=appcat-sli-exporter paths="{./pkg/sliexporter/...}" output:artifacts:config=config/sliexporter/rbac + go generate ./... go run k8s.io/code-generator/cmd/go-to-protobuf \ --packages=github.com/vshn/appcat-apiserver/apis/appcat/v1 \ --output-base=./.work/tmp \ @@ -85,23 +79,6 @@ generate: $(protoc_bin) generate-stackgres-crds ## Generate code with controller mv ./.work/tmp/github.com/vshn/appcat-apiserver/apis/appcat/v1/generated.pb.go ./apis/appcat/v1/ && \ rm -rf ./.work/tmp -.PHONY: generate-stackgres-crds -generate-stackgres-crds: - curl ${STACKGRES_CRD_URL}/SGDbOps.yaml?inline=false -o apis/stackgres/v1/sgdbops_crd.yaml - yq -i e apis/stackgres/v1/sgdbops.yaml --expression ".components.schemas.SGDbOpsSpec=load(\"apis/stackgres/v1/sgdbops_crd.yaml\").spec.versions[0].schema.openAPIV3Schema.properties.spec" - yq -i e apis/stackgres/v1/sgdbops.yaml --expression ".components.schemas.SGDbOpsStatus=load(\"apis/stackgres/v1/sgdbops_crd.yaml\").spec.versions[0].schema.openAPIV3Schema.properties.status" - go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=v1 -generate=types -o apis/stackgres/v1/sgdbops.gen.go apis/stackgres/v1/sgdbops.yaml - perl -i -0pe 's/\*struct\s\{\n\s\sAdditionalProperties\smap\[string\]string\s`json:"-"`\n\s}/map\[string\]string/gms' apis/stackgres/v1/sgdbops.gen.go - - curl ${STACKGRES_CRD_URL}/SGCluster.yaml?inline=false -o apis/stackgres/v1/sgcluster_crd.yaml - yq -i e apis/stackgres/v1/sgcluster.yaml --expression ".components.schemas.SGClusterSpec=load(\"apis/stackgres/v1/sgcluster_crd.yaml\").spec.versions[0].schema.openAPIV3Schema.properties.spec" - yq -i e apis/stackgres/v1/sgcluster.yaml --expression ".components.schemas.SGClusterStatus=load(\"apis/stackgres/v1/sgcluster_crd.yaml\").spec.versions[0].schema.openAPIV3Schema.properties.status" - go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=v1 -generate=types -o apis/stackgres/v1/sgcluster.gen.go apis/stackgres/v1/sgcluster.yaml - perl -i -0pe 's/\*struct\s\{\n\s\sAdditionalProperties\smap\[string\]string\s`json:"-"`\n\s}/map\[string\]string/gms' apis/stackgres/v1/sgcluster.gen.go - - go run sigs.k8s.io/controller-tools/cmd/controller-gen object paths=./apis/stackgres/v1/... - rm apis/stackgres/v1/*_crd.yaml - .PHONY: fmt fmt: ## Run go fmt against code. go fmt ./... @@ -137,7 +114,7 @@ docker-build: .PHONY: docker-build-branchtag docker-build-branchtag: docker-build ## Build docker image with current branch name tag=$$(git rev-parse --abbrev-ref HEAD) && \ - docker tag ${GHCR_IMG} ghcr.io/vshn/appcat:"$${tag////_}" + docker tag ${GHCR_IMG} ghcr.io/vshn/appcat-apiserver:"$${tag////_}" .PHONY: kind-load-branch-tag kind-load-branch-tag: ## load docker image with current branch tag into kind @@ -148,49 +125,6 @@ kind-load-branch-tag: ## load docker image with current branch tag into kind docker-push: docker-build ## Push docker image with the manager. docker push ${GHCR_IMG} -# Generate webhook certificates. -# This is only relevant when debugging. -# Component-appcat installs a proper certificate for this. -.PHONY: webhook-cert -webhook_key = .work/webhook/tls.key -webhook_cert = .work/webhook/tls.crt -webhook-cert: $(webhook_cert) ## Generate webhook certificates for out-of-cluster debugging in an IDE - -$(webhook_key): - mkdir -p .work/webhook - ipsan="" && \ - if [[ $(webhook_service_name) =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$$ ]]; then \ - ipsan=", IP:$(webhook_service_name)"; \ - fi; \ - openssl req -x509 -newkey rsa:4096 -nodes -keyout $@ --noout -days 3650 -subj "/CN=$(webhook_service_name)" -addext "subjectAltName = DNS:$(webhook_service_name)$$ipsan" - -$(webhook_cert): $(webhook_key) - ipsan="" && \ - if [[ $(webhook_service_name) =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$$ ]]; then \ - ipsan=", IP:$(webhook_service_name)"; \ - fi; \ - openssl req -x509 -key $(webhook_key) -nodes -out $@ -days 3650 -subj "/CN=$(webhook_service_name)" -addext "subjectAltName = DNS:$(webhook_service_name)$$ipsan" - - -.PHONY: webhook-debug -webhook_service_name = host.docker.internal - -webhook-debug: $(webhook_cert) ## Creates certificates, patches the webhook registrations and applies everything to the given kube cluster -webhook-debug: - kubectl -n syn-appcat scale deployment appcat-controller --replicas 0 - cabundle=$$(cat .work/webhook/tls.crt | base64) && \ - HOSTIP=$(webhook_service_name) && \ - kubectl annotate validatingwebhookconfigurations.admissionregistration.k8s.io appcat-pg-validation cert-manager.io/inject-ca-from- && \ - kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io appcat-pg-validation -oyaml | \ - yq e "del(.webhooks[0].clientConfig.service) | .webhooks[0].clientConfig.caBundle |= \"$$cabundle\" | .webhooks[0].clientConfig.url |= \"https://$$HOSTIP:9443/validate-vshn-appcat-vshn-io-v1-vshnpostgresql\"" - | \ - kubectl apply -f - && \ - kubectl annotate validatingwebhookconfigurations.admissionregistration.k8s.io appcat-redis-validation cert-manager.io/inject-ca-from- && \ - kubectl annotate validatingwebhookconfigurations.admissionregistration.k8s.io appcat-pg-validation kubectl.kubernetes.io/last-applied-configuration- && \ - kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io appcat-redis-validation -oyaml | \ - yq e "del(.webhooks[0].clientConfig.service) | .webhooks[0].clientConfig.caBundle |= \"$$cabundle\" | .webhooks[0].clientConfig.url |= \"https://$$HOSTIP:9443/validate-vshn-appcat-vshn-io-v1-vshnredis\"" - | \ - kubectl apply -f - && \ - kubectl annotate validatingwebhookconfigurations.admissionregistration.k8s.io appcat-redis-validation kubectl.kubernetes.io/last-applied-configuration- - .PHONY: clean clean: rm -rf bin/ appcat .work/ docs/node_modules $docs_out_dir .public .cache apiserver.local.config apis/generated default.sock diff --git a/apis/appcat/v1/generated.pb.go b/apis/appcat/v1/generated.pb.go index 131e5ec..e626cb7 100644 --- a/apis/appcat/v1/generated.pb.go +++ b/apis/appcat/v1/generated.pb.go @@ -32,7 +32,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package func (m *AppCat) Reset() { *m = AppCat{} } func (*AppCat) ProtoMessage() {} func (*AppCat) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{0} + return fileDescriptor_ade2e39f600d64ee, []int{0} } func (m *AppCat) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -60,7 +60,7 @@ var xxx_messageInfo_AppCat proto.InternalMessageInfo func (m *AppCatList) Reset() { *m = AppCatList{} } func (*AppCatList) ProtoMessage() {} func (*AppCatList) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{1} + return fileDescriptor_ade2e39f600d64ee, []int{1} } func (m *AppCatList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -88,7 +88,7 @@ var xxx_messageInfo_AppCatList proto.InternalMessageInfo func (m *AppCatStatus) Reset() { *m = AppCatStatus{} } func (*AppCatStatus) ProtoMessage() {} func (*AppCatStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{2} + return fileDescriptor_ade2e39f600d64ee, []int{2} } func (m *AppCatStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -116,7 +116,7 @@ var xxx_messageInfo_AppCatStatus proto.InternalMessageInfo func (m *SGBackupInfo) Reset() { *m = SGBackupInfo{} } func (*SGBackupInfo) ProtoMessage() {} func (*SGBackupInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{3} + return fileDescriptor_ade2e39f600d64ee, []int{3} } func (m *SGBackupInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -144,7 +144,7 @@ var xxx_messageInfo_SGBackupInfo proto.InternalMessageInfo func (m *VSHNPlan) Reset() { *m = VSHNPlan{} } func (*VSHNPlan) ProtoMessage() {} func (*VSHNPlan) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{4} + return fileDescriptor_ade2e39f600d64ee, []int{4} } func (m *VSHNPlan) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -172,7 +172,7 @@ var xxx_messageInfo_VSHNPlan proto.InternalMessageInfo func (m *VSHNPostgresBackup) Reset() { *m = VSHNPostgresBackup{} } func (*VSHNPostgresBackup) ProtoMessage() {} func (*VSHNPostgresBackup) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{5} + return fileDescriptor_ade2e39f600d64ee, []int{5} } func (m *VSHNPostgresBackup) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -200,7 +200,7 @@ var xxx_messageInfo_VSHNPostgresBackup proto.InternalMessageInfo func (m *VSHNPostgresBackupList) Reset() { *m = VSHNPostgresBackupList{} } func (*VSHNPostgresBackupList) ProtoMessage() {} func (*VSHNPostgresBackupList) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{6} + return fileDescriptor_ade2e39f600d64ee, []int{6} } func (m *VSHNPostgresBackupList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -228,7 +228,7 @@ var xxx_messageInfo_VSHNPostgresBackupList proto.InternalMessageInfo func (m *VSHNPostgresBackupStatus) Reset() { *m = VSHNPostgresBackupStatus{} } func (*VSHNPostgresBackupStatus) ProtoMessage() {} func (*VSHNPostgresBackupStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{7} + return fileDescriptor_ade2e39f600d64ee, []int{7} } func (m *VSHNPostgresBackupStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -256,7 +256,7 @@ var xxx_messageInfo_VSHNPostgresBackupStatus proto.InternalMessageInfo func (m *VSHNRedisBackup) Reset() { *m = VSHNRedisBackup{} } func (*VSHNRedisBackup) ProtoMessage() {} func (*VSHNRedisBackup) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{8} + return fileDescriptor_ade2e39f600d64ee, []int{8} } func (m *VSHNRedisBackup) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -284,7 +284,7 @@ var xxx_messageInfo_VSHNRedisBackup proto.InternalMessageInfo func (m *VSHNRedisBackupList) Reset() { *m = VSHNRedisBackupList{} } func (*VSHNRedisBackupList) ProtoMessage() {} func (*VSHNRedisBackupList) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{9} + return fileDescriptor_ade2e39f600d64ee, []int{9} } func (m *VSHNRedisBackupList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -312,7 +312,7 @@ var xxx_messageInfo_VSHNRedisBackupList proto.InternalMessageInfo func (m *VSHNRedisBackupStatus) Reset() { *m = VSHNRedisBackupStatus{} } func (*VSHNRedisBackupStatus) ProtoMessage() {} func (*VSHNRedisBackupStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{10} + return fileDescriptor_ade2e39f600d64ee, []int{10} } func (m *VSHNRedisBackupStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -340,7 +340,7 @@ var xxx_messageInfo_VSHNRedisBackupStatus proto.InternalMessageInfo func (m *VSHNSize) Reset() { *m = VSHNSize{} } func (*VSHNSize) ProtoMessage() {} func (*VSHNSize) Descriptor() ([]byte, []int) { - return fileDescriptor_7745f0f2a1a993fe, []int{11} + return fileDescriptor_ade2e39f600d64ee, []int{11} } func (m *VSHNSize) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -366,88 +366,88 @@ func (m *VSHNSize) XXX_DiscardUnknown() { var xxx_messageInfo_VSHNSize proto.InternalMessageInfo func init() { - proto.RegisterType((*AppCat)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.AppCat") - proto.RegisterMapType((Details)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.AppCat.DetailsEntry") - proto.RegisterMapType((map[string]VSHNPlan)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.AppCat.PlansEntry") - proto.RegisterType((*AppCatList)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.AppCatList") - proto.RegisterType((*AppCatStatus)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.AppCatStatus") - proto.RegisterType((*SGBackupInfo)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.SGBackupInfo") - proto.RegisterType((*VSHNPlan)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNPlan") - proto.RegisterType((*VSHNPostgresBackup)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNPostgresBackup") - proto.RegisterType((*VSHNPostgresBackupList)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNPostgresBackupList") - proto.RegisterType((*VSHNPostgresBackupStatus)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNPostgresBackupStatus") - proto.RegisterType((*VSHNRedisBackup)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNRedisBackup") - proto.RegisterType((*VSHNRedisBackupList)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNRedisBackupList") - proto.RegisterType((*VSHNRedisBackupStatus)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNRedisBackupStatus") - proto.RegisterType((*VSHNSize)(nil), "github.com.vshn.appcat.v4.apis.appcat.v1.VSHNSize") + proto.RegisterType((*AppCat)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.AppCat") + proto.RegisterMapType((Details)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.AppCat.DetailsEntry") + proto.RegisterMapType((map[string]VSHNPlan)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.AppCat.PlansEntry") + proto.RegisterType((*AppCatList)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.AppCatList") + proto.RegisterType((*AppCatStatus)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.AppCatStatus") + proto.RegisterType((*SGBackupInfo)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.SGBackupInfo") + proto.RegisterType((*VSHNPlan)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.VSHNPlan") + proto.RegisterType((*VSHNPostgresBackup)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.VSHNPostgresBackup") + proto.RegisterType((*VSHNPostgresBackupList)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.VSHNPostgresBackupList") + proto.RegisterType((*VSHNPostgresBackupStatus)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.VSHNPostgresBackupStatus") + proto.RegisterType((*VSHNRedisBackup)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.VSHNRedisBackup") + proto.RegisterType((*VSHNRedisBackupList)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.VSHNRedisBackupList") + proto.RegisterType((*VSHNRedisBackupStatus)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.VSHNRedisBackupStatus") + proto.RegisterType((*VSHNSize)(nil), "github.com.vshn.appcat_apiserver.apis.appcat.v1.VSHNSize") } func init() { - proto.RegisterFile(".work/tmp/github.com/vshn/appcat-apiserver/apis/appcat/v1/generated.proto", fileDescriptor_7745f0f2a1a993fe) -} - -var fileDescriptor_7745f0f2a1a993fe = []byte{ - // 951 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xdd, 0x6e, 0x1b, 0x45, - 0x14, 0xf6, 0xda, 0xce, 0x4f, 0x8f, 0x03, 0x49, 0x07, 0x0a, 0xc6, 0x12, 0x6e, 0xe4, 0x0b, 0x14, - 0x21, 0xba, 0xdb, 0x44, 0x51, 0x55, 0x0a, 0x08, 0x75, 0xe3, 0x8a, 0x06, 0xb5, 0x21, 0x4c, 0x1a, - 0x84, 0x10, 0xaa, 0x3a, 0xde, 0x9d, 0x3a, 0x53, 0x67, 0x77, 0x56, 0x3b, 0x63, 0x97, 0xf4, 0x0a, - 0x89, 0x17, 0xe0, 0x39, 0xb8, 0xe1, 0x19, 0xb8, 0x41, 0x91, 0xb8, 0xa9, 0xc4, 0x4d, 0x91, 0x50, - 0x45, 0xcc, 0x5b, 0x70, 0x85, 0x66, 0x67, 0xbc, 0x5e, 0xff, 0x11, 0xdb, 0x54, 0xb9, 0xdb, 0xf9, - 0xf9, 0xbe, 0xef, 0x9c, 0x33, 0xdf, 0x39, 0xb2, 0xc1, 0xb5, 0x9f, 0xf2, 0xb8, 0xe5, 0xc8, 0x20, - 0x72, 0x9a, 0x4c, 0x1e, 0xb5, 0x1b, 0xb6, 0xc7, 0x03, 0xa7, 0x23, 0x8e, 0x42, 0x87, 0x44, 0x91, - 0x47, 0xa4, 0xd3, 0xd9, 0x76, 0x48, 0xc4, 0x44, 0xba, 0xdc, 0x74, 0x9a, 0x34, 0xa4, 0x31, 0x91, - 0xd4, 0xb7, 0xa3, 0x98, 0x4b, 0x8e, 0x36, 0xfa, 0x48, 0x5b, 0x21, 0x6d, 0x7d, 0xd5, 0xee, 0x6c, - 0xdb, 0x0a, 0x99, 0x2e, 0x37, 0x2b, 0xd7, 0x32, 0x1a, 0x4d, 0xde, 0xe4, 0x4e, 0x42, 0xd0, 0x68, - 0x3f, 0x4e, 0x56, 0xc9, 0x22, 0xf9, 0xd2, 0xc4, 0x95, 0xed, 0xd6, 0x4d, 0x61, 0x33, 0xae, 0xf4, - 0x03, 0xe2, 0x1d, 0xb1, 0x90, 0xc6, 0x27, 0x4e, 0xd4, 0x6a, 0xea, 0x80, 0x02, 0x2a, 0xc9, 0x98, - 0x70, 0x2a, 0xce, 0x24, 0x54, 0xdc, 0x0e, 0x25, 0x0b, 0xe8, 0x08, 0xe0, 0xc6, 0x79, 0x00, 0xe1, - 0x1d, 0xd1, 0x80, 0x0c, 0xe3, 0x6a, 0xbf, 0x15, 0x61, 0xf1, 0x76, 0x14, 0xed, 0x10, 0x89, 0x1e, - 0xc1, 0xb2, 0x0a, 0xc7, 0x27, 0x92, 0x94, 0xad, 0x75, 0x6b, 0xa3, 0xb4, 0x75, 0xdd, 0xd6, 0xac, - 0x76, 0x96, 0xd5, 0x8e, 0x5a, 0x4d, 0x5d, 0x13, 0x75, 0xdb, 0xee, 0x6c, 0xda, 0x5f, 0x34, 0x9e, - 0x50, 0x4f, 0xde, 0xa7, 0x92, 0xb8, 0xe8, 0xf4, 0xe5, 0xd5, 0x5c, 0xf7, 0xe5, 0x55, 0xe8, 0xef, - 0xe1, 0x94, 0x15, 0x09, 0x58, 0xf2, 0xa9, 0x24, 0xec, 0x58, 0x94, 0xf3, 0xeb, 0x85, 0x8d, 0xd2, - 0xd6, 0x27, 0xf6, 0xb4, 0x65, 0xb7, 0x75, 0x90, 0x76, 0x5d, 0xe3, 0xef, 0x84, 0x32, 0x3e, 0x71, - 0x2b, 0x46, 0x6d, 0xc9, 0xec, 0xfe, 0xd3, 0xff, 0xc4, 0x3d, 0x25, 0xf4, 0x08, 0x16, 0xa2, 0x63, - 0x12, 0x8a, 0x72, 0x21, 0x91, 0xfc, 0x68, 0x66, 0xc9, 0x7d, 0x85, 0xd6, 0x82, 0xaf, 0x19, 0xc1, - 0x85, 0x64, 0x0f, 0x6b, 0x62, 0xf4, 0x10, 0x16, 0x85, 0x24, 0xb2, 0x2d, 0xca, 0xc5, 0xa4, 0x6c, - 0x37, 0x66, 0x95, 0x38, 0x48, 0xd0, 0xee, 0xeb, 0x86, 0x7d, 0x51, 0xaf, 0xb1, 0x61, 0xad, 0xdc, - 0x82, 0x95, 0x6c, 0xda, 0x68, 0x0d, 0x0a, 0x2d, 0x7a, 0x92, 0xbc, 0xd1, 0x25, 0xac, 0x3e, 0xd1, - 0x9b, 0xb0, 0xd0, 0x21, 0xc7, 0x6d, 0x5a, 0xce, 0x27, 0x7b, 0x7a, 0x71, 0x2b, 0x7f, 0xd3, 0xaa, - 0x1c, 0x03, 0xf4, 0xe3, 0x1f, 0x83, 0xbc, 0x9b, 0x45, 0x96, 0xb6, 0xb6, 0xa6, 0x0f, 0xfd, 0xab, - 0x83, 0xbb, 0x7b, 0x8a, 0x3a, 0xa3, 0x56, 0xfb, 0xc5, 0x02, 0xd0, 0x29, 0xdd, 0x63, 0x42, 0xa2, - 0x6f, 0x47, 0x1c, 0x65, 0x4f, 0xe7, 0x28, 0x85, 0x4e, 0xfc, 0xb4, 0x66, 0x4a, 0xb2, 0xdc, 0xdb, - 0xc9, 0xb8, 0xe9, 0x10, 0x16, 0x98, 0xa4, 0x41, 0xcf, 0x4b, 0xd7, 0x67, 0xad, 0x7a, 0xff, 0x35, - 0x77, 0x15, 0x0d, 0xd6, 0x6c, 0xb5, 0x2f, 0x61, 0x25, 0xfb, 0x2a, 0xe8, 0x36, 0xac, 0x7a, 0x3c, - 0x88, 0xb8, 0x60, 0x92, 0xf1, 0x70, 0x8f, 0x04, 0x54, 0xd7, 0xcf, 0x7d, 0xdb, 0xc0, 0x57, 0x77, - 0x06, 0x8f, 0xf1, 0xf0, 0xfd, 0xda, 0xaf, 0x79, 0x58, 0x39, 0xf8, 0xcc, 0x25, 0x5e, 0xab, 0x1d, - 0xed, 0x86, 0x8f, 0x39, 0xf2, 0x01, 0x78, 0xda, 0x20, 0xaf, 0xb4, 0xd9, 0x32, 0xbc, 0xe8, 0x6b, - 0x58, 0x8a, 0x62, 0xee, 0x51, 0x21, 0xcc, 0xeb, 0x5e, 0x9b, 0x28, 0x61, 0xa6, 0x84, 0x8d, 0xc9, - 0xd3, 0x3b, 0xdf, 0x49, 0x1a, 0x0a, 0xc6, 0x43, 0x77, 0xb5, 0xd7, 0x5e, 0xfb, 0x9a, 0x05, 0xf7, - 0xe8, 0x50, 0x07, 0x2e, 0x37, 0xd2, 0x6c, 0xe2, 0x80, 0xa8, 0x4c, 0xcb, 0x85, 0x79, 0x34, 0xde, - 0x31, 0x1a, 0x97, 0xdd, 0x61, 0x3e, 0x3c, 0x2a, 0x51, 0xfb, 0xc1, 0x82, 0xe5, 0x9e, 0xef, 0xd0, - 0x3a, 0x14, 0x43, 0x2e, 0x7b, 0xaf, 0xb1, 0x62, 0x88, 0x8a, 0x7b, 0x5c, 0x52, 0x9c, 0x9c, 0xa0, - 0x43, 0x28, 0x0a, 0xf6, 0x6c, 0x4e, 0x6f, 0x1f, 0xb0, 0x67, 0xb4, 0x6f, 0x91, 0xcf, 0xd5, 0x12, - 0x27, 0x74, 0xb5, 0xae, 0x05, 0x28, 0x89, 0x82, 0x0b, 0xd9, 0x8c, 0xa9, 0xd0, 0xa1, 0x5f, 0xc0, - 0xfc, 0x7c, 0x92, 0x0e, 0x1a, 0x9d, 0x91, 0x3b, 0x63, 0xb7, 0x0e, 0xc4, 0xfb, 0xdf, 0x43, 0xa7, - 0xf6, 0x87, 0x05, 0x6f, 0x8d, 0x82, 0x2e, 0xa0, 0xad, 0xc9, 0x60, 0x5b, 0x7f, 0xfc, 0x7f, 0x72, - 0x9c, 0xd0, 0xe2, 0x3f, 0xe5, 0xa1, 0x3c, 0xa9, 0x20, 0xe8, 0x41, 0xbf, 0x6b, 0xac, 0x79, 0x1c, - 0x5d, 0x1a, 0xdb, 0x31, 0xf1, 0xb8, 0x8e, 0x99, 0xab, 0x2b, 0xaf, 0x4c, 0xdb, 0x2d, 0xa8, 0x0e, - 0x6b, 0xaa, 0xa2, 0x0d, 0x22, 0xe8, 0x6e, 0x28, 0x24, 0x09, 0x3d, 0x9a, 0x34, 0xe9, 0x25, 0xb7, - 0x6c, 0xca, 0xb2, 0x56, 0x1f, 0x3a, 0xc7, 0x23, 0x88, 0xda, 0x9f, 0x16, 0xac, 0xaa, 0x62, 0x61, - 0xea, 0xb3, 0x8b, 0xb3, 0x7a, 0x73, 0xc8, 0xea, 0x9f, 0xce, 0x66, 0x83, 0x4c, 0xb0, 0xe7, 0xf8, - 0xfc, 0x77, 0x0b, 0xde, 0x18, 0x42, 0x5c, 0x80, 0xc9, 0x1f, 0x0e, 0x9a, 0xfc, 0xc3, 0xb9, 0xb3, - 0x9b, 0xe0, 0xf0, 0x9f, 0x2d, 0xb8, 0x32, 0xb6, 0x0e, 0xa8, 0x02, 0x79, 0xe6, 0x9b, 0x99, 0x09, - 0x06, 0x9b, 0xdf, 0xad, 0xe3, 0x3c, 0xf3, 0xd1, 0x3d, 0x28, 0xfa, 0x44, 0xf6, 0xe6, 0xe5, 0xfb, - 0xd3, 0xe5, 0xfb, 0x80, 0x05, 0xb4, 0x3f, 0x7d, 0xeb, 0x44, 0x4d, 0x5f, 0xc5, 0x82, 0x3e, 0x80, - 0x65, 0x36, 0x68, 0xbb, 0xb4, 0x22, 0xa9, 0xdd, 0xd2, 0x1b, 0x35, 0xa1, 0x27, 0xbb, 0x1a, 0xb3, - 0xe8, 0x5d, 0x28, 0x78, 0x51, 0xdb, 0x04, 0x59, 0x32, 0xa0, 0xc2, 0xce, 0xfe, 0x21, 0x56, 0xfb, - 0x6a, 0xf0, 0xfb, 0x4c, 0xb4, 0xf4, 0x8f, 0x9d, 0x8c, 0x34, 0x13, 0x2d, 0x9c, 0x9c, 0xa0, 0xf7, - 0x60, 0x31, 0xa0, 0x01, 0x8f, 0x4f, 0x8c, 0x70, 0xfa, 0xf8, 0xf7, 0x93, 0x5d, 0x6c, 0x4e, 0xdd, - 0xbd, 0xd3, 0xb3, 0x6a, 0xee, 0xf9, 0x59, 0x35, 0xf7, 0xe2, 0xac, 0x9a, 0xfb, 0xbe, 0x5b, 0xb5, - 0x4e, 0xbb, 0x55, 0xeb, 0x79, 0xb7, 0x6a, 0xbd, 0xe8, 0x56, 0xad, 0xbf, 0xba, 0x55, 0xeb, 0xc7, - 0xbf, 0xab, 0xb9, 0x6f, 0x36, 0xa6, 0xfd, 0x57, 0xf1, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x48, - 0x35, 0x98, 0x68, 0x8a, 0x0c, 0x00, 0x00, + proto.RegisterFile(".work/tmp/github.com/vshn/appcat-apiserver/apis/appcat/v1/generated.proto", fileDescriptor_ade2e39f600d64ee) +} + +var fileDescriptor_ade2e39f600d64ee = []byte{ + // 954 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xcf, 0xda, 0xce, 0x9f, 0x3e, 0x07, 0x92, 0x0e, 0x14, 0x8c, 0x25, 0xdc, 0xc8, 0x07, 0x54, + 0x21, 0x32, 0x4b, 0x22, 0x04, 0xa5, 0x12, 0x12, 0xdd, 0xb8, 0x80, 0x51, 0x9b, 0x86, 0x49, 0x8b, + 0x10, 0x8a, 0x04, 0xe3, 0xf5, 0xd4, 0x1e, 0x9c, 0xdd, 0x59, 0x76, 0xc6, 0x2e, 0xc9, 0x89, 0x33, + 0x27, 0x3e, 0x07, 0x17, 0xbe, 0x05, 0xca, 0xb1, 0xc7, 0x8a, 0x43, 0xd5, 0xb8, 0x07, 0xc4, 0x57, + 0xe0, 0x84, 0x66, 0x67, 0xbc, 0x5e, 0xff, 0x6b, 0x13, 0xb7, 0xca, 0x6d, 0xe6, 0xcd, 0xbc, 0xdf, + 0xef, 0xbd, 0x37, 0xbf, 0xf7, 0xec, 0x85, 0x3a, 0x7e, 0x28, 0xe2, 0x8e, 0xab, 0x82, 0xc8, 0x6d, + 0x71, 0xd5, 0xee, 0x36, 0xb0, 0x2f, 0x02, 0xb7, 0x27, 0xdb, 0xa1, 0x4b, 0xa3, 0xc8, 0xa7, 0x6a, + 0x93, 0x46, 0x5c, 0xb2, 0xb8, 0xc7, 0x62, 0x57, 0xaf, 0xac, 0xd5, 0xed, 0x6d, 0xb9, 0x2d, 0x16, + 0xb2, 0x98, 0x2a, 0xd6, 0xc4, 0x51, 0x2c, 0x94, 0x40, 0x19, 0x00, 0xac, 0x01, 0xb0, 0xb9, 0xfa, + 0x43, 0x0a, 0x80, 0xf5, 0xca, 0x5a, 0x71, 0x6f, 0xab, 0xbc, 0x99, 0x61, 0x6c, 0x89, 0x96, 0x70, + 0x13, 0x9c, 0x46, 0xf7, 0x41, 0xb2, 0x4b, 0x36, 0xc9, 0xca, 0xe0, 0x97, 0x3f, 0xea, 0x5c, 0x97, + 0x98, 0x0b, 0x1d, 0x46, 0x40, 0xfd, 0x36, 0x0f, 0x59, 0x7c, 0xe4, 0x46, 0x9d, 0x96, 0x89, 0x2b, + 0x60, 0x8a, 0x4e, 0x89, 0xaa, 0xec, 0xce, 0xf2, 0x8a, 0xbb, 0xa1, 0xe2, 0x01, 0x9b, 0x70, 0xf8, + 0xf8, 0x45, 0x0e, 0xd2, 0x6f, 0xb3, 0x80, 0x8e, 0xfb, 0x55, 0xff, 0x29, 0xc0, 0xd2, 0xcd, 0x28, + 0xda, 0xa1, 0x0a, 0xfd, 0x08, 0x2b, 0x3a, 0x9c, 0x26, 0x55, 0xb4, 0xe4, 0x6c, 0x38, 0xd7, 0x8a, + 0xdb, 0x1f, 0x62, 0x83, 0x8a, 0xb3, 0xa8, 0x38, 0xea, 0xb4, 0x4c, 0x4d, 0xf4, 0x6d, 0xdc, 0xdb, + 0xc2, 0x77, 0x1b, 0x3f, 0x31, 0x5f, 0xdd, 0x61, 0x8a, 0x7a, 0xe8, 0xe4, 0xc9, 0xd5, 0x85, 0xfe, + 0x93, 0xab, 0x30, 0xb4, 0x91, 0x14, 0x15, 0x1d, 0xc3, 0x72, 0x93, 0x29, 0xca, 0x0f, 0x65, 0x29, + 0xb7, 0x91, 0xbf, 0x56, 0xdc, 0xae, 0xe1, 0x73, 0x56, 0x1f, 0x9b, 0x58, 0x71, 0xcd, 0xc0, 0xdc, + 0x0a, 0x55, 0x7c, 0xe4, 0x95, 0x2d, 0xe9, 0xb2, 0xb5, 0xfe, 0x37, 0x5c, 0x92, 0x01, 0x21, 0x6a, + 0xc1, 0x62, 0x74, 0x48, 0x43, 0x59, 0xca, 0x27, 0xcc, 0xde, 0xbc, 0xcc, 0x7b, 0x1a, 0xc4, 0xf0, + 0xbe, 0x66, 0x79, 0x17, 0x13, 0x1b, 0x31, 0xf8, 0x88, 0xc1, 0x92, 0x54, 0x54, 0x75, 0x65, 0xa9, + 0x90, 0x14, 0xf1, 0xb3, 0x39, 0x99, 0xf6, 0x13, 0x10, 0xef, 0x75, 0x4b, 0xb2, 0x64, 0xf6, 0xc4, + 0x82, 0x97, 0x6f, 0xc0, 0x6a, 0xb6, 0x08, 0x68, 0x1d, 0xf2, 0x1d, 0x76, 0x94, 0x3c, 0xdc, 0x25, + 0xa2, 0x97, 0xe8, 0x4d, 0x58, 0xec, 0xd1, 0xc3, 0x2e, 0x2b, 0xe5, 0x12, 0x9b, 0xd9, 0xdc, 0xc8, + 0x5d, 0x77, 0xca, 0x12, 0x60, 0x98, 0xc6, 0x14, 0xcf, 0xbb, 0x59, 0xcf, 0xe2, 0xf6, 0xa7, 0xe7, + 0xce, 0xe0, 0xdb, 0xfd, 0xaf, 0x76, 0x35, 0x43, 0x86, 0xb4, 0x7a, 0xe2, 0x00, 0x98, 0xcc, 0x6e, + 0x73, 0xa9, 0xd0, 0xc1, 0x84, 0xda, 0xf0, 0xd9, 0xd4, 0xa6, 0xbd, 0x13, 0xad, 0xad, 0xdb, 0xca, + 0xac, 0x0c, 0x2c, 0x19, 0xa5, 0x1d, 0xc0, 0x22, 0x57, 0x2c, 0x18, 0xe8, 0xec, 0x93, 0x39, 0xdf, + 0x60, 0xf8, 0xc4, 0x75, 0x8d, 0x46, 0x0c, 0x68, 0xf5, 0x1b, 0x58, 0xcd, 0xbe, 0x11, 0xba, 0x09, + 0x6b, 0xbe, 0x08, 0x22, 0x21, 0xb9, 0xe2, 0x22, 0xdc, 0xa5, 0x01, 0x33, 0xd5, 0xf4, 0xde, 0xb6, + 0xee, 0x6b, 0x3b, 0xa3, 0xc7, 0x64, 0xfc, 0x7e, 0xf5, 0xaf, 0x1c, 0xac, 0xee, 0x7f, 0xe9, 0x51, + 0xbf, 0xd3, 0x8d, 0xea, 0xe1, 0x03, 0x81, 0x9a, 0x00, 0x22, 0xed, 0xa1, 0x57, 0xda, 0x8f, 0x19, + 0x5c, 0xf4, 0x1d, 0x2c, 0x47, 0xb1, 0xf0, 0x99, 0x94, 0xf6, 0xad, 0x37, 0x67, 0x52, 0xd8, 0x41, + 0x82, 0x09, 0x7d, 0x78, 0xeb, 0x17, 0xc5, 0x42, 0xc9, 0x45, 0xe8, 0xad, 0x0d, 0x5a, 0x6f, 0xcf, + 0xa0, 0x90, 0x01, 0x1c, 0xea, 0xc1, 0xe5, 0x46, 0x9a, 0x4d, 0x1c, 0x50, 0x9d, 0x69, 0x29, 0x3f, + 0x0f, 0xc7, 0x3b, 0x96, 0xe3, 0xb2, 0x37, 0x8e, 0x47, 0x26, 0x29, 0xaa, 0xbf, 0x39, 0xb0, 0x32, + 0x90, 0x1f, 0xda, 0x80, 0x42, 0x28, 0xd4, 0xe0, 0x35, 0x56, 0x2d, 0x50, 0x61, 0x57, 0x28, 0x46, + 0x92, 0x13, 0x74, 0x00, 0x05, 0xc9, 0x8f, 0x5f, 0x4e, 0xe9, 0xfb, 0xfc, 0x98, 0x0d, 0x95, 0xf2, + 0xb5, 0xde, 0x92, 0x04, 0xb5, 0xfa, 0xaf, 0x03, 0x28, 0x09, 0x46, 0x48, 0xd5, 0x8a, 0x99, 0x34, + 0x19, 0x5c, 0xc0, 0xa4, 0xfd, 0x39, 0x1d, 0x42, 0x26, 0xb1, 0xfa, 0x7c, 0x2d, 0x3c, 0x12, 0xf6, + 0xf3, 0x07, 0x52, 0xf5, 0xa9, 0x03, 0x6f, 0x4d, 0x3a, 0x5d, 0x40, 0xaf, 0xb7, 0x47, 0x7b, 0x7d, + 0xe7, 0x15, 0xa4, 0x3a, 0xa3, 0xef, 0xff, 0xc8, 0x41, 0x69, 0x56, 0x5d, 0xd0, 0xbd, 0x61, 0x2b, + 0x39, 0xf3, 0xc8, 0xbc, 0x38, 0xb5, 0x8d, 0xe2, 0x69, 0x6d, 0x34, 0x57, 0xab, 0x5e, 0x39, 0x6b, + 0x0b, 0xa1, 0x1a, 0xac, 0xeb, 0xc2, 0x36, 0xa8, 0x64, 0xf5, 0x50, 0x2a, 0x1a, 0xfa, 0x2c, 0xe9, + 0xdc, 0x4b, 0x5e, 0xc9, 0x96, 0x65, 0xbd, 0x36, 0x76, 0x4e, 0x26, 0x3c, 0xaa, 0xcf, 0x1c, 0x58, + 0xd3, 0xc5, 0x22, 0xac, 0xc9, 0x2f, 0x4e, 0xf8, 0xe1, 0x98, 0xf0, 0xbf, 0x98, 0x4b, 0x0d, 0x99, + 0x98, 0x5f, 0xa0, 0xfa, 0xbf, 0x1d, 0x78, 0x63, 0xcc, 0xe3, 0x02, 0x24, 0xcf, 0x46, 0x25, 0xff, + 0xf9, 0xcb, 0x26, 0x39, 0x43, 0xef, 0x7f, 0x3a, 0x70, 0x65, 0x6a, 0x39, 0x50, 0x19, 0x72, 0xbc, + 0x69, 0xc7, 0x2a, 0x58, 0xdf, 0x5c, 0xbd, 0x46, 0x72, 0xbc, 0x89, 0x6e, 0x43, 0xa1, 0x49, 0xd5, + 0x60, 0xa4, 0xbe, 0x7f, 0xb6, 0xb4, 0xef, 0xf1, 0x80, 0x0d, 0x07, 0x74, 0x8d, 0xea, 0x01, 0xad, + 0x51, 0xd0, 0x07, 0xb0, 0xc2, 0x47, 0x45, 0x98, 0x16, 0x26, 0x15, 0x5f, 0x7a, 0xa3, 0x2a, 0xcd, + 0xf0, 0xd7, 0x23, 0x18, 0xbd, 0x0b, 0x79, 0x3f, 0xea, 0xda, 0x20, 0x8b, 0xd6, 0x29, 0xbf, 0xb3, + 0x77, 0x9f, 0x68, 0xbb, 0xfe, 0x6d, 0x68, 0x72, 0xd9, 0x31, 0xff, 0x8e, 0x32, 0xd4, 0x5c, 0x76, + 0x48, 0x72, 0x82, 0xde, 0x83, 0xa5, 0x80, 0x05, 0x22, 0x3e, 0xb2, 0xc4, 0xa9, 0x06, 0xee, 0x24, + 0x56, 0x62, 0x4f, 0xbd, 0xfb, 0x27, 0xa7, 0x95, 0x85, 0x47, 0xa7, 0x95, 0x85, 0xc7, 0xa7, 0x95, + 0x85, 0x5f, 0xfb, 0x15, 0xe7, 0xa4, 0x5f, 0x71, 0x1e, 0xf5, 0x2b, 0xce, 0xe3, 0x7e, 0xc5, 0x79, + 0xda, 0xaf, 0x38, 0xbf, 0x3f, 0xab, 0x2c, 0x7c, 0x7f, 0xde, 0x2f, 0x95, 0xff, 0x03, 0x00, 0x00, + 0xff, 0xff, 0xb6, 0x7f, 0x37, 0x83, 0xe5, 0x0c, 0x00, 0x00, } func (m *AppCat) Marshal() (dAtA []byte, err error) { diff --git a/config/apiserver/role.yaml b/config/apiserver/role.yaml index 1f9a9bb..840e88f 100644 --- a/config/apiserver/role.yaml +++ b/config/apiserver/role.yaml @@ -2,6 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: + creationTimestamp: null name: appcat rules: - apiGroups: diff --git a/pkg/apiserver/appcat/appcat.go b/pkg/apiserver/appcat/appcat.go new file mode 100644 index 0000000..3eab31f --- /dev/null +++ b/pkg/apiserver/appcat/appcat.go @@ -0,0 +1,57 @@ +package appcat + +import ( + crossplane "github.com/crossplane/crossplane/apis/apiextensions/v1" + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "k8s.io/apimachinery/pkg/runtime" + genericregistry "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" + restbuilder "sigs.k8s.io/apiserver-runtime/pkg/builder/rest" + "sigs.k8s.io/apiserver-runtime/pkg/util/loopback" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch,resourceNames=extension-apiserver-authentication +// +kubebuilder:rbac:groups="admissionregistration.k8s.io",resources=mutatingwebhookconfigurations;validatingwebhookconfigurations,verbs=get;list;watch +// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create;delete;update +// +kubebuilder:rbac:groups="authorization.k8s.io",resources=subjectaccessreviews,verbs=get;list;watch;create;delete;update + +// New returns a new storage provider for AppCat +func New() restbuilder.ResourceHandlerProvider { + return func(s *runtime.Scheme, gasdf genericregistry.RESTOptionsGetter) (rest.Storage, error) { + c, err := client.NewWithWatch(loopback.GetLoopbackMasterClientConfig(), client.Options{}) + if err != nil { + return nil, err + } + err = v1.AddToScheme(c.Scheme()) + if err != nil { + return nil, err + } + err = crossplane.AddToScheme(c.Scheme()) + if err != nil { + return nil, err + } + return &appcatStorage{ + compositions: &kubeCompositionProvider{ + Client: c, + }, + }, nil + } +} + +type appcatStorage struct { + compositions compositionProvider +} + +func (s *appcatStorage) New() runtime.Object { + return &v1.AppCat{} +} + +func (s *appcatStorage) Destroy() {} + +var _ rest.Scoper = &appcatStorage{} +var _ rest.Storage = &appcatStorage{} + +func (s *appcatStorage) NamespaceScoped() bool { + return false +} diff --git a/pkg/apiserver/appcat/appcat_test.go b/pkg/apiserver/appcat/appcat_test.go new file mode 100644 index 0000000..ee9c595 --- /dev/null +++ b/pkg/apiserver/appcat/appcat_test.go @@ -0,0 +1,92 @@ +package appcat + +import ( + "testing" + + crossplanev1 "github.com/crossplane/crossplane/apis/apiextensions/v1" + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "github.com/vshn/appcat-apiserver/test/mocks" + "k8s.io/apiserver/pkg/registry/rest" + + "github.com/golang/mock/gomock" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// newMockedAppCatStorage is a mocked instance of AppCatStorage +func newMockedAppCatStorage(t *testing.T, ctrl *gomock.Controller) (rest.StandardStorage, *mocks.MockcompositionProvider) { + t.Helper() + comp := mocks.NewMockcompositionProvider(ctrl) + stor := &appcatStorage{ + compositions: comp, + } + return rest.Storage(stor).(rest.StandardStorage), comp +} + +// Test AppCat instances +var ( + appCatOne = &v1.AppCat{ + ObjectMeta: metav1.ObjectMeta{ + Name: "one", + }, + + Details: map[string]string{ + "zone": "rma1", + "displayname": "one", + "docs": "https://docs.com", + }, + + Status: v1.AppCatStatus{ + CompositionName: "one", + }, + } + compositionOne = &crossplanev1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "one", + Labels: map[string]string{ + v1.OfferedKey: v1.OfferedValue, + }, + Annotations: map[string]string{ + v1.PrefixAppCatKey + "/zone": "rma1", + v1.PrefixAppCatKey + "/displayname": "one", + v1.PrefixAppCatKey + "/docs": "https://docs.com", + }, + }, + } + appCatTwo = &v1.AppCat{ + ObjectMeta: metav1.ObjectMeta{ + Name: "two", + }, + + Details: map[string]string{ + "zone": "lpg", + "displayname": "two", + "docs": "https://docs.com", + "productDescription": "product desc", + }, + + Status: v1.AppCatStatus{ + CompositionName: "two", + }, + } + compositionTwo = &crossplanev1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "two", + Labels: map[string]string{ + v1.OfferedKey: v1.OfferedValue, + }, + Annotations: map[string]string{ + v1.PrefixAppCatKey + "/zone": "lpg", + v1.PrefixAppCatKey + "/displayname": "two", + v1.PrefixAppCatKey + "/docs": "https://docs.com", + v1.PrefixAppCatKey + "/product-description": "product desc", + }, + }, + } + compositionNonOffered = &crossplanev1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + v1.OfferedKey: "false", + }, + }, + } +) diff --git a/pkg/apiserver/appcat/composition.go b/pkg/apiserver/appcat/composition.go new file mode 100644 index 0000000..07491c1 --- /dev/null +++ b/pkg/apiserver/appcat/composition.go @@ -0,0 +1,51 @@ +package appcat + +import ( + "context" + v1 "github.com/crossplane/crossplane/apis/apiextensions/v1" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/watch" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// compositionProvider is an abstraction to interact with the K8s API +type compositionProvider interface { + GetComposition(ctx context.Context, name string, options *metav1.GetOptions) (*v1.Composition, error) + ListCompositions(ctx context.Context, options *metainternalversion.ListOptions) (*v1.CompositionList, error) + WatchCompositions(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) +} + +type kubeCompositionProvider struct { + Client client.WithWatch +} + +func (k *kubeCompositionProvider) GetComposition(ctx context.Context, name string, options *metav1.GetOptions) (*v1.Composition, error) { + c := v1.Composition{} + err := k.Client.Get(ctx, client.ObjectKey{Namespace: "", Name: name}, &c) + return &c, err +} + +func (k *kubeCompositionProvider) ListCompositions(ctx context.Context, options *metainternalversion.ListOptions) (*v1.CompositionList, error) { + cl := v1.CompositionList{} + err := k.Client.List(ctx, &cl, &client.ListOptions{ + LabelSelector: options.LabelSelector, + FieldSelector: options.FieldSelector, + Limit: options.Limit, + Continue: options.Continue, + }) + if err != nil { + return nil, err + } + return &cl, nil +} + +func (k *kubeCompositionProvider) WatchCompositions(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { + cl := v1.CompositionList{} + return k.Client.Watch(ctx, &cl, &client.ListOptions{ + LabelSelector: options.LabelSelector, + FieldSelector: options.FieldSelector, + Limit: options.Limit, + Continue: options.Continue, + }) +} diff --git a/pkg/apiserver/appcat/create.go b/pkg/apiserver/appcat/create.go new file mode 100644 index 0000000..b01d562 --- /dev/null +++ b/pkg/apiserver/appcat/create.go @@ -0,0 +1,15 @@ +package appcat + +import ( + "context" + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Creater = &appcatStorage{} + +func (s *appcatStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { + return nil, fmt.Errorf("method not implemented") +} diff --git a/pkg/apiserver/appcat/delete.go b/pkg/apiserver/appcat/delete.go new file mode 100644 index 0000000..266448f --- /dev/null +++ b/pkg/apiserver/appcat/delete.go @@ -0,0 +1,29 @@ +package appcat + +import ( + "context" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.GracefulDeleter = &appcatStorage{} +var _ rest.CollectionDeleter = &appcatStorage{} + +func (s *appcatStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + return &v1.AppCat{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + }, false, nil +} + +func (s *appcatStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + return &v1.AppCatList{ + Items: []v1.AppCat{}, + }, nil +} diff --git a/pkg/apiserver/appcat/get.go b/pkg/apiserver/appcat/get.go new file mode 100644 index 0000000..02df13b --- /dev/null +++ b/pkg/apiserver/appcat/get.go @@ -0,0 +1,30 @@ +package appcat + +import ( + "context" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "github.com/vshn/appcat-apiserver/pkg/apiserver" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Getter = &appcatStorage{} + +// Get returns an AppCat service based on its composition +func (s *appcatStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + composition, err := s.compositions.GetComposition(ctx, name, options) + if err != nil { + return nil, apiserver.ResolveError(v1.GetGroupResource(v1.Resource), err) + } + + appcat := v1.NewAppCatFromComposition(composition) + if appcat == nil { + // This composition is not an AppCat service + return nil, apierrors.NewNotFound(appcat.GetGroupVersionResource().GroupResource(), name) + } + + return appcat, nil +} diff --git a/pkg/apiserver/appcat/get_test.go b/pkg/apiserver/appcat/get_test.go new file mode 100644 index 0000000..ca5e35a --- /dev/null +++ b/pkg/apiserver/appcat/get_test.go @@ -0,0 +1,86 @@ +package appcat + +import ( + "testing" + + crossplanev1 "github.com/crossplane/crossplane/apis/apiextensions/v1" + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "k8s.io/apiserver/pkg/endpoints/request" +) + +func TestAppCatStorage_Get(t *testing.T) { + tests := map[string]struct { + name string + composition *crossplanev1.Composition + compErr error + appcat *v1.AppCat + err error + }{ + "GivenAComposition_ThenAppCat": { + name: "one", + composition: compositionOne, + appcat: appCatOne, + }, + "GivenErrNotFound_ThenErrNotFound": { + name: "not-found", + compErr: apierrors.NewNotFound(schema.GroupResource{ + Resource: "compositions", + }, "not-found"), + err: apierrors.NewNotFound(schema.GroupResource{ + Group: v1.GroupVersion.Group, + Resource: "appcats", + }, "not-found"), + }, + "GivenNonAppCatComp_ThenErrNotFound": { + name: "appcat-not-found", + composition: &crossplanev1.Composition{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + v1.OfferedKey: "false", + }, + }, + }, + err: apierrors.NewNotFound(schema.GroupResource{ + Group: v1.GroupVersion.Group, + Resource: "appcats", + }, "appcat-not-found"), + }, + } + + for n, tc := range tests { + + t.Run(n, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + stor, compProvider := newMockedAppCatStorage(t, ctrl) + + compProvider.EXPECT(). + GetComposition(gomock.Any(), tc.name, gomock.Any()). + Return(tc.composition, tc.compErr). + Times(1) + + appcat, err := stor.Get(request.WithRequestInfo(request.NewContext(), + &request.RequestInfo{ + Verb: "get", + APIGroup: v1.GroupVersion.Group, + Resource: "appcats", + Name: tc.name, + }), + tc.name, nil) + if tc.err != nil { + assert.EqualError(t, err, tc.err.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, tc.appcat, appcat) + }) + } +} diff --git a/pkg/apiserver/appcat/list.go b/pkg/apiserver/appcat/list.go new file mode 100644 index 0000000..23a9954 --- /dev/null +++ b/pkg/apiserver/appcat/list.go @@ -0,0 +1,89 @@ +package appcat + +import ( + "context" + + crossplanev1 "github.com/crossplane/crossplane/apis/apiextensions/v1" + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "github.com/vshn/appcat-apiserver/pkg/apiserver" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Lister = &appcatStorage{} + +func (s *appcatStorage) NewList() runtime.Object { + return &v1.AppCatList{} +} + +// List returns a list of AppCat services based on their compositions +func (s *appcatStorage) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { + cl, err := s.compositions.ListCompositions(ctx, addOfferedLabelSelector(options)) + if err != nil { + return nil, apiserver.ResolveError(v1.GetGroupResource(v1.Resource), err) + } + + res := v1.AppCatList{ + ListMeta: cl.ListMeta, + } + + for _, v := range cl.Items { + appCat := v1.NewAppCatFromComposition(&v) + if appCat != nil { + res.Items = append(res.Items, *appCat) + } + } + + return &res, nil +} + +var _ rest.Watcher = &appcatStorage{} + +// Watch returns a watched list of AppCat services based on their compositions +func (s *appcatStorage) Watch(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { + compWatcher, err := s.compositions.WatchCompositions(ctx, addOfferedLabelSelector(options)) + if err != nil { + return nil, apiserver.ResolveError(v1.GetGroupResource(v1.Resource), err) + } + + return watch.Filter(compWatcher, func(in watch.Event) (out watch.Event, keep bool) { + if in.Object == nil { + // This should never happen, let downstream deal with it + return in, true + } + comp, ok := in.Object.(*crossplanev1.Composition) + if !ok { + // We received a non Composition object + // This is most likely an error so we pass it on + return in, true + } + + in.Object = v1.NewAppCatFromComposition(comp) + if in.Object.(*v1.AppCat) == nil { + return in, false + } + + return in, true + }), nil +} + +func addOfferedLabelSelector(options *metainternalversion.ListOptions) *metainternalversion.ListOptions { + offeredComposition, err := labels.NewRequirement(v1.OfferedKey, selection.Equals, []string{v1.OfferedValue}) + if err != nil { + // The input is static. This call will only fail during development. + panic(err) + } + if options == nil { + options = &metainternalversion.ListOptions{} + } + if options.LabelSelector == nil { + options.LabelSelector = labels.NewSelector() + } + options.LabelSelector = options.LabelSelector.Add(*offeredComposition) + + return options +} diff --git a/pkg/apiserver/appcat/list_test.go b/pkg/apiserver/appcat/list_test.go new file mode 100644 index 0000000..2413dc3 --- /dev/null +++ b/pkg/apiserver/appcat/list_test.go @@ -0,0 +1,205 @@ +package appcat + +import ( + "testing" + + crossplanev1 "github.com/crossplane/crossplane/apis/apiextensions/v1" + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "k8s.io/apiserver/pkg/endpoints/request" +) + +func TestAppcatStorage_List(t *testing.T) { + tests := map[string]struct { + compositions *crossplanev1.CompositionList + compositionErr error + + appcats *v1.AppCatList + err error + }{ + "GivenListOfCompositions_ThenReturnAppCats": { + compositions: &crossplanev1.CompositionList{ + Items: []crossplanev1.Composition{ + *compositionOne, + *compositionTwo, + }, + }, + appcats: &v1.AppCatList{ + Items: []v1.AppCat{ + *appCatOne, + *appCatTwo, + }, + }, + }, + "GivenErrNotFound_ThenErrNotFound": { + compositionErr: apierrors.NewNotFound(schema.GroupResource{ + Resource: "compositions", + }, "not-found"), + err: apierrors.NewNotFound(schema.GroupResource{ + Group: v1.GroupVersion.Group, + Resource: v1.Resource, + }, "not-found"), + }, + "GivenList_ThenFilter": { + compositions: &crossplanev1.CompositionList{ + Items: []crossplanev1.Composition{ + *compositionOne, + *compositionNonOffered, + }, + }, + appcats: &v1.AppCatList{ + Items: []v1.AppCat{ + *appCatOne, + }, + }, + }, + } + for n, tc := range tests { + t.Run(n, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + stor, compProvider := newMockedAppCatStorage(t, ctrl) + + compProvider.EXPECT(). + ListCompositions(gomock.Any(), gomock.Any()). + Return(tc.compositions, tc.compositionErr). + Times(1) + + appcats, err := stor.List(request.WithRequestInfo(request.NewContext(), + &request.RequestInfo{ + Verb: "list", + APIGroup: v1.GroupVersion.Group, + Resource: v1.Resource, + }), nil) + if tc.err != nil { + assert.EqualError(t, err, tc.err.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, tc.appcats, appcats) + }) + } +} + +type testWatcher struct { + events chan watch.Event +} + +func (w testWatcher) Stop() {} + +func (w testWatcher) ResultChan() <-chan watch.Event { + return w.events +} + +func TestAppCatsStorage_Watch(t *testing.T) { + tests := map[string]struct { + compositionEvents []watch.Event + compositionErr error + + appcatEvents []watch.Event + err error + }{ + "GivenCompositionEvents_ThenAppCatEvents": { + compositionEvents: []watch.Event{ + { + Type: watch.Added, + Object: compositionOne, + }, + { + Type: watch.Modified, + Object: compositionTwo, + }, + }, + appcatEvents: []watch.Event{ + { + Type: watch.Added, + Object: appCatOne, + }, + { + Type: watch.Modified, + Object: appCatTwo, + }, + }, + }, + "GivenErrNotFound_ThenErrNotFound": { + compositionErr: apierrors.NewNotFound(schema.GroupResource{ + Resource: "compositions", + }, "not-found"), + err: apierrors.NewNotFound(schema.GroupResource{ + Group: v1.GroupVersion.Group, + Resource: v1.Resource, + }, "not-found"), + }, + "GivenVariousCompositionEvents_ThenFilter": { + compositionEvents: []watch.Event{ + { + Type: watch.Added, + Object: compositionOne, + }, + { + Type: watch.Modified, + Object: compositionNonOffered, + }, + { + Type: watch.Modified, + Object: compositionTwo, + }, + }, + appcatEvents: []watch.Event{ + { + Type: watch.Added, + Object: appCatOne, + }, + { + Type: watch.Modified, + Object: appCatTwo, + }, + }, + }, + } + + for n, tc := range tests { + t.Run(n, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + stor, compProvider := newMockedAppCatStorage(t, ctrl) + + compWatcher := testWatcher{ + events: make(chan watch.Event, len(tc.compositionEvents)), + } + for _, e := range tc.compositionEvents { + compWatcher.events <- e + } + close(compWatcher.events) + + compProvider.EXPECT(). + WatchCompositions(gomock.Any(), gomock.Any()). + Return(compWatcher, tc.compositionErr). + AnyTimes() + + appcatWatch, err := stor.Watch(request.WithRequestInfo(request.NewContext(), + &request.RequestInfo{ + Verb: "watch", + APIGroup: v1.GroupVersion.Group, + Resource: v1.Resource, + }), nil) + if tc.err != nil { + assert.EqualError(t, err, tc.err.Error()) + return + } + require.NoError(t, err) + appcatEvents := []watch.Event{} + for e := range appcatWatch.ResultChan() { + appcatEvents = append(appcatEvents, e) + } + assert.Equal(t, tc.appcatEvents, appcatEvents) + }) + } +} diff --git a/pkg/apiserver/appcat/table.go b/pkg/apiserver/appcat/table.go new file mode 100644 index 0000000..d35f0dc --- /dev/null +++ b/pkg/apiserver/appcat/table.go @@ -0,0 +1,70 @@ +package appcat + +import ( + "context" + "fmt" + "time" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/duration" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.TableConvertor = &appcatStorage{} + +var ( + appCatDisplayname = "displayname" + appCatZone = "zone" + appCatDocs = "endUserDocsUrl" +) + +// ConvertToTable translates the given object to a table for kubectl printing +func (s *appcatStorage) ConvertToTable(_ context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + var table metav1.Table + + appcats := []v1.AppCat{} + if meta.IsListType(obj) { + appcatList, ok := obj.(*v1.AppCatList) + if !ok { + return nil, fmt.Errorf("not an appcat: %#v", obj) + } + appcats = appcatList.Items + } else { + appcat, ok := obj.(*v1.AppCat) + if !ok { + return nil, fmt.Errorf("not an appcat: %#v", obj) + } + appcats = append(appcats, *appcat) + } + + for _, appcat := range appcats { + table.Rows = append(table.Rows, appcatToTableRow(&appcat)) + } + + if opt, ok := tableOptions.(*metav1.TableOptions); !ok || !opt.NoHeaders { + desc := metav1.ObjectMeta{}.SwaggerDoc() + table.ColumnDefinitions = []metav1.TableColumnDefinition{ + {Name: "AppCat Name", Type: "string", Format: "name", Description: desc["name"]}, + {Name: "AppCat Display Name", Type: "string", Format: "name", Description: "The display name of the service"}, + {Name: "Service Zone", Type: "string", Description: "Available zones of the service"}, + {Name: "User Docs", Type: "string", Description: "The user documentation of the service"}, + {Name: "Age", Type: "date", Description: desc["creationTimestamp"]}, + } + } + return &table, nil +} + +func appcatToTableRow(appcat *v1.AppCat) metav1.TableRow { + return metav1.TableRow{ + Cells: []interface{}{ + appcat.GetName(), + appcat.Details[appCatDisplayname], + appcat.Details[appCatZone], + appcat.Details[appCatDocs], + duration.HumanDuration(time.Since(appcat.GetCreationTimestamp().Time))}, + Object: runtime.RawExtension{Object: appcat}, + } +} diff --git a/pkg/apiserver/appcat/table_test.go b/pkg/apiserver/appcat/table_test.go new file mode 100644 index 0000000..d90d6ff --- /dev/null +++ b/pkg/apiserver/appcat/table_test.go @@ -0,0 +1,74 @@ +package appcat + +import ( + "context" + "testing" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestAppCatStorage_ConvertToTable(t *testing.T) { + tests := map[string]struct { + obj runtime.Object + tableOptions runtime.Object + fail bool + nrRows int + }{ + "GivenEmptyAppCat_ThenSingleRow": { + obj: &v1.AppCat{}, + nrRows: 1, + }, + "GivenAppCat_ThenSingleRow": { + obj: &v1.AppCat{ + ObjectMeta: metav1.ObjectMeta{Name: "pippo"}, + + Details: map[string]string{ + "zone": "rma1", + "displayname": "ObjectStorage", + }, + }, + nrRows: 1, + }, + "GivenAppCatList_ThenMultipleRow": { + obj: &v1.AppCatList{ + Items: []v1.AppCat{ + {}, + {}, + {}, + }, + }, + nrRows: 3, + }, + "GivenNil_ThenFail": { + obj: nil, + fail: true, + }, + "GivenNonAppCat_ThenFail": { + obj: &corev1.Pod{}, + fail: true, + }, + "GivenNonAppCatList_ThenFail": { + obj: &corev1.PodList{}, + fail: true, + }, + } + appcatStore := &appcatStorage{} + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + table, err := appcatStore.ConvertToTable(context.TODO(), tc.obj, tc.tableOptions) + if tc.fail { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Len(t, table.Rows, tc.nrRows) + }) + } +} diff --git a/pkg/apiserver/appcat/update.go b/pkg/apiserver/appcat/update.go new file mode 100644 index 0000000..5a062b8 --- /dev/null +++ b/pkg/apiserver/appcat/update.go @@ -0,0 +1,21 @@ +package appcat + +import ( + "context" + + v1 "github.com/vshn/appcat-apiserver/apis/appcat/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" +) + +var _ rest.Updater = &appcatStorage{} +var _ rest.CreaterUpdater = &appcatStorage{} + +func (s *appcatStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + return &v1.AppCat{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + }, false, nil +}