mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-25 23:34:20 +00:00
Compare commits
138 Commits
helm-v0.4.
...
helm-v0.6.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
800a8ffa88 | ||
|
|
74d3ac504e | ||
|
|
591a66ea20 | ||
|
|
b58fe04026 | ||
|
|
c30b5e911c | ||
|
|
3dc25673b4 | ||
|
|
e9ed7b29d4 | ||
|
|
2792b5894b | ||
|
|
21c0d04ead | ||
|
|
181cb67893 | ||
|
|
c58b46cedf | ||
|
|
3c85657d9a | ||
|
|
242fdd23ee | ||
|
|
8ed302dd8a | ||
|
|
2c70bf0a08 | ||
|
|
976d504392 | ||
|
|
8303421453 | ||
|
|
0241603f2b | ||
|
|
52aa83796e | ||
|
|
b27780d74c | ||
|
|
8695dfb7a2 | ||
|
|
d94430466e | ||
|
|
14eb8f20be | ||
|
|
1e70cd335f | ||
|
|
4b8989530c | ||
|
|
e61152a484 | ||
|
|
c208f5e66e | ||
|
|
5e8d0a0960 | ||
|
|
9a87364288 | ||
|
|
34977aa5d8 | ||
|
|
2465b66840 | ||
|
|
c0e48d1bd9 | ||
|
|
3b0b6cf5ad | ||
|
|
84254019cf | ||
|
|
c735c3c8c9 | ||
|
|
c13e45281e | ||
|
|
2e5c232188 | ||
|
|
5e13ac94cf | ||
|
|
9a21b408dd | ||
|
|
25b4a35b65 | ||
|
|
98b5c3f076 | ||
|
|
9f63aabbb1 | ||
|
|
d09a1c51c7 | ||
|
|
cde44ba14e | ||
|
|
2baf604511 | ||
|
|
34fc260963 | ||
|
|
4ed48e5136 | ||
|
|
abdfdaf297 | ||
|
|
6f80b2bcf8 | ||
|
|
ad8957ca7e | ||
|
|
afd9aebf8a | ||
|
|
72f25c83e1 | ||
|
|
6fe400a937 | ||
|
|
75659a2bee | ||
|
|
a4805b771c | ||
|
|
efc3a1ed2b | ||
|
|
9750302a6b | ||
|
|
e9c756ee04 | ||
|
|
e55bac9dd6 | ||
|
|
a4e83286a6 | ||
|
|
7acf60a67a | ||
|
|
1a7b0e1a3c | ||
|
|
2d5b1e3b2d | ||
|
|
ee991ea03a | ||
|
|
54531bab72 | ||
|
|
de868e1e3f | ||
|
|
6ecf478281 | ||
|
|
cd1736caf2 | ||
|
|
358692de87 | ||
|
|
3d0a781985 | ||
|
|
cba060dc60 | ||
|
|
cebb7025b6 | ||
|
|
8989e37ce9 | ||
|
|
70c8465721 | ||
|
|
4d25594df9 | ||
|
|
6d7523addf | ||
|
|
cfca55cf74 | ||
|
|
0e9d15d98a | ||
|
|
21eadaf1f3 | ||
|
|
682e372b8f | ||
|
|
3bd4bc6441 | ||
|
|
747af4642f | ||
|
|
ed854f99c0 | ||
|
|
9d3e9da1d0 | ||
|
|
5c189094d0 | ||
|
|
2cef776a59 | ||
|
|
364332c380 | ||
|
|
c42c9ed88f | ||
|
|
e0548e1556 | ||
|
|
13c5377ec4 | ||
|
|
8aa527f1c3 | ||
|
|
4ad905e090 | ||
|
|
10bbf39ac1 | ||
|
|
34d6416b1e | ||
|
|
851c3a3765 | ||
|
|
d232791780 | ||
|
|
543757bddb | ||
|
|
c16ea89532 | ||
|
|
147f973c6b | ||
|
|
fe582f4c2f | ||
|
|
b5d1537dc3 | ||
|
|
52c089414b | ||
|
|
404ba237ad | ||
|
|
a09c95550a | ||
|
|
754bf536b3 | ||
|
|
a88a76bd62 | ||
|
|
f2e4cac249 | ||
|
|
3ef55ac08f | ||
|
|
e407d11c50 | ||
|
|
a8a2f8de3f | ||
|
|
2236281a78 | ||
|
|
6cdcc48a4b | ||
|
|
c30de4bb5c | ||
|
|
c3627fba37 | ||
|
|
0830b3629e | ||
|
|
8a09e787f8 | ||
|
|
31f463b143 | ||
|
|
b32ee29c6e | ||
|
|
7ea8ff6327 | ||
|
|
d7a48d771f | ||
|
|
d6ca71d7d8 | ||
|
|
e093e2d83c | ||
|
|
febcc0db80 | ||
|
|
efd12e537b | ||
|
|
eafdddec3e | ||
|
|
e7f871d2f5 | ||
|
|
56f51855e2 | ||
|
|
37455417bc | ||
|
|
29d46529de | ||
|
|
057b9c1411 | ||
|
|
447cd09b0d | ||
|
|
713db11bfc | ||
|
|
605a11d38f | ||
|
|
e1e57658a4 | ||
|
|
444a2f15cb | ||
|
|
d096a25d66 | ||
|
|
ffa4a47e6b | ||
|
|
08389a2797 |
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,20 +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
|
||||
|
||||
If you have any issue or question, reach out us!
|
||||
https://clastix.slack.com >>> #capsule channel
|
||||
-->
|
||||
|
||||
21
.github/actions/exists/action.yaml
vendored
Normal file
21
.github/actions/exists/action.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Checks if an input is defined
|
||||
|
||||
description: Checks if an input is defined and outputs 'true' or 'false'.
|
||||
|
||||
inputs:
|
||||
value:
|
||||
description: value to test
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
result:
|
||||
description: outputs 'true' or 'false' if input value is defined or not
|
||||
value: ${{ steps.check.outputs.result }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- shell: bash
|
||||
id: check
|
||||
run: |
|
||||
echo "result=${{ inputs.value != '' }}" >> $GITHUB_OUTPUT
|
||||
20
.github/actions/setup-caches/action.yaml
vendored
Normal file
20
.github/actions/setup-caches/action.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Setup caches
|
||||
|
||||
description: Setup caches for go modules and build cache.
|
||||
|
||||
inputs:
|
||||
build-cache-key:
|
||||
description: build cache prefix
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/cache@4723a57e26efda3a62cbde1812113b730952852d # v3.2.2
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-pkg-mod-${{ hashFiles('**/go.sum') }}-${{ hashFiles('Makefile') }}
|
||||
- uses: actions/cache@4723a57e26efda3a62cbde1812113b730952852d # v3.2.2
|
||||
if: ${{ inputs.build-cache-key }}
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: ${{ runner.os }}-build-cache-${{ inputs.build-cache-key }}-${{ hashFiles('**/go.sum') }}-${{ hashFiles('Makefile') }}
|
||||
2
.github/configs/ct.yaml
vendored
2
.github/configs/ct.yaml
vendored
@@ -1,5 +1,5 @@
|
||||
remote: origin
|
||||
target-branch: master
|
||||
target-branch: main
|
||||
chart-dirs:
|
||||
- charts
|
||||
helm-extra-args: "--timeout 600s"
|
||||
|
||||
16
.github/dependabot.yml
vendored
Normal file
16
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
rebase-strategy: disabled
|
||||
commit-message:
|
||||
prefix: "feat(deps)"
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
rebase-strategy: disabled
|
||||
commit-message:
|
||||
prefix: "ci(deps)"
|
||||
10
.github/maintainers.yaml
vendored
10
.github/maintainers.yaml
vendored
@@ -2,22 +2,22 @@
|
||||
github: https://github.com/bsctl
|
||||
company: Clastix
|
||||
projects:
|
||||
- https://github.com/clastix/capsule
|
||||
- https://github.com/projectcapsule/capsule
|
||||
- https://github.com/clastix/capsule-proxy
|
||||
- name: Dario Tranchitella
|
||||
github: https://github.com/prometherion
|
||||
company: Clastix
|
||||
projects:
|
||||
- https://github.com/clastix/capsule
|
||||
- https://github.com/projectcapsule/capsule
|
||||
- https://github.com/clastix/capsule-proxy
|
||||
- name: Maksim Fedotov
|
||||
github: https://github.com/MaxFedotov
|
||||
company: wargaming.net
|
||||
projects:
|
||||
- https://github.com/clastix/capsule
|
||||
- https://github.com/projectcapsule/capsule
|
||||
- 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/clastix/capsule
|
||||
- https://github.com/projectcapsule/capsule
|
||||
|
||||
24
.github/workflows/check-actions.yml
vendored
Normal file
24
.github/workflows/check-actions.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Check actions
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Ensure SHA pinned actions
|
||||
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
|
||||
allowlist: |
|
||||
slsa-framework/slsa-github-generator
|
||||
23
.github/workflows/check-commit.yml
vendored
Normal file
23
.github/workflows/check-commit.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Check Commit
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
commit_lint:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: wagoid/commitlint-github-action@0d749a1a91d4770e983a7b8f83d4a3f0e7e0874e #v5.4.4
|
||||
with:
|
||||
firstParent: true
|
||||
37
.github/workflows/check-pr.yml
vendored
Normal file
37
.github/workflows/check-pr.yml
vendored
Normal 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
|
||||
38
.github/workflows/codecov.yml
vendored
Normal file
38
.github/workflows/codecov.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Codecov
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
codecov:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup caches
|
||||
uses: ./.github/actions/setup-caches
|
||||
timeout-minutes: 5
|
||||
continue-on-error: true
|
||||
with:
|
||||
build-cache-key: codecov
|
||||
- name: Check secret
|
||||
id: checksecret
|
||||
uses: ./.github/actions/exists
|
||||
with:
|
||||
value: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Generate Code Coverage Report
|
||||
if: steps.checksecret.outputs.result == 'true'
|
||||
run: make test
|
||||
- name: Upload Report to Codecov
|
||||
if: steps.checksecret.outputs.result == 'true'
|
||||
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
|
||||
with:
|
||||
file: ./coverage.out
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
@@ -1,4 +1,5 @@
|
||||
name: CI
|
||||
name: Diff checks
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -6,37 +7,21 @@ on:
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
commit_lint:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: wagoid/commitlint-github-action@v2
|
||||
with:
|
||||
firstParent: true
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2.3.0
|
||||
with:
|
||||
version: v1.51.2
|
||||
only-new-issues: false
|
||||
args: --timeout 5m --config .golangci.yml
|
||||
diff:
|
||||
name: diff
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v2
|
||||
- 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
|
||||
97
.github/workflows/docker-ci.yml
vendored
97
.github/workflows/docker-ci.yml
vendored
@@ -1,97 +0,0 @@
|
||||
name: docker-ci
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
docker-ci:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Generate build-args
|
||||
id: build-args
|
||||
run: |
|
||||
# Declare vars for internal use
|
||||
VERSION=$(git describe --abbrev=0 --tags)
|
||||
GIT_HEAD_COMMIT=$(git rev-parse --short HEAD)
|
||||
GIT_TAG_COMMIT=$(git rev-parse --short $VERSION)
|
||||
GIT_MODIFIED_1=$(git diff $GIT_HEAD_COMMIT $GIT_TAG_COMMIT --quiet && echo "" || echo ".dev")
|
||||
GIT_MODIFIED_2=$(git diff --quiet && echo "" || echo ".dirty")
|
||||
# Export to GH_ENV
|
||||
echo "GIT_LAST_TAG=$VERSION" >> $GITHUB_ENV
|
||||
echo "GIT_HEAD_COMMIT=$GIT_HEAD_COMMIT" >> $GITHUB_ENV
|
||||
echo "GIT_TAG_COMMIT=$GIT_TAG_COMMIT" >> $GITHUB_ENV
|
||||
echo "GIT_MODIFIED=$(echo "$GIT_MODIFIED_1""$GIT_MODIFIED_2")" >> $GITHUB_ENV
|
||||
echo "GIT_REPO=$(git config --get remote.origin.url)" >> $GITHUB_ENV
|
||||
echo "BUILD_DATE=$(git log -1 --format="%at" | xargs -I{} date -d @{} +%Y-%m-%dT%H:%M:%S)" >> $GITHUB_ENV
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
quay.io/${{ github.repository }}
|
||||
docker.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=semver,pattern={{raw}}
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: arm64,arm
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Inspect builder
|
||||
run: |
|
||||
echo "Name: ${{ steps.buildx.outputs.name }}"
|
||||
echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}"
|
||||
echo "Status: ${{ steps.buildx.outputs.status }}"
|
||||
echo "Flags: ${{ steps.buildx.outputs.flags }}"
|
||||
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
|
||||
|
||||
- name: Login to quay.io Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ github.repository_owner }}+github
|
||||
password: ${{ secrets.BOT_QUAY_IO }}
|
||||
|
||||
- name: Login to docker.io Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.USER_DOCKER_IO }}
|
||||
password: ${{ secrets.BOT_DOCKER_IO }}
|
||||
|
||||
- name: Build and push
|
||||
id: build-release
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
GIT_HEAD_COMMIT=${{ env.GIT_HEAD_COMMIT }}
|
||||
GIT_TAG_COMMIT=${{ env.GIT_TAG_COMMIT }}
|
||||
GIT_REPO=${{ env.GIT_REPO }}
|
||||
GIT_LAST_TAG=${{ env.GIT_LAST_TAG }}
|
||||
GIT_MODIFIED=${{ env.GIT_MODIFIED }}
|
||||
BUILD_DATE=${{ env.BUILD_DATE }}
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.build-release.outputs.digest }}
|
||||
69
.github/workflows/docker-publish.yml
vendored
Normal file
69
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: Publish images
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
publish-images:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
id-token: write
|
||||
outputs:
|
||||
capsule-digest: ${{ steps.publish-capsule.outputs.digest }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Setup caches
|
||||
uses: ./.github/actions/setup-caches
|
||||
timeout-minutes: 5
|
||||
continue-on-error: true
|
||||
with:
|
||||
build-cache-key: publish-images
|
||||
- name: Run Trivy vulnerability (Repo)
|
||||
uses: aquasecurity/trivy-action@22d2755f774d925b191a185b74e782a4b0638a41 # v0.15.0
|
||||
with:
|
||||
scan-type: 'fs'
|
||||
ignore-unfixed: true
|
||||
format: 'sarif'
|
||||
output: 'trivy-results.sarif'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
|
||||
- name: Publish Capsule
|
||||
id: publish-capsule
|
||||
uses: peak-scale/github-actions/make-ko-publish@38322faabccd75abfa581c435e367d446b6d2c3b # v0.1.0
|
||||
with:
|
||||
makefile-target: ko-publish-capsule
|
||||
registry: ghcr.io
|
||||
registry-username: ${{ github.actor }}
|
||||
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: ${{ github.repository_owner }}
|
||||
version: ${{ github.ref_name }}
|
||||
sign-image: true
|
||||
sbom-name: capsule
|
||||
sbom-repository: ghcr.io/${{ github.repository_owner }}/sbom
|
||||
signature-repository: ghcr.io/${{ github.repository_owner }}/signatures
|
||||
main-path: ./
|
||||
env:
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
generate-capsule-provenance:
|
||||
needs: publish-images
|
||||
permissions:
|
||||
id-token: write # To sign the provenance.
|
||||
packages: write # To upload assets to release.
|
||||
actions: read # To read the workflow path.
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
|
||||
with:
|
||||
image: ghcr.io/${{ github.repository_owner }}/capsule
|
||||
digest: "${{ needs.publish-images.outputs.capsule-digest }}"
|
||||
registry-username: ${{ github.actor }}
|
||||
secrets:
|
||||
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||
31
.github/workflows/docs-lint.yml
vendored
Normal file
31
.github/workflows/docs-lint.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: docs-lint
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
paths:
|
||||
- '.github/workflows/docs-lint.yml'
|
||||
- 'docs/content/**'
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
paths:
|
||||
- '.github/workflows/docs-lint.yml'
|
||||
- 'docs/content/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
spelling:
|
||||
name: Spell Check
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: 18
|
||||
- run: make docs-lint
|
||||
15
.github/workflows/e2e.yml
vendored
15
.github/workflows/e2e.yml
vendored
@@ -1,4 +1,5 @@
|
||||
name: e2e
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -26,6 +27,10 @@ on:
|
||||
- 'main.go'
|
||||
- 'Makefile'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
kind:
|
||||
name: Kubernetes
|
||||
@@ -35,22 +40,22 @@ jobs:
|
||||
k8s-version: ['v1.20.7', 'v1.21.2', 'v1.22.4', 'v1.23.6', 'v1.24.7', 'v1.25.3', 'v1.26.3', 'v1.27.2']
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v2
|
||||
- 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)"
|
||||
- name: Checking if manifests generated untracked files
|
||||
run: test -z "$(git ls-files --others --exclude-standard 2> /dev/null)"
|
||||
- uses: engineerd/setup-kind@v0.5.0
|
||||
- uses: engineerd/setup-kind@aa272fe2a7309878ffc2a81c56cfe3ef108ae7d0 # v0.5.0
|
||||
with:
|
||||
skipClusterCreation: true
|
||||
version: v0.14.0
|
||||
- uses: azure/setup-helm@v1
|
||||
- uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3
|
||||
with:
|
||||
version: 3.3.4
|
||||
- name: e2e testing
|
||||
|
||||
35
.github/workflows/fossa.yml
vendored
Normal file
35
.github/workflows/fossa.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: FOSSA
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fossa-scan:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: "Checkout Code"
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Check secret
|
||||
id: checksecret
|
||||
uses: ./.github/actions/exists
|
||||
with:
|
||||
value: ${{ secrets.FOSSA_API_KEY }}
|
||||
- name: "Run FOSSA Scan"
|
||||
if: steps.checksecret.outputs.result == 'true'
|
||||
uses: fossas/fossa-action@f61a4c0c263690f2ddb54b9822a719c25a7b608f # v1.3.1
|
||||
with:
|
||||
api-key: ${{ secrets.FOSSA_API_KEY }}
|
||||
- name: "Run FOSSA Test"
|
||||
if: steps.checksecret.outputs.result == 'true'
|
||||
uses: fossas/fossa-action@f61a4c0c263690f2ddb54b9822a719c25a7b608f # v1.3.1
|
||||
with:
|
||||
api-key: ${{ secrets.FOSSA_API_KEY }}
|
||||
run-tests: true
|
||||
10
.github/workflows/gosec.yml
vendored
10
.github/workflows/gosec.yml
vendored
@@ -1,9 +1,15 @@
|
||||
name: CI gosec
|
||||
permissions: {}
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -11,8 +17,8 @@ jobs:
|
||||
GO111MODULE: on
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Run Gosec Security Scanner
|
||||
uses: securego/gosec@master
|
||||
uses: securego/gosec@55d79496019a560e16e73e1948dee20a1fad631a # v2.18.2
|
||||
with:
|
||||
args: ./...
|
||||
|
||||
78
.github/workflows/helm-publish.yml
vendored
Normal file
78
.github/workflows/helm-publish.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
name: Publish charts
|
||||
permissions: read-all
|
||||
on:
|
||||
push:
|
||||
tags: [ "helm-v*" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
publish-helm:
|
||||
# Skip this Release on forks
|
||||
if: github.repository_owner == 'projectcapsule'
|
||||
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 }}
|
||||
repository: charts
|
||||
branch: gh-pages
|
||||
commit_username: ${{ github.actor }}
|
||||
publish-helm-oci:
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
packages: write
|
||||
outputs:
|
||||
chart-digest: ${{ steps.helm_publish.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- 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: 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
|
||||
sign-image: 'true'
|
||||
signature-repository: ghcr.io/${{ github.repository_owner }}/signatures
|
||||
helm-provenance:
|
||||
needs: publish-helm-oci
|
||||
permissions:
|
||||
id-token: write # To sign the provenance.
|
||||
packages: write # To upload assets to release.
|
||||
actions: read # To read the workflow path.
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
|
||||
with:
|
||||
image: ghcr.io/${{ github.repository_owner }}/charts/capsule
|
||||
digest: "${{ needs.publish-helm-oci.outputs.chart-digest }}"
|
||||
registry-username: ${{ github.actor }}
|
||||
secrets:
|
||||
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||
48
.github/workflows/helm-test.yml
vendored
Normal file
48
.github/workflows/helm-test.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Test charts
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3
|
||||
- name: Linting Chart
|
||||
run: helm lint ./charts/capsule
|
||||
- name: Setup Chart Linting
|
||||
id: lint
|
||||
uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1
|
||||
- name: Run chart-testing (list-changed)
|
||||
id: list-changed
|
||||
run: |
|
||||
changed=$(ct list-changed --config ./.github/configs/ct.yaml)
|
||||
if [[ -n "$changed" ]]; then
|
||||
echo "::set-output name=changed::true"
|
||||
fi
|
||||
- name: Run chart-testing (lint)
|
||||
run: ct lint --debug --config ./.github/configs/ct.yaml --lint-conf ./.github/configs/lintconf.yaml
|
||||
- name: Run docs-testing (helm-docs)
|
||||
id: helm-docs
|
||||
run: |
|
||||
make helm-docs
|
||||
if [[ $(git diff --stat) != '' ]]; then
|
||||
echo -e '\033[0;31mDocumentation outdated! (Run make helm-docs locally and commit)\033[0m ❌'
|
||||
git diff --color
|
||||
exit 1
|
||||
else
|
||||
echo -e '\033[0;32mDocumentation up to date\033[0m ✔'
|
||||
fi
|
||||
|
||||
- name: Run chart-testing (install)
|
||||
run: make helm-test
|
||||
if: steps.list-changed.outputs.changed == 'true'
|
||||
86
.github/workflows/helm.yml
vendored
86
.github/workflows/helm.yml
vendored
@@ -1,86 +0,0 @@
|
||||
name: Helm Chart
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
tags: [ "helm-v*" ]
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: azure/setup-helm@v1
|
||||
with:
|
||||
version: 3.3.4
|
||||
- name: Linting Chart
|
||||
run: helm lint ./charts/capsule
|
||||
- name: Setup Chart Linting
|
||||
id: lint
|
||||
uses: helm/chart-testing-action@v2.3.0
|
||||
- name: Run chart-testing (list-changed)
|
||||
id: list-changed
|
||||
run: |
|
||||
changed=$(ct list-changed --config ./.github/configs/ct.yaml)
|
||||
if [[ -n "$changed" ]]; then
|
||||
echo "::set-output name=changed::true"
|
||||
fi
|
||||
- name: Run chart-testing (lint)
|
||||
run: ct lint --debug --config ./.github/configs/ct.yaml --lint-conf ./.github/configs/lintconf.yaml
|
||||
- name: Run docs-testing (helm-docs)
|
||||
id: helm-docs
|
||||
run: |
|
||||
make helm-docs
|
||||
if [[ $(git diff --stat) != '' ]]; then
|
||||
echo -e '\033[0;31mDocumentation outdated! (Run make helm-docs locally and commit)\033[0m ❌'
|
||||
git diff --color
|
||||
exit 1
|
||||
else
|
||||
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@v1.2.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'
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/helm-v')
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Publish Helm chart
|
||||
uses: stefanprodan/helm-gh-pages@master
|
||||
with:
|
||||
token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
charts_dir: charts
|
||||
charts_url: https://clastix.github.io/charts
|
||||
owner: clastix
|
||||
repository: charts
|
||||
branch: gh-pages
|
||||
target_dir: .
|
||||
commit_username: prometherion
|
||||
commit_email: dario@tranchitella.eu
|
||||
25
.github/workflows/lint.yml
vendored
Normal file
25
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Linting
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
|
||||
with:
|
||||
version: v1.51.2
|
||||
only-new-issues: false
|
||||
args: --timeout 5m --config .golangci.yml
|
||||
38
.github/workflows/releaser.yml
vendored
Normal file
38
.github/workflows/releaser.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Go Release
|
||||
|
||||
permissions: {}
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
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@5ecf649a417b8ae17dc8383dc32d46c03f2312df
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 # v3.2.0
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean --timeout 90m --debug
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
42
.github/workflows/scorecard.yml
vendored
Normal file
42
.github/workflows/scorecard.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Scorecards supply-chain security
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run analysis
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
|
||||
publish_results: true
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
- name: Upload to code-scanning
|
||||
uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
*.so
|
||||
*.dylib
|
||||
bin
|
||||
dist/
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
@@ -14,10 +14,10 @@ linters-settings:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/clastix/capsule)
|
||||
- prefix(github.com/projectcapsule/capsule)
|
||||
goheader:
|
||||
template: |-
|
||||
Copyright 2020-2021 Clastix Labs
|
||||
Copyright 2020-2023 Project Capsule Authors.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
linters:
|
||||
|
||||
86
.goreleaser.yml
Normal file
86
.goreleaser.yml
Normal file
@@ -0,0 +1,86 @@
|
||||
project_name: capsule
|
||||
env:
|
||||
- COSIGN_EXPERIMENTAL=true
|
||||
- GO111MODULE=on
|
||||
before:
|
||||
hooks:
|
||||
- go mod download
|
||||
gomod:
|
||||
proxy: false
|
||||
builds:
|
||||
- main: .
|
||||
binary: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}"
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
goos:
|
||||
- linux
|
||||
flags:
|
||||
- -trimpath
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
ldflags:
|
||||
- >-
|
||||
-X main.Version={{ .Tag }}
|
||||
-X main.GitCommit={{ .Commit }}
|
||||
-X main.GitTag={{ .Tag }}
|
||||
-X main.GitTreeState={{ .Date }}
|
||||
-X main.BuildDate={{ .Date }}
|
||||
-X main.GitRepo={{ .ProjectName }}
|
||||
release:
|
||||
prerelease: auto
|
||||
footer: |
|
||||
Thanks to all the contributors!
|
||||
|
||||
**Full Changelog**: https://github.com/projectcapsule/{{ .ProjectName }}/compare/{{ .PreviousTag }}...{{ .Tag }}
|
||||
|
||||
**Docker Images**
|
||||
- `ghcr.io/projectcapsule/{{ .ProjectName }}:{{ .Tag }}`
|
||||
- `ghcr.io/projectcapsule/{{ .ProjectName }}:latest`
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
changelog:
|
||||
sort: asc
|
||||
use: github
|
||||
filters:
|
||||
exclude:
|
||||
- '^test:'
|
||||
- '^chore'
|
||||
- '^rebase:'
|
||||
- 'merge conflict'
|
||||
- Merge pull request
|
||||
- Merge remote-tracking branch
|
||||
- Merge branch
|
||||
groups:
|
||||
# https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional
|
||||
- title: '🛠 Dependency updates'
|
||||
regexp: '^.*?(feat|fix)\(deps\)!?:.+$'
|
||||
order: 300
|
||||
- title: '✨ New Features'
|
||||
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
|
||||
order: 100
|
||||
- title: '🐛 Bug fixes'
|
||||
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
|
||||
order: 200
|
||||
- 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: 600
|
||||
- title: '📦 Other work'
|
||||
order: 9999
|
||||
sboms:
|
||||
- artifacts: archive
|
||||
signs:
|
||||
- cmd: cosign
|
||||
args:
|
||||
- "sign-blob"
|
||||
- "--output-signature=${signature}"
|
||||
- "${artifact}"
|
||||
- "--yes"
|
||||
artifacts: all
|
||||
9
.ko.yaml
Normal file
9
.ko.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
defaultPlatforms:
|
||||
- linux/arm64
|
||||
- linux/amd64
|
||||
- linux/arm
|
||||
builds:
|
||||
- id: capsule
|
||||
main: ./
|
||||
ldflags:
|
||||
- '{{ if index .Env "LD_FLAGS" }}{{ .Env.LD_FLAGS }}{{ end }}'
|
||||
10
CHANGELOG.md
Normal file
10
CHANGELOG.md
Normal 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)).
|
||||
@@ -1,5 +1,7 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
Capsule follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
@@ -114,7 +116,7 @@ the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
This Code of Conduct follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md) and is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
|
||||
189
CONTRIBUTING.md
Normal file
189
CONTRIBUTING.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# Contributing
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
### Reorganising commits
|
||||
|
||||
To reorganise your commits, do the following (or use your way of doing it):
|
||||
|
||||
|
||||
1. Pull upstream changes
|
||||
|
||||
```bash
|
||||
git remote add upstream git@github.com:projectcapsule/capsule.git
|
||||
git pull upstream main
|
||||
```
|
||||
|
||||
2. Pick the current upstream HEAD (the commit is marked with `(remote/main, main)`)
|
||||
|
||||
```bash
|
||||
git log
|
||||
....
|
||||
commit 10bbf39ac1ac3ad4f8485422e54faa9aadf03315 (remote/main, main)
|
||||
Author: Oliver Bähler <oliverbaehler@hotmail.com>
|
||||
Date: Mon Oct 23 10:24:44 2023 +0200
|
||||
|
||||
docs(repo): add sbom reference
|
||||
|
||||
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
|
||||
|
||||
Developer Certificate of Origin (DCO) Sign off
|
||||
For contributors to certify that they wrote or otherwise have the right to submit the code they are contributing to the project, we are requiring everyone to acknowledge this by signing their work which indicates you agree to the DCO found here.
|
||||
|
||||
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'
|
||||
|
||||
## 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
47
DEPENDENCY.md
Normal 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
233
DEVELOPMENT.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
## 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`.
|
||||
@@ -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
|
||||
|
||||
189
Makefile
189
Makefile
@@ -1,8 +1,20 @@
|
||||
# Current Operator version
|
||||
VERSION ?= $$(git describe --abbrev=0 --tags --match "v*")
|
||||
# Version
|
||||
GIT_HEAD_COMMIT ?= $(shell git rev-parse --short HEAD)
|
||||
VERSION ?= $(or $(shell git describe --abbrev=0 --tags --match "v*" 2>/dev/null),$(GIT_HEAD_COMMIT))
|
||||
|
||||
# Defaults
|
||||
REGISTRY ?= ghcr.io
|
||||
REPOSITORY ?= projectcapsule/capsule
|
||||
GIT_TAG_COMMIT ?= $(shell git rev-parse --short $(VERSION))
|
||||
GIT_MODIFIED_1 ?= $(shell git diff $(GIT_HEAD_COMMIT) $(GIT_TAG_COMMIT) --quiet && echo "" || echo ".dev")
|
||||
GIT_MODIFIED_2 ?= $(shell git diff --quiet && echo "" || echo ".dirty")
|
||||
GIT_MODIFIED ?= $(shell echo "$(GIT_MODIFIED_1)$(GIT_MODIFIED_2)")
|
||||
GIT_REPO ?= $(shell git config --get remote.origin.url)
|
||||
BUILD_DATE ?= $(shell git log -1 --format="%at" | xargs -I{} sh -c 'if [ "$(shell uname)" = "Darwin" ]; then date -r {} +%Y-%m-%dT%H:%M:%S; else date -d @{} +%Y-%m-%dT%H:%M:%S; fi')
|
||||
IMG_BASE ?= $(REPOSITORY)
|
||||
IMG ?= $(IMG_BASE):$(VERSION)
|
||||
CAPSULE_IMG ?= $(REGISTRY)/$(IMG_BASE)
|
||||
|
||||
# Default bundle image tag
|
||||
BUNDLE_IMG ?= clastix/capsule:$(VERSION)-bundle
|
||||
# Options for 'bundle-build'
|
||||
ifneq ($(origin CHANNELS), undefined)
|
||||
BUNDLE_CHANNELS := --channels=$(CHANNELS)
|
||||
@@ -12,9 +24,6 @@ BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL)
|
||||
endif
|
||||
BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
|
||||
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= clastix/capsule:$(VERSION)
|
||||
|
||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
GOBIN=$(shell go env GOPATH)/bin
|
||||
@@ -22,20 +31,16 @@ else
|
||||
GOBIN=$(shell go env GOBIN)
|
||||
endif
|
||||
|
||||
# Get information about git current status
|
||||
GIT_HEAD_COMMIT ?= $$(git rev-parse --short HEAD)
|
||||
GIT_TAG_COMMIT ?= $$(git rev-parse --short $(VERSION))
|
||||
GIT_MODIFIED_1 ?= $$(git diff $(GIT_HEAD_COMMIT) $(GIT_TAG_COMMIT) --quiet && echo "" || echo ".dev")
|
||||
GIT_MODIFIED_2 ?= $$(git diff --quiet && echo "" || echo ".dirty")
|
||||
GIT_MODIFIED ?= $$(echo "$(GIT_MODIFIED_1)$(GIT_MODIFIED_2)")
|
||||
GIT_REPO ?= $$(git config --get remote.origin.url)
|
||||
BUILD_DATE ?= $$(git log -1 --format="%at" | xargs -I{} date -d @{} +%Y-%m-%dT%H:%M:%S)
|
||||
|
||||
all: manager
|
||||
|
||||
# Run tests
|
||||
test: generate manifests
|
||||
go test ./... -coverprofile cover.out
|
||||
.PHONY: test
|
||||
test: test-clean generate manifests test-clean
|
||||
@GO111MODULE=on go test -v ./... -coverprofile coverage.out
|
||||
|
||||
.PHONY: test-clean
|
||||
test-clean: ## Clean tests cache
|
||||
@go clean -testcache
|
||||
|
||||
# Build manager binary
|
||||
manager: generate golint
|
||||
@@ -47,7 +52,7 @@ run: generate manifests
|
||||
|
||||
# Creates the single file to install Capsule without any external dependency
|
||||
installer: manifests kustomize
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${CAPSULE_IMG}
|
||||
$(KUSTOMIZE) build config/default > config/install.yaml
|
||||
|
||||
# Install CRDs into a cluster
|
||||
@@ -82,17 +87,24 @@ 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
|
||||
|
||||
helm-lint: ct
|
||||
@ct lint --config $(SRC_ROOT)/.github/configs/ct.yaml --lint-conf $(SRC_ROOT)/.github/configs/lintconf.yaml --all --debug
|
||||
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 docker-build
|
||||
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 ${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
|
||||
|
||||
@@ -154,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="[\
|
||||
@@ -166,42 +179,99 @@ dev-setup:
|
||||
]";
|
||||
|
||||
|
||||
# Build the docker image
|
||||
docker-build: test
|
||||
docker build . -t ${IMG} --build-arg GIT_HEAD_COMMIT=$(GIT_HEAD_COMMIT) \
|
||||
--build-arg GIT_TAG_COMMIT=$(GIT_TAG_COMMIT) \
|
||||
--build-arg GIT_MODIFIED=$(GIT_MODIFIED) \
|
||||
--build-arg GIT_REPO=$(GIT_REPO) \
|
||||
--build-arg GIT_LAST_TAG=$(VERSION) \
|
||||
--build-arg BUILD_DATE=$(BUILD_DATE)
|
||||
####################
|
||||
# -- Docker
|
||||
####################
|
||||
|
||||
# Push the docker image
|
||||
docker-push:
|
||||
docker push ${IMG}
|
||||
KOCACHE ?= /tmp/ko-cache
|
||||
KO_REGISTRY := ko.local
|
||||
KO_TAGS ?= "latest"
|
||||
ifdef VERSION
|
||||
KO_TAGS := $(KO_TAGS),$(VERSION)
|
||||
endif
|
||||
|
||||
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
|
||||
LD_FLAGS := "-X main.Version=$(VERSION) \
|
||||
-X main.GitCommit=$(GIT_HEAD_COMMIT) \
|
||||
-X main.GitTag=$(VERSION) \
|
||||
-X main.GitTreeState=$(GIT_MODIFIED) \
|
||||
-X main.BuildDate=$(BUILD_DATE) \
|
||||
-X main.GitRepo=$(GIT_REPO)"
|
||||
|
||||
# Docker Image Build
|
||||
# ------------------
|
||||
|
||||
.PHONY: ko-build-capsule
|
||||
ko-build-capsule: ko
|
||||
@echo Building Capsule $(KO_TAGS) >&2
|
||||
@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
|
||||
|
||||
# Docker Image Publish
|
||||
# ------------------
|
||||
|
||||
REGISTRY_PASSWORD ?= dummy
|
||||
REGISTRY_USERNAME ?= dummy
|
||||
|
||||
.PHONY: ko-login
|
||||
ko-login: ko
|
||||
@$(KO) login $(REGISTRY) --username $(REGISTRY_USERNAME) --password $(REGISTRY_PASSWORD)
|
||||
|
||||
.PHONY: ko-publish-capsule
|
||||
ko-publish-capsule: ko-login ## Build and publish kyvernopre image (with ko)
|
||||
@LD_FLAGS=$(LD_FLAGS) KOCACHE=$(KOCACHE) KO_DOCKER_REPO=$(CAPSULE_IMG) \
|
||||
$(KO) build ./ --bare --tags=$(KO_TAGS)
|
||||
|
||||
.PHONY: ko-publish-all
|
||||
ko-publish-all: ko-publish-capsule
|
||||
|
||||
####################
|
||||
# -- Binaries
|
||||
####################
|
||||
|
||||
CONTROLLER_GEN := $(shell pwd)/bin/controller-gen
|
||||
CONTROLLER_GEN_VERSION := v0.10.0
|
||||
controller-gen: ## Download controller-gen locally if necessary.
|
||||
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.10.0)
|
||||
$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION))
|
||||
|
||||
APIDOCS_GEN = $(shell pwd)/bin/crdoc
|
||||
APIDOCS_GEN := $(shell pwd)/bin/crdoc
|
||||
APIDOCS_GEN_VERSION := latest
|
||||
apidocs-gen: ## Download crdoc locally if necessary.
|
||||
$(call go-install-tool,$(APIDOCS_GEN),fybrik.io/crdoc@latest)
|
||||
$(call go-install-tool,$(APIDOCS_GEN),fybrik.io/crdoc@$(APIDOCS_GEN_VERSION))
|
||||
|
||||
GINKGO = $(shell pwd)/bin/ginkgo
|
||||
GINKGO := $(shell pwd)/bin/ginkgo
|
||||
GINGKO_VERSION := v2.13.2
|
||||
ginkgo: ## Download ginkgo locally if necessary.
|
||||
$(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo@v2.9.5)
|
||||
$(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo@$(GINGKO_VERSION))
|
||||
|
||||
CT = $(shell pwd)/bin/ct
|
||||
CT := $(shell pwd)/bin/ct
|
||||
CT_VERSION := v3.7.1
|
||||
ct: ## Download ct locally if necessary.
|
||||
$(call go-install-tool,$(CT),github.com/helm/chart-testing/v3/ct@v3.7.1)
|
||||
$(call go-install-tool,$(CT),github.com/helm/chart-testing/v3/ct@$(CT_VERSION))
|
||||
|
||||
KIND = $(shell pwd)/bin/kind
|
||||
KIND := $(shell pwd)/bin/kind
|
||||
KIND_VERSION := v0.17.0
|
||||
kind: ## Download kind locally if necessary.
|
||||
$(call go-install-tool,$(KIND),sigs.k8s.io/kind/cmd/kind@v0.17.0)
|
||||
$(call go-install-tool,$(KIND),sigs.k8s.io/kind/cmd/kind@$(KIND_VERSION))
|
||||
|
||||
KUSTOMIZE = $(shell pwd)/bin/kustomize
|
||||
KUSTOMIZE := $(shell pwd)/bin/kustomize
|
||||
KUSTOMIZE_VERSION := 3.8.7
|
||||
kustomize: ## Download kustomize locally if necessary.
|
||||
$(call install-kustomize,$(KUSTOMIZE),3.8.7)
|
||||
$(call install-kustomize,$(KUSTOMIZE),$(KUSTOMIZE_VERSION))
|
||||
|
||||
KO = $(shell pwd)/bin/ko
|
||||
KO_VERSION = v0.14.1
|
||||
ko:
|
||||
$(call go-install-tool,$(KO),github.com/google/ko@$(KO_VERSION))
|
||||
|
||||
####################
|
||||
# -- Helpers
|
||||
####################
|
||||
pull-upstream:
|
||||
git remote add upstream https://github.com/capsuleproject/capsule.git
|
||||
git fetch --all && git pull upstream
|
||||
|
||||
define install-kustomize
|
||||
@[ -f $(1) ] || { \
|
||||
@@ -218,7 +288,6 @@ PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
define go-install-tool
|
||||
@[ -f $(1) ] || { \
|
||||
set -e ;\
|
||||
echo "Installing $(2)" ;\
|
||||
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
|
||||
}
|
||||
endef
|
||||
@@ -229,14 +298,10 @@ bundle: manifests
|
||||
kustomize build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
|
||||
operator-sdk bundle validate ./bundle
|
||||
|
||||
# Build the bundle image.
|
||||
bundle-build:
|
||||
docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) .
|
||||
|
||||
# Sorting imports
|
||||
.PHONY: goimports
|
||||
goimports:
|
||||
goimports -w -l -local "github.com/clastix/capsule" .
|
||||
goimports -w -l -local "github.com/projectcapsule/capsule" .
|
||||
|
||||
GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint
|
||||
golangci-lint: ## Download golangci-lint locally if necessary.
|
||||
@@ -254,8 +319,11 @@ e2e/%: ginkgo
|
||||
|
||||
e2e-build/%:
|
||||
kind create cluster --wait=60s --name capsule --image=kindest/node:$*
|
||||
make docker-build
|
||||
kind load docker-image --nodes capsule-control-plane --name capsule $(IMG)
|
||||
make e2e-load-image
|
||||
make e2e-install
|
||||
|
||||
.PHONY: e2e-install
|
||||
e2e-install:
|
||||
helm upgrade \
|
||||
--debug \
|
||||
--install \
|
||||
@@ -270,8 +338,19 @@ e2e-build/%:
|
||||
capsule \
|
||||
./charts/capsule
|
||||
|
||||
.PHONY: e2e-load-image
|
||||
e2e-load-image: ko-build-all
|
||||
kind load docker-image --nodes capsule-control-plane --name capsule $(CAPSULE_IMG):$(VERSION)
|
||||
|
||||
.PHONY: e2e-exec
|
||||
e2e-exec: ginkgo
|
||||
$(GINKGO) -v -tags e2e ./e2e
|
||||
|
||||
.PHONY: e2e-destroy
|
||||
e2e-destroy:
|
||||
kind delete cluster --name capsule
|
||||
|
||||
SPELL_CHECKER = npx spellchecker-cli
|
||||
docs-lint:
|
||||
cd docs/content && $(SPELL_CHECKER) -f "*.md" "*/*.md" -d dictionary.txt
|
||||
|
||||
|
||||
31
PROJECT
31
PROJECT
@@ -5,40 +5,21 @@ plugins:
|
||||
manifests.sdk.operatorframework.io/v2: {}
|
||||
scorecard.sdk.operatorframework.io/v2: {}
|
||||
projectName: capsule
|
||||
repo: github.com/clastix/capsule
|
||||
repo: github.com/projectcapsule/capsule
|
||||
resources:
|
||||
- api:
|
||||
crdVersion: v1
|
||||
controller: true
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: Tenant
|
||||
path: github.com/clastix/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/clastix/capsule/api/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: Tenant
|
||||
path: github.com/clastix/capsule/api/v1beta1
|
||||
path: github.com/projectcapsule/capsule/api/v1beta1
|
||||
version: v1beta1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: Tenant
|
||||
path: github.com/clastix/capsule/api/v1beta2
|
||||
path: github.com/projectcapsule/capsule/api/v1beta2
|
||||
version: v1beta2
|
||||
- api:
|
||||
crdVersion: v1
|
||||
@@ -46,7 +27,7 @@ resources:
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: CapsuleConfiguration
|
||||
path: github.com/clastix/capsule/api/v1beta2
|
||||
path: github.com/projectcapsule/capsule/api/v1beta2
|
||||
version: v1beta2
|
||||
- api:
|
||||
crdVersion: v1
|
||||
@@ -54,13 +35,13 @@ resources:
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: TenantResource
|
||||
path: github.com/clastix/capsule/api/v1beta2
|
||||
path: github.com/projectcapsule/capsule/api/v1beta2
|
||||
version: v1beta2
|
||||
- api:
|
||||
crdVersion: v1
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: GlobalTenantResource
|
||||
path: github.com/clastix/capsule/api/v1beta2
|
||||
path: github.com/projectcapsule/capsule/api/v1beta2
|
||||
version: v1beta2
|
||||
version: "3"
|
||||
|
||||
35
README.md
35
README.md
@@ -1,14 +1,25 @@
|
||||
|
||||
<p align="left">
|
||||
<img src="https://github.com/clastix/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/clastix/capsule/releases">
|
||||
<a href="https://github.com/projectcapsule/capsule/releases">
|
||||
<img src="https://img.shields.io/github/v/release/clastix/capsule"/>
|
||||
</a>
|
||||
<a href="https://charmhub.io/capsule-k8s">
|
||||
<img src="https://charmhub.io/capsule-k8s/badge.svg"/>
|
||||
</a>
|
||||
<a href="https://www.bestpractices.dev/projects/5601">
|
||||
<img src="https://www.bestpractices.dev/projects/5601/badge"/>
|
||||
</a>
|
||||
<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">
|
||||
@@ -89,9 +100,11 @@ 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/clastix/capsule-community).
|
||||
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).
|
||||
|
||||
Please read the [code of conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## Adopters
|
||||
|
||||
@@ -105,6 +118,18 @@ You can find how the Capsule project is governed [here](https://capsule.clastix.
|
||||
|
||||
Please, refer to the maintainers file available [here](.github/maintainers.yaml).
|
||||
|
||||
## Release process
|
||||
|
||||
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?
|
||||
@@ -113,7 +138,7 @@ Please, refer to the maintainers file available [here](.github/maintainers.yaml)
|
||||
|
||||
- Q. Is it production grade?
|
||||
|
||||
A. Although under frequent development and improvements, Capsule is ready to be used in production environments as currently, people are using it in public and private deployments. Check out the [release](https://github.com/clastix/capsule/releases) page for a detailed list of available versions.
|
||||
A. Although under frequent development and improvements, Capsule is ready to be used in production environments as currently, people are using it in public and private deployments. Check out the [release](https://github.com/projectcapsule/capsule/releases) page for a detailed list of available versions.
|
||||
|
||||
- Q. Does it work with my Kubernetes XYZ distribution?
|
||||
|
||||
|
||||
3
ROADMAP.md
Normal file
3
ROADMAP.md
Normal 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
60
SECURITY-INSIGHTS.yml
Normal 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
115
SECURITY.md
Normal 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
201
SELF_ASSESSMENT.md
Normal 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 Capsule’s 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.
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// 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"`
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// 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"
|
||||
)
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// 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{})
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// 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()
|
||||
}
|
||||
@@ -1,583 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// 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/clastix/capsule/api/v1beta1"
|
||||
"github.com/clastix/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
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// 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/clastix/capsule/api/v1beta1"
|
||||
"github.com/clastix/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)
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// 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
|
||||
)
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// 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)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// 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))
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// 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/clastix/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{})
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// 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()
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/clastix/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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package v1beta1 contains API Schema definitions for the capsule v1beta1 API group
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
type IngressOptions struct {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
@@ -6,7 +6,7 @@ package v1beta1
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
type NamespaceOptions struct {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
type ServiceOptions struct {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
@@ -6,7 +6,7 @@ package v1beta1
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
// TenantSpec defines the desired state of Tenant.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
@@ -9,7 +9,7 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/conversion"
|
||||
|
||||
capsulev1alpha1 "github.com/clastix/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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
@@ -6,7 +6,7 @@ package v1beta2
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
// CapsuleConfigurationSpec defines the Capsule configuration.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package v1beta2 contains API Schema definitions for the capsule v1beta2 API group
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
type IngressOptions struct {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
type NamespaceOptions struct {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
@@ -48,6 +48,7 @@ const (
|
||||
PriorityClassesProxy ProxyServiceKind = "PriorityClasses"
|
||||
RuntimeClassesProxy ProxyServiceKind = "RuntimeClasses"
|
||||
PersistentVolumesProxy ProxyServiceKind = "PersistentVolumes"
|
||||
TenantProxy ProxyServiceKind = "Tenant"
|
||||
|
||||
ListOperation ProxyOperation = "List"
|
||||
UpdateOperation ProxyOperation = "Update"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
@@ -1,17 +1,48 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
"crypto/md5" //#nosec
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func UsedQuotaFor(resource fmt.Stringer) string {
|
||||
return "quota.capsule.clastix.io/used-" + strings.ReplaceAll(resource.String(), "/", "_")
|
||||
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) {
|
||||
resourceStr := strings.ReplaceAll(resource.String(), "/", "_")
|
||||
|
||||
hash := md5.Sum([]byte(resourceStr)) //#nosec
|
||||
|
||||
hashed := hex.EncodeToString(hash[:])
|
||||
capsuleHashed := format + hashed
|
||||
capsuleAnnotation := format + resourceStr
|
||||
|
||||
switch {
|
||||
case len(capsuleAnnotation) <= maxAnnotationLength:
|
||||
return capsuleAnnotation, nil
|
||||
case len(capsuleHashed) <= maxAnnotationLength:
|
||||
return capsuleHashed, nil
|
||||
case len(hashed) <= maxAnnotationLength:
|
||||
return hashed, nil
|
||||
default:
|
||||
return "", fmt.Errorf("the annotation name would exceed the maximum supported length (%d), skipping", maxAnnotationLength)
|
||||
}
|
||||
}
|
||||
|
||||
func HardQuotaFor(resource fmt.Stringer) string {
|
||||
return "quota.capsule.clastix.io/hard-" + strings.ReplaceAll(resource.String(), "/", "_")
|
||||
func UsedQuotaFor(resource fmt.Stringer) (string, error) {
|
||||
return createAnnotation(UsedCapsuleQuotaAnnotation, resource)
|
||||
}
|
||||
|
||||
func HardQuotaFor(resource fmt.Stringer) (string, error) {
|
||||
return createAnnotation(HardCapsuleQuotaAnnotation, resource)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/conversion"
|
||||
|
||||
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
capsulev1beta1 "github.com/projectcapsule/capsule/api/v1beta1"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
func (in *Tenant) ConvertFrom(raw conversion.Hub) error {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
@@ -6,7 +6,7 @@ package v1beta2
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
// TenantSpec defines the desired state of Tenant.
|
||||
@@ -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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
// TenantResourceSpec defines the desired state of TenantResource.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2021 Clastix Labs
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
@@ -9,7 +9,7 @@
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
"k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -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
BIN
assets/docs/dev-env.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
@@ -2,8 +2,8 @@ apiVersion: v2
|
||||
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/clastix/capsule
|
||||
icon: https://github.com/clastix/capsule/raw/master/assets/logo/capsule_small.png
|
||||
home: https://github.com/projectcapsule/capsule
|
||||
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/clastix/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
|
||||
|
||||
- https://github.com/projectcapsule/capsule
|
||||
# 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
|
||||
|
||||
@@ -22,11 +22,15 @@ The Capsule Operator Chart can be used to instantly deploy the Capsule Operator
|
||||
|
||||
1. Add this repository:
|
||||
|
||||
$ helm repo add clastix https://clastix.github.io/charts
|
||||
$ helm repo add projectcapsule https://projectcapsule.github.io/charts
|
||||
|
||||
2. Install the Chart:
|
||||
|
||||
$ helm install capsule clastix/capsule -n capsule-system --create-namespace
|
||||
$ helm install capsule projectcapsule/capsule -n capsule-system --create-namespace
|
||||
|
||||
or
|
||||
|
||||
$ helm install capsule oci://ghcr.io/projectcapsule/charts/capsule --version 0.4.6 -n capsule-system --create-namespace
|
||||
|
||||
3. Show the status:
|
||||
|
||||
@@ -34,7 +38,11 @@ The Capsule Operator Chart can be used to instantly deploy the Capsule Operator
|
||||
|
||||
4. Upgrade the Chart
|
||||
|
||||
$ helm upgrade capsule clastix/capsule -n capsule-system
|
||||
$ helm upgrade capsule projectcapsule/capsule -n capsule-system
|
||||
|
||||
or
|
||||
|
||||
$ helm upgrade capsule oci://ghcr.io/projectcapsule/charts/capsule --version 0.4.7
|
||||
|
||||
5. Uninstall the Chart
|
||||
|
||||
@@ -68,6 +76,7 @@ Here the values you can override:
|
||||
| customLabels | object | `{}` | Additional labels which will be added to all resources created by Capsule helm chart |
|
||||
| imagePullSecrets | list | `[]` | Configuration for `imagePullSecrets` so that you can use a private images registry. |
|
||||
| jobs.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy of the helm chart job |
|
||||
| jobs.image.registry | string | `"docker.io"` | Set the image repository of the helm chart job |
|
||||
| jobs.image.repository | string | `"clastix/kubectl"` | Set the image repository of the helm chart job |
|
||||
| jobs.image.tag | string | `""` | Set the image tag of the helm chart job |
|
||||
| mutatingWebhooksTimeoutSeconds | int | `30` | Timeout in seconds for mutating webhooks |
|
||||
@@ -94,7 +103,8 @@ Here the values you can override:
|
||||
|-----|------|---------|-------------|
|
||||
| manager.hostNetwork | bool | `false` | Specifies if the container should be started in hostNetwork mode. Required for use in some managed kubernetes clusters (such as AWS EKS) with custom CNI (such as calico), because control-plane managed by AWS cannot communicate with pods' IP CIDR and admission webhooks are not working |
|
||||
| manager.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy. |
|
||||
| manager.image.repository | string | `"clastix/capsule"` | Set the image repository of the capsule. |
|
||||
| manager.image.registry | string | `"ghcr.io"` | Set the image registry of capsule. |
|
||||
| manager.image.repository | string | `"projectcapsule/capsule"` | Set the image repository of capsule. |
|
||||
| manager.image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. |
|
||||
| manager.kind | string | `"Deployment"` | Set the controller deployment mode as `Deployment` or `DaemonSet`. |
|
||||
| manager.livenessProbe | object | `{"httpGet":{"path":"/healthz","port":10080}}` | Configure the liveness probe using Deployment probe spec |
|
||||
@@ -104,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"` | |
|
||||
@@ -196,7 +209,7 @@ Capsule, as many other add-ons, defines its own set of Custom Resource Definitio
|
||||
You can enable the generation of certificates using `cert-manager` as follows.
|
||||
|
||||
```
|
||||
helm upgrade --install capsule clastix/capsule --namespace capsule-system --create-namespace \
|
||||
helm upgrade --install capsule projectcapsule/capsule --namespace capsule-system --create-namespace \
|
||||
--set "certManager.generateCertificates=true" \
|
||||
--set "tls.create=false" \
|
||||
--set "tls.enableController=false"
|
||||
@@ -221,4 +234,4 @@ metadata:
|
||||
|
||||
## More
|
||||
|
||||
See Capsule [tutorial](https://github.com/clastix/capsule/blob/master/docs/content/general/tutorial.md) for more information about how to use Capsule.
|
||||
See Capsule [tutorial](https://github.com/projectcapsule/capsule/blob/master/docs/content/general/tutorial.md) for more information about how to use Capsule.
|
||||
|
||||
@@ -22,11 +22,15 @@ The Capsule Operator Chart can be used to instantly deploy the Capsule Operator
|
||||
|
||||
1. Add this repository:
|
||||
|
||||
$ helm repo add clastix https://clastix.github.io/charts
|
||||
$ helm repo add projectcapsule https://projectcapsule.github.io/charts
|
||||
|
||||
2. Install the Chart:
|
||||
|
||||
$ helm install capsule clastix/capsule -n capsule-system --create-namespace
|
||||
$ helm install capsule projectcapsule/capsule -n capsule-system --create-namespace
|
||||
|
||||
or
|
||||
|
||||
$ helm install capsule oci://ghcr.io/projectcapsule/charts/capsule --version 0.4.6 -n capsule-system --create-namespace
|
||||
|
||||
3. Show the status:
|
||||
|
||||
@@ -34,7 +38,11 @@ The Capsule Operator Chart can be used to instantly deploy the Capsule Operator
|
||||
|
||||
4. Upgrade the Chart
|
||||
|
||||
$ helm upgrade capsule clastix/capsule -n capsule-system
|
||||
$ helm upgrade capsule projectcapsule/capsule -n capsule-system
|
||||
|
||||
or
|
||||
|
||||
$ helm upgrade capsule oci://ghcr.io/projectcapsule/charts/capsule --version 0.4.7
|
||||
|
||||
5. Uninstall the Chart
|
||||
|
||||
@@ -132,7 +140,7 @@ Capsule, as many other add-ons, defines its own set of Custom Resource Definitio
|
||||
You can enable the generation of certificates using `cert-manager` as follows.
|
||||
|
||||
```
|
||||
helm upgrade --install capsule clastix/capsule --namespace capsule-system --create-namespace \
|
||||
helm upgrade --install capsule projectcapsule/capsule --namespace capsule-system --create-namespace \
|
||||
--set "certManager.generateCertificates=true" \
|
||||
--set "tls.create=false" \
|
||||
--set "tls.enableController=false"
|
||||
@@ -157,4 +165,4 @@ metadata:
|
||||
|
||||
## More
|
||||
|
||||
See Capsule [tutorial](https://github.com/clastix/capsule/blob/master/docs/content/general/tutorial.md) for more information about how to use Capsule.
|
||||
See Capsule [tutorial](https://github.com/projectcapsule/capsule/blob/master/docs/content/general/tutorial.md) for more information about how to use Capsule.
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
fullnameOverride: capsule
|
||||
manager:
|
||||
# Manager RBAC
|
||||
rbac:
|
||||
create: true
|
||||
existingClusterRoles:
|
||||
- "view"
|
||||
existingRoles:
|
||||
- "some-role"
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -80,7 +80,7 @@ Create the name of the service account to use
|
||||
Create the manager fully-qualified Docker image to use
|
||||
*/}}
|
||||
{{- define "capsule.managerFullyQualifiedDockerImage" -}}
|
||||
{{- printf "%s:%s" .Values.manager.image.repository ( .Values.manager.image.tag | default (printf "v%s" .Chart.AppVersion) ) -}}
|
||||
{{- printf "%s/%s:%s" .Values.manager.image.registry .Values.manager.image.repository ( .Values.manager.image.tag | default (printf "v%s" .Chart.AppVersion) ) -}}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
@@ -106,9 +106,9 @@ Create the jobs fully-qualified Docker image to use
|
||||
*/}}
|
||||
{{- define "capsule.jobsFullyQualifiedDockerImage" -}}
|
||||
{{- if .Values.jobs.image.tag }}
|
||||
{{- printf "%s:%s" .Values.jobs.image.repository .Values.jobs.image.tag -}}
|
||||
{{- printf "%s/%s:%s" .Values.jobs.image.registry .Values.jobs.image.repository .Values.jobs.image.tag -}}
|
||||
{{- else }}
|
||||
{{- printf "%s:%s" .Values.jobs.image.repository (include "capsule.jobsTagKubeVersion" .) -}}
|
||||
{{- printf "%s/%s:%s" .Values.jobs.image.registry .Values.jobs.image.repository (include "capsule.jobsTagKubeVersion" .) -}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
|
||||
@@ -60,8 +60,6 @@ spec:
|
||||
secretName: {{ include "capsule.secretTlsName" . }}
|
||||
containers:
|
||||
- name: manager
|
||||
command:
|
||||
- /manager
|
||||
args:
|
||||
- --webhook-port={{ .Values.manager.webhookPort }}
|
||||
- --enable-leader-election
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -14,12 +14,25 @@ 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
|
||||
|
||||
image:
|
||||
# -- Set the image repository of the capsule.
|
||||
repository: clastix/capsule
|
||||
# -- Set the image registry of capsule.
|
||||
registry: ghcr.io
|
||||
# -- Set the image repository of capsule.
|
||||
repository: projectcapsule/capsule
|
||||
# -- Set the image pull policy.
|
||||
pullPolicy: IfNotPresent
|
||||
# -- Overrides the image tag whose default is the chart appVersion.
|
||||
@@ -135,6 +148,8 @@ podSecurityPolicy:
|
||||
|
||||
jobs:
|
||||
image:
|
||||
# -- Set the image repository of the helm chart job
|
||||
registry: docker.io
|
||||
# -- Set the image repository of the helm chart job
|
||||
repository: clastix/kubectl
|
||||
# -- Set the image pull policy of the helm chart job
|
||||
|
||||
19
commitlint.config.js
Normal file
19
commitlint.config.js
Normal 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;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user