Compare commits

...

103 Commits

Author SHA1 Message Date
Oliver Bähler
800a8ffa88 chore(helm): bump app version 0.5.0 (#923)
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-12-07 20:21:46 +01:00
Oliver Bähler
74d3ac504e fix(controller): respect metadata of replicated items (#922)
* fix(controller): respect metadata of replicated items

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore(makefile): fix dev-setup

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

---------

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-12-07 16:49:35 +01:00
dependabot[bot]
591a66ea20 ci(deps): Bump actions/setup-go from 4.1.0 to 5.0.0 (#921)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4.1.0 to 5.0.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](93397bea11...0c52d547c9)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-07 16:39:10 +01:00
dependabot[bot]
b58fe04026 feat(deps): Bump sigs.k8s.io/cluster-api from 1.6.0-beta.1 to 1.6.0 (#920)
Bumps [sigs.k8s.io/cluster-api](https://github.com/kubernetes-sigs/cluster-api) from 1.6.0-beta.1 to 1.6.0.
- [Release notes](https://github.com/kubernetes-sigs/cluster-api/releases)
- [Commits](https://github.com/kubernetes-sigs/cluster-api/compare/v1.6.0-beta.1...v1.6.0)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/cluster-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-07 08:13:54 +01:00
dependabot[bot]
c30b5e911c ci(deps): Bump anchore/sbom-action from 0.15.0 to 0.15.1 (#919)
Bumps [anchore/sbom-action](https://github.com/anchore/sbom-action) from 0.15.0 to 0.15.1.
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Commits](fd74a6fb98...5ecf649a41)

---
updated-dependencies:
- dependency-name: anchore/sbom-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-07 08:13:13 +01:00
dependabot[bot]
3dc25673b4 ci(deps): Bump aquasecurity/trivy-action from 0.14.0 to 0.15.0 (#918)
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.14.0 to 0.15.0.
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](2b6a709cf9...22d2755f77)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-07 08:12:38 +01:00
dependabot[bot]
e9ed7b29d4 ci(deps): bump zgosalvez/github-actions-ensure-sha-pinned-actions (#917)
Bumps [zgosalvez/github-actions-ensure-sha-pinned-actions](https://github.com/zgosalvez/github-actions-ensure-sha-pinned-actions) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/zgosalvez/github-actions-ensure-sha-pinned-actions/releases)
- [Commits](b35f285b9b...b1b635d242)

---
updated-dependencies:
- dependency-name: zgosalvez/github-actions-ensure-sha-pinned-actions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 17:14:48 +01:00
Oliver Bähler
2792b5894b feat(chart): allow custom role bindings
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-11-30 14:48:16 +01:00
Oliver Bähler
21c0d04ead fix(controller): respect group quotas for number of operating system threads allocated to goroutines
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-11-30 14:47:30 +01:00
dependabot[bot]
181cb67893 feat(deps): bump github.com/onsi/ginkgo/v2 from 2.13.1 to 2.13.2 (#912)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.13.1 to 2.13.2.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.1...v2.13.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-29 15:26:54 +01:00
Oliver Bähler
c58b46cedf feat(tenant): add label with tenant name for each tenant
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-11-29 07:24:27 +01:00
Dario Tranchitella
3c85657d9a fix(e2e): ensuring well-known label
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-28 14:56:09 +01:00
Dario Tranchitella
242fdd23ee feat: ensuring well-known label
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-28 14:56:09 +01:00
Dario Tranchitella
8ed302dd8a fix: v1alpha1 api type has been removed
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-28 14:56:09 +01:00
Dario Tranchitella
2c70bf0a08 docs: deprecating v1alpha1 api
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-26 17:16:33 +01:00
Dario Tranchitella
976d504392 chore(helm): deprecating v1alpha1 api
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-26 17:16:33 +01:00
Dario Tranchitella
8303421453 chore(kustomize): deprecating v1alpha1 api
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-26 17:16:33 +01:00
Dario Tranchitella
0241603f2b feat!: deprecating v1alpha1 api
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-26 17:16:33 +01:00
Siarhei Rasiukevich
52aa83796e chore(kustomize): update kustomize crd
Signed-off-by: Siarhei Rasiukevich <s.rasiukevich@gmail.com>
2023-11-24 12:30:59 +01:00
Siarhei Rasiukevich
b27780d74c feat(manager): add forbidden annotations, forbidden labels to service options
Signed-off-by: Siarhei Rasiukevich <s.rasiukevich@gmail.com>
2023-11-24 12:30:59 +01:00
Dario Tranchitella
8695dfb7a2 test: additional metadata for pods
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
Co-authored-by: Giuseppe Chiesa <mail@giuseppechiesa.it>
2023-11-24 11:10:42 +01:00
Dario Tranchitella
d94430466e feat: additional metadata for pods
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
Co-authored-by: Giuseppe Chiesa <mail@giuseppechiesa.it>
2023-11-24 11:10:42 +01:00
Dario Tranchitella
14eb8f20be docs: additional metadata for pods
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
Co-authored-by: Giuseppe Chiesa <mail@giuseppechiesa.it>
2023-11-24 11:10:42 +01:00
Dario Tranchitella
1e70cd335f feat(helm): additional metadata for pods
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
Co-authored-by: Giuseppe Chiesa <mail@giuseppechiesa.it>
2023-11-24 11:10:42 +01:00
Dario Tranchitella
4b8989530c feat(kustomize): additional metadata for pods
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
Co-authored-by: Giuseppe Chiesa <mail@giuseppechiesa.it>
2023-11-24 11:10:42 +01:00
Dario Tranchitella
e61152a484 feat(api): additional metadata for pods
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
Co-authored-by: Giuseppe Chiesa <mail@giuseppechiesa.it>
2023-11-24 11:10:42 +01:00
Dario Tranchitella
c208f5e66e fix(makefile): missing webhook field
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
Co-authored-by: Giuseppe Chiesa <mail@giuseppechiesa.it>
2023-11-24 11:10:42 +01:00
dependabot[bot]
5e8d0a0960 ci(deps): bump anchore/sbom-action from 0.14.3 to 0.15.0
Bumps [anchore/sbom-action](https://github.com/anchore/sbom-action) from 0.14.3 to 0.15.0.
- [Release notes](https://github.com/anchore/sbom-action/releases)
- [Commits](78fc58e266...fd74a6fb98)

---
updated-dependencies:
- dependency-name: anchore/sbom-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-23 14:13:17 +01:00
Dario Tranchitella
9a87364288 chore(helm): releasing v0.5.3
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-21 14:38:49 +01:00
Dario Tranchitella
34977aa5d8 fix: ensuring resourcequota name doesn't break DNS-1123
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-21 13:15:04 +01:00
Dario Tranchitella
2465b66840 chore(deps): bumping up k8s.io packages to v0.28.4
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-20 17:03:19 +01:00
Dario Tranchitella
c0e48d1bd9 fix(quota-resources): skipping from count delete-marked objects
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-20 15:11:33 +01:00
Dario Tranchitella
3b0b6cf5ad test: removing unquired sleep
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-20 14:50:18 +01:00
Dario Tranchitella
84254019cf chore(e2e): bumping up ginkgo cli version
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-20 14:50:18 +01:00
Dario Tranchitella
c735c3c8c9 test: throttling k8sclient
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-20 14:50:18 +01:00
Dario Tranchitella
c13e45281e test: using pointer for test env existing cluster
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-20 14:50:18 +01:00
Dario Tranchitella
2e5c232188 fix: sync quota values from tenant to resourcequota object
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-14 17:16:32 +01:00
dependabot[bot]
5e13ac94cf feat(deps): bump github.com/onsi/ginkgo/v2 from 2.13.0 to 2.13.1
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.13.0 to 2.13.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.0...v2.13.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 15:13:07 +01:00
Dario Tranchitella
9a21b408dd chore(helm): releasing v0.5.2
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-09 17:11:09 +01:00
Dario Tranchitella
25b4a35b65 feat(ux): namespace oncrete hook to check namespace exsistence
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-09 15:48:04 +01:00
dependabot[bot]
98b5c3f076 feat(deps): bump golang.org/x/sync from 0.4.0 to 0.5.0
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.4.0 to 0.5.0.
- [Commits](https://github.com/golang/sync/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-09 14:19:09 +01:00
Dario Tranchitella
9f63aabbb1 chore(e2e): bump github.com/onsi/gomega from 1.29.0 to 1.30.0
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-09 14:18:40 +01:00
dependabot[bot]
d09a1c51c7 feat(deps): bump github.com/onsi/gomega from 1.29.0 to 1.30.0
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.29.0 to 1.30.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.29.0...v1.30.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-09 14:18:40 +01:00
Oliver Bähler
cde44ba14e fix(controller): copy ownerreference from oldNs on namespace UPDATE admission requests
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-11-09 13:59:21 +01:00
dependabot[bot]
2baf604511 ci(deps): bump aquasecurity/trivy-action from 0.13.1 to 0.14.0 (#886)
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.13.1 to 0.14.0.
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](f78e9ecf42...2b6a709cf9)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-09 10:19:12 +01:00
dependabot[bot]
34fc260963 ci(deps): bump sigstore/cosign-installer from 3.1.2 to 3.2.0 (#887)
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.1.2 to 3.2.0.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](11086d2504...1fc5bd396d)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-09 10:18:38 +01:00
Oliver Bähler
4ed48e5136 chore(helm): prepare helm release
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-11-07 11:09:59 +01:00
dependabot[bot]
abdfdaf297 ci(deps): bump zgosalvez/github-actions-ensure-sha-pinned-actions (#883)
Bumps [zgosalvez/github-actions-ensure-sha-pinned-actions](https://github.com/zgosalvez/github-actions-ensure-sha-pinned-actions) from 2.1.5 to 3.0.1.
- [Release notes](https://github.com/zgosalvez/github-actions-ensure-sha-pinned-actions/releases)
- [Commits](c481dd7047...b35f285b9b)

---
updated-dependencies:
- dependency-name: zgosalvez/github-actions-ensure-sha-pinned-actions
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-07 10:34:39 +01:00
dependabot[bot]
6f80b2bcf8 ci(deps): bump helm/chart-testing-action from 2.6.0 to 2.6.1 (#884)
Bumps [helm/chart-testing-action](https://github.com/helm/chart-testing-action) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/helm/chart-testing-action/releases)
- [Commits](b43128a8b2...e6669bcd63)

---
updated-dependencies:
- dependency-name: helm/chart-testing-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-07 10:33:04 +01:00
dependabot[bot]
ad8957ca7e ci(deps): bump amannn/action-semantic-pull-request from 5.3.0 to 5.4.0 (#881)
Bumps [amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/amannn/action-semantic-pull-request/releases)
- [Changelog](https://github.com/amannn/action-semantic-pull-request/blob/main/CHANGELOG.md)
- [Commits](47b15d52c5...e9fabac35e)

---
updated-dependencies:
- dependency-name: amannn/action-semantic-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 09:47:17 +01:00
Dario Tranchitella
afd9aebf8a chore(security-insights): self-assessment evidence (#879)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-01 19:28:22 +01:00
dependabot[bot]
72f25c83e1 feat(deps): bump github.com/hashicorp/go-multierror from 1.1.0 to 1.1.1
Bumps [github.com/hashicorp/go-multierror](https://github.com/hashicorp/go-multierror) from 1.1.0 to 1.1.1.
- [Commits](https://github.com/hashicorp/go-multierror/compare/v1.1.0...v1.1.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-multierror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 19:15:20 +01:00
dependabot[bot]
6fe400a937 feat(deps): bump go.uber.org/zap from 1.25.0 to 1.26.0
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.25.0 to 1.26.0.
- [Release notes](https://github.com/uber-go/zap/releases)
- [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/zap/compare/v1.25.0...v1.26.0)

---
updated-dependencies:
- dependency-name: go.uber.org/zap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 19:06:39 +01:00
dependabot[bot]
75659a2bee chore(website): bump browserify-sign
Bumps [browserify-sign](https://github.com/crypto-browserify/browserify-sign) from 4.2.1 to 4.2.2.
- [Changelog](https://github.com/browserify/browserify-sign/blob/main/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/browserify-sign/compare/v4.2.1...v4.2.2)

---
updated-dependencies:
- dependency-name: browserify-sign
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 19:01:42 +01:00
dependabot[bot]
a4805b771c feat(deps): bump sigs.k8s.io/cluster-api
Bumps [sigs.k8s.io/cluster-api](https://github.com/kubernetes-sigs/cluster-api) from 1.4.0-beta.2.0.20230524193452-89a36acc3c3f to 1.6.0-beta.1.
- [Release notes](https://github.com/kubernetes-sigs/cluster-api/releases)
- [Commits](https://github.com/kubernetes-sigs/cluster-api/commits/v1.6.0-beta.1)

---
updated-dependencies:
- dependency-name: sigs.k8s.io/cluster-api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 18:55:32 +01:00
dependabot[bot]
efc3a1ed2b ci(deps): bump wagoid/commitlint-github-action from 5.4.3 to 5.4.4
Bumps [wagoid/commitlint-github-action](https://github.com/wagoid/commitlint-github-action) from 5.4.3 to 5.4.4.
- [Changelog](https://github.com/wagoid/commitlint-github-action/blob/master/CHANGELOG.md)
- [Commits](6319f54d83...0d749a1a91)

---
updated-dependencies:
- dependency-name: wagoid/commitlint-github-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 18:53:42 +01:00
dependabot[bot]
9750302a6b ci(deps): bump aquasecurity/trivy-action from 0.12.0 to 0.13.1
Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.12.0 to 0.13.1.
- [Release notes](https://github.com/aquasecurity/trivy-action/releases)
- [Commits](fbd16365eb...f78e9ecf42)

---
updated-dependencies:
- dependency-name: aquasecurity/trivy-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 18:53:08 +01:00
dependabot[bot]
e9c756ee04 ci(deps): bump zgosalvez/github-actions-ensure-sha-pinned-actions
Bumps [zgosalvez/github-actions-ensure-sha-pinned-actions](https://github.com/zgosalvez/github-actions-ensure-sha-pinned-actions) from 2.1.4 to 2.1.5.
- [Release notes](https://github.com/zgosalvez/github-actions-ensure-sha-pinned-actions/releases)
- [Commits](f32435541e...c481dd7047)

---
updated-dependencies:
- dependency-name: zgosalvez/github-actions-ensure-sha-pinned-actions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-01 18:52:28 +01:00
Oliver Bähler
e55bac9dd6 docs: security self assessment
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-11-01 17:25:09 +01:00
Oliver Bähler
a4e83286a6 chore(maintainers): new organization contribution
* docs(repo): add dependency policy

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* docs(repo): migrate development guide

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore(ci): migrate workflows and change company for oliver

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

---------

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-11-01 17:04:51 +01:00
dependabot[bot]
7acf60a67a ci(deps): bump helm/chart-testing-action from 2.4.0 to 2.6.0 (#875)
Bumps [helm/chart-testing-action](https://github.com/helm/chart-testing-action) from 2.4.0 to 2.6.0.
- [Release notes](https://github.com/helm/chart-testing-action/releases)
- [Commits](e878887317...b43128a8b2)

---
updated-dependencies:
- dependency-name: helm/chart-testing-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 16:45:30 +01:00
Oliver Bähler
1a7b0e1a3c docs(repo): migrate development guide
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-29 08:17:56 +01:00
Oliver Bähler
2d5b1e3b2d docs(repo): add dependency policy
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-29 08:17:56 +01:00
Dario Tranchitella
ee991ea03a chore(website): reporting gua code for the clomonitor
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-10-26 15:01:35 +02:00
Oliver Bähler
54531bab72 chore(ci): change build registry and fix helm test action
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-26 14:00:51 +02:00
Oliver Bähler
de868e1e3f chore(chart): bump 0.4.0-rc.3
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-26 14:00:51 +02:00
Oliver Bähler
6ecf478281 feat(image): release arm artifact
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-26 14:00:51 +02:00
dependabot[bot]
cd1736caf2 ci: bump oliverbaehler/github-actions from 0.1.0 to 0.1.1 (#832)
Bumps [oliverbaehler/github-actions](https://github.com/oliverbaehler/github-actions) from 0.1.0 to 0.1.1.
- [Commits](https://github.com/oliverbaehler/github-actions/compare/v0.1.0...979018716f7d0cbe8d2711f572b350afad4ef211)

---
updated-dependencies:
- dependency-name: oliverbaehler/github-actions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-25 23:37:39 +02:00
dependabot[bot]
358692de87 ci: bump actions/setup-node from 3.8.1 to 4.0.0 (#855)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3.8.1 to 4.0.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](5e21ff4d9b...8f152de45c)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-25 23:36:11 +02:00
dependabot[bot]
3d0a781985 ci: bump ossf/scorecard-action from 2.3.0 to 2.3.1 (#854)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.3.0 to 2.3.1.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](483ef80eb9...0864cf1902)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-25 23:34:46 +02:00
Dario Tranchitella
cba060dc60 chore(website): adding google analytics
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-10-25 12:19:59 +02:00
Oliver Bähler
cebb7025b6 chore: add more required metadata to security-insights
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-25 11:03:43 +02:00
Oliver Bähler
8989e37ce9 chore(repo): add distribution reference
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 18:40:41 +02:00
Oliver Bähler
70c8465721 chore(repo): correct dependabot prefix
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 18:40:41 +02:00
Oliver Bähler
4d25594df9 chore(repo): remove scopes as mandatory
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 18:40:41 +02:00
Dario Tranchitella
6d7523addf chore(repo): adding badge to readme.md (#848)
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-10-24 15:48:48 +02:00
Oliver Bähler
cfca55cf74 chore(repo): remove wip feature
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 15:40:44 +02:00
Oliver Bähler
0e9d15d98a ci(repo): fix token permissions
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 14:33:21 +02:00
Oliver Bähler
21eadaf1f3 docs(repo): add security insights
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 14:33:21 +02:00
Oliver Bähler
682e372b8f docs(repo): improve report process
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 14:33:21 +02:00
Oliver Bähler
3bd4bc6441 docs(repo): documentation improvements
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 10:03:45 +02:00
Oliver Bähler
747af4642f ci(repo): pull request linter
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 10:03:45 +02:00
Oliver Bähler
ed854f99c0 feat(chart): annotations and maintainers
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 10:03:45 +02:00
Oliver Bähler
9d3e9da1d0 chore(repo): configre commitlint.config.js
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 10:03:45 +02:00
Oliver Bähler
5c189094d0 docs(repo): add development
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 10:03:45 +02:00
Oliver Bähler
2cef776a59 docs(repo): add changelog
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-24 10:03:45 +02:00
Dario Tranchitella
364332c380 deps(controller-runtime): upgrading to v0.16.3
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-10-24 10:00:46 +02:00
Dario Tranchitella
c42c9ed88f deps(go): upgrading to 1.20
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-10-24 10:00:46 +02:00
dependabot[bot]
e0548e1556 feat(deps): bump github.com/stretchr/testify from 1.8.1 to 1.8.4
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.4.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.4)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 15:05:15 +02:00
dependabot[bot]
13c5377ec4 feat(deps): bump golang.org/x/sync from 0.2.0 to 0.4.0
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.2.0 to 0.4.0.
- [Commits](https://github.com/golang/sync/compare/v0.2.0...v0.4.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 15:03:20 +02:00
dependabot[bot]
8aa527f1c3 ci: bump securego/gosec from 2.18.1 to 2.18.2
Bumps [securego/gosec](https://github.com/securego/gosec) from 2.18.1 to 2.18.2.
- [Release notes](https://github.com/securego/gosec/releases)
- [Changelog](https://github.com/securego/gosec/blob/master/.goreleaser.yml)
- [Commits](0ec6cd95d7...55d7949601)

---
updated-dependencies:
- dependency-name: securego/gosec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 15:02:51 +02:00
Dario Tranchitella
4ad905e090 docs: using cncf logo
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-10-23 11:52:09 +02:00
Oliver Bähler
10bbf39ac1 docs(repo): add sbom reference
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-23 11:18:19 +02:00
Oliver Bähler
34d6416b1e docs(security): add security process
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-23 09:45:03 +02:00
Oliver Bähler
851c3a3765 fix(ci): fetch previous tags
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-23 09:45:03 +02:00
Oliver Bähler
d232791780 docs(repo): add roadmap file
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-23 09:45:03 +02:00
Dario Tranchitella
543757bddb fix(docs): url for artifacthub badge
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-10-20 17:42:12 +02:00
Dario Tranchitella
c16ea89532 chore(docs): removing deprecated workflow
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-10-20 17:42:12 +02:00
Dario Tranchitella
147f973c6b docs(community): pointing to the community meetings repo
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
2023-10-20 17:42:12 +02:00
Oliver Bähler
fe582f4c2f chore(chart): fix image location
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-20 16:50:35 +02:00
Oliver Bähler
b5d1537dc3 chore(chart): bump 0.4.0-rc.1
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-20 16:50:35 +02:00
Oliver Bähler
52c089414b ci(chart): overwrite release version from tag
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-20 16:50:35 +02:00
Oliver Bähler
404ba237ad docs(chart): add artifacthub badge
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2023-10-20 16:50:35 +02:00
106 changed files with 2829 additions and 6427 deletions

View File

@@ -1,18 +1,7 @@
<!--
# General contribution criteria
Read the contribution guidelines before creating a pull request.
https://github.com/projectcapsule/capsule/blob/main/CONTRIBUTING.md
Thanks for spending some time for improving and fixing Capsule!
We're still working on the outline of the contribution guidelines but we're
following ourselves these points:
- reference a previously opened issue: https://docs.github.com/en/github/writing-on-github/autolinked-references-and-urls#issues-and-pull-requests
- including a sentence or two in the commit description for the
changelog/release notes
- splitting changes into several and documented small commits
- limit the git subject to 50 characters and write as the continuation of the
sentence "If applied, this commit will ..."
- explain what and why in the body, if more than a trivial change, wrapping at
72 characters
-->

View File

@@ -13,4 +13,4 @@ updates:
interval: daily
rebase-strategy: disabled
commit-message:
prefix: "ci"
prefix: "ci(deps)"

View File

@@ -18,6 +18,6 @@
- https://github.com/clastix/capsule-proxy
- name: Oliver Bähler
github: https://github.com/oliverbaehler
company: Bedag Informatik AG
company: Peak Scale
projects:
- https://github.com/projectcapsule/capsule

View File

@@ -16,7 +16,7 @@ jobs:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Ensure SHA pinned actions
uses: zgosalvez/github-actions-ensure-sha-pinned-actions@f32435541e24cd6a4700a7f52bb2ec59e80603b1 # v2.1.4
uses: zgosalvez/github-actions-ensure-sha-pinned-actions@b1b635d24259e8a047a6ce7d6501ea432aa7a830 # v3.0.2
with:
# slsa-github-generator requires using a semver tag for reusable workflows.
# See: https://github.com/slsa-framework/slsa-github-generator#referencing-slsa-builders-and-generators

View File

@@ -18,6 +18,6 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@6319f54d83768b60acd6fd60e61007ccc583e62f #v5.4.3
- uses: wagoid/commitlint-github-action@0d749a1a91d4770e983a7b8f83d4a3f0e7e0874e #v5.4.4
with:
firstParent: true

37
.github/workflows/check-pr.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: "Check Pull Request"
on:
pull_request_target:
types:
- opened
- edited
- synchronize
permissions:
pull-requests: write
jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@e9fabac35e210fea40ca5b14c0da95a099eff26f
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
chore
ci
docs
feat
fix
test
sec
requireScope: false
wip: false
# If the PR only contains a single commit, the action will validate that
# it matches the configured pattern.
validateSingleCommit: true
# Related to `validateSingleCommit` you can opt-in to validate that the PR
# title matches a single commit to avoid confusion.
validateSingleCommitMatchesPrTitle: true

View File

@@ -19,9 +19,9 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version: '1.19'
go-version: '1.20'
- run: make installer
- name: Checking if YAML installer file is not aligned
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked generated files have not been committed" && git --no-pager diff && exit 1; fi

View File

@@ -28,7 +28,7 @@ jobs:
with:
build-cache-key: publish-images
- name: Run Trivy vulnerability (Repo)
uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f # v0.12.0
uses: aquasecurity/trivy-action@22d2755f774d925b191a185b74e782a4b0638a41 # v0.15.0
with:
scan-type: 'fs'
ignore-unfixed: true
@@ -36,10 +36,10 @@ jobs:
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Install Cosign
uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # v3.1.2
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
- name: Publish Capsule
id: publish-capsule
uses: oliverbaehler/github-actions/ko-publish-image@979018716f7d0cbe8d2711f572b350afad4ef211 # v0.1.1
uses: peak-scale/github-actions/make-ko-publish@38322faabccd75abfa581c435e367d446b6d2c3b # v0.1.0
with:
makefile-target: ko-publish-capsule
registry: ghcr.io

View File

@@ -25,7 +25,7 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
with:
node-version: 18
- run: make docs-lint

View File

@@ -43,9 +43,9 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version: '1.19'
go-version: '1.20'
- run: make manifests
- name: Checking if manifests are disaligned
run: test -z "$(git diff 2> /dev/null)"

View File

@@ -19,6 +19,6 @@ jobs:
- name: Checkout Source
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Run Gosec Security Scanner
uses: securego/gosec@0ec6cd95d7bf02aef4ec2786e884868e0044875b # v2.18.1
uses: securego/gosec@55d79496019a560e16e73e1948dee20a1fad631a # v2.18.2
with:
args: ./...

View File

@@ -15,11 +15,18 @@ jobs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: "Extract Version"
id: extract_version
run: |
GIT_TAG=${GITHUB_REF##*/}
VERSION=${GIT_TAG##*v}
echo "version=$(echo $VERSION)" >> $GITHUB_OUTPUT
- name: Publish Helm chart
uses: stefanprodan/helm-gh-pages@0ad2bb377311d61ac04ad9eb6f252fb68e207260 # v1.7.0
with:
token: "${{ secrets.HELM_CHARTS_PUSH_TOKEN }}"
linting: off
chart_version: ${{ steps.extract_version.outputs.version }}
charts_dir: charts
charts_url: https://${{ github.repository_owner }}.github.io/charts
owner: ${{ github.repository_owner }}
@@ -36,14 +43,21 @@ jobs:
chart-digest: ${{ steps.helm_publish.outputs.digest }}
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # v3.1.2
- uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
- name: "Extract Version"
id: extract_version
run: |
GIT_TAG=${GITHUB_REF##*/}
VERSION=${GIT_TAG##*v}
echo "version=$(echo $VERSION)" >> $GITHUB_OUTPUT
- name: Helm | Publish
id: helm_publish
uses: oliverbaehler/github-actions/helm-oci-chart@8dfd42735c85f6c58d5d4d6f3232cd0e39d1fe73 # v0.1.0
uses: peak-scale/github-actions/helm-oci-chart@38322faabccd75abfa581c435e367d446b6d2c3b # v0.1.0
with:
registry: ghcr.io
repository: ${{ github.repository_owner }}/charts
name: "capsule"
version: ${{ steps.extract_version.outputs.version }}
registry-username: ${{ github.actor }}
registry-password: ${{ secrets.GITHUB_TOKEN }}
update-dependencies: 'true' # Defaults to false

View File

@@ -21,7 +21,7 @@ jobs:
run: helm lint ./charts/capsule
- name: Setup Chart Linting
id: lint
uses: helm/chart-testing-action@e8788873172cb653a90ca2e819d79d65a66d4e76 # v2.4.0
uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1
- name: Run chart-testing (list-changed)
id: list-changed
run: |
@@ -43,27 +43,6 @@ jobs:
echo -e '\033[0;32mDocumentation up to date\033[0m ✔'
fi
# ATTENTION: This is a workaround for the upcoming ApiVersion Conversions for the capsule CRDs
# With this workflow the current docker image is build and loaded into kind, otherwise the install fails
# In the future this must be removed and the chart-testing-action must be used
- name: Run chart-testing (install)
run: make helm-test
if: steps.list-changed.outputs.changed == 'true'
## Create KIND Cluster
- name: Create kind cluster
uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0
if: steps.list-changed.outputs.changed == 'true'
# Install Required Operators/CRDs
- name: Prepare Cluster Operators/CRDs
run: |
# Cert-Manager CRDs
kubectl create -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.crds.yaml
# Prometheus CRDs
kubectl create -f https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.58.0/bundle.yaml
if: steps.list-changed.outputs.changed == 'true'
# Install Charts
- name: Run chart-testing (install)
run: ct install --debug --config ./.github/configs/ct.yaml
if: steps.list-changed.outputs.changed == 'true'
if: steps.list-changed.outputs.changed == 'true'

View File

@@ -19,14 +19,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
- name: Setup caches
uses: ./.github/actions/setup-caches
timeout-minutes: 5
continue-on-error: true
- uses: creekorful/goreportcard-action@1f35ced8cdac2cba28c9a2f2288a16aacfd507f9 # v1.0
- uses: anchore/sbom-action/download-syft@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1
- uses: anchore/sbom-action/download-syft@5ecf649a417b8ae17dc8383dc32d46c03f2312df
- name: Install Cosign
uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # v3.1.2
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
with:

View File

@@ -24,7 +24,7 @@ jobs:
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif

View File

@@ -66,9 +66,12 @@ changelog:
- title: '📖 Documentation updates'
regexp: ^.*?docs(\([[:word:]]+\))??!?:.+$
order: 400
- title: '🛡️ Security updates'
regexp: ^.*?(sec)(\([[:word:]]+\))??!?:.+$
order: 500
- title: '🚀 Build process updates'
regexp: ^.*?(build|ci)(\([[:word:]]+\))??!?:.+$
order: 400
order: 600
- title: '📦 Other work'
order: 9999
sboms:

View File

@@ -1,6 +1,7 @@
defaultPlatforms:
- linux/arm64
- linux/amd64
- linux/arm
builds:
- id: capsule
main: ./

10
CHANGELOG.md Normal file
View File

@@ -0,0 +1,10 @@
# Changelog
Changes are published with their type and scope for each release in the release description. Changes are assigned based on their commit description. Read more on how commits should be formatted in the [Contributing](CONTRIBUTING.md#commits) guide.
See the [Releases](https://github.com/projectcapsule/capsule/releases)
## Helm Chart
For the helm chart, a dedicated changelog is created based on the chart's annotations ([See](./DEVELOPMENT.md#helm-changelog)).

View File

@@ -2,48 +2,154 @@
All contributions are welcome! If you find a bug or have a feature request, please open an issue or submit a pull request.
## Ways to contribute
### 1. Report Issues
Issues to Capsule help improve the project in multiple ways including the following:
* Report potential bugs
* Request a feature
* Request a sample policy
### 2. Engagement
Engage with the community on [Slack](https://kubernetes.slack.com/archives/C03GETTJQRL) and help new users with questions or issues they may have.
### 3. Submit changes
Submit technical changes via pull requests. New contributors may easily view all open issues labeled as [good first issues](https://github.com/projectcapsule/capsule/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) allowing you to get started in an approachable manner.
Once you wish to get started contributing to the code base, please refer to our [development guide](DEVELOPMENT.md) for a how-to. **[We accept pull requests from forks only](#create-a-pull-request)**.
Before creating a pull request, please ensure that your changes are tested and that the documentation is updated accordingly.
When creating a pull request, please visit:
* [commits](#commits)
## Guidelines
The following guidelines outline the semantics and processes which apply to technical contributions to the project.
## Supported Versions
Versions follow [Semantic Versioning](https://semver.org/) terminology and are expressed as `x.y.z`:
- where x is the major version
- y is the minor version
- and z is the patch version
Security fixes, may be backported to the three most recent minor releases, depending on severity and feasibility.
Prereleases are marked as `-rc.x` (release candidate) and may refere to any type of version bump.
## Pull Requests
The pull request title is checked according to the described [semantics](#semantics) (pull requests don't require a scope). However pull requests are currently not used to generate the changelog. Check if your pull requests body meets the following criteria:
- reference a previously opened issue: https://docs.github.com/en/github/writing-on-github/autolinked-references-and-urls#issues-and-pull-requests
- splitting changes into several and documented small commits
- limit the git subject to 50 characters and write as the continuation of the
sentence "If applied, this commit will ..."
- explain what and why in the body, if more than a trivial change, wrapping at
72 characters
If your pull request in a draft state and not ready yet for review, you can prefix the title with `[WIP]`. This will indicate that work is still ongoing:
[WIP] feat(controller): new cool feature
### Create a Pull Request
Head over to the project repository on GitHub and click the **"Fork"** button. With the forked copy, you can try new ideas and implement changes to the project.
1. **Clone the repository to your device:**
Get the link of your forked repository, paste it in your device terminal and clone it using the command.
```sh
git clone https://hostname/YOUR-USERNAME/YOUR-REPOSITORY
```
2. **Create a branch:**
Create a new brach and navigate to the branch using this command.
```sh
git checkout -b <new-branch>
```
3. **Stage, Commit, and Push changes:**
Now that we have implemented the required changes, use the command below to stage the changes and commit them.
```sh
git add .
```
```sh
git commit -s -m "Commit message"
```
Go ahead and push your changes to GitHub using this command.
```sh
git push
```
## Commits
Commit messages should indicate the change and it's impact. The general format for commit messages is the following:
The commit message is checked according to the described [semantics](#semantics). Commits are used to generate the changelog and their author will be referenced in the changelog.
feat(ui): Add `Button` component
^ ^ ^
| | |__ Subject
| |_______ Scope
|____________ Type
### Reorganising commits
The commits are checked on pull-request. If the commit message does not follow the format, the workflow will fail. See the [Types](#types) and [Scopes](#scopes) sections for more information.
To reorganise your commits, do the following (or use your way of doing it):
## Types
The following types are allowed for commits and pull requests:
1. Pull upstream changes
```bash
git remote add upstream git@github.com:projectcapsule/capsule.git
git pull upstream main
```
* `ci` or `build`: changes to buillding process/workflows
* `docs`: changes to documentation
* `feat`: new features
* `fix`: bug fixes
2. Pick the current upstream HEAD (the commit is marked with `(remote/main, main)`)
## Scopes
```bash
git log
....
commit 10bbf39ac1ac3ad4f8485422e54faa9aadf03315 (remote/main, main)
Author: Oliver Bähler <oliverbaehler@hotmail.com>
Date: Mon Oct 23 10:24:44 2023 +0200
The following types are allowed for commits and pull requests:
docs(repo): add sbom reference
* `all`: changes that affect all components
* `chart`: changes to the Helm chart
* `operator`: changes to the operator
* `docs`: changes to the documentation
* `website`: changes to the website
* `ci`: changes to the CI/CD workflows
* `build`: changes to the build process
* `test`: changes to the testing process
* `release`: changes to the release process
* `deps`: dependency updates
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
```
3. Soft reset to the commit of the upstream HEAD
```bash
git reset --soft 10bbf39ac1ac3ad4f8485422e54faa9aadf03315
```
4. Remove staged files (if any)
```bash
git restore --staged .
```
5. Add files manually and create new [commits](#commits), until all files are included
```bash
git add charts/capsule/
git commit -s -m "feat(chart): add nodeselector value"
...
```
6. Force push the changes to your fork
```bash
git push origin main -f
```
### Sign-Off
@@ -55,4 +161,29 @@ To sign your work, just add a line like this at the end of your commit message:
Signed-off-by: Random J Developer <random@developer.example.org>
This can easily be done with the -s command line option to append this automatically to your commit message.
git commit -s -m 'This is my commit message'
git commit -s -m 'This is my commit message'
## Semantics
The semantics should indicate the change and it's impact. The general format for commit messages and pull requests is the following:
feat(ui): Add `Button` component
^ ^ ^
| | |__ Subject
| |_______ Scope
|____________ Type
The commits are checked on pull-request. If the commit message does not follow the format, the workflow will fail. See the [Types](#types) for the supported types. The scope is not required but helps to provide more context for your changes. Try to use a scope if possible.
### Types
The following types are allowed for commits and pull requests:
* `chore`: housekeeping changes, no production code change
* `ci`: changes to buillding process/workflows
* `docs`: changes to documentation
* `feat`: new features
* `fix`: bug fixes
* `test`: test related changes
* `sec`: security related changes

47
DEPENDENCY.md Normal file
View File

@@ -0,0 +1,47 @@
# Environment Dependencies Policy
## Purpose
This policy describes how Capsule maintainers consume third-party packages.
## Scope
This policy applies to all Capsule maintainers and all third-party packages used in the Capsule project.
## Policy
Capsule maintainers must follow these guidelines when consuming third-party packages:
- Only use third-party packages that are necessary for the functionality of Capsule.
- Use the latest version of all third-party packages whenever possible.
- Avoid using third-party packages that are known to have security vulnerabilities.
- Pin all third-party packages to specific versions in the Capsule codebase.
- Use a dependency management tool, such as Go modules, to manage third-party dependencies.
- Dependencies must pass all automated tests before being merged into the Capsule codebase.
## Procedure
When adding a new third-party package to Capsule, maintainers must follow these steps:
1. Evaluate the need for the package. Is it necessary for the functionality of Capsule?
2. Research the package. Is it well-maintained? Does it have a good reputation?
3. Choose a version of the package. Use the latest version whenever possible.
4. Pin the package to the specific version in the Capsule codebase.
5. Update the Capsule documentation to reflect the new dependency.
## Archive/Deprecation
When a third-party package is discontinued, the Capsule maintainers must fensure to replace the package with a suitable alternative.
## Enforcement
This policy is enforced by the Capsule maintainers.
Maintainers are expected to review each other's code changes to ensure that they comply with this policy.
## Exceptions
Exceptions to this policy may be granted by the Capsule project lead on a case-by-case basis.
## Credits
This policy was adapted from the [Kubescape Community](https://github.com/kubescape/kubescape/blob/master/docs/environment-dependencies-policy.md)

233
DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,233 @@
# Development
Our Makefile helps you with the development of new changes or fixes. [You may have a look at it](./Makefile), since not all targets are documented.
To execute your changes locally, you can run the binary locally. This will run just the capsule controller. We recommend [to setup a development environment](#development-environment) for a better development experience:
```bash
make run
```
## Building
You can build the docker image locally, Ko will be installed via go, so you don't need to install it manually.
```bash
make ko-build-all
```
This will push the build to your local docker images.
## Test
Execute unit testing:
```bash
make test
```
## E2E Test
**New changes always require dedcated E2E tests. E2E help us to ensure the quality of the code and it's functionality.**
For E2E test we use the [ginkgo](https://github.com/onsi/ginkgo) framework. Ou can see all the test under [e2e](./e2e/).
With the following command a new KinD cluster is created with the Kubernetes version `v1.20.7` (This can be done with any available Kubernetes version). A docker image is created and pushed and loaded into the KinD cluster. Then the E2E tests are executed against the KinD cluster.
```bash
make e2e/v1.20.7
```
You can also just run the e2e tests without the creation of a new kind cluster:
```
make e2e-exec
```
The E2E tests are also executed via the [github workflow](./.github/workflows/e2e.yaml) on every PR and push to the main branch.
# Development Environment
During development, we prefer that the code is running within our IDE locally, instead of running as the normal Pod(s) within the Kubernetes cluster.
Such a setup can be illustrated as below diagram:
![Development Environment](./assets/docs/dev-env.png)
## Setup Development Environment
To achieve that, there are some necessary steps we need to walk through, which have been made as a make target within our Makefile.
So the TL;DR answer is:
**Make sure a *KinD* cluster is running on your laptop, and then run `make dev-setup` to setup the dev environment.**. This is not done in the `make dev-setup` setup.
```bash
# If you haven't installed or run `make deploy` before, do it first
# Note: please retry if you saw errors
$ make deploy
# To retrieve your laptop's IP and execute `make dev-setup` to setup dev env
# For example: LAPTOP_HOST_IP=192.168.10.101 make dev-setup
$ LAPTOP_HOST_IP="<YOUR_LAPTOP_IP>" make dev-setup
```
### Explenation
We recommend to setup the development environment with the make `dev-setup` target. However here is a step by step guide to setup the development environment for understanding.
1. Scaling down the deployed Pod(s) to 0
We need to scale the existing replicas of capsule-controller-manager to 0 to avoid reconciliation competition between the Pod(s) and the code running outside of the cluster, in our preferred IDE for example.
```bash
$ kubectl -n capsule-system scale deployment capsule-controller-manager --replicas=0
deployment.apps/capsule-controller-manager scaled
```
2. Preparing TLS certificate for the webhooks
Running webhooks requires TLS, we can prepare the TLS key pair in our development env to handle HTTPS requests.
```bash
# Prepare a simple OpenSSL config file
# Do remember to export LAPTOP_HOST_IP before running this command
$ cat > _tls.cnf <<EOF
[ req ]
default_bits = 4096
distinguished_name = req_distinguished_name
req_extensions = req_ext
[ req_distinguished_name ]
countryName = SG
stateOrProvinceName = SG
localityName = SG
organizationName = CAPSULE
commonName = CAPSULE
[ req_ext ]
subjectAltName = @alt_names
[alt_names]
IP.1 = ${LAPTOP_HOST_IP}
EOF
# Create this dir to mimic the Pod mount point
$ mkdir -p /tmp/k8s-webhook-server/serving-certs
# Generate the TLS cert/key under /tmp/k8s-webhook-server/serving-certs
$ openssl req -newkey rsa:4096 -days 3650 -nodes -x509 \
-subj "/C=SG/ST=SG/L=SG/O=CAPSULE/CN=CAPSULE" \
-extensions req_ext \
-config _tls.cnf \
-keyout /tmp/k8s-webhook-server/serving-certs/tls.key \
-out /tmp/k8s-webhook-server/serving-certs/tls.crt
# Clean it up
$ rm -f _tls.cnf
```
3. Patching the Webhooks
By default, the webhooks will be registered with the services, which will route to the Pods, inside the cluster. We need to delegate the controllers' and webhook's services to the code running in our IDE by patching the `MutatingWebhookConfiguration` and `ValidatingWebhookConfiguration`.
```bash
# Export your laptop's IP with the 9443 port exposed by controllers/webhooks' services
$ export WEBHOOK_URL="https://${LAPTOP_HOST_IP}:9443"
# Export the cert we just generated as the CA bundle for webhook TLS
$ export CA_BUNDLE=`openssl base64 -in /tmp/k8s-webhook-server/serving-certs/tls.crt | tr -d '\n'`
kubectl patch MutatingWebhookConfiguration capsule-mutating-webhook-configuration \
--type='json' -p="[\
{'op': 'replace', 'path': '/webhooks/0/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/defaults\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/1/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/defaults\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/2/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/defaults\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/3/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/namespace-owner-reference\",'caBundle':\"$${CA_BUNDLE}\"}}\
]"
kubectl patch ValidatingWebhookConfiguration capsule-validating-webhook-configuration \
--type='json' -p="[\
{'op': 'replace', 'path': '/webhooks/0/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/cordoning\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/1/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/ingresses\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/2/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/namespaces\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/3/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/networkpolicies\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/4/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/nodes\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/5/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/pods\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/6/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/persistentvolumeclaims\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/7/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/services\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/8/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenants\",'caBundle':\"$${CA_BUNDLE}\"}}\
]"
kubectl patch crd tenants.capsule.clastix.io \
--type='json' -p="[\
{'op': 'replace', 'path': '/spec/conversion/webhook/clientConfig', 'value':{'url': \"$${WEBHOOK_URL}\", 'caBundle': \"$${CA_BUNDLE}\"}}\
]"
kubectl patch crd capsuleconfigurations.capsule.clastix.io \
--type='json' -p="[\
{'op': 'replace', 'path': '/spec/conversion/webhook/clientConfig', 'value':{'url': \"$${WEBHOOK_URL}\", 'caBundle': \"$${CA_BUNDLE}\"}}\
]";
```
## Running Capsule
When the Development Environment is set up, we can run Capsule controllers with webhooks outside of the Kubernetes cluster:
```bash
$ export NAMESPACE=capsule-system && export TMPDIR=/tmp/
$ go run .
```
To verify that, we can open a new console and create a new Tenant in a new shell:
```bash
$ kubectl apply -f - <<EOF
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
name: gas
spec:
owners:
- name: alice
kind: User
EOF
```
We should see output and logs in the make run console.
Now it's time to work through our familiar inner loop for development in our preferred IDE. For example, if you're using [Visual Studio Code](https://code.visualstudio.com/), this launch.json file can be a good start.
## Helm Chart
You can test your changes made to the helm chart locally. They are almost identical to the checks executed in the github workflows.
Run chart linting (ct lint):
```bash
make helm-lint
```
Run chart tests (ct install). This creates a KinD cluster, builds the current image and loads it into the cluster and installs the helm chart:
```bash
make helm-test
```
### Documentation
Documentation of the chart is done with [helm-docs](https://github.com/norwoodj/helm-docs). Therefor all documentation relevant changes for the chart must be done in the [README.md.gotmpl](./charts/capsule/README.md.gotmpl) file. You can run this locally with this command (requires running docker daemon):
```bash
make helm-docs
...
time="2023-10-23T13:45:08Z" level=info msg="Found Chart directories [charts/capsule]"
time="2023-10-23T13:45:08Z" level=info msg="Generating README Documentation for chart /helm-docs/charts/capsule"
```
This will update the documentation for the chart in the `README.md` file.
### Helm Changelog
The `version` of the chart does not require a bump, since it's driven by our release process. The `appVersion` of the chart is the version of the Capsule project. This is the version that should be bumped when a new Capsule version is released. This will be done by the maintainers.
To create the proper changelog for the helm chart, all changes which affect the helm chart must be documented as chart annotation. See all the available [chart annotations](https://artifacthub.io/docs/topics/annotations/helm/).
This annotation can be provided using two different formats: using a plain list of strings with the description of the change or using a list of objects with some extra structured information (see example below). Please feel free to use the one that better suits your needs. The UI experience will be slightly different depending on the choice. When using the list of objects option the valid supported kinds are `added`, `changed`, `deprecated`, `removed`, `fixed` and `security`.

View File

@@ -1,5 +1,5 @@
# Build the manager binary
FROM golang:1.19.10 as builder
FROM golang:1.20.10 as builder
WORKDIR /workspace
# Copy the Go Modules manifests

View File

@@ -1,9 +1,6 @@
# Version
GIT_HEAD_COMMIT ?= $(shell git rev-parse --short HEAD)
VERSION ?= $(shell git describe --abbrev=0 --tags --match "v*")
ifndef VERSION
VERSION = $(GIT_HEAD_COMMIT)
endif
VERSION ?= $(or $(shell git describe --abbrev=0 --tags --match "v*" 2>/dev/null),$(GIT_HEAD_COMMIT))
# Defaults
REGISTRY ?= ghcr.io
@@ -90,6 +87,10 @@ apidoc: apidocs-gen
# Helm
SRC_ROOT = $(shell git rev-parse --show-toplevel)
helm-controller-version:
$(eval VERSION := $(shell grep 'appVersion:' charts/capsule/Chart.yaml | awk '{print "v"$$2}'))
$(eval KO_TAGS := $(shell grep 'appVersion:' charts/capsule/Chart.yaml | awk '{print "v"$$2}'))
helm-docs: HELMDOCS_VERSION := v1.11.0
helm-docs: docker
@docker run -v "$(SRC_ROOT):/helm-docs" jnorwood/helm-docs:$(HELMDOCS_VERSION) --chart-search-root /helm-docs
@@ -98,10 +99,12 @@ helm-lint: CT_VERSION := v3.3.1
helm-lint: docker
@docker run -v "$(SRC_ROOT):/workdir" --entrypoint /bin/sh quay.io/helmpack/chart-testing:$(CT_VERSION) -c "cd /workdir; ct lint --config .github/configs/ct.yaml --lint-conf .github/configs/lintconf.yaml --all --debug"
helm-test: kind ct ko-build-all
helm-test: helm-controller-version kind ct ko-build-all
@kind create cluster --wait=60s --name capsule-charts
@kind load docker-image --name capsule-charts $(LOCAL_CAPSULE_IMG)
@kind load docker-image --name capsule-charts $(CAPSULE_IMG):$(VERSION)
@kubectl create ns capsule-system
@kubectl create -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.crds.yaml
@kubectl create -f https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.58.0/bundle.yaml
@ct install --config $(SRC_ROOT)/.github/configs/ct.yaml --namespace=capsule-system --all --debug
@kind delete cluster --name capsule-charts
@@ -163,7 +166,8 @@ dev-setup:
{'op': 'replace', 'path': '/webhooks/5/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/pods\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/6/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/persistentvolumeclaims\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/7/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/services\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/8/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenants\",'caBundle':\"$${CA_BUNDLE}\"}}\
{'op': 'replace', 'path': '/webhooks/8/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenantresource-objects\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/9/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenants\",'caBundle':\"$${CA_BUNDLE}\"}}\
]" && \
kubectl patch crd tenants.capsule.clastix.io \
--type='json' -p="[\
@@ -197,12 +201,10 @@ LD_FLAGS := "-X main.Version=$(VERSION) \
# ------------------
.PHONY: ko-build-capsule
LOCAL_CAPSULE_IMG_BASE := github.com/$(REPOSITORY)
LOCAL_CAPSULE_IMG := $(KO_REGISTRY)/$(LOCAL_CAPSULE_IMG_BASE)
ko-build-capsule: ko
@echo Building Capsule $(KO_TAGS) >&2
@LD_FLAGS=$(LD_FLAGS) KOCACHE=$(KOCACHE) KO_DOCKER_REPO=$(KO_REGISTRY) \
$(KO) build ./ --preserve-import-paths --tags=$(KO_TAGS) --push=false
@LD_FLAGS=$(LD_FLAGS) KOCACHE=$(KOCACHE) KO_DOCKER_REPO=$(CAPSULE_IMG) \
$(KO) build ./ --bare --tags=$(KO_TAGS) --push=false --local
.PHONY: ko-build-all
ko-build-all: ko-build-capsule
@@ -240,7 +242,7 @@ apidocs-gen: ## Download crdoc locally if necessary.
$(call go-install-tool,$(APIDOCS_GEN),fybrik.io/crdoc@$(APIDOCS_GEN_VERSION))
GINKGO := $(shell pwd)/bin/ginkgo
GINGKO_VERSION := v2.9.5
GINGKO_VERSION := v2.13.2
ginkgo: ## Download ginkgo locally if necessary.
$(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo@$(GINGKO_VERSION))
@@ -262,7 +264,7 @@ kustomize: ## Download kustomize locally if necessary.
KO = $(shell pwd)/bin/ko
KO_VERSION = v0.14.1
ko:
$(call go-install-tool,$(KO),github.com/google/ko@v0.14.1)
$(call go-install-tool,$(KO),github.com/google/ko@$(KO_VERSION))
####################
# -- Helpers
@@ -330,8 +332,6 @@ e2e-install:
--set 'manager.image.pullPolicy=Never' \
--set 'manager.resources=null'\
--set "manager.image.tag=$(VERSION)" \
--set 'manager.image.registry=$(KO_REGISTRY)' \
--set 'manager.image.repository=$(LOCAL_CAPSULE_IMG_BASE)' \
--set 'manager.livenessProbe.failureThreshold=10' \
--set 'manager.readinessProbe.failureThreshold=10' \
--set 'podSecurityContext.seccompProfile=null' \
@@ -340,7 +340,7 @@ e2e-install:
.PHONY: e2e-load-image
e2e-load-image: ko-build-all
kind load docker-image --nodes capsule-control-plane --name capsule $(LOCAL_CAPSULE_IMG):$(VERSION)
kind load docker-image --nodes capsule-control-plane --name capsule $(CAPSULE_IMG):$(VERSION)
.PHONY: e2e-exec
e2e-exec: ginkgo

19
PROJECT
View File

@@ -7,25 +7,6 @@ plugins:
projectName: capsule
repo: github.com/projectcapsule/capsule
resources:
- api:
crdVersion: v1
controller: true
domain: clastix.io
group: capsule
kind: Tenant
path: github.com/projectcapsule/capsule/api/v1alpha1
version: v1alpha1
webhooks:
conversion: true
webhookVersion: v1
- api:
crdVersion: v1
controller: true
domain: clastix.io
group: capsule
kind: CapsuleConfiguration
path: github.com/projectcapsule/capsule/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
domain: clastix.io

View File

@@ -1,6 +1,5 @@
<p align="left">
<img src="https://github.com/projectcapsule/capsule/actions/workflows/ci.yml/badge.svg"/>
<img src="https://img.shields.io/github/license/clastix/capsule"/>
<img src="https://img.shields.io/github/go-mod/go-version/clastix/capsule"/>
<a href="https://github.com/projectcapsule/capsule/releases">
@@ -15,6 +14,12 @@
<a href="https://api.securityscorecards.dev/projects/github.com/projectcapsule/capsule/badge">
<img src="https://api.securityscorecards.dev/projects/github.com/projectcapsule/capsule/badge"/>
</a>
<a href="https://artifacthub.io/packages/search?repo=projectcapsule">
<img src="https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/projectcapsule"/>
</a>
<a href="https://app.fossa.com/projects/git%2Bgithub.com%2Fprojectcapsule%2Fcapsule?ref=badge_shield&issueType=license" alt="FOSSA Status">
<img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2Fprojectcapsule%2Fcapsule.svg?type=shield&issueType=license"/>
</a>
</p>
<p align="center">
@@ -95,7 +100,7 @@ The documentation for each chart is done with [helm-docs](https://github.com/nor
make helm-docs
```
## Community
## Community meeting
Join the community, share and learn from it. You can find all the resources to how to contribute code and docs, connect with people in the [community repository](https://github.com/projectcapsule/capsule-community).
@@ -117,6 +122,14 @@ Please, refer to the maintainers file available [here](.github/maintainers.yaml)
Please, refer to the [documentation page](https://capsule.clastix.io/docs/contributing/release).
### Changelog
Read how we log changes [here](CHANGELOG.md)
### Software Bill of Materials
All OCI release artifacts include a Software Bill of Materials (SBOM) in CycloneDX JSON format. More information on this is available [here](SECURITY.md#software-bill-of-materials-sbom)
# FAQ
- Q. How to pronounce Capsule?

3
ROADMAP.md Normal file
View File

@@ -0,0 +1,3 @@
# Roadmap
future features and fixes are planned with [release milestones on GitHub](https://github.com/projectcapsule/capsule/milestones?direction=asc&sort=due_date&state=open). You can influence the roadmap by opening issues or joining our community meetings.

60
SECURITY-INSIGHTS.yml Normal file
View File

@@ -0,0 +1,60 @@
# Reference https://github.com/ossf/security-insights-spec/blob/v1.0.0/specification.md
header:
schema-version: 1.0.0
expiration-date: '2024-10-24T01:00:00.000Z'
last-updated: '2023-10-24'
last-reviewed: '2023-10-24'
project-url: https://github.com/projectcapsule/capsule
changelog: https://github.com/projectcapsule/capsule/blob/main/CHANGELOG.md
license: https://github.com/projectcapsule/capsule/blob/main/LICENSE
project-lifecycle:
status: active
bug-fixes-only: false
core-maintainers:
- github:prometherion
- github:oliverbaehler
- github:bsctl
- github:MaxFedotov
distribution-points:
- https://github.com/orgs/projectcapsule/packages?repo_name=capsule
contribution-policy:
accepts-pull-requests: true
accepts-automated-pull-requests: true
contributing-policy: https://github.com/projectcapsule/capsule/blob/main/CONTRIBUTING.md
code-of-conduct: https://github.com/projectcapsule/capsule/blob/main/CODE_OF_CONDUCT.md
vulnerability-reporting:
accepts-vulnerability-reports: true
security-policy: https://github.com/projectcapsule/capsule/blob/main/SECURITY.md
email-contact: cncf-capsule-maintainers@lists.cncf.io
comment: |
Report a vulnerability by using private security issues in GitHub.
security-testing:
- tool-type: sca
tool-name: Dependabot
tool-version: latest
integration:
ad-hoc: false
ci: true
before-release: true
comment: |
Dependabot is enabled for this repo.
dependencies:
third-party-packages: true
dependencies-lists:
- https://github.com/projectcapsule/capsule/blob/main/go.mod
env-dependencies-policy:
policy-url: https://github.com/projectcapsule/capsule/blob/main/DEPENDENCY.md
sbom:
- sbom-file: https://github.com/projectcapsule/capsule/pkgs/container/sbom
sbom-format: CycloneDX
sbom-url: https://github.com/projectcapsule/capsule/blob/main/SECURITY.md#software-bill-of-materials-sbom
security-artifacts:
self-assessment:
self-assessment-created: true
evidence-url:
- https://github.com/projectcapsule/capsule/blob/main/SELF_ASSESSMENT.md
security-contacts:
- type: email
value: cncf-capsule-maintainers@lists.cncf.io
primary: true

115
SECURITY.md Normal file
View File

@@ -0,0 +1,115 @@
# Security Policy
The Capsule community has adopted this security disclosures and response policy to ensure we responsibly handle critical issues.
## Bulletins
For information regarding the security of this project please join our [slack channel](https://kubernetes.slack.com/archives/C03GETTJQRL).
## Covered Repositories and Issues
When we say "a security vulnerability in capsule" we mean a security issue
in any repository under the [projectcapsule GitHub organization](https://github.com/projectcapsule/).
This reporting process is intended only for security issues in the capsule
project itself, and doesn't apply to applications _using_ capsule or to
issues which do not affect security.
Don't use this process if:
* You have issues with your capsule installation or configuration
* Your issue is not security related
### Explicitly Not Covered: Vulnerability Scanner Reports
We do not accept reports which amount to copy and pasted output from a vulnerability
scanning tool **unless** work has specifically been done to confirm that a vulnerability
reported by the tool _actually exists_ in capsule.
## Reporting a Vulnerability
To report a security issue or vulnerability, [submit a private vulnerability report via GitHub](https://github.com/projectcapsule/capsule/security/advisories/new) to the repository maintainers with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
Describe the issue in English, ideally with some example configuration or code which allows the issue to be reproduced. Explain why you believe this to be a security issue in capsule, if that's not obvious. should contain the following:
* description of the problem
* precise and detailed steps (include screenshots)
* the affected version(s). This may also include environment relevant versions.
* any possible mitigations
If the issue is confirmed as a vulnerability, we will open a Security Advisory and acknowledge your contributions as part of it.
## Reponse
Response times could be affected by weekends, holidays, breaks or time zone differences. That said, the security response team will endeavour to reply as soon as possible, ideally within 5 working days.
## Security Contacts
[Maintainers](./github/maintainers.yaml) of this project are responsible for the security of the project as outlined in this policy.
# Release Artifacts
[See all the available artifacts](https://github.com/orgs/projectcapsule/packages?repo_name=capsule)
## Verifing
To verify artifacts you need to have [cosign installed](https://github.com/sigstore/cosign#installation). This guide assumes you are using v2.x of cosign. All of the signatures are created using [keyless signing](https://docs.sigstore.dev/verifying/verify/#keyless-verification-using-openid-connect). We have a seperate repository for all the signatures for all the artifacts released under the projectcapsule - `ghcr.io/projectcapsule/signatures`. You can set the environment variable `COSIGN_REPOSITORY` to point to this repository. For example:
export COSIGN_REPOSITORY=ghcr.io/projectcapsule/signatures
To verify the signature of the docker image, run the following command. Replace `<release_tag>` with an [available release tag](https://github.com/projectcapsule/capsule/pkgs/container/capsule):
COSIGN_REPOSITORY=ghcr.io/projectcapsule/signatures cosign verify ghcr.io/projectcapsule/capsule:<release_tag> \
--certificate-identity-regexp="https://github.com/projectcapsule/capsule/.github/workflows/docker-publish.yml@refs/tags/*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" | jq
To verify the signature of the helm image, run the following command. Replace `<release_tag>` with an [available release tag](https://github.com/projectcapsule/capsule/pkgs/container/charts%2Fcapsule):
COSIGN_REPOSITORY=ghcr.io/projectcapsule/signatures cosign verify ghcr.io/projectcapsule/charts/capsule:<release_tag> \
--certificate-identity-regexp="https://github.com/projectcapsule/capsule/.github/workflows/helm-publish.yml@refs/tags/*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" | jq
## Verifying Provenance
Capsule creates and attests to the provenance of its builds using the [SLSA standard](https://slsa.dev/spec/v0.2/provenance) and meets the [SLSA Level 3](https://slsa.dev/spec/v0.1/levels) specification. The attested provenance may be verified using the cosign tool.
Verify the provenance of the docker image. Replace `<release_tag>` with an [available release tag](https://github.com/projectcapsule/capsule/pkgs/container/capsule)
```bash
cosign verify-attestation --type slsaprovenance \
--certificate-identity-regexp="https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
ghcr.io/projectcapsule/capsule:<release_tag> | jq .payload -r | base64 --decode | jq
```
Verify the provenance of the helm image. Replace `<release_tag>` with an [available release tag](https://github.com/projectcapsule/capsule/pkgs/container/charts%2Fcapsule)
```bash
cosign verify-attestation --type slsaprovenance \
--certificate-identity-regexp="https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
ghcr.io/projectcapsule/charts/capsule:<release_tag> | jq .payload -r | base64 --decode | jq
```
## Software Bill of Materials (SBOM)
An SBOM (Software Bill of Materials) in CycloneDX JSON format is published for each Kyverno release, including pre-releases. Like signatures, SBOMs are stored in a separate repository at `ghcr.io/projectcapsule/sbom`. You can set the environment variable `COSIGN_REPOSITORY` to point to this repository. For example:
export COSIGN_REPOSITORY=ghcr.io/projectcapsule/sbom
To inspect the SBOM of the docker image, run the following command. Replace `<release_tag>` with an [available release tag](https://github.com/projectcapsule/capsule/pkgs/container/capsule):
COSIGN_REPOSITORY=ghcr.io/projectcapsule/sbom cosign download sbom ghcr.io/projectcapsule/capsule:<release_tag>
To inspect the SBOM of the helm image, run the following command. Replace `<release_tag>` with an [available release tag](https://github.com/projectcapsule/capsule/pkgs/container/charts%2Fcapsule):
COSIGN_REPOSITORY=ghcr.io/projectcapsule/sbom cosign download sbom ghcr.io/projectcapsule/charts/capsule:<release_tag>
# Credits
Our Security Policy and Workflows are based on the work of the [Kyverno](https://github.com/kyverno) and [Cert-Manager](https://github.com/cert-manager) community.

201
SELF_ASSESSMENT.md Normal file
View File

@@ -0,0 +1,201 @@
# Capsule Security Self-Assessment
## Metadata
<table>
<tr>
<td>Software
</td>
<td><a href="https://github.com/projectcapsule/capsule">https://github.com/projectcapsule/capsule</a>
</td>
</tr>
<tr>
<td>Website
</td>
<td><a href="https://capsule.clastix.io/">https://capsule.clastix.io/</a>
</td>
</tr>
<tr>
<td>Security Provider
</td>
<td>No
</td>
</tr>
<tr>
<td>Languages
</td>
<td>Golang
</td>
</tr>
<tr>
<td>SBOM
</td>
<td><a href="https://github.com/projectcapsule/capsule/pkgs/container/sbom">https://github.com/projectcapsule/capsule/pkgs/container/sbom</a>
</td>
</tr>
</table>
## Security Links
<table>
<tr>
<td><strong>Doc</strong>
</td>
<td><strong>URL</strong>
</td>
</tr>
<tr>
<td>Security file
</td>
<td><a href="https://github.com/projectcapsule/capsule/blob/main/SECURITY.md">https://github.com/projectcapsule/capsule/blob/main/SECURITY.md</a>
</td>
</tr>
<tr>
<td>Default and optional configs
</td>
<td><a href="https://github.com/projectcapsule/capsule/blob/main/charts/capsule/values.yaml">https://github.com/projectcapsule/capsule/blob/main/charts/capsule/values.yaml</a>
</td>
</tr>
</table>
## Overview
Capsule implements a multi-tenant and policy-based environment in your Kubernetes cluster.
It is designed as a micro-services-based ecosystem with a minimalist approach, leveraging only upstream Kubernetes.
### Background
Capsule takes a different approach.
In a single cluster, the Capsule Controller aggregates multiple namespaces in a lightweight abstraction called Tenant, basically a grouping of Kubernetes Namespaces.
Within each tenant, users are free to create their namespaces and share all the assigned resources.
On the other side, the Capsule Policy Engine keeps the different tenants isolated from each other.
Network and Security Policies, Resource Quota, Limit Ranges, RBAC, and other policies defined at the tenant level are automatically inherited by all the namespaces in the tenant.
Then users are free to operate their tenants in autonomy, without the intervention of the cluster administrator.
Capsule was accepted as a CNCF sandbox project in December 2022.
## Actors
### Capsule Operator
It's the Operator which provides all the multi-tenant capabilities offered by Capsule.
It's made of two internal components, such as the webhooks server (known as _policy engine_), and the _tenant controller_.
**Capsule Tenant Controller**
The controller is responsible for managing the tenants by reconciling the required objects at the Namespace level, such as _Network Policy_, _LimitRange_, _ResourceQuota_, _Role Binding_, as well as labelling the Namespace objects belonging to a Tenant according to their desired metadata.
It is responsible for binding Namespaces to the selected Tenant, and managing their lifecycle.
Furthermore, the manager can replicate objects thanks to the **Tenant Resource** API, which offers two levels of interactions: a cluster-scoped one thanks to the `GlobalTenantResource` API, and a namespace-scoped one named `TenantResource`.
The replicated resources are dynamically created, and replicated by Capsule itself, as well as preserving the deletion of these objects by the Tenant owner.
**Capsule Tenant Controller (Policy Engine)**
Policies are defined on a Tenant basis: therefore the policy engine is enforcing these policies on the tenants's Namespaces and their children's resources.
The Policy Engine is currently not a dedicated component, but a part of the Capsule Tenant Controller.
The webhook server, also known as the policy engine, interpolates the Tenant rules and takes full advantage of the dynamic admission controllers offered by Kubernetes itself (such as `ValidatingWebhookConfiguration` and `MutatingWebhookConfiguration`).
Thanks to the _policy engine_ the cluster administrators can enforce specific rules such as preventing _Pod_ objects from untrusted registries to run or preventing the creation of _PersistentVolumeClaim_ resources using a non-allowed _StorageClass_, etc.
It also acts as a defaulter webhook, offloading the need to specify some classes (IngressClass, StorageClass, RuntimeClass, etc.).
### Capsule Proxy
The `capsule-proxy` is an addon which is offering a Kubernetes API Server shim aware of the multi-tenancy levels implemented by Capsule.
It's essentially a reverse proxy that decorates the incoming requests with the required `labelSelector` query string parameters to filter out some objects, such as:
- Namespaces
- IngressClass
- StorageClass
- PriorityClass
- RuntimeClass
- PersistentVolumes
Permissions on those resources are not enforced through the classic Kubernetes RBAC but rather at the Tenant Owner level, allowing fine-grained control over specific resources.
`capsule-proxy` is not serving itself Kubernetes API server responses, but rather, it's acting as a middle proxy server offering dynamic filtering of requests: this means the resulting responses from the upstream are not mangled, and don't require any additional plugins, or third-party binaries, and integrating with any external components, such as the Kubernetes dashboard, the `kubectl` binary, etc.
## Actions
Tenants are created by cluster administrators, who have the right to create Tenant custom resource instances.
End users should not manage tenants.
Therefore users without any cluster administration rights can't list tenants or create tenants.
## Creating namespaces in a tenant
When creating a tenant, the Capsule controller inspects the user's supplied groups and matches, if the groups were defined in the `capsuleConfiguration`. If at least one matching group is found, the user request is considered by Capsule. If not, Capsule ignores the request and does not perform any action. This also applies to modifications (`UPDATE` request).
To create namespaces within a tenant a User, Group or ServiceAccount must be configured as owner of the tenant.
A namespace is assigned to a tenant based on its label, its owner (if the owner only has one tenant) or the prefix of the namespace (which matches the tenant name).
If the request is considered, the namespace is created with all the configuration on the tenant, which is relevant. The additional resources (NetworkPolicy, ResourceQuota, LimitRange) are created in the new namespace.
### Applying Workload and configs to namespaces within a tenant
Whenever a tenant user applies new workloads or configs to a namespace, the capsule controller inspects the namespace and checks if it belongs to a tenant. If so, the capsule controller applies policies from the tenant configuration to the given workloads and configs.
If there are defaults defined on the tenant, they are applied to the workloads as well.
This is a further abstraction from having cluster defaults (eg. default `StorageClass`) to having tenant defaults (eg. default `StorageClass` for a tenant).
### Goals
**General**
* **Multitenancy**: Capsule should be able to support multiple tenants in a single Kubernetes cluster without introducing overhead or cognitive load, and barely relying on Namespace objects.
* **Kubernetes agnostic**: Capsule should integrate with Kubernetes primitives, such as _RBAC_, _NetworkPolicy_, _LimitRange_, and _ResourceQuota_.
* **Policy-based**: Capsule should be able to enforce policies on tenants, which are defined on a tenant basis.
* **Native User Experience**: Capsule shouldn't increase the cognitive load of developers, such as introducing `kubectl` plugins, or forcing the tenant owners to operate their tenant objects using Custom Resource Definitions.
### Non-Goals
**General**
* **Control Plane**: Capsule can't mimic for each tenant a feeling of a dedicated control plane.
* **Custom Resource Definitions**: Capsule doesn't want to provide virtual cluster capabilities and it's sticking to the native Kubernetes user experience and design; rather, its focus is to provide a governance solution by focusing on resource optimization and security lockdown.
## Self-assessment use
This self-assessment is created by the Capsule team to perform an internal analysis of the project's security.
It is not intended to provide a security audit of Capsule, or function as an independent assessment or attestation of Capsules security health.
This document serves to provide Capsule users with an initial understanding of Capsule's security, where to find existing security documentation, Capsule plans for security, and a general overview of Capsule security practices, both for the development of Capsule as well as security of Capsule.
This document provides the CNCF TAG-Security with an initial understanding of Capsule to assist in a joint review, necessary for projects under incubation. Taken together, this document and the joint review serve as a cornerstone for if and when Capsule seeks graduation.
## Security functions and features
See [Actors](#actors) and [Actions](#actions) for a more detailed description of the critical actors, actions, and potential threats.
## Project compliance
As of now, not applicable.
## Secure development practices
The Capsule project follows established CNCF and OSS best practices for code development and delivery.
Capsule follows [OpenSSF Best Practices](https://www.bestpractices.dev/en/projects/5601).
Although not perfect yet, we are constantly trying to improve and score optimal scores.
We will assess the issues during our community meetings and try to plan them for future releases.
### Development Pipeline
Changes must be reviewed and merged by the project maintainers.
Before changes are merged, all the changes must pass static checks, license checks, verifications on `gofmt`, `go lint`, `go vet`, and pass all unit tests and e2e tests.
Changes are scanned by trivy for the docker images.
We run E2E tests for different Kubernetes versions on Pull Requests.
Code changes are submitted via Pull Requests (PRs) and must be signed and verified.
Commits to the main branch directly are not allowed.
## Security issue resolution
Capsule project vulnerability handling related processes are recorded in the [Capsule Security Doc](https://github.com/projectcapsule/capsule/blob/main/SECURITY.md).
Related security vulnerabilities can be reported and communicated via email to [cncf-capsule-maintainers@lists.cncf.io](mailto:cncf-capsule-maintainers@lists.cncf.i).
## Appendix
All Capsule security-related issues (both fixes and enhancements) are labelled with "security" and can be queried using[ https://github.com/projectcapsule/capsule/labels/security](https://github.com/projectcapsule/capsule/labels/security).
The code review process requires maintainers to consider security while reviewing designs and pull requests.

View File

@@ -1,9 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
type AdditionalMetadata struct {
Labels map[string]string `json:"additionalLabels,omitempty"`
Annotations map[string]string `json:"additionalAnnotations,omitempty"`
}

View File

@@ -1,15 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
const (
ForbiddenNodeLabelsAnnotation = "capsule.clastix.io/forbidden-node-labels"
ForbiddenNodeLabelsRegexpAnnotation = "capsule.clastix.io/forbidden-node-labels-regexp"
ForbiddenNodeAnnotationsAnnotation = "capsule.clastix.io/forbidden-node-annotations"
ForbiddenNodeAnnotationsRegexpAnnotation = "capsule.clastix.io/forbidden-node-annotations-regexp"
TLSSecretNameAnnotation = "capsule.clastix.io/tls-secret-name"
MutatingWebhookConfigurationName = "capsule.clastix.io/mutating-webhook-configuration-name"
ValidatingWebhookConfigurationName = "capsule.clastix.io/validating-webhook-configuration-name"
EnableTLSConfigurationAnnotationName = "capsule.clastix.io/enable-tls-configuration"
)

View File

@@ -1,47 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// CapsuleConfigurationSpec defines the Capsule configuration.
type CapsuleConfigurationSpec struct {
// Names of the groups for Capsule users.
// +kubebuilder:default={capsule.clastix.io}
UserGroups []string `json:"userGroups,omitempty"`
// Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix,
// separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment.
// +kubebuilder:default=false
ForceTenantPrefix bool `json:"forceTenantPrefix,omitempty"`
// Disallow creation of namespaces, whose name matches this regexp
ProtectedNamespaceRegexpString string `json:"protectedNamespaceRegex,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster
// CapsuleConfiguration is the Schema for the Capsule configuration API.
type CapsuleConfiguration struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec CapsuleConfigurationSpec `json:"spec,omitempty"`
}
func (in *CapsuleConfiguration) Hub() {}
// +kubebuilder:object:root=true
// CapsuleConfigurationList contains a list of CapsuleConfiguration.
type CapsuleConfigurationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CapsuleConfiguration `json:"items"`
}
func init() {
SchemeBuilder.Register(&CapsuleConfiguration{}, &CapsuleConfigurationList{})
}

View File

@@ -1,21 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"os"
ctrl "sigs.k8s.io/controller-runtime"
)
func (in *CapsuleConfiguration) SetupWebhookWithManager(mgr ctrl.Manager) error {
certData, _ := os.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt")
if len(certData) == 0 {
return nil
}
return ctrl.NewWebhookManagedBy(mgr).
For(in).
Complete()
}

View File

@@ -1,583 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/pkg/errors"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/conversion"
capsulev1beta1 "github.com/projectcapsule/capsule/api/v1beta1"
"github.com/projectcapsule/capsule/pkg/api"
)
const (
resourceQuotaScopeAnnotation = "capsule.clastix.io/resource-quota-scope"
podAllowedImagePullPolicyAnnotation = "capsule.clastix.io/allowed-image-pull-policy"
podPriorityAllowedAnnotation = "priorityclass.capsule.clastix.io/allowed"
podPriorityAllowedRegexAnnotation = "priorityclass.capsule.clastix.io/allowed-regex"
enableNodePortsAnnotation = "capsule.clastix.io/enable-node-ports"
enableExternalNameAnnotation = "capsule.clastix.io/enable-external-name"
enableLoadBalancerAnnotation = "capsule.clastix.io/enable-loadbalancer-service"
ownerGroupsAnnotation = "owners.capsule.clastix.io/group"
ownerUsersAnnotation = "owners.capsule.clastix.io/user"
ownerServiceAccountAnnotation = "owners.capsule.clastix.io/serviceaccount"
enableNodeListingAnnotation = "capsule.clastix.io/enable-node-listing"
enableNodeUpdateAnnotation = "capsule.clastix.io/enable-node-update"
enableNodeDeletionAnnotation = "capsule.clastix.io/enable-node-deletion"
enableStorageClassListingAnnotation = "capsule.clastix.io/enable-storageclass-listing"
enableStorageClassUpdateAnnotation = "capsule.clastix.io/enable-storageclass-update"
enableStorageClassDeletionAnnotation = "capsule.clastix.io/enable-storageclass-deletion"
enableIngressClassListingAnnotation = "capsule.clastix.io/enable-ingressclass-listing"
enableIngressClassUpdateAnnotation = "capsule.clastix.io/enable-ingressclass-update"
enableIngressClassDeletionAnnotation = "capsule.clastix.io/enable-ingressclass-deletion"
enablePriorityClassListingAnnotation = "capsule.clastix.io/enable-priorityclass-listing"
enablePriorityClassUpdateAnnotation = "capsule.clastix.io/enable-priorityclass-update"
enablePriorityClassDeletionAnnotation = "capsule.clastix.io/enable-priorityclass-deletion"
ingressHostnameCollisionScope = "ingress.capsule.clastix.io/hostname-collision-scope"
)
func (in *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec {
serviceKindToAnnotationMap := map[capsulev1beta1.ProxyServiceKind][]string{
capsulev1beta1.NodesProxy: {enableNodeListingAnnotation, enableNodeUpdateAnnotation, enableNodeDeletionAnnotation},
capsulev1beta1.StorageClassesProxy: {enableStorageClassListingAnnotation, enableStorageClassUpdateAnnotation, enableStorageClassDeletionAnnotation},
capsulev1beta1.IngressClassesProxy: {enableIngressClassListingAnnotation, enableIngressClassUpdateAnnotation, enableIngressClassDeletionAnnotation},
capsulev1beta1.PriorityClassesProxy: {enablePriorityClassListingAnnotation, enablePriorityClassUpdateAnnotation, enablePriorityClassDeletionAnnotation},
}
annotationToOperationMap := map[string]capsulev1beta1.ProxyOperation{
enableNodeListingAnnotation: capsulev1beta1.ListOperation,
enableNodeUpdateAnnotation: capsulev1beta1.UpdateOperation,
enableNodeDeletionAnnotation: capsulev1beta1.DeleteOperation,
enableStorageClassListingAnnotation: capsulev1beta1.ListOperation,
enableStorageClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
enableStorageClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
enableIngressClassListingAnnotation: capsulev1beta1.ListOperation,
enableIngressClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
enableIngressClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
enablePriorityClassListingAnnotation: capsulev1beta1.ListOperation,
enablePriorityClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
enablePriorityClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
}
annotationToOwnerKindMap := map[string]capsulev1beta1.OwnerKind{
ownerUsersAnnotation: capsulev1beta1.UserOwner,
ownerGroupsAnnotation: capsulev1beta1.GroupOwner,
ownerServiceAccountAnnotation: capsulev1beta1.ServiceAccountOwner,
}
annotations := in.GetAnnotations()
operations := make(map[string]map[capsulev1beta1.ProxyServiceKind][]capsulev1beta1.ProxyOperation)
for serviceKind, operationAnnotations := range serviceKindToAnnotationMap {
for _, operationAnnotation := range operationAnnotations {
val, ok := annotations[operationAnnotation]
if ok {
for _, owner := range strings.Split(val, ",") {
if _, exists := operations[owner]; !exists {
operations[owner] = make(map[capsulev1beta1.ProxyServiceKind][]capsulev1beta1.ProxyOperation)
}
operations[owner][serviceKind] = append(operations[owner][serviceKind], annotationToOperationMap[operationAnnotation])
}
}
}
}
var owners capsulev1beta1.OwnerListSpec
getProxySettingsForOwner := func(ownerName string) (settings []capsulev1beta1.ProxySettings) {
ownerOperations, ok := operations[ownerName]
if ok {
for k, v := range ownerOperations {
settings = append(settings, capsulev1beta1.ProxySettings{
Kind: k,
Operations: v,
})
}
}
return
}
owners = append(owners, capsulev1beta1.OwnerSpec{
Kind: capsulev1beta1.OwnerKind(in.Spec.Owner.Kind),
Name: in.Spec.Owner.Name,
ProxyOperations: getProxySettingsForOwner(in.Spec.Owner.Name),
})
for ownerAnnotation, ownerKind := range annotationToOwnerKindMap {
val, ok := annotations[ownerAnnotation]
if ok {
for _, owner := range strings.Split(val, ",") {
owners = append(owners, capsulev1beta1.OwnerSpec{
Kind: ownerKind,
Name: owner,
ProxyOperations: getProxySettingsForOwner(owner),
})
}
}
}
return owners
}
//nolint:gocognit,gocyclo,cyclop,maintidx
func (in *Tenant) ConvertTo(dstRaw conversion.Hub) error {
dst, ok := dstRaw.(*capsulev1beta1.Tenant)
if !ok {
return fmt.Errorf("expected type *capsulev1beta1.Tenant, got %T", dst)
}
annotations := in.GetAnnotations()
// ObjectMeta
dst.ObjectMeta = in.ObjectMeta
// Spec
if in.Spec.NamespaceQuota != nil {
if dst.Spec.NamespaceOptions == nil {
dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{}
}
dst.Spec.NamespaceOptions.Quota = in.Spec.NamespaceQuota
}
dst.Spec.NodeSelector = in.Spec.NodeSelector
dst.Spec.Owners = in.convertV1Alpha1OwnerToV1Beta1()
if in.Spec.NamespacesMetadata != nil {
if dst.Spec.NamespaceOptions == nil {
dst.Spec.NamespaceOptions = &capsulev1beta1.NamespaceOptions{}
}
dst.Spec.NamespaceOptions.AdditionalMetadata = &api.AdditionalMetadataSpec{
Labels: in.Spec.NamespacesMetadata.Labels,
Annotations: in.Spec.NamespacesMetadata.Annotations,
}
}
if in.Spec.ServicesMetadata != nil {
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &api.ServiceOptions{}
}
dst.Spec.ServiceOptions.AdditionalMetadata = &api.AdditionalMetadataSpec{
Labels: in.Spec.ServicesMetadata.Labels,
Annotations: in.Spec.ServicesMetadata.Annotations,
}
}
if in.Spec.StorageClasses != nil {
dst.Spec.StorageClasses = in.Spec.StorageClasses
}
if v, annotationOk := in.Annotations[ingressHostnameCollisionScope]; annotationOk {
switch v {
case string(api.HostnameCollisionScopeCluster), string(api.HostnameCollisionScopeTenant), string(api.HostnameCollisionScopeNamespace):
dst.Spec.IngressOptions.HostnameCollisionScope = api.HostnameCollisionScope(v)
default:
dst.Spec.IngressOptions.HostnameCollisionScope = api.HostnameCollisionScopeDisabled
}
}
if in.Spec.IngressClasses != nil {
dst.Spec.IngressOptions.AllowedClasses = &api.AllowedListSpec{
Exact: in.Spec.IngressClasses.Exact,
Regex: in.Spec.IngressClasses.Regex,
}
}
if in.Spec.IngressHostnames != nil {
dst.Spec.IngressOptions.AllowedHostnames = &api.AllowedListSpec{
Exact: in.Spec.IngressHostnames.Exact,
Regex: in.Spec.IngressHostnames.Regex,
}
}
if in.Spec.ContainerRegistries != nil {
dst.Spec.ContainerRegistries = &api.AllowedListSpec{
Exact: in.Spec.ContainerRegistries.Exact,
Regex: in.Spec.ContainerRegistries.Regex,
}
}
if len(in.Spec.NetworkPolicies) > 0 {
dst.Spec.NetworkPolicies = api.NetworkPolicySpec{
Items: in.Spec.NetworkPolicies,
}
}
if len(in.Spec.LimitRanges) > 0 {
dst.Spec.LimitRanges = api.LimitRangesSpec{
Items: in.Spec.LimitRanges,
}
}
if len(in.Spec.ResourceQuota) > 0 {
dst.Spec.ResourceQuota = api.ResourceQuotaSpec{
Scope: func() api.ResourceQuotaScope {
if v, annotationOk := in.GetAnnotations()[resourceQuotaScopeAnnotation]; annotationOk {
switch v {
case string(api.ResourceQuotaScopeNamespace):
return api.ResourceQuotaScopeNamespace
case string(api.ResourceQuotaScopeTenant):
return api.ResourceQuotaScopeTenant
}
}
return api.ResourceQuotaScopeTenant
}(),
Items: in.Spec.ResourceQuota,
}
}
dst.Spec.AdditionalRoleBindings = in.Spec.AdditionalRoleBindings
if in.Spec.ExternalServiceIPs != nil {
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &api.ServiceOptions{}
}
dst.Spec.ServiceOptions.ExternalServiceIPs = in.Spec.ExternalServiceIPs
}
pullPolicies, ok := annotations[podAllowedImagePullPolicyAnnotation]
if ok {
for _, policy := range strings.Split(pullPolicies, ",") {
dst.Spec.ImagePullPolicies = append(dst.Spec.ImagePullPolicies, api.ImagePullPolicySpec(policy))
}
}
priorityClasses := api.AllowedListSpec{}
priorityClassAllowed, ok := annotations[podPriorityAllowedAnnotation]
if ok {
priorityClasses.Exact = strings.Split(priorityClassAllowed, ",")
}
priorityClassesRegexp, ok := annotations[podPriorityAllowedRegexAnnotation]
if ok {
priorityClasses.Regex = priorityClassesRegexp
}
if !reflect.ValueOf(priorityClasses).IsZero() {
dst.Spec.PriorityClasses = &priorityClasses
}
enableNodePorts, ok := annotations[enableNodePortsAnnotation]
if ok {
val, err := strconv.ParseBool(enableNodePorts)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableNodePortsAnnotation, in.GetName()))
}
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &api.ServiceOptions{}
}
if dst.Spec.ServiceOptions.AllowedServices == nil {
dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{}
}
dst.Spec.ServiceOptions.AllowedServices.NodePort = pointer.Bool(val)
}
enableExternalName, ok := annotations[enableExternalNameAnnotation]
if ok {
val, err := strconv.ParseBool(enableExternalName)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableExternalNameAnnotation, in.GetName()))
}
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &api.ServiceOptions{}
}
if dst.Spec.ServiceOptions.AllowedServices == nil {
dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{}
}
dst.Spec.ServiceOptions.AllowedServices.ExternalName = pointer.Bool(val)
}
loadBalancerService, ok := annotations[enableLoadBalancerAnnotation]
if ok {
val, err := strconv.ParseBool(loadBalancerService)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableLoadBalancerAnnotation, in.GetName()))
}
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &api.ServiceOptions{}
}
if dst.Spec.ServiceOptions.AllowedServices == nil {
dst.Spec.ServiceOptions.AllowedServices = &api.AllowedServices{}
}
dst.Spec.ServiceOptions.AllowedServices.LoadBalancer = pointer.Bool(val)
}
// Status
dst.Status = capsulev1beta1.TenantStatus{
Size: in.Status.Size,
Namespaces: in.Status.Namespaces,
}
// Remove unneeded annotations
delete(dst.ObjectMeta.Annotations, podAllowedImagePullPolicyAnnotation)
delete(dst.ObjectMeta.Annotations, podPriorityAllowedAnnotation)
delete(dst.ObjectMeta.Annotations, podPriorityAllowedRegexAnnotation)
delete(dst.ObjectMeta.Annotations, enableNodePortsAnnotation)
delete(dst.ObjectMeta.Annotations, enableExternalNameAnnotation)
delete(dst.ObjectMeta.Annotations, enableLoadBalancerAnnotation)
delete(dst.ObjectMeta.Annotations, ownerGroupsAnnotation)
delete(dst.ObjectMeta.Annotations, ownerUsersAnnotation)
delete(dst.ObjectMeta.Annotations, ownerServiceAccountAnnotation)
delete(dst.ObjectMeta.Annotations, enableNodeListingAnnotation)
delete(dst.ObjectMeta.Annotations, enableNodeUpdateAnnotation)
delete(dst.ObjectMeta.Annotations, enableNodeDeletionAnnotation)
delete(dst.ObjectMeta.Annotations, enableStorageClassListingAnnotation)
delete(dst.ObjectMeta.Annotations, enableStorageClassUpdateAnnotation)
delete(dst.ObjectMeta.Annotations, enableStorageClassDeletionAnnotation)
delete(dst.ObjectMeta.Annotations, enableIngressClassListingAnnotation)
delete(dst.ObjectMeta.Annotations, enableIngressClassUpdateAnnotation)
delete(dst.ObjectMeta.Annotations, enableIngressClassDeletionAnnotation)
delete(dst.ObjectMeta.Annotations, enablePriorityClassListingAnnotation)
delete(dst.ObjectMeta.Annotations, enablePriorityClassUpdateAnnotation)
delete(dst.ObjectMeta.Annotations, enablePriorityClassDeletionAnnotation)
delete(dst.ObjectMeta.Annotations, resourceQuotaScopeAnnotation)
delete(dst.ObjectMeta.Annotations, ingressHostnameCollisionScope)
return nil
}
//nolint:gocognit,gocyclo,cyclop
func (in *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) {
ownersAnnotations := map[string][]string{
ownerGroupsAnnotation: nil,
ownerUsersAnnotation: nil,
ownerServiceAccountAnnotation: nil,
}
proxyAnnotations := map[string][]string{
enableNodeListingAnnotation: nil,
enableNodeUpdateAnnotation: nil,
enableNodeDeletionAnnotation: nil,
enableStorageClassListingAnnotation: nil,
enableStorageClassUpdateAnnotation: nil,
enableStorageClassDeletionAnnotation: nil,
enableIngressClassListingAnnotation: nil,
enableIngressClassUpdateAnnotation: nil,
enableIngressClassDeletionAnnotation: nil,
}
for i, owner := range src.Spec.Owners {
if i == 0 {
in.Spec.Owner = OwnerSpec{
Name: owner.Name,
Kind: Kind(owner.Kind),
}
} else {
switch owner.Kind {
case capsulev1beta1.UserOwner:
ownersAnnotations[ownerUsersAnnotation] = append(ownersAnnotations[ownerUsersAnnotation], owner.Name)
case capsulev1beta1.GroupOwner:
ownersAnnotations[ownerGroupsAnnotation] = append(ownersAnnotations[ownerGroupsAnnotation], owner.Name)
case capsulev1beta1.ServiceAccountOwner:
ownersAnnotations[ownerServiceAccountAnnotation] = append(ownersAnnotations[ownerServiceAccountAnnotation], owner.Name)
}
}
for _, setting := range owner.ProxyOperations {
switch setting.Kind {
case capsulev1beta1.NodesProxy:
for _, operation := range setting.Operations {
switch operation {
case capsulev1beta1.ListOperation:
proxyAnnotations[enableNodeListingAnnotation] = append(proxyAnnotations[enableNodeListingAnnotation], owner.Name)
case capsulev1beta1.UpdateOperation:
proxyAnnotations[enableNodeUpdateAnnotation] = append(proxyAnnotations[enableNodeUpdateAnnotation], owner.Name)
case capsulev1beta1.DeleteOperation:
proxyAnnotations[enableNodeDeletionAnnotation] = append(proxyAnnotations[enableNodeDeletionAnnotation], owner.Name)
}
}
case capsulev1beta1.PriorityClassesProxy:
for _, operation := range setting.Operations {
switch operation {
case capsulev1beta1.ListOperation:
proxyAnnotations[enablePriorityClassListingAnnotation] = append(proxyAnnotations[enablePriorityClassListingAnnotation], owner.Name)
case capsulev1beta1.UpdateOperation:
proxyAnnotations[enablePriorityClassUpdateAnnotation] = append(proxyAnnotations[enablePriorityClassUpdateAnnotation], owner.Name)
case capsulev1beta1.DeleteOperation:
proxyAnnotations[enablePriorityClassDeletionAnnotation] = append(proxyAnnotations[enablePriorityClassDeletionAnnotation], owner.Name)
}
}
case capsulev1beta1.StorageClassesProxy:
for _, operation := range setting.Operations {
switch operation {
case capsulev1beta1.ListOperation:
proxyAnnotations[enableStorageClassListingAnnotation] = append(proxyAnnotations[enableStorageClassListingAnnotation], owner.Name)
case capsulev1beta1.UpdateOperation:
proxyAnnotations[enableStorageClassUpdateAnnotation] = append(proxyAnnotations[enableStorageClassUpdateAnnotation], owner.Name)
case capsulev1beta1.DeleteOperation:
proxyAnnotations[enableStorageClassDeletionAnnotation] = append(proxyAnnotations[enableStorageClassDeletionAnnotation], owner.Name)
}
}
case capsulev1beta1.IngressClassesProxy:
for _, operation := range setting.Operations {
switch operation {
case capsulev1beta1.ListOperation:
proxyAnnotations[enableIngressClassListingAnnotation] = append(proxyAnnotations[enableIngressClassListingAnnotation], owner.Name)
case capsulev1beta1.UpdateOperation:
proxyAnnotations[enableIngressClassUpdateAnnotation] = append(proxyAnnotations[enableIngressClassUpdateAnnotation], owner.Name)
case capsulev1beta1.DeleteOperation:
proxyAnnotations[enableIngressClassDeletionAnnotation] = append(proxyAnnotations[enableIngressClassDeletionAnnotation], owner.Name)
}
}
}
}
}
for k, v := range ownersAnnotations {
if len(v) > 0 {
in.Annotations[k] = strings.Join(v, ",")
}
}
for k, v := range proxyAnnotations {
if len(v) > 0 {
in.Annotations[k] = strings.Join(v, ",")
}
}
}
//nolint:cyclop
func (in *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
src, ok := srcRaw.(*capsulev1beta1.Tenant)
if !ok {
return fmt.Errorf("expected *capsulev1beta1.Tenant, got %T", srcRaw)
}
// ObjectMeta
in.ObjectMeta = src.ObjectMeta
// Spec
if src.Spec.NamespaceOptions != nil && src.Spec.NamespaceOptions.Quota != nil {
in.Spec.NamespaceQuota = src.Spec.NamespaceOptions.Quota
}
in.Spec.NodeSelector = src.Spec.NodeSelector
if in.Annotations == nil {
in.Annotations = make(map[string]string)
}
in.convertV1Beta1OwnerToV1Alpha1(src)
if src.Spec.NamespaceOptions != nil && src.Spec.NamespaceOptions.AdditionalMetadata != nil {
in.Spec.NamespacesMetadata = &AdditionalMetadata{
Labels: src.Spec.NamespaceOptions.AdditionalMetadata.Labels,
Annotations: src.Spec.NamespaceOptions.AdditionalMetadata.Annotations,
}
}
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AdditionalMetadata != nil {
in.Spec.ServicesMetadata = &AdditionalMetadata{
Labels: src.Spec.ServiceOptions.AdditionalMetadata.Labels,
Annotations: src.Spec.ServiceOptions.AdditionalMetadata.Annotations,
}
}
if src.Spec.StorageClasses != nil {
in.Spec.StorageClasses = src.Spec.StorageClasses
}
in.Annotations[ingressHostnameCollisionScope] = string(src.Spec.IngressOptions.HostnameCollisionScope)
if src.Spec.IngressOptions.AllowedClasses != nil {
in.Spec.IngressClasses = src.Spec.IngressOptions.AllowedClasses
}
if src.Spec.IngressOptions.AllowedHostnames != nil {
in.Spec.IngressHostnames = src.Spec.IngressOptions.AllowedHostnames
}
if src.Spec.ContainerRegistries != nil {
in.Spec.ContainerRegistries = src.Spec.ContainerRegistries
}
if len(src.Spec.NetworkPolicies.Items) > 0 {
in.Spec.NetworkPolicies = src.Spec.NetworkPolicies.Items
}
if len(src.Spec.LimitRanges.Items) > 0 {
in.Spec.LimitRanges = src.Spec.LimitRanges.Items
}
if len(src.Spec.ResourceQuota.Items) > 0 {
in.Annotations[resourceQuotaScopeAnnotation] = string(src.Spec.ResourceQuota.Scope)
in.Spec.ResourceQuota = src.Spec.ResourceQuota.Items
}
in.Spec.AdditionalRoleBindings = src.Spec.AdditionalRoleBindings
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.ExternalServiceIPs != nil {
in.Spec.ExternalServiceIPs = src.Spec.ServiceOptions.ExternalServiceIPs
}
if len(src.Spec.ImagePullPolicies) != 0 {
var pullPolicies []string
for _, policy := range src.Spec.ImagePullPolicies {
pullPolicies = append(pullPolicies, string(policy))
}
in.Annotations[podAllowedImagePullPolicyAnnotation] = strings.Join(pullPolicies, ",")
}
if src.Spec.PriorityClasses != nil {
if len(src.Spec.PriorityClasses.Exact) != 0 {
in.Annotations[podPriorityAllowedAnnotation] = strings.Join(src.Spec.PriorityClasses.Exact, ",")
}
if src.Spec.PriorityClasses.Regex != "" {
in.Annotations[podPriorityAllowedRegexAnnotation] = src.Spec.PriorityClasses.Regex
}
}
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AllowedServices != nil {
if src.Spec.ServiceOptions.AllowedServices.NodePort != nil {
in.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort)
}
if src.Spec.ServiceOptions.AllowedServices.ExternalName != nil {
in.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName)
}
if src.Spec.ServiceOptions.AllowedServices.LoadBalancer != nil {
in.Annotations[enableLoadBalancerAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.LoadBalancer)
}
}
// Status
in.Status = TenantStatus{
Size: src.Status.Size,
Namespaces: src.Status.Namespaces,
}
return nil
}

View File

@@ -1,393 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"sort"
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
capsulev1beta1 "github.com/projectcapsule/capsule/api/v1beta1"
"github.com/projectcapsule/capsule/pkg/api"
)
//nolint:maintidx
func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
var namespaceQuota int32 = 5
nodeSelector := map[string]string{
"foo": "bar",
}
v1alpha1AdditionalMetadataSpec := &AdditionalMetadata{
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{
"foo": "bar",
},
}
v1alpha1AllowedListSpec := &api.AllowedListSpec{
Exact: []string{"foo", "bar"},
Regex: "^foo*",
}
v1beta1AdditionalMetadataSpec := &api.AdditionalMetadataSpec{
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{
"foo": "bar",
},
}
v1beta1NamespaceOptions := &capsulev1beta1.NamespaceOptions{
Quota: &namespaceQuota,
AdditionalMetadata: v1beta1AdditionalMetadataSpec,
}
v1beta1ServiceOptions := &api.ServiceOptions{
AdditionalMetadata: v1beta1AdditionalMetadataSpec,
AllowedServices: &api.AllowedServices{
NodePort: pointer.Bool(false),
ExternalName: pointer.Bool(false),
LoadBalancer: pointer.Bool(false),
},
ExternalServiceIPs: &api.ExternalServiceIPsSpec{
Allowed: []api.AllowedIP{"192.168.0.1"},
},
}
v1beta2AllowedListSpec := &api.SelectorAllowedListSpec{
AllowedListSpec: api.AllowedListSpec{
Exact: []string{"foo", "bar"},
Regex: "^foo*",
},
}
networkPolicies := []networkingv1.NetworkPolicySpec{
{
Ingress: []networkingv1.NetworkPolicyIngressRule{
{
From: []networkingv1.NetworkPolicyPeer{
{
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "tenant-resources",
},
},
},
{
PodSelector: &metav1.LabelSelector{},
},
{
IPBlock: &networkingv1.IPBlock{
CIDR: "192.168.0.0/12",
},
},
},
},
},
},
}
limitRanges := []corev1.LimitRangeSpec{
{
Limits: []corev1.LimitRangeItem{
{
Type: corev1.LimitTypePod,
Min: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("50m"),
corev1.ResourceMemory: resource.MustParse("5Mi"),
},
Max: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceCPU: resource.MustParse("1"),
corev1.ResourceMemory: resource.MustParse("1Gi"),
},
},
},
},
}
resourceQuotas := []corev1.ResourceQuotaSpec{
{
Hard: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceLimitsCPU: resource.MustParse("8"),
corev1.ResourceLimitsMemory: resource.MustParse("16Gi"),
corev1.ResourceRequestsCPU: resource.MustParse("8"),
corev1.ResourceRequestsMemory: resource.MustParse("16Gi"),
},
Scopes: []corev1.ResourceQuotaScope{
corev1.ResourceQuotaScopeNotTerminating,
},
},
}
v1beta1Tnt := capsulev1beta1.Tenant{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "alice",
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{
"foo": "bar",
},
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Kind: "User",
Name: "alice",
ProxyOperations: []capsulev1beta1.ProxySettings{
{
Kind: "IngressClasses",
Operations: []capsulev1beta1.ProxyOperation{"List", "Update", "Delete"},
},
{
Kind: "Nodes",
Operations: []capsulev1beta1.ProxyOperation{"Update", "Delete"},
},
{
Kind: "StorageClasses",
Operations: []capsulev1beta1.ProxyOperation{"Update", "Delete"},
},
},
},
{
Kind: "User",
Name: "bob",
ProxyOperations: []capsulev1beta1.ProxySettings{
{
Kind: "IngressClasses",
Operations: []capsulev1beta1.ProxyOperation{"Update"},
},
{
Kind: "StorageClasses",
Operations: []capsulev1beta1.ProxyOperation{"List"},
},
},
},
{
Kind: "User",
Name: "jack",
ProxyOperations: []capsulev1beta1.ProxySettings{
{
Kind: "IngressClasses",
Operations: []capsulev1beta1.ProxyOperation{"Delete"},
},
{
Kind: "Nodes",
Operations: []capsulev1beta1.ProxyOperation{"Delete"},
},
{
Kind: "StorageClasses",
Operations: []capsulev1beta1.ProxyOperation{"List"},
},
{
Kind: "PriorityClasses",
Operations: []capsulev1beta1.ProxyOperation{"List"},
},
},
},
{
Kind: "Group",
Name: "owner-foo",
ProxyOperations: []capsulev1beta1.ProxySettings{
{
Kind: "IngressClasses",
Operations: []capsulev1beta1.ProxyOperation{"List"},
},
},
},
{
Kind: "Group",
Name: "owner-bar",
ProxyOperations: []capsulev1beta1.ProxySettings{
{
Kind: "IngressClasses",
Operations: []capsulev1beta1.ProxyOperation{"List"},
},
{
Kind: "StorageClasses",
Operations: []capsulev1beta1.ProxyOperation{"Delete"},
},
},
},
{
Kind: "ServiceAccount",
Name: "system:serviceaccount:oil-production:default",
ProxyOperations: []capsulev1beta1.ProxySettings{
{
Kind: "Nodes",
Operations: []capsulev1beta1.ProxyOperation{"Update"},
},
},
},
{
Kind: "ServiceAccount",
Name: "system:serviceaccount:gas-production:gas",
ProxyOperations: []capsulev1beta1.ProxySettings{
{
Kind: "StorageClasses",
Operations: []capsulev1beta1.ProxyOperation{"Update"},
},
},
},
},
NamespaceOptions: v1beta1NamespaceOptions,
ServiceOptions: v1beta1ServiceOptions,
StorageClasses: &v1beta2AllowedListSpec.AllowedListSpec,
IngressOptions: capsulev1beta1.IngressOptions{
HostnameCollisionScope: api.HostnameCollisionScopeDisabled,
AllowedClasses: &v1beta2AllowedListSpec.AllowedListSpec,
AllowedHostnames: &v1beta2AllowedListSpec.AllowedListSpec,
},
ContainerRegistries: &v1beta2AllowedListSpec.AllowedListSpec,
NodeSelector: nodeSelector,
NetworkPolicies: api.NetworkPolicySpec{
Items: networkPolicies,
},
LimitRanges: api.LimitRangesSpec{
Items: limitRanges,
},
ResourceQuota: api.ResourceQuotaSpec{
Scope: api.ResourceQuotaScopeNamespace,
Items: resourceQuotas,
},
AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{
{
ClusterRoleName: "crds-rolebinding",
Subjects: []rbacv1.Subject{
{
Kind: "Group",
APIGroup: rbacv1.GroupName,
Name: "system:authenticated",
},
},
},
},
ImagePullPolicies: []api.ImagePullPolicySpec{"Always", "IfNotPresent"},
PriorityClasses: &api.AllowedListSpec{
Exact: []string{"default"},
Regex: "^tier-.*$",
},
},
Status: capsulev1beta1.TenantStatus{
Size: 1,
Namespaces: []string{"foo", "bar"},
},
}
v1alpha1Tnt := Tenant{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "alice",
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{
"foo": "bar",
podAllowedImagePullPolicyAnnotation: "Always,IfNotPresent",
enableExternalNameAnnotation: "false",
enableNodePortsAnnotation: "false",
enableLoadBalancerAnnotation: "false",
podPriorityAllowedAnnotation: "default",
podPriorityAllowedRegexAnnotation: "^tier-.*$",
ownerGroupsAnnotation: "owner-foo,owner-bar",
ownerUsersAnnotation: "bob,jack",
ownerServiceAccountAnnotation: "system:serviceaccount:oil-production:default,system:serviceaccount:gas-production:gas",
enableNodeUpdateAnnotation: "alice,system:serviceaccount:oil-production:default",
enableNodeDeletionAnnotation: "alice,jack",
enableStorageClassListingAnnotation: "bob,jack",
enableStorageClassUpdateAnnotation: "alice,system:serviceaccount:gas-production:gas",
enableStorageClassDeletionAnnotation: "alice,owner-bar",
enableIngressClassListingAnnotation: "alice,owner-foo,owner-bar",
enableIngressClassUpdateAnnotation: "alice,bob",
enableIngressClassDeletionAnnotation: "alice,jack",
enablePriorityClassListingAnnotation: "jack",
resourceQuotaScopeAnnotation: "Namespace",
ingressHostnameCollisionScope: "Disabled",
},
},
Spec: TenantSpec{
Owner: OwnerSpec{
Name: "alice",
Kind: "User",
},
NamespaceQuota: &namespaceQuota,
NamespacesMetadata: v1alpha1AdditionalMetadataSpec,
ServicesMetadata: v1alpha1AdditionalMetadataSpec,
StorageClasses: v1alpha1AllowedListSpec,
IngressClasses: v1alpha1AllowedListSpec,
IngressHostnames: v1alpha1AllowedListSpec,
ContainerRegistries: v1alpha1AllowedListSpec,
NodeSelector: nodeSelector,
NetworkPolicies: networkPolicies,
LimitRanges: limitRanges,
ResourceQuota: resourceQuotas,
AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{
{
ClusterRoleName: "crds-rolebinding",
Subjects: []rbacv1.Subject{
{
Kind: "Group",
APIGroup: rbacv1.GroupName,
Name: "system:authenticated",
},
},
},
},
ExternalServiceIPs: &api.ExternalServiceIPsSpec{
Allowed: []api.AllowedIP{"192.168.0.1"},
},
},
Status: TenantStatus{
Size: 1,
Namespaces: []string{"foo", "bar"},
},
}
return v1alpha1Tnt, v1beta1Tnt
}
func TestConversionHub_ConvertTo(t *testing.T) {
v1beta1ConvertedTnt := capsulev1beta1.Tenant{}
v1alpha1Tnt, v1beta1tnt := generateTenantsSpecs()
err := v1alpha1Tnt.ConvertTo(&v1beta1ConvertedTnt)
if assert.NoError(t, err) {
sort.Slice(v1beta1tnt.Spec.Owners, func(i, j int) bool {
return v1beta1tnt.Spec.Owners[i].Name < v1beta1tnt.Spec.Owners[j].Name
})
sort.Slice(v1beta1ConvertedTnt.Spec.Owners, func(i, j int) bool {
return v1beta1ConvertedTnt.Spec.Owners[i].Name < v1beta1ConvertedTnt.Spec.Owners[j].Name
})
for _, owner := range v1beta1tnt.Spec.Owners {
sort.Slice(owner.ProxyOperations, func(i, j int) bool {
return owner.ProxyOperations[i].Kind < owner.ProxyOperations[j].Kind
})
}
for _, owner := range v1beta1ConvertedTnt.Spec.Owners {
sort.Slice(owner.ProxyOperations, func(i, j int) bool {
return owner.ProxyOperations[i].Kind < owner.ProxyOperations[j].Kind
})
}
assert.Equal(t, v1beta1tnt, v1beta1ConvertedTnt)
}
}
func TestConversionHub_ConvertFrom(t *testing.T) {
v1alpha1ConvertedTnt := Tenant{}
v1alpha1Tnt, v1beta1tnt := generateTenantsSpecs()
err := v1alpha1ConvertedTnt.ConvertFrom(&v1beta1tnt)
if assert.NoError(t, err) {
assert.EqualValues(t, v1alpha1Tnt, v1alpha1ConvertedTnt)
}
}

View File

@@ -1,23 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
// Package v1alpha1 contains API Schema definitions for the capsule.clastix.io v1alpha1 API group
// +kubebuilder:object:generate=true
// +groupName=capsule.clastix.io
package v1alpha1
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: "capsule.clastix.io", Version: "v1alpha1"}
// 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
)

View File

@@ -1,17 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
// OwnerSpec defines tenant owner name and kind.
type OwnerSpec struct {
Name string `json:"name"`
Kind Kind `json:"kind"`
}
// +kubebuilder:validation:Enum=User;Group
type Kind string
func (k Kind) String() string {
return string(k)
}

View File

@@ -1,34 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"sort"
corev1 "k8s.io/api/core/v1"
)
func (in *Tenant) IsFull() bool {
// we don't have limits on assigned Namespaces
if in.Spec.NamespaceQuota == nil {
return false
}
return len(in.Status.Namespaces) >= int(*in.Spec.NamespaceQuota)
}
func (in *Tenant) AssignNamespaces(namespaces []corev1.Namespace) {
var l []string
for _, ns := range namespaces {
if ns.Status.Phase == corev1.NamespaceActive {
l = append(l, ns.GetName())
}
}
sort.Strings(l)
in.Status.Namespaces = l
in.Status.Size = uint(len(l))
}

View File

@@ -1,71 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/projectcapsule/capsule/pkg/api"
)
// TenantSpec defines the desired state of Tenant.
type TenantSpec struct {
Owner OwnerSpec `json:"owner"`
// +kubebuilder:validation:Minimum=1
NamespaceQuota *int32 `json:"namespaceQuota,omitempty"`
NamespacesMetadata *AdditionalMetadata `json:"namespacesMetadata,omitempty"`
ServicesMetadata *AdditionalMetadata `json:"servicesMetadata,omitempty"`
StorageClasses *api.AllowedListSpec `json:"storageClasses,omitempty"`
IngressClasses *api.AllowedListSpec `json:"ingressClasses,omitempty"`
IngressHostnames *api.AllowedListSpec `json:"ingressHostnames,omitempty"`
ContainerRegistries *api.AllowedListSpec `json:"containerRegistries,omitempty"`
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
NetworkPolicies []networkingv1.NetworkPolicySpec `json:"networkPolicies,omitempty"`
LimitRanges []corev1.LimitRangeSpec `json:"limitRanges,omitempty"`
ResourceQuota []corev1.ResourceQuotaSpec `json:"resourceQuotas,omitempty"`
AdditionalRoleBindings []api.AdditionalRoleBindingsSpec `json:"additionalRoleBindings,omitempty"`
ExternalServiceIPs *api.ExternalServiceIPsSpec `json:"externalServiceIPs,omitempty"`
}
// TenantStatus defines the observed state of Tenant.
type TenantStatus struct {
Size uint `json:"size"`
Namespaces []string `json:"namespaces,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster,shortName=tnt
// +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceQuota",description="The max amount of Namespaces can be created"
// +kubebuilder:printcolumn:name="Namespace count",type="integer",JSONPath=".status.size",description="The total amount of Namespaces in use"
// +kubebuilder:printcolumn:name="Owner name",type="string",JSONPath=".spec.owner.name",description="The assigned Tenant owner"
// +kubebuilder:printcolumn:name="Owner kind",type="string",JSONPath=".spec.owner.kind",description="The assigned Tenant owner kind"
// +kubebuilder:printcolumn:name="Node selector",type="string",JSONPath=".spec.nodeSelector",description="Node Selector applied to Pods"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
// +kubebuilder:deprecatedversion:warning="This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version."
// Tenant is the Schema for the tenants API.
type Tenant struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec TenantSpec `json:"spec,omitempty"`
Status TenantStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// TenantList contains a list of Tenant.
type TenantList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Tenant `json:"items"`
}
func init() {
SchemeBuilder.Register(&Tenant{}, &TenantList{})
}

View File

@@ -1,21 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"os"
ctrl "sigs.k8s.io/controller-runtime"
)
func (in *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error {
certData, _ := os.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt")
if len(certData) == 0 {
return nil
}
return ctrl.NewWebhookManagedBy(mgr).
For(in).
Complete()
}

View File

@@ -1,308 +0,0 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
"github.com/projectcapsule/capsule/pkg/api"
corev1 "k8s.io/api/core/v1"
"k8s.io/api/networking/v1"
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 *AdditionalMetadata) DeepCopyInto(out *AdditionalMetadata) {
*out = *in
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Annotations != nil {
in, out := &in.Annotations, &out.Annotations
*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 AdditionalMetadata.
func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata {
if in == nil {
return nil
}
out := new(AdditionalMetadata)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CapsuleConfiguration) DeepCopyInto(out *CapsuleConfiguration) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfiguration.
func (in *CapsuleConfiguration) DeepCopy() *CapsuleConfiguration {
if in == nil {
return nil
}
out := new(CapsuleConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CapsuleConfiguration) 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 *CapsuleConfigurationList) DeepCopyInto(out *CapsuleConfigurationList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]CapsuleConfiguration, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfigurationList.
func (in *CapsuleConfigurationList) DeepCopy() *CapsuleConfigurationList {
if in == nil {
return nil
}
out := new(CapsuleConfigurationList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CapsuleConfigurationList) 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 *CapsuleConfigurationSpec) DeepCopyInto(out *CapsuleConfigurationSpec) {
*out = *in
if in.UserGroups != nil {
in, out := &in.UserGroups, &out.UserGroups
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfigurationSpec.
func (in *CapsuleConfigurationSpec) DeepCopy() *CapsuleConfigurationSpec {
if in == nil {
return nil
}
out := new(CapsuleConfigurationSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerSpec.
func (in *OwnerSpec) DeepCopy() *OwnerSpec {
if in == nil {
return nil
}
out := new(OwnerSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Tenant) DeepCopyInto(out *Tenant) {
*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 Tenant.
func (in *Tenant) DeepCopy() *Tenant {
if in == nil {
return nil
}
out := new(Tenant)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Tenant) 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 *TenantList) DeepCopyInto(out *TenantList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Tenant, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantList.
func (in *TenantList) DeepCopy() *TenantList {
if in == nil {
return nil
}
out := new(TenantList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TenantList) 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 *TenantSpec) DeepCopyInto(out *TenantSpec) {
*out = *in
out.Owner = in.Owner
if in.NamespaceQuota != nil {
in, out := &in.NamespaceQuota, &out.NamespaceQuota
*out = new(int32)
**out = **in
}
if in.NamespacesMetadata != nil {
in, out := &in.NamespacesMetadata, &out.NamespacesMetadata
*out = new(AdditionalMetadata)
(*in).DeepCopyInto(*out)
}
if in.ServicesMetadata != nil {
in, out := &in.ServicesMetadata, &out.ServicesMetadata
*out = new(AdditionalMetadata)
(*in).DeepCopyInto(*out)
}
if in.StorageClasses != nil {
in, out := &in.StorageClasses, &out.StorageClasses
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.IngressClasses != nil {
in, out := &in.IngressClasses, &out.IngressClasses
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.IngressHostnames != nil {
in, out := &in.IngressHostnames, &out.IngressHostnames
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.ContainerRegistries != nil {
in, out := &in.ContainerRegistries, &out.ContainerRegistries
*out = new(api.AllowedListSpec)
(*in).DeepCopyInto(*out)
}
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
}
}
if in.NetworkPolicies != nil {
in, out := &in.NetworkPolicies, &out.NetworkPolicies
*out = make([]v1.NetworkPolicySpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.LimitRanges != nil {
in, out := &in.LimitRanges, &out.LimitRanges
*out = make([]corev1.LimitRangeSpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ResourceQuota != nil {
in, out := &in.ResourceQuota, &out.ResourceQuota
*out = make([]corev1.ResourceQuotaSpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AdditionalRoleBindings != nil {
in, out := &in.AdditionalRoleBindings, &out.AdditionalRoleBindings
*out = make([]api.AdditionalRoleBindingsSpec, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ExternalServiceIPs != nil {
in, out := &in.ExternalServiceIPs, &out.ExternalServiceIPs
*out = new(api.ExternalServiceIPsSpec)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec.
func (in *TenantSpec) DeepCopy() *TenantSpec {
if in == nil {
return nil
}
out := new(TenantSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TenantStatus) DeepCopyInto(out *TenantStatus) {
*out = *in
if in.Namespaces != nil {
in, out := &in.Namespaces, &out.Namespaces
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantStatus.
func (in *TenantStatus) DeepCopy() *TenantStatus {
if in == nil {
return nil
}
out := new(TenantStatus)
in.DeepCopyInto(out)
return out
}

View File

@@ -1,142 +0,0 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package v1beta2
import (
"fmt"
"strconv"
"strings"
"sigs.k8s.io/controller-runtime/pkg/conversion"
capsulev1alpha1 "github.com/projectcapsule/capsule/api/v1alpha1"
)
func (in *CapsuleConfiguration) ConvertTo(raw conversion.Hub) error {
dst, ok := raw.(*capsulev1alpha1.CapsuleConfiguration)
if !ok {
return fmt.Errorf("expected type *capsulev1alpha1.CapsuleConfiguration, got %T", dst)
}
dst.ObjectMeta = in.ObjectMeta
dst.Spec.ProtectedNamespaceRegexpString = in.Spec.ProtectedNamespaceRegexpString
dst.Spec.UserGroups = in.Spec.UserGroups
dst.Spec.ProtectedNamespaceRegexpString = in.Spec.ProtectedNamespaceRegexpString
annotations := dst.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
if in.Spec.NodeMetadata != nil {
if len(in.Spec.NodeMetadata.ForbiddenLabels.Exact) > 0 {
annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenLabels.Exact, ",")
}
if len(in.Spec.NodeMetadata.ForbiddenLabels.Regex) > 0 {
annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenLabels.Regex
}
if len(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact) > 0 {
annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation] = strings.Join(in.Spec.NodeMetadata.ForbiddenAnnotations.Exact, ",")
}
if len(in.Spec.NodeMetadata.ForbiddenAnnotations.Regex) > 0 {
annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation] = in.Spec.NodeMetadata.ForbiddenAnnotations.Regex
}
}
annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName] = fmt.Sprintf("%t", in.Spec.EnableTLSReconciler)
annotations[capsulev1alpha1.TLSSecretNameAnnotation] = in.Spec.CapsuleResources.TLSSecretName
annotations[capsulev1alpha1.MutatingWebhookConfigurationName] = in.Spec.CapsuleResources.MutatingWebhookConfigurationName
annotations[capsulev1alpha1.ValidatingWebhookConfigurationName] = in.Spec.CapsuleResources.ValidatingWebhookConfigurationName
dst.SetAnnotations(annotations)
return nil
}
func (in *CapsuleConfiguration) ConvertFrom(raw conversion.Hub) error {
src, ok := raw.(*capsulev1alpha1.CapsuleConfiguration)
if !ok {
return fmt.Errorf("expected type *capsulev1alpha1.CapsuleConfiguration, got %T", src)
}
in.ObjectMeta = src.ObjectMeta
in.Spec.ProtectedNamespaceRegexpString = src.Spec.ProtectedNamespaceRegexpString
in.Spec.UserGroups = src.Spec.UserGroups
in.Spec.ProtectedNamespaceRegexpString = src.Spec.ProtectedNamespaceRegexpString
annotations := src.GetAnnotations()
if value, found := annotations[capsulev1alpha1.ForbiddenNodeLabelsAnnotation]; found {
if in.Spec.NodeMetadata == nil {
in.Spec.NodeMetadata = &NodeMetadata{}
}
in.Spec.NodeMetadata.ForbiddenLabels.Exact = strings.Split(value, ",")
delete(annotations, capsulev1alpha1.ForbiddenNodeLabelsAnnotation)
}
if value, found := annotations[capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation]; found {
if in.Spec.NodeMetadata == nil {
in.Spec.NodeMetadata = &NodeMetadata{}
}
in.Spec.NodeMetadata.ForbiddenLabels.Regex = value
delete(annotations, capsulev1alpha1.ForbiddenNodeLabelsRegexpAnnotation)
}
if value, found := annotations[capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation]; found {
if in.Spec.NodeMetadata == nil {
in.Spec.NodeMetadata = &NodeMetadata{}
}
in.Spec.NodeMetadata.ForbiddenAnnotations.Exact = strings.Split(value, ",")
delete(annotations, capsulev1alpha1.ForbiddenNodeAnnotationsAnnotation)
}
if value, found := annotations[capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation]; found {
if in.Spec.NodeMetadata == nil {
in.Spec.NodeMetadata = &NodeMetadata{}
}
in.Spec.NodeMetadata.ForbiddenAnnotations.Regex = value
delete(annotations, capsulev1alpha1.ForbiddenNodeAnnotationsRegexpAnnotation)
}
if value, found := annotations[capsulev1alpha1.EnableTLSConfigurationAnnotationName]; found {
v, _ := strconv.ParseBool(value)
in.Spec.EnableTLSReconciler = v
delete(annotations, capsulev1alpha1.EnableTLSConfigurationAnnotationName)
}
if value, found := annotations[capsulev1alpha1.TLSSecretNameAnnotation]; found {
in.Spec.CapsuleResources.TLSSecretName = value
delete(annotations, capsulev1alpha1.TLSSecretNameAnnotation)
}
if value, found := annotations[capsulev1alpha1.MutatingWebhookConfigurationName]; found {
in.Spec.CapsuleResources.MutatingWebhookConfigurationName = value
delete(annotations, capsulev1alpha1.MutatingWebhookConfigurationName)
}
if value, found := annotations[capsulev1alpha1.ValidatingWebhookConfigurationName]; found {
in.Spec.CapsuleResources.ValidatingWebhookConfigurationName = value
delete(annotations, capsulev1alpha1.ValidatingWebhookConfigurationName)
}
in.SetAnnotations(annotations)
return nil
}

View File

@@ -48,6 +48,7 @@ const (
PriorityClassesProxy ProxyServiceKind = "PriorityClasses"
RuntimeClassesProxy ProxyServiceKind = "RuntimeClasses"
PersistentVolumesProxy ProxyServiceKind = "PersistentVolumes"
TenantProxy ProxyServiceKind = "Tenant"
ListOperation ProxyOperation = "List"
UpdateOperation ProxyOperation = "Update"

View File

@@ -7,21 +7,25 @@ import (
"crypto/md5" //#nosec
"encoding/hex"
"fmt"
"strings"
)
const (
// Annotation name part must be no more than 63 characters.
maxAnnotationLength = 63
HardCapsuleQuotaAnnotation = "quota.capsule.clastix.io/hard-"
UsedCapsuleQuotaAnnotation = "quota.capsule.clastix.io/used-"
)
func createAnnotation(format string, resource fmt.Stringer) (string, error) {
suffix := resource.String()
resourceStr := strings.ReplaceAll(resource.String(), "/", "_")
hash := md5.Sum([]byte(resource.String())) //#nosec
hash := md5.Sum([]byte(resourceStr)) //#nosec
hashed := hex.EncodeToString(hash[:])
capsuleHashed := format + hashed
capsuleAnnotation := format + suffix
capsuleAnnotation := format + resourceStr
switch {
case len(capsuleAnnotation) <= maxAnnotationLength:
@@ -36,9 +40,9 @@ func createAnnotation(format string, resource fmt.Stringer) (string, error) {
}
func UsedQuotaFor(resource fmt.Stringer) (string, error) {
return createAnnotation("quota.capsule.clastix.io/used-", resource)
return createAnnotation(UsedCapsuleQuotaAnnotation, resource)
}
func HardQuotaFor(resource fmt.Stringer) (string, error) {
return createAnnotation("quota.capsule.clastix.io/hard-", resource)
return createAnnotation(HardCapsuleQuotaAnnotation, resource)
}

View File

@@ -17,6 +17,8 @@ type TenantSpec struct {
NamespaceOptions *NamespaceOptions `json:"namespaceOptions,omitempty"`
// Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
ServiceOptions *api.ServiceOptions `json:"serviceOptions,omitempty"`
// Specifies options for the Pods deployed in the Tenant namespaces, such as additional metadata.
PodOptions *api.PodOptions `json:"podOptions,omitempty"`
// Specifies the allowed StorageClasses assigned to the Tenant.
// Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses.
// A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class.

View File

@@ -716,6 +716,11 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
*out = new(api.ServiceOptions)
(*in).DeepCopyInto(*out)
}
if in.PodOptions != nil {
in, out := &in.PodOptions, &out.PodOptions
*out = new(api.PodOptions)
(*in).DeepCopyInto(*out)
}
if in.StorageClasses != nil {
in, out := &in.StorageClasses, &out.StorageClasses
*out = new(api.DefaultAllowedListSpec)

BIN
assets/docs/dev-env.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@@ -3,7 +3,7 @@ type: application
description: A Helm chart to deploy the Capsule Operator for easily implementing,
managing, and maintaining mutitenancy and access control in Kubernetes.
home: https://github.com/projectcapsule/capsule
icon: https://github.com/projectcapsule/capsule/raw/master/assets/logo/capsule_small.png
icon: https://github.com/projectcapsule/capsule/raw/main/assets/logo/capsule_small.png
keywords:
- kubernetes
- operator
@@ -13,16 +13,29 @@ keywords:
- multitenant
- namespace
maintainers:
- email: hello@clastix.io
name: Clastix Labs Team
- name: capsule-maintainers
email: cncf-capsule-maintainers@lists.cncf.io
name: capsule
sources:
- https://github.com/projectcapsule/capsule
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
version: 0.4.6
# The version is overwritten by the release workflow.
version: 0.6.0
# This is the version number of the application being deployed.
# This version number should be incremented each time you make changes to the application.
appVersion: 0.3.3
appVersion: 0.5.0
annotations:
artifacthub.io/operator: "true"
artifacthub.io/prerelease: "false"
artifacthub.io/category: security
artifacthub.io/license: Apache-2.0
artifacthub.io/maintainers: |
- name: capsule-maintainers
email: cncf-capsule-maintainers@lists.cncf.io
artifacthub.io/links: |
- name: Documentation
url: https://capsule.clastix.io/
# artifacthub.io/changes: |
# - kind: added
# description: artifacthub annotations
# - kind: changed
# description: maintainers contact

View File

@@ -114,6 +114,9 @@ Here the values you can override:
| manager.options.logLevel | string | `"4"` | Set the log verbosity of the capsule with a value from 1 to 10 |
| manager.options.nodeMetadata | object | `{"forbiddenAnnotations":{"denied":[],"deniedRegex":""},"forbiddenLabels":{"denied":[],"deniedRegex":""}}` | Allows to set the forbidden metadata for the worker nodes that could be patched by a Tenant |
| manager.options.protectedNamespaceRegex | string | `""` | If specified, disallows creation of namespaces matching the passed regexp |
| manager.rbac.create | bool | `true` | Specifies whether RBAC resources should be created. |
| manager.rbac.existingClusterRoles | list | `[]` | Specifies further cluster roles to be added to the Capsule manager service account. |
| manager.rbac.existingRoles | list | `[]` | Specifies further cluster roles to be added to the Capsule manager service account. |
| manager.readinessProbe | object | `{"httpGet":{"path":"/readyz","port":10080}}` | Configure the readiness probe using Deployment probe spec |
| manager.resources.limits.cpu | string | `"200m"` | |
| manager.resources.limits.memory | string | `"128Mi"` | |

View File

@@ -1,5 +1,12 @@
fullnameOverride: capsule
manager:
# Manager RBAC
rbac:
create: true
existingClusterRoles:
- "view"
existingRoles:
- "some-role"
resources:
limits:
cpu: 500m

View File

@@ -16,7 +16,6 @@ spec:
namespace: capsule-system
path: /convert
conversionReviewVersions:
- v1alpha1
- v1beta1
- v1beta2
group: capsule.clastix.io
@@ -27,40 +26,6 @@ spec:
singular: capsuleconfiguration
scope: Cluster
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: CapsuleConfiguration is the Schema for the Capsule configuration API.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: CapsuleConfigurationSpec defines the Capsule configuration.
properties:
forceTenantPrefix:
default: false
description: Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment.
type: boolean
protectedNamespaceRegex:
description: Disallow creation of namespaces, whose name matches this regexp
type: string
userGroups:
default:
- capsule.clastix.io
description: Names of the groups for Capsule users.
items:
type: string
type: array
type: object
type: object
served: true
storage: false
- name: v1beta2
schema:
openAPIV3Schema:

View File

@@ -15,7 +15,6 @@ spec:
namespace: capsule-system
path: /convert
conversionReviewVersions:
- v1alpha1
- v1beta1
- v1beta2
group: capsule.clastix.io
@@ -28,859 +27,6 @@ spec:
singular: tenant
scope: Cluster
versions:
- additionalPrinterColumns:
- description: The max amount of Namespaces can be created
jsonPath: .spec.namespaceQuota
name: Namespace quota
type: integer
- description: The total amount of Namespaces in use
jsonPath: .status.size
name: Namespace count
type: integer
- description: The assigned Tenant owner
jsonPath: .spec.owner.name
name: Owner name
type: string
- description: The assigned Tenant owner kind
jsonPath: .spec.owner.kind
name: Owner kind
type: string
- description: Node Selector applied to Pods
jsonPath: .spec.nodeSelector
name: Node selector
type: string
- description: Age
jsonPath: .metadata.creationTimestamp
name: Age
type: date
deprecated: true
deprecationWarning: This version is going to be dropped in the upcoming version
of Capsule; please, migrate to v1beta2 version.
name: v1alpha1
schema:
openAPIV3Schema:
description: Tenant is the Schema for the tenants API.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: TenantSpec defines the desired state of Tenant.
properties:
additionalRoleBindings:
description: Specifies additional RoleBindings assigned to the Tenant. Capsule will ensure that all namespaces in the Tenant always contain the RoleBinding for the given ClusterRole. Optional.
items:
properties:
clusterRoleName:
type: string
subjects:
description: kubebuilder:validation:Minimum=1
items:
description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names.
properties:
apiGroup:
description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects.
type: string
kind:
description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error.
type: string
name:
description: Name of the object being referenced.
type: string
namespace:
description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error.
type: string
required:
- kind
- name
type: object
x-kubernetes-map-type: atomic
type: array
required:
- clusterRoleName
- subjects
type: object
type: array
containerRegistries:
description: Specifies the trusted Image Registries assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed trusted registries. Optional.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
cordoned:
description: Toggling the Tenant resources cordoning, when enable resources cannot be deleted.
type: boolean
imagePullPolicies:
description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
items:
enum:
- Always
- Never
- IfNotPresent
type: string
type: array
ingressOptions:
description: Specifies options for the Ingress resources, such as allowed hostnames and IngressClass. Optional.
properties:
allowWildcardHostnames:
description: Toggles the ability for Ingress resources created in a Tenant to have a hostname wildcard.
type: boolean
allowedClasses:
description: Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. A default value can be specified, and all the Ingress resources created will inherit the declared class. Optional.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
default:
type: string
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
allowedHostnames:
description: Specifies the allowed hostnames in Ingresses for the given Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed hostnames. Optional.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
hostnameCollisionScope:
default: Disabled
description: "Defines the scope of hostname collision check performed when Tenant Owners create Ingress with allowed hostnames. \n - Cluster: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces managed by Capsule. \n - Tenant: disallow the creation of an Ingress if the pair hostname and path is already used across the Namespaces of the Tenant. \n - Namespace: disallow the creation of an Ingress if the pair hostname and path is already used in the Ingress Namespace. \n Optional."
enum:
- Cluster
- Tenant
- Namespace
- Disabled
type: string
type: object
limitRanges:
description: Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
properties:
items:
items:
description: LimitRangeSpec defines a min/max usage limit for resources that match on kind.
properties:
limits:
description: Limits is the list of LimitRangeItem objects that are enforced.
items:
description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind.
properties:
default:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Default resource requirement limit value by resource name if resource limit is omitted.
type: object
defaultRequest:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.
type: object
max:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Max usage constraints on this kind by resource name.
type: object
maxLimitRequestRatio:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource.
type: object
min:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Min usage constraints on this kind by resource name.
type: object
type:
description: Type of resource that this limit applies to.
type: string
required:
- type
type: object
type: array
required:
- limits
type: object
type: array
type: object
namespaceOptions:
description: Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot set for their Namespace resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set for their Namespace resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
quota:
description: Specifies the maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional.
format: int32
minimum: 1
type: integer
type: object
networkPolicies:
description: Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
properties:
items:
items:
description: NetworkPolicySpec provides the specification of a NetworkPolicy
properties:
egress:
description: egress is a list of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
items:
description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8
properties:
ports:
description: ports is a list of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
items:
description: NetworkPolicyPort describes a port to allow traffic on
properties:
endPort:
description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.
format: int32
type: integer
port:
anyOf:
- type: integer
- type: string
description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
x-kubernetes-int-or-string: true
protocol:
default: TCP
description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
type: string
type: object
type: array
to:
description: to is a list of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
items:
description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
properties:
ipBlock:
description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
properties:
cidr:
description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
type: string
except:
description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
items:
type: string
type: array
required:
- cidr
type: object
namespaceSelector:
description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
type: object
type: array
ingress:
description: ingress is a list of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
items:
description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from.
properties:
from:
description: from is a list of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
items:
description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
properties:
ipBlock:
description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
properties:
cidr:
description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
type: string
except:
description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
items:
type: string
type: array
required:
- cidr
type: object
namespaceSelector:
description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
ports:
description: ports is a list of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
items:
description: NetworkPolicyPort describes a port to allow traffic on
properties:
endPort:
description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.
format: int32
type: integer
port:
anyOf:
- type: integer
- type: string
description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
x-kubernetes-int-or-string: true
protocol:
default: TCP
description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
type: string
type: object
type: array
type: object
type: array
podSelector:
description: podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
policyTypes:
description: policyTypes is a list of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of ingress or egress rules; policies that contain an egress section are assumed to affect egress, and all policies (whether or not they contain an ingress section) are assumed to affect ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
items:
description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8
type: string
type: array
required:
- podSelector
type: object
type: array
type: object
nodeSelector:
additionalProperties:
type: string
description: Specifies the label to control the placement of pods on a given pool of worker nodes. All namespaces created within the Tenant will have the node selector annotation. This annotation tells the Kubernetes scheduler to place pods on the nodes having the selector label. Optional.
type: object
owners:
description: Specifies the owners of the Tenant. Mandatory.
items:
properties:
clusterRoles:
default:
- admin
- capsule-namespace-deleter
description: Defines additional cluster-roles for the specific Owner.
items:
type: string
type: array
kind:
description: Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount"
enum:
- User
- Group
- ServiceAccount
type: string
name:
description: Name of tenant owner.
type: string
proxySettings:
description: Proxy settings for tenant owner.
items:
properties:
kind:
enum:
- Nodes
- StorageClasses
- IngressClasses
- PriorityClasses
- RuntimeClasses
- PersistentVolumes
type: string
operations:
items:
enum:
- List
- Update
- Delete
type: string
type: array
required:
- kind
- operations
type: object
type: array
required:
- kind
- name
type: object
type: array
preventDeletion:
description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
type: boolean
priorityClasses:
description: Specifies the allowed priorityClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed PriorityClasses. A default value can be specified, and all the Pod resources created will inherit the declared class. Optional.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
default:
type: string
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
resourceQuotas:
description: Specifies a list of ResourceQuota resources assigned to the Tenant. The assigned values are inherited by any namespace created in the Tenant. The Capsule operator aggregates ResourceQuota at Tenant level, so that the hard quota is never crossed for the given Tenant. This permits the Tenant owner to consume resources in the Tenant regardless of the namespace. Optional.
properties:
items:
items:
description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
properties:
hard:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/'
type: object
scopeSelector:
description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.
properties:
matchExpressions:
description: A list of scope selector requirements by scope of the resources.
items:
description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.
properties:
operator:
description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.
type: string
scopeName:
description: The name of the scope that the selector applies to.
type: string
values:
description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- operator
- scopeName
type: object
type: array
type: object
x-kubernetes-map-type: atomic
scopes:
description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
items:
description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota
type: string
type: array
type: object
type: array
scope:
default: Tenant
description: Define if the Resource Budget should compute resource across all Namespaces in the Tenant or individually per cluster. Default is Tenant
enum:
- Tenant
- Namespace
type: string
type: object
runtimeClasses:
description: Specifies the allowed RuntimeClasses assigned to the Tenant. Capsule assures that all Pods resources created in the Tenant can use only one of the allowed RuntimeClasses. Optional.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
serviceOptions:
description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
allowedServices:
description: Block or deny certain type of Services. Optional.
properties:
externalName:
default: true
description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
loadBalancer:
default: true
description: Specifies if LoadBalancer service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
nodePort:
default: true
description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
type: object
externalIPs:
description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
properties:
allowed:
items:
pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$
type: string
type: array
required:
- allowed
type: object
type: object
storageClasses:
description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class. Optional.
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
default:
type: string
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
required:
- owners
type: object
status:
description: Returns the observed state of the Tenant.
properties:
namespaces:
description: List of namespaces assigned to the Tenant.
items:
type: string
type: array
size:
description: How many namespaces are assigned to the Tenant.
type: integer
state:
default: Active
description: The operational state of the Tenant. Possible values are "Active", "Cordoned".
enum:
- Cordoned
- Active
type: string
required:
- size
- state
type: object
type: object
served: true
storage: false
subresources:
status: {}
- additionalPrinterColumns:
- description: The actual state of the Tenant
jsonPath: .status.state
@@ -1737,6 +883,22 @@ spec:
- name
type: object
type: array
podOptions:
description: Specifies options for the Pod, such as additional metadata. Optional.
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
type: object
priorityClasses:
description: Specifies the allowed priorityClasses assigned to the
Tenant. Capsule assures that all Pods resources created in the Tenant
@@ -2869,6 +2031,22 @@ spec:
- name
type: object
type: array
podOptions:
description: Specifies options for the Pod, such as additional metadata. Optional.
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
type: object
preventDeletion:
description: Prevent accidental deletion of the Tenant. When enabled,
the deletion request will be declined.
@@ -3085,6 +2263,28 @@ spec:
type: string
type: object
type: object
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot
set for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set
for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
allowedServices:
description: Block or deny certain type of Services. Optional.
properties:

View File

@@ -1,61 +1,4 @@
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "capsule.fullname" . }}-proxy-role
labels:
{{- include "capsule.labels" . | nindent 4 }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
- apiGroups:
- authorization.k8s.io
resources:
- subjectaccessreviews
verbs:
- create
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "capsule.fullname" . }}-metrics-reader
labels:
{{- include "capsule.labels" . | nindent 4 }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
- nonResourceURLs:
- /metrics
verbs:
- get
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "capsule.fullname" . }}-proxy-rolebinding
labels:
{{- include "capsule.labels" . | nindent 4 }}
{{- with .Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "capsule.fullname" . }}-proxy-role
subjects:
- kind: ServiceAccount
name: {{ include "capsule.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- if $.Values.manager.rbac.create }}
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
@@ -74,4 +17,47 @@ roleRef:
subjects:
- kind: ServiceAccount
name: {{ include "capsule.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
namespace: {{ .Release.Namespace }}
{{- end }}
{{- range $_, $cr := $.Values.manager.rbac.existingClusterRoles }}
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "capsule.fullname" $ }}-{{ $cr }}
labels:
{{- include "capsule.labels" $ | nindent 4 }}
{{- with $.Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ $cr }}
subjects:
- kind: ServiceAccount
name: {{ include "capsule.serviceAccountName" $ }}
namespace: {{ $.Release.Namespace }}
{{- end }}
{{- range $_, $nr := $.Values.manager.rbac.existingRoles }}
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "capsule.fullname" $ }}-{{ $nr }}
labels:
{{- include "capsule.labels" $ | nindent 4 }}
{{- with $.Values.customAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ $nr }}
subjects:
- kind: ServiceAccount
name: {{ include "capsule.serviceAccountName" $ }}
namespace: {{ $.Release.Namespace }}
{{- end }}

View File

@@ -14,6 +14,17 @@ tls:
# Manager Options
manager:
# Manager RBAC
rbac:
# -- Specifies whether RBAC resources should be created.
create: true
# -- Specifies further cluster roles to be added to the Capsule manager service account.
existingClusterRoles: []
# - cluster-admin
# -- Specifies further cluster roles to be added to the Capsule manager service account.
existingRoles: []
# - namespace-admin
# -- Set the controller deployment mode as `Deployment` or `DaemonSet`.
kind: Deployment

19
commitlint.config.js Normal file
View File

@@ -0,0 +1,19 @@
const Configuration = {
extends: ['@commitlint/config-conventional'],
plugins: ['commitlint-plugin-function-rules'],
rules: {
'type-enum': [2, 'always', ['chore', 'ci', 'docs', 'feat', 'test', 'fix', 'sec']],
'body-max-line-length': [1, 'always', 500],
},
/*
* Whether commitlint uses the default ignore rules, see the description above.
*/
defaultIgnores: true,
/*
* Custom URL to show upon failure
*/
helpUrl:
'https://github.com/projectcapsule/capsule/blob/main/CONTRIBUTING.md#commits',
};
module.exports = Configuration;

View File

@@ -15,49 +15,6 @@ spec:
singular: capsuleconfiguration
scope: Cluster
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: CapsuleConfiguration is the Schema for the Capsule configuration
API.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: CapsuleConfigurationSpec defines the Capsule configuration.
properties:
forceTenantPrefix:
default: false
description: Enforces the Tenant owner, during Namespace creation,
to name it using the selected Tenant name as prefix, separated by
a dash. This is useful to avoid Namespace name collision in a public
CaaS environment.
type: boolean
protectedNamespaceRegex:
description: Disallow creation of namespaces, whose name matches this
regexp
type: string
userGroups:
default:
- capsule.clastix.io
description: Names of the groups for Capsule users.
items:
type: string
type: array
type: object
type: object
served: true
storage: false
- name: v1beta2
schema:
openAPIV3Schema:

View File

@@ -17,860 +17,6 @@ spec:
singular: tenant
scope: Cluster
versions:
- additionalPrinterColumns:
- description: The max amount of Namespaces can be created
jsonPath: .spec.namespaceQuota
name: Namespace quota
type: integer
- description: The total amount of Namespaces in use
jsonPath: .status.size
name: Namespace count
type: integer
- description: The assigned Tenant owner
jsonPath: .spec.owner.name
name: Owner name
type: string
- description: The assigned Tenant owner kind
jsonPath: .spec.owner.kind
name: Owner kind
type: string
- description: Node Selector applied to Pods
jsonPath: .spec.nodeSelector
name: Node selector
type: string
- description: Age
jsonPath: .metadata.creationTimestamp
name: Age
type: date
deprecated: true
deprecationWarning: This version is going to be dropped in the upcoming version
of Capsule; please, migrate to v1beta2 version.
name: v1alpha1
schema:
openAPIV3Schema:
description: Tenant is the Schema for the tenants API.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: TenantSpec defines the desired state of Tenant.
properties:
additionalRoleBindings:
items:
properties:
clusterRoleName:
type: string
subjects:
description: kubebuilder:validation:Minimum=1
items:
description: Subject contains a reference to the object or
user identities a role binding applies to. This can either
hold a direct API object reference, or a value for non-objects
such as user and group names.
properties:
apiGroup:
description: APIGroup holds the API group of the referenced
subject. Defaults to "" for ServiceAccount subjects.
Defaults to "rbac.authorization.k8s.io" for User and
Group subjects.
type: string
kind:
description: Kind of object being referenced. Values defined
by this API group are "User", "Group", and "ServiceAccount".
If the Authorizer does not recognized the kind value,
the Authorizer should report an error.
type: string
name:
description: Name of the object being referenced.
type: string
namespace:
description: Namespace of the referenced object. If the
object kind is non-namespace, such as "User" or "Group",
and this value is not empty the Authorizer should report
an error.
type: string
required:
- kind
- name
type: object
x-kubernetes-map-type: atomic
type: array
required:
- clusterRoleName
- subjects
type: object
type: array
containerRegistries:
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
externalServiceIPs:
properties:
allowed:
items:
pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$
type: string
type: array
required:
- allowed
type: object
ingressClasses:
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
ingressHostnames:
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
limitRanges:
items:
description: LimitRangeSpec defines a min/max usage limit for resources
that match on kind.
properties:
limits:
description: Limits is the list of LimitRangeItem objects that
are enforced.
items:
description: LimitRangeItem defines a min/max usage limit
for any resource that matches on kind.
properties:
default:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Default resource requirement limit value
by resource name if resource limit is omitted.
type: object
defaultRequest:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: DefaultRequest is the default resource requirement
request value by resource name if resource request is
omitted.
type: object
max:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Max usage constraints on this kind by resource
name.
type: object
maxLimitRequestRatio:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: MaxLimitRequestRatio if specified, the named
resource must have a request and limit that are both
non-zero where limit divided by request is less than
or equal to the enumerated value; this represents the
max burst for the named resource.
type: object
min:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Min usage constraints on this kind by resource
name.
type: object
type:
description: Type of resource that this limit applies
to.
type: string
required:
- type
type: object
type: array
required:
- limits
type: object
type: array
namespaceQuota:
format: int32
minimum: 1
type: integer
namespacesMetadata:
properties:
additionalAnnotations:
additionalProperties:
type: string
type: object
additionalLabels:
additionalProperties:
type: string
type: object
type: object
networkPolicies:
items:
description: NetworkPolicySpec provides the specification of a NetworkPolicy
properties:
egress:
description: egress is a list of egress rules to be applied
to the selected pods. Outgoing traffic is allowed if there
are no NetworkPolicies selecting the pod (and cluster policy
otherwise allows the traffic), OR if the traffic matches at
least one egress rule across all of the NetworkPolicy objects
whose podSelector matches the pod. If this field is empty
then this NetworkPolicy limits all outgoing traffic (and serves
solely to ensure that the pods it selects are isolated by
default). This field is beta-level in 1.8
items:
description: NetworkPolicyEgressRule describes a particular
set of traffic that is allowed out of pods matched by a
NetworkPolicySpec's podSelector. The traffic must match
both ports and to. This type is beta-level in 1.8
properties:
ports:
description: ports is a list of destination ports for
outgoing traffic. Each item in this list is combined
using a logical OR. If this field is empty or missing,
this rule matches all ports (traffic not restricted
by port). If this field is present and contains at least
one item, then this rule allows traffic only if the
traffic matches at least one port in the list.
items:
description: NetworkPolicyPort describes a port to allow
traffic on
properties:
endPort:
description: endPort indicates that the range of
ports from port to endPort if set, inclusive,
should be allowed by the policy. This field cannot
be defined if the port field is not defined or
if the port field is defined as a named (string)
port. The endPort must be equal or greater than
port.
format: int32
type: integer
port:
anyOf:
- type: integer
- type: string
description: port represents the port on the given
protocol. This can either be a numerical or named
port on a pod. If this field is not provided,
this matches all port names and numbers. If present,
only traffic on the specified protocol AND port
will be matched.
x-kubernetes-int-or-string: true
protocol:
default: TCP
description: protocol represents the protocol (TCP,
UDP, or SCTP) which traffic must match. If not
specified, this field defaults to TCP.
type: string
type: object
type: array
to:
description: to is a list of destinations for outgoing
traffic of pods selected for this rule. Items in this
list are combined using a logical OR operation. If this
field is empty or missing, this rule matches all destinations
(traffic not restricted by destination). If this field
is present and contains at least one item, this rule
allows traffic only if the traffic matches at least
one item in the to list.
items:
description: NetworkPolicyPeer describes a peer to allow
traffic to/from. Only certain combinations of fields
are allowed
properties:
ipBlock:
description: ipBlock defines policy on a particular
IPBlock. If this field is set then neither of
the other fields can be.
properties:
cidr:
description: cidr is a string representing the
IPBlock Valid examples are "192.168.1.0/24"
or "2001:db8::/64"
type: string
except:
description: except is a slice of CIDRs that
should not be included within an IPBlock Valid
examples are "192.168.1.0/24" or "2001:db8::/64"
Except values will be rejected if they are
outside the cidr range
items:
type: string
type: array
required:
- cidr
type: object
namespaceSelector:
description: "namespaceSelector selects namespaces
using cluster-scoped labels. This field follows
standard label selector semantics; if present
but empty, it selects all namespaces. \n If podSelector
is also set, then the NetworkPolicyPeer as a whole
selects the pods matching podSelector in the namespaces
selected by namespaceSelector. Otherwise it selects
all pods in the namespaces selected by namespaceSelector."
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are
ANDed.
items:
description: A label selector requirement
is a selector that contains values, a key,
and an operator that relates the key and
values.
properties:
key:
description: key is the label key that
the selector applies to.
type: string
operator:
description: operator represents a key's
relationship to a set of values. Valid
operators are In, NotIn, Exists and
DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty.
If the operator is Exists or DoesNotExist,
the values array must be empty. This
array is replaced during a strategic
merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is
"In", and the values array contains only "value".
The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "podSelector is a label selector which
selects pods. This field follows standard label
selector semantics; if present but empty, it selects
all pods. \n If namespaceSelector is also set,
then the NetworkPolicyPeer as a whole selects
the pods matching podSelector in the Namespaces
selected by NamespaceSelector. Otherwise it selects
the pods matching podSelector in the policy's
own namespace."
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are
ANDed.
items:
description: A label selector requirement
is a selector that contains values, a key,
and an operator that relates the key and
values.
properties:
key:
description: key is the label key that
the selector applies to.
type: string
operator:
description: operator represents a key's
relationship to a set of values. Valid
operators are In, NotIn, Exists and
DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty.
If the operator is Exists or DoesNotExist,
the values array must be empty. This
array is replaced during a strategic
merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is
"In", and the values array contains only "value".
The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
type: object
type: array
ingress:
description: ingress is a list of ingress rules to be applied
to the selected pods. Traffic is allowed to a pod if there
are no NetworkPolicies selecting the pod (and cluster policy
otherwise allows the traffic), OR if the traffic source is
the pod's local node, OR if the traffic matches at least one
ingress rule across all of the NetworkPolicy objects whose
podSelector matches the pod. If this field is empty then this
NetworkPolicy does not allow any traffic (and serves solely
to ensure that the pods it selects are isolated by default)
items:
description: NetworkPolicyIngressRule describes a particular
set of traffic that is allowed to the pods matched by a
NetworkPolicySpec's podSelector. The traffic must match
both ports and from.
properties:
from:
description: from is a list of sources which should be
able to access the pods selected for this rule. Items
in this list are combined using a logical OR operation.
If this field is empty or missing, this rule matches
all sources (traffic not restricted by source). If this
field is present and contains at least one item, this
rule allows traffic only if the traffic matches at least
one item in the from list.
items:
description: NetworkPolicyPeer describes a peer to allow
traffic to/from. Only certain combinations of fields
are allowed
properties:
ipBlock:
description: ipBlock defines policy on a particular
IPBlock. If this field is set then neither of
the other fields can be.
properties:
cidr:
description: cidr is a string representing the
IPBlock Valid examples are "192.168.1.0/24"
or "2001:db8::/64"
type: string
except:
description: except is a slice of CIDRs that
should not be included within an IPBlock Valid
examples are "192.168.1.0/24" or "2001:db8::/64"
Except values will be rejected if they are
outside the cidr range
items:
type: string
type: array
required:
- cidr
type: object
namespaceSelector:
description: "namespaceSelector selects namespaces
using cluster-scoped labels. This field follows
standard label selector semantics; if present
but empty, it selects all namespaces. \n If podSelector
is also set, then the NetworkPolicyPeer as a whole
selects the pods matching podSelector in the namespaces
selected by namespaceSelector. Otherwise it selects
all pods in the namespaces selected by namespaceSelector."
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are
ANDed.
items:
description: A label selector requirement
is a selector that contains values, a key,
and an operator that relates the key and
values.
properties:
key:
description: key is the label key that
the selector applies to.
type: string
operator:
description: operator represents a key's
relationship to a set of values. Valid
operators are In, NotIn, Exists and
DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty.
If the operator is Exists or DoesNotExist,
the values array must be empty. This
array is replaced during a strategic
merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is
"In", and the values array contains only "value".
The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "podSelector is a label selector which
selects pods. This field follows standard label
selector semantics; if present but empty, it selects
all pods. \n If namespaceSelector is also set,
then the NetworkPolicyPeer as a whole selects
the pods matching podSelector in the Namespaces
selected by NamespaceSelector. Otherwise it selects
the pods matching podSelector in the policy's
own namespace."
properties:
matchExpressions:
description: matchExpressions is a list of label
selector requirements. The requirements are
ANDed.
items:
description: A label selector requirement
is a selector that contains values, a key,
and an operator that relates the key and
values.
properties:
key:
description: key is the label key that
the selector applies to.
type: string
operator:
description: operator represents a key's
relationship to a set of values. Valid
operators are In, NotIn, Exists and
DoesNotExist.
type: string
values:
description: values is an array of string
values. If the operator is In or NotIn,
the values array must be non-empty.
If the operator is Exists or DoesNotExist,
the values array must be empty. This
array is replaced during a strategic
merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value}
pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions,
whose key field is "key", the operator is
"In", and the values array contains only "value".
The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
ports:
description: ports is a list of ports which should be
made accessible on the pods selected for this rule.
Each item in this list is combined using a logical OR.
If this field is empty or missing, this rule matches
all ports (traffic not restricted by port). If this
field is present and contains at least one item, then
this rule allows traffic only if the traffic matches
at least one port in the list.
items:
description: NetworkPolicyPort describes a port to allow
traffic on
properties:
endPort:
description: endPort indicates that the range of
ports from port to endPort if set, inclusive,
should be allowed by the policy. This field cannot
be defined if the port field is not defined or
if the port field is defined as a named (string)
port. The endPort must be equal or greater than
port.
format: int32
type: integer
port:
anyOf:
- type: integer
- type: string
description: port represents the port on the given
protocol. This can either be a numerical or named
port on a pod. If this field is not provided,
this matches all port names and numbers. If present,
only traffic on the specified protocol AND port
will be matched.
x-kubernetes-int-or-string: true
protocol:
default: TCP
description: protocol represents the protocol (TCP,
UDP, or SCTP) which traffic must match. If not
specified, this field defaults to TCP.
type: string
type: object
type: array
type: object
type: array
podSelector:
description: podSelector selects the pods to which this NetworkPolicy
object applies. The array of ingress rules is applied to any
pods selected by this field. Multiple network policies can
select the same set of pods. In this case, the ingress rules
for each are combined additively. This field is NOT optional
and follows standard label selector semantics. An empty podSelector
matches all pods in this namespace.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector
that contains values, a key, and an operator that relates
the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: operator represents a key's relationship
to a set of values. Valid operators are In, NotIn,
Exists and DoesNotExist.
type: string
values:
description: values is an array of string values.
If the operator is In or NotIn, the values array
must be non-empty. If the operator is Exists or
DoesNotExist, the values array must be empty. This
array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs.
A single {key,value} in the matchLabels map is equivalent
to an element of matchExpressions, whose key field is
"key", the operator is "In", and the values array contains
only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
policyTypes:
description: policyTypes is a list of rule types that the NetworkPolicy
relates to. Valid options are ["Ingress"], ["Egress"], or
["Ingress", "Egress"]. If this field is not specified, it
will default based on the existence of ingress or egress rules;
policies that contain an egress section are assumed to affect
egress, and all policies (whether or not they contain an ingress
section) are assumed to affect ingress. If you want to write
an egress-only policy, you must explicitly specify policyTypes
[ "Egress" ]. Likewise, if you want to write a policy that
specifies that no egress is allowed, you must specify a policyTypes
value that include "Egress" (since such a policy would not
include an egress section and would otherwise default to just
[ "Ingress" ]). This field is beta-level in 1.8
items:
description: PolicyType string describes the NetworkPolicy
type This type is beta-level in 1.8
type: string
type: array
required:
- podSelector
type: object
type: array
nodeSelector:
additionalProperties:
type: string
type: object
owner:
description: OwnerSpec defines tenant owner name and kind.
properties:
kind:
enum:
- User
- Group
type: string
name:
type: string
required:
- kind
- name
type: object
resourceQuotas:
items:
description: ResourceQuotaSpec defines the desired hard limits to
enforce for Quota.
properties:
hard:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: 'hard is the set of desired hard limits for each
named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/'
type: object
scopeSelector:
description: scopeSelector is also a collection of filters like
scopes that must match each object tracked by a quota but
expressed using ScopeSelectorOperator in combination with
possible values. For a resource to match, both scopes AND
scopeSelector (if specified in spec), must be matched.
properties:
matchExpressions:
description: A list of scope selector requirements by scope
of the resources.
items:
description: A scoped-resource selector requirement is
a selector that contains values, a scope name, and an
operator that relates the scope name and values.
properties:
operator:
description: Represents a scope's relationship to
a set of values. Valid operators are In, NotIn,
Exists, DoesNotExist.
type: string
scopeName:
description: The name of the scope that the selector
applies to.
type: string
values:
description: An array of string values. If the operator
is In or NotIn, the values array must be non-empty.
If the operator is Exists or DoesNotExist, the values
array must be empty. This array is replaced during
a strategic merge patch.
items:
type: string
type: array
required:
- operator
- scopeName
type: object
type: array
type: object
x-kubernetes-map-type: atomic
scopes:
description: A collection of filters that must match each object
tracked by a quota. If not specified, the quota matches all
objects.
items:
description: A ResourceQuotaScope defines a filter that must
match each object tracked by a quota
type: string
type: array
type: object
type: array
servicesMetadata:
properties:
additionalAnnotations:
additionalProperties:
type: string
type: object
additionalLabels:
additionalProperties:
type: string
type: object
type: object
storageClasses:
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
required:
- owner
type: object
status:
description: TenantStatus defines the observed state of Tenant.
properties:
namespaces:
items:
type: string
type: array
size:
type: integer
required:
- size
type: object
type: object
served: true
storage: false
subresources:
status: {}
- additionalPrinterColumns:
- description: The actual state of the Tenant
jsonPath: .status.state
@@ -1873,6 +1019,28 @@ spec:
required:
- allowed
type: object
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot
set for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set
for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
type: object
storageClasses:
description: Specifies the allowed StorageClasses assigned to the
@@ -2859,6 +2027,24 @@ spec:
- name
type: object
type: array
podOptions:
description: Specifies options for the Pods deployed in the Tenant
namespaces, such as additional metadata.
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule
operator places on any Pod resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
type: object
preventDeletion:
description: Prevent accidental deletion of the Tenant. When enabled,
the deletion request will be declined.
@@ -3107,6 +2293,28 @@ spec:
required:
- allowed
type: object
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot
set for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set
for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
type: object
storageClasses:
description: Specifies the allowed StorageClasses assigned to the

View File

@@ -13,6 +13,5 @@ spec:
name: webhook-service
path: /convert
conversionReviewVersions:
- v1alpha1
- v1beta1
- v1beta2

View File

@@ -13,6 +13,5 @@ spec:
name: webhook-service
path: /convert
conversionReviewVersions:
- v1alpha1
- v1beta1
- v1beta2

View File

@@ -21,7 +21,6 @@ spec:
namespace: capsule-system
path: /convert
conversionReviewVersions:
- v1alpha1
- v1beta1
- v1beta2
group: capsule.clastix.io
@@ -32,40 +31,6 @@ spec:
singular: capsuleconfiguration
scope: Cluster
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: CapsuleConfiguration is the Schema for the Capsule configuration API.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: CapsuleConfigurationSpec defines the Capsule configuration.
properties:
forceTenantPrefix:
default: false
description: Enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash. This is useful to avoid Namespace name collision in a public CaaS environment.
type: boolean
protectedNamespaceRegex:
description: Disallow creation of namespaces, whose name matches this regexp
type: string
userGroups:
default:
- capsule.clastix.io
description: Names of the groups for Capsule users.
items:
type: string
type: array
type: object
type: object
served: true
storage: false
- name: v1beta2
schema:
openAPIV3Schema:
@@ -579,7 +544,6 @@ spec:
namespace: capsule-system
path: /convert
conversionReviewVersions:
- v1alpha1
- v1beta1
- v1beta2
group: capsule.clastix.io
@@ -592,571 +556,6 @@ spec:
singular: tenant
scope: Cluster
versions:
- additionalPrinterColumns:
- description: The max amount of Namespaces can be created
jsonPath: .spec.namespaceQuota
name: Namespace quota
type: integer
- description: The total amount of Namespaces in use
jsonPath: .status.size
name: Namespace count
type: integer
- description: The assigned Tenant owner
jsonPath: .spec.owner.name
name: Owner name
type: string
- description: The assigned Tenant owner kind
jsonPath: .spec.owner.kind
name: Owner kind
type: string
- description: Node Selector applied to Pods
jsonPath: .spec.nodeSelector
name: Node selector
type: string
- description: Age
jsonPath: .metadata.creationTimestamp
name: Age
type: date
deprecated: true
deprecationWarning: This version is going to be dropped in the upcoming version of Capsule; please, migrate to v1beta2 version.
name: v1alpha1
schema:
openAPIV3Schema:
description: Tenant is the Schema for the tenants API.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: TenantSpec defines the desired state of Tenant.
properties:
additionalRoleBindings:
items:
properties:
clusterRoleName:
type: string
subjects:
description: kubebuilder:validation:Minimum=1
items:
description: Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, or a value for non-objects such as user and group names.
properties:
apiGroup:
description: APIGroup holds the API group of the referenced subject. Defaults to "" for ServiceAccount subjects. Defaults to "rbac.authorization.k8s.io" for User and Group subjects.
type: string
kind:
description: Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". If the Authorizer does not recognized the kind value, the Authorizer should report an error.
type: string
name:
description: Name of the object being referenced.
type: string
namespace:
description: Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty the Authorizer should report an error.
type: string
required:
- kind
- name
type: object
x-kubernetes-map-type: atomic
type: array
required:
- clusterRoleName
- subjects
type: object
type: array
containerRegistries:
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
externalServiceIPs:
properties:
allowed:
items:
pattern: ^([0-9]{1,3}.){3}[0-9]{1,3}(/([0-9]|[1-2][0-9]|3[0-2]))?$
type: string
type: array
required:
- allowed
type: object
ingressClasses:
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
ingressHostnames:
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
limitRanges:
items:
description: LimitRangeSpec defines a min/max usage limit for resources that match on kind.
properties:
limits:
description: Limits is the list of LimitRangeItem objects that are enforced.
items:
description: LimitRangeItem defines a min/max usage limit for any resource that matches on kind.
properties:
default:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Default resource requirement limit value by resource name if resource limit is omitted.
type: object
defaultRequest:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.
type: object
max:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Max usage constraints on this kind by resource name.
type: object
maxLimitRequestRatio:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource.
type: object
min:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: Min usage constraints on this kind by resource name.
type: object
type:
description: Type of resource that this limit applies to.
type: string
required:
- type
type: object
type: array
required:
- limits
type: object
type: array
namespaceQuota:
format: int32
minimum: 1
type: integer
namespacesMetadata:
properties:
additionalAnnotations:
additionalProperties:
type: string
type: object
additionalLabels:
additionalProperties:
type: string
type: object
type: object
networkPolicies:
items:
description: NetworkPolicySpec provides the specification of a NetworkPolicy
properties:
egress:
description: egress is a list of egress rules to be applied to the selected pods. Outgoing traffic is allowed if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic matches at least one egress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy limits all outgoing traffic (and serves solely to ensure that the pods it selects are isolated by default). This field is beta-level in 1.8
items:
description: NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8
properties:
ports:
description: ports is a list of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
items:
description: NetworkPolicyPort describes a port to allow traffic on
properties:
endPort:
description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.
format: int32
type: integer
port:
anyOf:
- type: integer
- type: string
description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
x-kubernetes-int-or-string: true
protocol:
default: TCP
description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
type: string
type: object
type: array
to:
description: to is a list of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.
items:
description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
properties:
ipBlock:
description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
properties:
cidr:
description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
type: string
except:
description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
items:
type: string
type: array
required:
- cidr
type: object
namespaceSelector:
description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
type: object
type: array
ingress:
description: ingress is a list of ingress rules to be applied to the selected pods. Traffic is allowed to a pod if there are no NetworkPolicies selecting the pod (and cluster policy otherwise allows the traffic), OR if the traffic source is the pod's local node, OR if the traffic matches at least one ingress rule across all of the NetworkPolicy objects whose podSelector matches the pod. If this field is empty then this NetworkPolicy does not allow any traffic (and serves solely to ensure that the pods it selects are isolated by default)
items:
description: NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from.
properties:
from:
description: from is a list of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.
items:
description: NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed
properties:
ipBlock:
description: ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be.
properties:
cidr:
description: cidr is a string representing the IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64"
type: string
except:
description: except is a slice of CIDRs that should not be included within an IPBlock Valid examples are "192.168.1.0/24" or "2001:db8::/64" Except values will be rejected if they are outside the cidr range
items:
type: string
type: array
required:
- cidr
type: object
namespaceSelector:
description: "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces. \n If podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
podSelector:
description: "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods. \n If namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace."
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: array
ports:
description: ports is a list of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.
items:
description: NetworkPolicyPort describes a port to allow traffic on
properties:
endPort:
description: endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.
format: int32
type: integer
port:
anyOf:
- type: integer
- type: string
description: port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched.
x-kubernetes-int-or-string: true
protocol:
default: TCP
description: protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.
type: string
type: object
type: array
type: object
type: array
podSelector:
description: podSelector selects the pods to which this NetworkPolicy object applies. The array of ingress rules is applied to any pods selected by this field. Multiple network policies can select the same set of pods. In this case, the ingress rules for each are combined additively. This field is NOT optional and follows standard label selector semantics. An empty podSelector matches all pods in this namespace.
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
items:
description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
properties:
key:
description: key is the label key that the selector applies to.
type: string
operator:
description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
policyTypes:
description: policyTypes is a list of rule types that the NetworkPolicy relates to. Valid options are ["Ingress"], ["Egress"], or ["Ingress", "Egress"]. If this field is not specified, it will default based on the existence of ingress or egress rules; policies that contain an egress section are assumed to affect egress, and all policies (whether or not they contain an ingress section) are assumed to affect ingress. If you want to write an egress-only policy, you must explicitly specify policyTypes [ "Egress" ]. Likewise, if you want to write a policy that specifies that no egress is allowed, you must specify a policyTypes value that include "Egress" (since such a policy would not include an egress section and would otherwise default to just [ "Ingress" ]). This field is beta-level in 1.8
items:
description: PolicyType string describes the NetworkPolicy type This type is beta-level in 1.8
type: string
type: array
required:
- podSelector
type: object
type: array
nodeSelector:
additionalProperties:
type: string
type: object
owner:
description: OwnerSpec defines tenant owner name and kind.
properties:
kind:
enum:
- User
- Group
type: string
name:
type: string
required:
- kind
- name
type: object
resourceQuotas:
items:
description: ResourceQuotaSpec defines the desired hard limits to enforce for Quota.
properties:
hard:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: 'hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/'
type: object
scopeSelector:
description: scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.
properties:
matchExpressions:
description: A list of scope selector requirements by scope of the resources.
items:
description: A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.
properties:
operator:
description: Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.
type: string
scopeName:
description: The name of the scope that the selector applies to.
type: string
values:
description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
items:
type: string
type: array
required:
- operator
- scopeName
type: object
type: array
type: object
x-kubernetes-map-type: atomic
scopes:
description: A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.
items:
description: A ResourceQuotaScope defines a filter that must match each object tracked by a quota
type: string
type: array
type: object
type: array
servicesMetadata:
properties:
additionalAnnotations:
additionalProperties:
type: string
type: object
additionalLabels:
additionalProperties:
type: string
type: object
type: object
storageClasses:
properties:
allowed:
items:
type: string
type: array
allowedRegex:
type: string
type: object
required:
- owner
type: object
status:
description: TenantStatus defines the observed state of Tenant.
properties:
namespaces:
items:
type: string
type: array
size:
type: integer
required:
- size
type: object
type: object
served: true
storage: false
subresources:
status: {}
- additionalPrinterColumns:
- description: The actual state of the Tenant
jsonPath: .status.state
@@ -1799,6 +1198,26 @@ spec:
required:
- allowed
type: object
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot set for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
type: object
storageClasses:
description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
@@ -2437,6 +1856,22 @@ spec:
- name
type: object
type: array
podOptions:
description: Specifies options for the Pods deployed in the Tenant namespaces, such as additional metadata.
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Pod resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
type: object
preventDeletion:
description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
type: boolean
@@ -2616,6 +2051,26 @@ spec:
required:
- allowed
type: object
forbiddenAnnotations:
description: Define the annotations that a Tenant Owner cannot set for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
forbiddenLabels:
description: Define the labels that a Tenant Owner cannot set for their Service resources.
properties:
denied:
items:
type: string
type: array
deniedRegex:
type: string
type: object
type: object
storageClasses:
description: Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class. Optional.

View File

@@ -1,9 +0,0 @@
---
apiVersion: capsule.clastix.io/v1alpha1
kind: CapsuleConfiguration
metadata:
name: default
spec:
userGroups: ["capsule.clastix.io"]
forceTenantPrefix: false
protectedNamespaceRegex: ""

View File

@@ -1,101 +0,0 @@
---
apiVersion: capsule.clastix.io/v1alpha1
kind: Tenant
metadata:
name: oil
spec:
ingressHostnames:
allowed:
- my.oil.acmecorp.com
- my.gas.acmecorp.com
allowedRegex: "^.*acmecorp.com$"
ingressClasses:
allowed:
- default
allowedRegex: ""
limitRanges:
-
limits:
-
max:
cpu: "1"
memory: 1Gi
min:
cpu: 50m
memory: 5Mi
type: Pod
-
default:
cpu: 200m
memory: 100Mi
defaultRequest:
cpu: 100m
memory: 10Mi
max:
cpu: "1"
memory: 1Gi
min:
cpu: 50m
memory: 5Mi
type: Container
-
max:
storage: 10Gi
min:
storage: 1Gi
type: PersistentVolumeClaim
namespaceQuota: 3
networkPolicies:
-
egress:
-
to:
-
ipBlock:
cidr: 0.0.0.0/0
except:
- 192.168.0.0/12
ingress:
-
from:
-
namespaceSelector:
matchLabels:
capsule.clastix.io/tenant: oil
-
podSelector: {}
-
ipBlock:
cidr: 192.168.0.0/12
podSelector: {}
policyTypes:
- Ingress
- Egress
nodeSelector:
kubernetes.io/os: linux
owner:
kind: User
name: alice
resourceQuotas:
-
hard:
limits.cpu: "8"
limits.memory: 16Gi
requests.cpu: "8"
requests.memory: 16Gi
scopes:
- NotTerminating
-
hard:
pods: "10"
-
hard:
requests.storage: 100Gi
storageClasses:
allowed:
- default
allowedRegex: ""
containerRegistries:
allowed:
- docker.io
allowedRegex: ""

View File

@@ -1,5 +1,3 @@
## This file is auto-generated, do not modify ##
resources:
- capsule_v1alpha1_capsuleconfiguration.yaml
- capsule_v1alpha1_tenant.yaml
- capsule_v1beta1_tenant.yaml

30
controllers/pod/errors.go Normal file
View File

@@ -0,0 +1,30 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package pod
import "fmt"
type NonTenantObjectError struct {
objectName string
}
func NewNonTenantObject(objectName string) error {
return &NonTenantObjectError{objectName: objectName}
}
func (n NonTenantObjectError) Error() string {
return fmt.Sprintf("Skipping labels sync for %s as it doesn't belong to tenant", n.objectName)
}
type NoPodMetadataError struct {
objectName string
}
func NewNoPodMetadata(objectName string) error {
return &NoPodMetadataError{objectName: objectName}
}
func (n NoPodMetadataError) Error() string {
return fmt.Sprintf("Skipping labels sync for %s because no AdditionalLabels or AdditionalAnnotations presents in Tenant spec", n.objectName)
}

130
controllers/pod/metadata.go Normal file
View File

@@ -0,0 +1,130 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package pod
import (
"context"
"fmt"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/pkg/utils"
)
type MetadataReconciler struct {
Client client.Client
}
func (m *MetadataReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
var pod corev1.Pod
logger := log.FromContext(ctx)
tenant, err := m.getTenant(ctx, request.NamespacedName, m.Client)
if err != nil {
noTenantObjError := &NonTenantObjectError{}
noPodMetaError := &NoPodMetadataError{}
if errors.As(err, &noTenantObjError) || errors.As(err, &noPodMetaError) {
return reconcile.Result{}, nil
}
logger.Error(err, fmt.Sprintf("Cannot get tenant corev1.Pod %s/%s", request.Namespace, request.Name))
return reconcile.Result{}, err
}
err = m.Client.Get(ctx, request.NamespacedName, &pod)
if err != nil {
if apierr.IsNotFound(err) {
return reconcile.Result{}, nil
}
return reconcile.Result{}, err
}
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, &pod, func() (err error) {
pod.SetLabels(m.sync(pod.GetLabels(), tenant.Spec.PodOptions.AdditionalMetadata.Labels))
pod.SetAnnotations(m.sync(pod.GetAnnotations(), tenant.Spec.PodOptions.AdditionalMetadata.Annotations))
return nil
})
return reconcile.Result{}, err
}
func (m *MetadataReconciler) getTenant(ctx context.Context, namespacedName types.NamespacedName, client client.Client) (*capsulev1beta2.Tenant, error) {
ns := &corev1.Namespace{}
tenant := &capsulev1beta2.Tenant{}
if err := client.Get(ctx, types.NamespacedName{Name: namespacedName.Namespace}, ns); err != nil {
return nil, err
}
capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
if _, ok := ns.GetLabels()[capsuleLabel]; !ok {
return nil, NewNonTenantObject(namespacedName.Name)
}
if err := client.Get(ctx, types.NamespacedName{Name: ns.Labels[capsuleLabel]}, tenant); err != nil {
return nil, err
}
if tenant.Spec.PodOptions == nil || tenant.Spec.PodOptions.AdditionalMetadata == nil {
return nil, NewNoPodMetadata(namespacedName.Name)
}
return tenant, nil
}
func (m *MetadataReconciler) sync(available map[string]string, tenantSpec map[string]string) map[string]string {
if tenantSpec != nil {
if available == nil {
return tenantSpec
}
for key, value := range tenantSpec {
if available[key] != value {
available[key] = value
}
}
}
return available
}
func (m *MetadataReconciler) forOptionPerInstanceName(ctx context.Context) builder.ForOption {
return builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
return m.isNamespaceInTenant(ctx, object.GetNamespace())
}))
}
func (m *MetadataReconciler) isNamespaceInTenant(ctx context.Context, namespace string) bool {
tl := &capsulev1beta2.TenantList{}
if err := m.Client.List(ctx, tl, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", namespace),
}); err != nil {
return false
}
return len(tl.Items) > 0
}
func (m *MetadataReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Pod{}, m.forOptionPerInstanceName(ctx)).
Complete(m)
}

View File

@@ -124,6 +124,7 @@ func (r *Processor) HandleSection(ctx context.Context, tnt capsulev1beta2.Tenant
objLabels[Label] = fmt.Sprintf("%d", resourceIndex)
objLabels[tenantLabel] = tnt.GetName()
// processed will contain the sets of resources replicated, both for the raw and the Namespaced ones:
// these are required to perform a final pruning once the replication has been occurred.
processed := sets.NewString()
@@ -265,8 +266,22 @@ func (r *Processor) createOrUpdate(ctx context.Context, obj *unstructured.Unstru
rv := actual.GetResourceVersion()
actual.SetUnstructuredContent(desired.Object)
actual.SetLabels(labels)
actual.SetAnnotations(annotations)
combinedLabels := obj.GetLabels()
if combinedLabels == nil {
combinedLabels = make(map[string]string)
}
for key, value := range labels {
combinedLabels[key] = value
}
actual.SetLabels(combinedLabels)
combinedAnnotations := obj.GetAnnotations()
if combinedAnnotations == nil {
combinedAnnotations = make(map[string]string)
}
for key, value := range annotations {
combinedAnnotations[key] = value
}
actual.SetAnnotations(combinedAnnotations)
actual.SetResourceVersion(rv)
actual.SetUID(UID)

View File

@@ -60,6 +60,13 @@ func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ct
return
}
// Ensuring Metadata
if err = r.ensureMetadata(ctx, instance); err != nil {
r.Log.Error(err, "Cannot ensure metadata")
return
}
// Ensuring ResourceQuota
r.Log.Info("Ensuring limit resources count is updated")

View File

@@ -0,0 +1,23 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package tenant
import (
"context"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
capsuleapi "github.com/projectcapsule/capsule/pkg/api"
)
// Sets a label on the Tenant object with it's name.
func (r *Manager) ensureMetadata(ctx context.Context, tnt *capsulev1beta2.Tenant) (err error) {
// Assign Labels
if tnt.Labels == nil {
tnt.Labels = make(map[string]string)
}
tnt.Labels[capsuleapi.TenantNameLabel] = tnt.Name
return r.Client.Update(ctx, tnt)
}

View File

@@ -57,8 +57,8 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t
res, conflictErr = controllerutil.CreateOrUpdate(ctx, r.Client, ns, func() error {
annotations := make(map[string]string)
labels := map[string]string{
"name": namespace,
capsuleLabel: tnt.GetName(),
"kubernetes.io/metadata.name": namespace,
capsuleLabel: tnt.GetName(),
}
if tnt.Spec.NamespaceOptions != nil && tnt.Spec.NamespaceOptions.AdditionalMetadata != nil {

View File

@@ -7,6 +7,7 @@ import (
"context"
"fmt"
"strconv"
"strings"
"golang.org/x/sync/errgroup"
corev1 "k8s.io/api/core/v1"
@@ -15,6 +16,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -52,9 +54,12 @@ func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta2
group := new(errgroup.Group)
for i, q := range tenant.Spec.ResourceQuota.Items {
index := i
index, resourceQuota := i, q
resourceQuota := q
toKeep := sets.New[corev1.ResourceName]()
for k := range resourceQuota.Hard {
toKeep.Insert(k)
}
group.Go(func() (scopeErr error) {
// Calculating the Resource Budget at Tenant scope just if this is put in place.
@@ -120,9 +125,15 @@ func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta2
list.Items[item].Spec.Hard = map[corev1.ResourceName]resource.Quantity{}
}
list.Items[item].Spec.Hard[name] = resourceQuota.Hard[name]
for k := range list.Items[item].Spec.Hard {
if !toKeep.Has(k) {
delete(list.Items[item].Spec.Hard, k)
}
}
}
}
if scopeErr = r.resourceQuotasUpdate(ctx, name, quantity, resourceQuota.Hard[name], list.Items...); scopeErr != nil {
if scopeErr = r.resourceQuotasUpdate(ctx, name, quantity, toKeep, resourceQuota.Hard[name], list.Items...); scopeErr != nil {
r.Log.Error(scopeErr, "cannot proceed with outer ResourceQuota")
return
@@ -217,9 +228,21 @@ func (r *Manager) syncResourceQuota(ctx context.Context, tenant *capsulev1beta2.
// Serial ResourceQuota processing is expensive: using Go routines we can speed it up.
// In case of multiple errors these are logged properly, returning a generic error since we have to repush back the
// reconciliation loop.
func (r *Manager) resourceQuotasUpdate(ctx context.Context, resourceName corev1.ResourceName, actual, limit resource.Quantity, list ...corev1.ResourceQuota) (err error) {
func (r *Manager) resourceQuotasUpdate(ctx context.Context, resourceName corev1.ResourceName, actual resource.Quantity, toKeep sets.Set[corev1.ResourceName], limit resource.Quantity, list ...corev1.ResourceQuota) (err error) {
group := new(errgroup.Group)
annotationsToKeep := sets.New[string]()
for _, item := range toKeep.UnsortedList() {
if v, vErr := capsulev1beta2.UsedQuotaFor(item); vErr == nil {
annotationsToKeep.Insert(v)
}
if v, vErr := capsulev1beta2.HardQuotaFor(item); vErr == nil {
annotationsToKeep.Insert(v)
}
}
for _, item := range list {
rq := item
@@ -236,6 +259,16 @@ func (r *Manager) resourceQuotasUpdate(ctx context.Context, resourceName corev1.
if found.Annotations == nil {
found.Annotations = make(map[string]string)
}
// Pruning the Capsule quota annotations:
// if the ResourceQuota is updated by removing some objects,
// we could still have left-overs which could be misleading.
// This will not lead to a reconciliation loop since the whole code is idempotent.
for k := range found.Annotations {
if (strings.HasPrefix(k, capsulev1beta2.HardCapsuleQuotaAnnotation) || strings.HasPrefix(k, capsulev1beta2.UsedCapsuleQuotaAnnotation)) && !annotationsToKeep.Has(k) {
delete(found.Annotations, k)
}
}
found.Labels = rq.Labels
if actualKey, keyErr := capsulev1beta2.UsedQuotaFor(resourceName); keyErr == nil {
found.Annotations[actualKey] = actual.String()

View File

@@ -110,7 +110,17 @@ func (r *Manager) syncCustomResourceQuotaUsages(ctx context.Context, tenant *cap
usedMap[key] = 0
}
usedMap[key] += len(list.Items)
var used int
for _, k := range list.Items {
if k.GetDeletionTimestamp() != nil {
continue
}
used++
}
usedMap[key] += used
}
return

View File

@@ -239,7 +239,7 @@ func (r *Reconciler) updateTenantCustomResourceDefinition(ctx context.Context, n
},
CABundle: caBundle,
},
ConversionReviewVersions: []string{"v1alpha1", "v1beta1", "v1beta2"},
ConversionReviewVersions: []string{"v1beta1", "v1beta2"},
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,14 @@ module.exports = {
favicon: './src/assets/favicon.png',
},
plugins: [
{
use: 'gridsome-plugin-gtag',
options: {
config: {
id: 'G-ZL1M3TWPY2',
},
},
},
{
use: "gridsome-plugin-tailwindcss",

48
docs/package-lock.json generated
View File

@@ -1546,9 +1546,9 @@
"integrity": "sha512-atlRPM4gM/BABQ2MiXm38veMVL+kz6vFAj1hvqC1wDxWNrnr3t58PozLSecgLBrKNGISunQl2SxxIJcYV3tO2w=="
},
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.1.tgz",
"integrity": "sha512-3YmXzzPAdOTVljVMkTMBdBEvlOLg2cDQaDhnnhT3nT9uDbnJzjWhKlzb+desT12Y7tGqaN6d+AbozcKzyL36Ng==",
"dev": true
},
"@types/prop-types": {
@@ -3081,25 +3081,30 @@
}
},
"browserify-sign": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
"integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
"integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
"requires": {
"bn.js": "^5.1.1",
"browserify-rsa": "^4.0.1",
"bn.js": "^5.2.1",
"browserify-rsa": "^4.1.0",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
"elliptic": "^6.5.3",
"elliptic": "^6.5.4",
"inherits": "^2.0.4",
"parse-asn1": "^5.1.5",
"readable-stream": "^3.6.0",
"safe-buffer": "^5.2.0"
"parse-asn1": "^5.1.6",
"readable-stream": "^3.6.2",
"safe-buffer": "^5.2.1"
},
"dependencies": {
"bn.js": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -6469,6 +6474,14 @@
}
}
},
"gridsome-plugin-gtag": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/gridsome-plugin-gtag/-/gridsome-plugin-gtag-0.1.10.tgz",
"integrity": "sha512-/T1snhRRO4akDx6Hi4rYpMtXo8jo8uEf0brHbOI6bbb+vg7XTFucEAOKklUdKFTLKFAwH1VIQ+AZajU88U6iHg==",
"requires": {
"vue-gtag": "^1.10.0"
}
},
"gridsome-plugin-tailwindcss": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/gridsome-plugin-tailwindcss/-/gridsome-plugin-tailwindcss-4.1.1.tgz",
@@ -14111,6 +14124,11 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
},
"vue-gtag": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/vue-gtag/-/vue-gtag-1.16.1.tgz",
"integrity": "sha512-5vs0pSGxdqrfXqN1Qwt0ZFXG0iTYjRMu/saddc7QIC5yp+DKgjWQRpGYVa7Pq+KbThxwzzMfo0sGi7ISa6NowA=="
},
"vue-hot-reload-api": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",

View File

@@ -11,7 +11,8 @@
"@gridsome/source-filesystem": "^0.6.2",
"@gridsome/transformer-remark": "^0.6.4",
"fuse.js": "^6.4.6",
"gridsome": "^0.7.0"
"gridsome": "^0.7.0",
"gridsome-plugin-gtag": "^0.1.10"
},
"devDependencies": {
"autoprefixer": "^9.8.8",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -9,9 +9,13 @@
"
>
<div class="space-y-2">
<h1 class="font-bold text-2xl inline-block">Clastix Labs</h1>
<icon-cncf class="h-50" />
<small class="block">
© {{ new Date().getFullYear() }} All rights reserved
© {{ new Date().getFullYear() }} All rights reserved<br />
<a href="https://www.linuxfoundation.org/legal/trademark-usage" target="_blank" rel="noopener noreferrer">
Cloud Native Computing Foundation is part of the Linux Foundation.<br />
The Linux Foundation has registered trademarks and uses trademarks.
</a>
</small>
</div>
<ul class="lg:flex lg:items-center lg:space-x-5 space-y-2 lg:space-y-0">
@@ -48,12 +52,14 @@
</template>
<script>
import IconCncf from "~/assets/icon/cncf.svg?inline";
import IconGithub from "~/assets/icon/github.svg?inline";
import IconTwitter from "~/assets/icon/twitter.svg?inline";
import IconLinkedin from "~/assets/icon/linkedin.svg?inline";
export default {
components: {
IconCncf,
IconGithub,
IconLinkedin,
IconTwitter,

View File

@@ -9,4 +9,9 @@ export default function (Vue, { router, head, isClient }) {
// Set default layout as a global component
Vue.component('LayoutDefault', DefaultLayout)
Vue.component('LayoutMarkdown', MarkdownLayout)
head.script.push({
src: 'https://www.googletagmanager.com/gtag/js?id=G-ZL1M3TWPY2',
async: true
})
}

View File

@@ -2068,11 +2068,16 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
bn.js@^5.0.0, bn.js@^5.1.1:
bn.js@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
bn.js@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70"
integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==
body-parser@1.20.1:
version "1.20.1"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
@@ -2177,7 +2182,7 @@ browserify-des@^1.0.0:
inherits "^2.0.1"
safe-buffer "^5.1.2"
browserify-rsa@^4.0.0, browserify-rsa@^4.0.1:
browserify-rsa@^4.0.0, browserify-rsa@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d"
integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==
@@ -2186,19 +2191,19 @@ browserify-rsa@^4.0.0, browserify-rsa@^4.0.1:
randombytes "^2.0.1"
browserify-sign@^4.0.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3"
integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==
version "4.2.2"
resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.2.tgz#e78d4b69816d6e3dd1c747e64e9947f9ad79bc7e"
integrity sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==
dependencies:
bn.js "^5.1.1"
browserify-rsa "^4.0.1"
bn.js "^5.2.1"
browserify-rsa "^4.1.0"
create-hash "^1.2.0"
create-hmac "^1.1.7"
elliptic "^6.5.3"
elliptic "^6.5.4"
inherits "^2.0.4"
parse-asn1 "^5.1.5"
readable-stream "^3.6.0"
safe-buffer "^5.2.0"
parse-asn1 "^5.1.6"
readable-stream "^3.6.2"
safe-buffer "^5.2.1"
browserify-zlib@^0.2.0:
version "0.2.0"
@@ -3651,7 +3656,7 @@ electron-to-chromium@^1.3.867:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.867.tgz#7cb484db4b57c28da0b65c51e434c3a1f3f9aa0d"
integrity sha512-WbTXOv7hsLhjJyl7jBfDkioaY++iVVZomZ4dU6TMe/SzucV6mUAs2VZn/AehBwuZMiNEQDaPuTGn22YK5o+aDw==
elliptic@^6.5.3:
elliptic@^6.5.3, elliptic@^6.5.4:
version "6.5.4"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
@@ -4693,6 +4698,13 @@ gray-matter@^4.0.2:
section-matter "^1.0.0"
strip-bom-string "^1.0.0"
gridsome-plugin-gtag@^0.1.10:
version "0.1.10"
resolved "https://registry.yarnpkg.com/gridsome-plugin-gtag/-/gridsome-plugin-gtag-0.1.10.tgz#99ab1b494a874a996b08ee7073766c8132614d59"
integrity sha512-/T1snhRRO4akDx6Hi4rYpMtXo8jo8uEf0brHbOI6bbb+vg7XTFucEAOKklUdKFTLKFAwH1VIQ+AZajU88U6iHg==
dependencies:
vue-gtag "^1.10.0"
gridsome-plugin-tailwindcss@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/gridsome-plugin-tailwindcss/-/gridsome-plugin-tailwindcss-4.1.1.tgz#fd167f6f4ffec6c2400b5a3011200761b72f4ec4"
@@ -7142,7 +7154,7 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parse-asn1@^5.0.0, parse-asn1@^5.1.5:
parse-asn1@^5.0.0, parse-asn1@^5.1.6:
version "5.1.6"
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==
@@ -8403,6 +8415,15 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@^3.6.2:
version "3.6.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readdirp@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
@@ -8762,7 +8783,7 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -10358,6 +10379,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
vue-gtag@^1.10.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/vue-gtag/-/vue-gtag-1.16.1.tgz#edb2f20ab4f6c4d4d372dfecf8c1fcc8ab890181"
integrity sha512-5vs0pSGxdqrfXqN1Qwt0ZFXG0iTYjRMu/saddc7QIC5yp+DKgjWQRpGYVa7Pq+KbThxwzzMfo0sGi7ISa6NowA==
vue-hot-reload-api@^2.3.0:
version "2.3.4"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"

View File

@@ -249,7 +249,7 @@ var _ = Describe("Creating a GlobalTenantResource object", func() {
gtr.Spec.Resources[0].NamespaceSelector = &metav1.LabelSelector{
MatchLabels: map[string]string{
"name": "solar-three",
"kubernetes.io/metadata.name": "solar-three",
},
}

115
e2e/pod_metadata_test.go Normal file
View File

@@ -0,0 +1,115 @@
//go:build e2e
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"fmt"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/pkg/api"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
var _ = Describe("adding metadata to Pod objects", func() {
tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-metadata",
},
Spec: capsulev1beta2.TenantSpec{
Owners: capsulev1beta2.OwnerListSpec{
{
Name: "gatsby",
Kind: "User",
},
},
PodOptions: &api.PodOptions{
AdditionalMetadata: &api.AdditionalMetadataSpec{
Labels: map[string]string{
"k8s.io/custom-label": "foo",
"clastix.io/custom-label": "bar",
},
Annotations: map[string]string{
"k8s.io/custom-annotation": "bizz",
"clastix.io/custom-annotation": "buzz",
},
},
},
},
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
tnt.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
})
It("should apply them to Pod", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
fmt.Sprint("namespace created")
//TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
fmt.Sprint("tenant contains list namespace")
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-metadata",
Namespace: ns.GetName(),
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "container",
Image: "quay.io/google-containers/pause-amd64:3.0",
ImagePullPolicy: "IfNotPresent",
},
},
RestartPolicy: "Always",
},
}
EventuallyCreation(func() (err error) {
_, err = ownerClient(tnt.Spec.Owners[0]).CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{})
return
}).Should(Succeed())
By("checking additional labels", func() {
Eventually(func() (ok bool) {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: pod.GetName(), Namespace: ns.GetName()}, pod)).Should(Succeed())
for k, v := range tnt.Spec.PodOptions.AdditionalMetadata.Labels {
ok, _ = HaveKeyWithValue(k, v).Match(pod.GetLabels())
if !ok {
return false
}
}
return true
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
})
By("checking additional annotations", func() {
Eventually(func() (ok bool) {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: pod.GetName(), Namespace: ns.GetName()}, pod)).Should(Succeed())
for k, v := range tnt.Spec.PodOptions.AdditionalMetadata.Annotations {
ok, _ = HaveKeyWithValue(k, v).Match(pod.GetAnnotations())
if !ok {
return false
}
}
return true
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
})
})
})

View File

@@ -0,0 +1,229 @@
//go:build e2e
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/pkg/api"
)
var _ = Describe("creating a Service with user-specified labels and annotations", func() {
tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-user-metadata-forbidden",
},
Spec: capsulev1beta2.TenantSpec{
ServiceOptions: &api.ServiceOptions{
ForbiddenLabels: api.ForbiddenListSpec{
Exact: []string{"foo", "bar"},
Regex: "^gatsby-.*$",
},
ForbiddenAnnotations: api.ForbiddenListSpec{
Exact: []string{"foo", "bar"},
Regex: "^gatsby-.*$",
},
},
Owners: capsulev1beta2.OwnerListSpec{
{
Name: "gatsby",
Kind: "User",
},
},
},
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
tnt.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
})
It("should allow", func() {
By("specifying non-forbidden labels", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "non-forbidden-labels",
})
svc.SetLabels(map[string]string{"bim": "baz"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
})
By("specifying non-forbidden annotations", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "non-forbidden-annotations",
})
svc.SetAnnotations(map[string]string{"bim": "baz"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
})
})
It("should fail when creating a Service", func() {
By("specifying forbidden labels using exact match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-labels-exact",
})
svc.SetLabels(map[string]string{"foo": "bar"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
By("specifying forbidden labels using regex match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-labels-regex",
})
svc.SetLabels(map[string]string{"gatsby-foo": "bar"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
By("specifying forbidden annotations using exact match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-annotations-exact",
})
svc.SetAnnotations(map[string]string{"foo": "bar"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
By("specifying forbidden annotations using regex match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-annotations-regex",
})
svc.SetAnnotations(map[string]string{"gatsby-foo": "bar"})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed())
})
})
It("should fail when updating a Service", func() {
cs := ownerClient(tnt.Spec.Owners[0])
By("specifying forbidden labels using exact match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-labels-exact-match",
})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
Consistently(func() error {
svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{})
if err != nil {
return nil
}
svc.SetLabels(map[string]string{"foo": "bar"})
_, err = cs.CoreV1().Services(svc.Namespace).Update(context.Background(), svc, metav1.UpdateOptions{})
return err
}, 10*time.Second, time.Second).ShouldNot(Succeed())
})
By("specifying forbidden labels using regex match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-labels-regex-match",
})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
Consistently(func() error {
svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{})
if err != nil {
return nil
}
svc.SetLabels(map[string]string{"gatsby-foo": "bar"})
_, err = cs.CoreV1().Services(svc.Namespace).Update(context.Background(), svc, metav1.UpdateOptions{})
return err
}, 3*time.Second, time.Second).ShouldNot(Succeed())
})
By("specifying forbidden annotations using exact match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-annotations-exact-match",
})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
Consistently(func() error {
svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{})
if err != nil {
return nil
}
svc.SetAnnotations(map[string]string{"foo": "bar"})
_, err = cs.CoreV1().Services(svc.Namespace).Update(context.Background(), svc, metav1.UpdateOptions{})
return err
}, 10*time.Second, time.Second).ShouldNot(Succeed())
})
By("specifying forbidden annotations using regex match", func() {
ns := NewNamespace("")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
svc := NewService(types.NamespacedName{
Namespace: ns.GetName(),
Name: "forbidden-annotations-regex-match",
})
ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
Consistently(func() error {
svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{})
if err != nil {
return nil
}
svc.SetAnnotations(map[string]string{"gatsby-foo": "bar"})
_, err = cs.CoreV1().Services(svc.Namespace).Update(context.Background(), svc, metav1.UpdateOptions{})
return err
}, 10*time.Second, time.Second).ShouldNot(Succeed())
})
})
})

63
e2e/suite_client_test.go Normal file
View File

@@ -0,0 +1,63 @@
//go:build e2e
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"time"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type e2eClient struct {
client.Client
}
func (e *e2eClient) sleep() {
time.Sleep(250 * time.Millisecond)
}
func (e *e2eClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
defer e.sleep()
return e.Client.Get(ctx, key, obj, opts...)
}
func (e *e2eClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
defer e.sleep()
return e.Client.List(ctx, list, opts...)
}
func (e *e2eClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
defer e.sleep()
return e.Client.Create(ctx, obj, opts...)
}
func (e *e2eClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
defer e.sleep()
return e.Client.Delete(ctx, obj, opts...)
}
func (e *e2eClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
defer e.sleep()
return e.Client.Update(ctx, obj, opts...)
}
func (e *e2eClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
defer e.sleep()
return e.Client.Patch(ctx, obj, patch, opts...)
}
func (e *e2eClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
defer e.sleep()
return e.Client.DeleteAllOf(ctx, obj, opts...)
}

View File

@@ -13,6 +13,7 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/envtest"
@@ -42,9 +43,7 @@ var _ = BeforeSuite(func() {
By("bootstrapping test environment")
testEnv = &envtest.Environment{
UseExistingCluster: func(v bool) *bool {
return &v
}(true),
UseExistingCluster: pointer.Bool(true),
}
var err error
@@ -54,9 +53,11 @@ var _ = BeforeSuite(func() {
Expect(capsulev1beta2.AddToScheme(scheme.Scheme)).NotTo(HaveOccurred())
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
ctrlClient, err := client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8sClient).ToNot(BeNil())
Expect(ctrlClient).ToNot(BeNil())
k8sClient = &e2eClient{Client: ctrlClient}
})
var _ = AfterSuite(func() {
@@ -71,5 +72,6 @@ func ownerClient(owner capsulev1beta2.OwnerSpec) (cs kubernetes.Interface) {
c.Impersonate.UserName = owner.Name
cs, err = kubernetes.NewForConfig(c)
Expect(err).ToNot(HaveOccurred())
return
return cs
}

67
e2e/tenant_metadata.go Normal file
View File

@@ -0,0 +1,67 @@
//go:build e2e
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
)
func getLabels(tnt capsulev1beta2.Tenant) (map[string]string, error) {
current := &capsulev1beta2.Tenant{}
err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, current)
if err != nil {
return nil, err
}
return current.GetLabels(), nil
}
var _ = Describe("adding metadata to a Tenant", func() {
tnt := &capsulev1beta2.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "tenant-metadata",
Labels: map[string]string{
"custom-label": "test",
},
},
Spec: capsulev1beta2.TenantSpec{
Owners: capsulev1beta2.OwnerListSpec{
{
Name: "jim",
Kind: "User",
},
},
},
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
})
It("Should ensure label metadata", func() {
By("Default labels", func() {
currentlabels, _ := getLabels(*tnt)
Expect(currentlabels["kubernetes.io/metadata.name"]).To(Equal("tenant-metadata"))
Expect(currentlabels["custom-label"]).To(Equal("test"))
})
By("Disallow name overwritte", func() {
tnt.Labels["kubernetes.io/metadata.name"] = "evil"
Expect(k8sClient.Update(context.TODO(), tnt)).ShouldNot(Succeed())
})
})
})

View File

@@ -63,6 +63,13 @@ var _ = Describe("Creating a TenantResource object", func() {
Type: corev1.SecretTypeOpaque,
}
testLabels := map[string]string{
"labels.energy.io": "namespaced",
}
testAnnotations := map[string]string{
"annotations.energy.io": "namespaced",
}
tr := &capsulev1beta2.TenantResource{
ObjectMeta: metav1.ObjectMeta{
Name: "replicate-energies",
@@ -101,7 +108,9 @@ var _ = Describe("Creating a TenantResource object", func() {
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "raw-secret-1",
Name: "raw-secret-1",
Labels: testLabels,
Annotations: testAnnotations,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
@@ -119,7 +128,9 @@ var _ = Describe("Creating a TenantResource object", func() {
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "raw-secret-2",
Name: "raw-secret-2",
Labels: testLabels,
Annotations: testAnnotations,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
@@ -137,7 +148,9 @@ var _ = Describe("Creating a TenantResource object", func() {
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "raw-secret-3",
Name: "raw-secret-3",
Labels: testLabels,
Annotations: testAnnotations,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
@@ -249,7 +262,7 @@ var _ = Describe("Creating a TenantResource object", func() {
tr.Spec.Resources[0].NamespaceSelector = &metav1.LabelSelector{
MatchLabels: map[string]string{
"name": "solar-three",
"kubernetes.io/metadata.name": "solar-three",
},
}
@@ -288,11 +301,18 @@ var _ = Describe("Creating a TenantResource object", func() {
_, err := HaveKeyWithValue(k, v).Match(secret.GetLabels())
Expect(err).ToNot(HaveOccurred())
}
for k, v := range testLabels {
_, err := HaveKeyWithValue(k, v).Match(secret.GetLabels())
Expect(err).ToNot(HaveOccurred())
}
for k, v := range tr.Spec.Resources[0].AdditionalMetadata.Annotations {
_, err := HaveKeyWithValue(k, v).Match(secret.GetAnnotations())
Expect(err).ToNot(HaveOccurred())
}
for k, v := range testAnnotations {
_, err := HaveKeyWithValue(k, v).Match(secret.GetAnnotations())
Expect(err).ToNot(HaveOccurred())
}
}
})

View File

@@ -31,6 +31,28 @@ const (
defaultPollInterval = time.Second
)
func NewService(svc types.NamespacedName) *corev1.Service {
return &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: svc.Name,
Namespace: svc.Namespace,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Port: int32(80)},
},
},
}
}
func ServiceCreation(svc *corev1.Service, owner capsulev1beta2.OwnerSpec, timeout time.Duration) AsyncAssertion {
cs := ownerClient(owner)
return Eventually(func() (err error) {
_, err = cs.CoreV1().Services(svc.Namespace).Create(context.TODO(), svc, metav1.CreateOptions{})
return
}, timeout, defaultPollInterval)
}
func NewNamespace(name string) *corev1.Namespace {
if len(name) == 0 {
name = rand.String(10)
@@ -77,8 +99,6 @@ func ModifyCapsuleConfigurationOpts(fn func(configuration *capsulev1beta2.Capsul
fn(config)
Expect(k8sClient.Update(context.Background(), config)).ToNot(HaveOccurred())
time.Sleep(1 * time.Second)
}
func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner capsulev1beta2.OwnerSpec, roles map[string]bool) func() error {

84
go.mod
View File

@@ -1,49 +1,50 @@
module github.com/projectcapsule/capsule
go 1.19
go 1.20
require (
github.com/go-logr/logr v1.2.4
github.com/hashicorp/go-multierror v1.1.0
github.com/onsi/ginkgo/v2 v2.9.5
github.com/onsi/gomega v1.27.7
github.com/go-logr/logr v1.3.0
github.com/hashicorp/go-multierror v1.1.1
github.com/onsi/ginkgo/v2 v2.13.2
github.com/onsi/gomega v1.30.0
github.com/pkg/errors v0.9.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.4
github.com/valyala/fasttemplate v1.2.2
go.uber.org/zap v1.24.0
golang.org/x/sync v0.2.0
k8s.io/api v0.27.2
k8s.io/apiextensions-apiserver v0.27.2
k8s.io/apimachinery v0.27.2
k8s.io/client-go v0.27.2
k8s.io/utils v0.0.0-20230209194617-a36077c30491
sigs.k8s.io/cluster-api v1.4.0-beta.2.0.20230524193452-89a36acc3c3f
sigs.k8s.io/controller-runtime v0.15.0
go.uber.org/automaxprocs v1.5.3
go.uber.org/zap v1.26.0
golang.org/x/sync v0.5.0
k8s.io/api v0.28.4
k8s.io/apiextensions-apiserver v0.28.4
k8s.io/apimachinery v0.28.4
k8s.io/client-go v0.28.4
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2
sigs.k8s.io/cluster-api v1.6.0
sigs.k8s.io/controller-runtime v0.16.3
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // 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 v5.6.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/zapr v1.2.4 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.1 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobuffalo/flect v1.0.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/josharian/intern v1.0.0 // indirect
@@ -54,30 +55,31 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.15.1 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/prometheus/client_golang v1.17.0 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
go.uber.org/goleak v1.3.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/oauth2 v0.14.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/term v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.9.1 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
golang.org/x/tools v0.14.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/component-base v0.27.2 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
k8s.io/component-base v0.28.4 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

289
go.sum
View File

@@ -1,65 +1,44 @@
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=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
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/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
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 v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
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/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
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/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-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0=
github.com/coredns/corefile-migration v1.0.20 h1:MdOkT6F3ehju/n9tgxlGct8XAajOX2vN+wG7To4BWSI=
github.com/coredns/corefile-migration v1.0.21 h1:W/DCETrHDiFo0Wj03EyMkaQ9fwsmSgqTCQDHpceaSsE=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
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.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
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/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc=
github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=
github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8=
github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
@@ -68,55 +47,35 @@ github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA
github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
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/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/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.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/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M=
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.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/cel-go v0.16.1 h1:3hZfSNiAU3KOiNtxuFXVp5WFy4hf/Ly3Sa4/7F8SXNo=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.5/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/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.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/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -124,7 +83,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
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/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.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -136,7 +94,6 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
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/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
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=
@@ -145,225 +102,171 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
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/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/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
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/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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
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/yuin/goldmark v1.1.27/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.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
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.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-20201020160332-67f06af15bc9/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/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/text v0.3.0/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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.5/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/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
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.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc=
gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
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=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
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.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA=
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM=
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=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
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.8/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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo=
k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4=
k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo=
k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ=
k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg=
k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
k8s.io/apiserver v0.27.2 h1:p+tjwrcQEZDrEorCZV2/qE8osGTINPuS5ZNqWAvKm5E=
k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE=
k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ=
k8s.io/cluster-bootstrap v0.27.2 h1:OL3onrOwrUD7NQxBUqQwTl1Uu2GQKCkw9BMHpc4PbiA=
k8s.io/component-base v0.27.2 h1:neju+7s/r5O4x4/txeUONNTS9r1HsPbyoPBAtHsDCpo=
k8s.io/component-base v0.27.2/go.mod h1:5UPk7EjfgrfgRIuDBFtsEFAe4DAvP3U+M8RTzoSJkpo=
k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg=
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg=
k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/cluster-api v1.4.0-beta.2.0.20230524193452-89a36acc3c3f h1:mqGSAkdrKHfeUB4A4mD55NfHXfQe+FqaB5tUFSCSptY=
sigs.k8s.io/cluster-api v1.4.0-beta.2.0.20230524193452-89a36acc3c3f/go.mod h1:VgMs4bjc3P0igCtHAbG9+jix2gyYtECQhKfNfRedStc=
sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU=
sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=
k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY=
k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0=
k8s.io/apiextensions-apiserver v0.28.4 h1:AZpKY/7wQ8n+ZYDtNHbAJBb+N4AXXJvyZx6ww6yAJvU=
k8s.io/apiextensions-apiserver v0.28.4/go.mod h1:pgQIZ1U8eJSMQcENew/0ShUTlePcSGFq6dxSxf2mwPM=
k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8=
k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg=
k8s.io/apiserver v0.28.4 h1:BJXlaQbAU/RXYX2lRz+E1oPe3G3TKlozMMCZWu5GMgg=
k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY=
k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4=
k8s.io/cluster-bootstrap v0.28.4 h1:4MKNy1Qd9QY7pl47rSMGIORF+tm3CUaqC1M8U9bjn4Q=
k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo=
k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU=
k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=
k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/cluster-api v1.6.0 h1:2bhVSnUbtWI8taCjd9lGiHExsRUpKf7Z1fXqi/IwYx4=
sigs.k8s.io/cluster-api v1.6.0/go.mod h1:LB7u/WxiWj4/bbpHNOa1oQ8nq0MQ5iYlD0pGfRSBGLI=
sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4=
sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0=
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/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.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

32
main.go
View File

@@ -10,6 +10,7 @@ import (
goRuntime "runtime"
flag "github.com/spf13/pflag"
_ "go.uber.org/automaxprocs"
"go.uber.org/zap/zapcore"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -24,12 +25,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
ctrlwebhook "sigs.k8s.io/controller-runtime/pkg/webhook"
capsulev1alpha1 "github.com/projectcapsule/capsule/api/v1alpha1"
capsulev1beta1 "github.com/projectcapsule/capsule/api/v1beta1"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
configcontroller "github.com/projectcapsule/capsule/controllers/config"
podlabelscontroller "github.com/projectcapsule/capsule/controllers/pod"
"github.com/projectcapsule/capsule/controllers/pv"
rbaccontroller "github.com/projectcapsule/capsule/controllers/rbac"
"github.com/projectcapsule/capsule/controllers/resources"
@@ -62,7 +64,6 @@ var (
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(capsulev1alpha1.AddToScheme(scheme))
utilruntime.Must(capsulev1beta1.AddToScheme(scheme))
utilruntime.Must(capsulev1beta2.AddToScheme(scheme))
utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
@@ -76,7 +77,7 @@ func printVersion() {
setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH))
}
//nolint:maintidx,cyclop
//nolint:maintidx
func main() {
var enableLeaderElection, version bool
@@ -123,8 +124,10 @@ func main() {
}
manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: metricsAddr,
},
WebhookServer: ctrlwebhook.NewServer(ctrlwebhook.Options{
Port: webhookPort,
}),
@@ -196,16 +199,6 @@ func main() {
os.Exit(1)
}
if err = (&capsulev1alpha1.Tenant{}).SetupWebhookWithManager(manager); err != nil {
setupLog.Error(err, "unable to create conversion webhook", "webhook", "capsulev1alpha1.Tenant")
os.Exit(1)
}
if err = (&capsulev1alpha1.CapsuleConfiguration{}).SetupWebhookWithManager(manager); err != nil {
setupLog.Error(err, "unable to create conversion webhook", "webhook", "capsulev1alpha1.CapsuleConfiguration")
os.Exit(1)
}
if err = (&capsulev1beta1.Tenant{}).SetupWebhookWithManager(manager); err != nil {
setupLog.Error(err, "unable to create conversion webhook", "webhook", "capsulev1beta1.Tenant")
os.Exit(1)
@@ -233,8 +226,8 @@ func main() {
route.Service(service.Handler()),
route.TenantResourceObjects(utils.InCapsuleGroups(cfg, tntresource.WriteOpsHandler())),
route.NetworkPolicy(utils.InCapsuleGroups(cfg, networkpolicy.Handler())),
route.Tenant(tenant.NameHandler(), tenant.RoleBindingRegexHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler(), tenant.ForbiddenAnnotationsRegexHandler(), tenant.ProtectedHandler()),
route.OwnerReference(utils.InCapsuleGroups(cfg, namespacewebhook.OwnerReferenceHandler(), ownerreference.Handler(cfg))),
route.Tenant(tenant.NameHandler(), tenant.RoleBindingRegexHandler(), tenant.IngressClassRegexHandler(), tenant.StorageClassRegexHandler(), tenant.ContainerRegistryRegexHandler(), tenant.HostnameRegexHandler(), tenant.FreezedEmitter(), tenant.ServiceAccountNameHandler(), tenant.ForbiddenAnnotationsRegexHandler(), tenant.ProtectedHandler(), tenant.MetaHandler()),
route.OwnerReference(utils.InCapsuleGroups(cfg, ownerreference.Handler(cfg))),
route.Cordoning(tenant.CordoningHandler(cfg), tenant.ResourceCounterHandler(manager.GetClient())),
route.Node(utils.InCapsuleGroups(cfg, node.UserMetadataHandler(cfg, kubeVersion))),
route.Defaults(defaults.Handler(cfg, kubeVersion)),
@@ -288,6 +281,11 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "EndpointSliceLabels")
}
if err = (&podlabelscontroller.MetadataReconciler{Client: manager.GetClient()}).SetupWithManager(ctx, manager); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "PodLabels")
os.Exit(1)
}
if err = (&pv.Controller{}).SetupWithManager(manager); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "PersistentVolume")
os.Exit(1)

View File

@@ -4,13 +4,21 @@
package api
import (
"fmt"
"reflect"
"regexp"
"sort"
"strings"
)
// +kubebuilder:object:generate=true
const (
// ForbiddenLabelReason used as reason string to deny forbidden labels.
ForbiddenLabelReason = "ForbiddenLabel"
// ForbiddenAnnotationReason used as reason string to deny forbidden annotations.
ForbiddenAnnotationReason = "ForbiddenAnnotation"
)
// +kubebuilder:object:generate=true
type ForbiddenListSpec struct {
Exact []string `json:"denied,omitempty"`
Regex string `json:"deniedRegex,omitempty"`
@@ -37,3 +45,57 @@ func (in ForbiddenListSpec) RegexMatch(value string) (ok bool) {
return
}
type ForbiddenError struct {
key string
spec ForbiddenListSpec
}
func NewForbiddenError(key string, forbiddenSpec ForbiddenListSpec) error {
return &ForbiddenError{
key: key,
spec: forbiddenSpec,
}
}
//nolint:predeclared
func (f *ForbiddenError) appendForbiddenError() (append string) {
append += "Forbidden are "
if len(f.spec.Exact) > 0 {
append += fmt.Sprintf("one of the following (%s)", strings.Join(f.spec.Exact, ", "))
if len(f.spec.Regex) > 0 {
append += " or "
}
}
if len(f.spec.Regex) > 0 {
append += fmt.Sprintf("matching the regex %s", f.spec.Regex)
}
return
}
func (f ForbiddenError) Error() string {
return fmt.Sprintf("%s is forbidden for the current Tenant. %s", f.key, f.appendForbiddenError())
}
func ValidateForbidden(metadata map[string]string, forbiddenList ForbiddenListSpec) error {
if reflect.DeepEqual(ForbiddenListSpec{}, forbiddenList) {
return nil
}
for key := range metadata {
var forbidden, matched bool
forbidden = forbiddenList.ExactMatch(key)
matched = forbiddenList.RegexMatch(key)
if forbidden || matched {
return NewForbiddenError(
key,
forbiddenList,
)
}
}
return nil
}

View File

@@ -72,3 +72,50 @@ func TestForbiddenListSpec_RegexMatch(t *testing.T) {
}
}
}
func TestValidateForbidden(t *testing.T) {
type tc struct {
Keys map[string]string
ForbiddenSpec ForbiddenListSpec
HasError bool
}
for _, tc := range []tc{
{
Keys: map[string]string{"foobar": "", "thesecondkey": "", "anotherkey": ""},
ForbiddenSpec: ForbiddenListSpec{
Exact: []string{"foobar", "somelabelkey1"},
},
HasError: true,
},
{
Keys: map[string]string{"foobar": ""},
ForbiddenSpec: ForbiddenListSpec{
Exact: []string{"foobar.io", "somelabelkey1", "test-exact"},
},
HasError: false,
},
{
Keys: map[string]string{"foobar": "", "barbaz": ""},
ForbiddenSpec: ForbiddenListSpec{
Regex: "foo.*",
},
HasError: true,
},
{
Keys: map[string]string{"foobar": "", "another-annotation-key": ""},
ForbiddenSpec: ForbiddenListSpec{
Regex: "foo1111",
},
HasError: false,
},
} {
if tc.HasError {
assert.Error(t, ValidateForbidden(tc.Keys, tc.ForbiddenSpec))
}
if !tc.HasError {
assert.NoError(t, ValidateForbidden(tc.Keys, tc.ForbiddenSpec))
}
}
}

View File

@@ -0,0 +1,8 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package api
const (
TenantNameLabel = "kubernetes.io/metadata.name"
)

11
pkg/api/pod_options.go Normal file
View File

@@ -0,0 +1,11 @@
// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0
package api
// +kubebuilder:object:generate=true
type PodOptions struct {
// Specifies additional labels and annotations the Capsule operator places on any Pod resource in the Tenant. Optional.
AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
}

View File

@@ -12,4 +12,8 @@ type ServiceOptions struct {
AllowedServices *AllowedServices `json:"allowedServices,omitempty"`
// Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means no IPs are allowed. Optional.
ExternalServiceIPs *ExternalServiceIPsSpec `json:"externalIPs,omitempty"`
// Define the labels that a Tenant Owner cannot set for their Service resources.
ForbiddenLabels ForbiddenListSpec `json:"forbiddenLabels,omitempty"`
// Define the annotations that a Tenant Owner cannot set for their Service resources.
ForbiddenAnnotations ForbiddenListSpec `json:"forbiddenAnnotations,omitempty"`
}

View File

@@ -213,6 +213,26 @@ func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodOptions) DeepCopyInto(out *PodOptions) {
*out = *in
if in.AdditionalMetadata != nil {
in, out := &in.AdditionalMetadata, &out.AdditionalMetadata
*out = new(AdditionalMetadataSpec)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodOptions.
func (in *PodOptions) DeepCopy() *PodOptions {
if in == nil {
return nil
}
out := new(PodOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) {
*out = *in
@@ -270,6 +290,8 @@ func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) {
*out = new(ExternalServiceIPsSpec)
(*in).DeepCopyInto(*out)
}
in.ForbiddenLabels.DeepCopyInto(&out.ForbiddenLabels)
in.ForbiddenAnnotations.DeepCopyInto(&out.ForbiddenAnnotations)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions.

View File

@@ -7,10 +7,11 @@ import (
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/client-go/discovery"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)
func IsUnsupportedAPI(err error) bool {
missingAPIError, discoveryError := &meta.NoKindMatchError{}, &discovery.ErrGroupDiscoveryFailed{}
missingAPIError, discoveryGropuError, discoveryResourceError := &meta.NoKindMatchError{}, &discovery.ErrGroupDiscoveryFailed{}, &apiutil.ErrResourceDiscoveryFailed{}
return errors.As(err, &missingAPIError) || errors.As(err, &discoveryError)
return errors.As(err, &missingAPIError) || errors.As(err, &discoveryGropuError) || errors.As(err, &discoveryResourceError)
}

View File

@@ -11,14 +11,13 @@ import (
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/projectcapsule/capsule/api/v1alpha1"
"github.com/projectcapsule/capsule/api/v1beta1"
"github.com/projectcapsule/capsule/api/v1beta2"
)
func GetTypeLabel(t runtime.Object) (label string, err error) {
switch v := t.(type) {
case *v1alpha1.Tenant, *v1beta1.Tenant, *v1beta2.Tenant:
case *v1beta1.Tenant, *v1beta2.Tenant:
return "capsule.clastix.io/tenant", nil
case *corev1.LimitRange:
return "capsule.clastix.io/limit-range", nil

View File

@@ -3,30 +3,6 @@
package namespace
import (
"fmt"
"strings"
capsuleapi "github.com/projectcapsule/capsule/pkg/api"
)
//nolint:predeclared
func appendForbiddenError(spec *capsuleapi.ForbiddenListSpec) (append string) {
append += "Forbidden are "
if len(spec.Exact) > 0 {
append += fmt.Sprintf("one of the following (%s)", strings.Join(spec.Exact, ", "))
if len(spec.Regex) > 0 {
append += " or "
}
}
if len(spec.Regex) > 0 {
append += fmt.Sprintf("matching the regex %s", spec.Regex)
}
return
}
type namespaceQuotaExceededError struct{}
func NewNamespaceQuotaExceededError() error {
@@ -36,35 +12,3 @@ func NewNamespaceQuotaExceededError() error {
func (namespaceQuotaExceededError) Error() string {
return "Cannot exceed Namespace quota: please, reach out to the system administrators"
}
type namespaceLabelForbiddenError struct {
label string
spec *capsuleapi.ForbiddenListSpec
}
func NewNamespaceLabelForbiddenError(label string, forbiddenSpec *capsuleapi.ForbiddenListSpec) error {
return &namespaceLabelForbiddenError{
label: label,
spec: forbiddenSpec,
}
}
func (f namespaceLabelForbiddenError) Error() string {
return fmt.Sprintf("Label %s is forbidden for namespaces in the current Tenant. %s", f.label, appendForbiddenError(f.spec))
}
type namespaceAnnotationForbiddenError struct {
annotation string
spec *capsuleapi.ForbiddenListSpec
}
func NewNamespaceAnnotationForbiddenError(annotation string, forbiddenSpec *capsuleapi.ForbiddenListSpec) error {
return &namespaceAnnotationForbiddenError{
annotation: annotation,
spec: forbiddenSpec,
}
}
func (f namespaceAnnotationForbiddenError) Error() string {
return fmt.Sprintf("Annotation %s is forbidden for namespaces in the current Tenant. %s", f.annotation, appendForbiddenError(f.spec))
}

Some files were not shown because too many files have changed in this diff Show More