mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-14 09:59:57 +00:00
feat(config): add combined users property as successor for usergroups (#1767)
* feat(config): add combined users property as successor for usergroups and usernames configuration Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * fix(crds): add proper deprecation notices on properties and via admission warnings Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: add local monitoring environment Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> --------- Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
This commit is contained in:
49
.github/workflows/e2e-internal.yml
vendored
49
.github/workflows/e2e-internal.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: e2e-internal
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- '.github/workflows/e2e.yml'
|
||||
- '.github/workflows/e2e-internal.yml'
|
||||
- 'api/**'
|
||||
- 'controllers/**'
|
||||
- 'internal/**'
|
||||
- 'pkg/**'
|
||||
- 'e2e/*'
|
||||
- 'Dockerfile'
|
||||
- 'go.*'
|
||||
- 'main.go'
|
||||
- 'Makefile'
|
||||
|
||||
jobs:
|
||||
internal-e2e:
|
||||
name: Trigger internal E2E Testing
|
||||
runs-on:
|
||||
labels: ubuntu-latest
|
||||
if: github.repository_owner == 'projectcapsule'
|
||||
steps:
|
||||
- name: Trigger internal e2e repo
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.INTERNAL_E2E_PAT }}
|
||||
run: |
|
||||
if [ -z "${GH_TOKEN}" ]; then
|
||||
echo "GH_TOKEN is empty; secrets are not available. Skipping."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl -fsS -X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer $GH_TOKEN" \
|
||||
https://api.github.com/repos/projectcapsule/enterprise-e2e/dispatches \
|
||||
-d "$(jq -n --arg ref "main" \
|
||||
--arg pr_number "${{ github.event.pull_request.number }}" \
|
||||
--arg sha "${{ github.sha }}" \
|
||||
--arg repo "${{ github.repository }}" \
|
||||
'{event_type:"internal-e2e", client_payload:{ref:$ref, pr_number:$pr_number, sha:$sha, repo:$repo}}')"
|
||||
33
.github/workflows/e2e.yml
vendored
33
.github/workflows/e2e.yml
vendored
@@ -23,18 +23,45 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
name: E2E Testing
|
||||
name: E2E Testing (CE)
|
||||
runs-on:
|
||||
labels: ubuntu-latest-8-cores
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
|
||||
with:
|
||||
version: v3.14.2
|
||||
|
||||
- name: e2e
|
||||
run: sudo make e2e
|
||||
run-e2e:
|
||||
name: E2E Testing
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
k8s-version:
|
||||
- '1.30.0'
|
||||
- '1.31.0'
|
||||
- '1.32.0'
|
||||
- '1.33.0'
|
||||
runs-on:
|
||||
labels: ubuntu-latest-8-cores
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
repository: ${{ github.event.client_payload.repo }}
|
||||
ref: ${{ github.event.client_payload.sha }}
|
||||
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
|
||||
|
||||
- name: e2e (Enterprise)
|
||||
run: KUBERNETES_SUPPORTED_VERSION=${{ matrix.k8s-version }} sudo make e2e
|
||||
|
||||
1
.github/workflows/lint.yml
vendored
1
.github/workflows/lint.yml
vendored
@@ -23,6 +23,7 @@ jobs:
|
||||
go-version-file: 'go.mod'
|
||||
- name: Generate manifests
|
||||
run: |
|
||||
make generate
|
||||
make manifests
|
||||
if [[ $(git diff --stat) != '' ]]; then
|
||||
echo -e '\033[0;31mManifests outdated! (Run make manifests locally and commit)\033[0m ❌'
|
||||
|
||||
@@ -70,9 +70,13 @@ $ 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
|
||||
|
||||
|
||||
# Monitoring Setup (Grafana/Prometheus/Pyroscope)
|
||||
$ LAPTOP_HOST_IP="<YOUR_LAPTOP_IP>" make dev-setup-monitoring
|
||||
```
|
||||
|
||||
### Explenation
|
||||
### Setup
|
||||
|
||||
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.
|
||||
|
||||
|
||||
22
Makefile
22
Makefile
@@ -97,9 +97,7 @@ helm-test: kind
|
||||
@$(KIND) delete cluster --name capsule-charts
|
||||
|
||||
helm-test-exec: ct helm-controller-version ko-build-all
|
||||
$(MAKE) docker-build-capsule-trace
|
||||
$(MAKE) e2e-load-image CLUSTER_NAME=capsule-charts IMAGE=$(CAPSULE_IMG) VERSION=v0.0.0
|
||||
$(MAKE) e2e-load-image CLUSTER_NAME=capsule-charts IMAGE=$(CAPSULE_IMG) VERSION=tracing
|
||||
@$(KUBECTL) create ns capsule-system || true
|
||||
@$(KUBECTL) apply --force-conflicts --server-side=true -f https://github.com/grafana/grafana-operator/releases/download/v5.18.0/crds.yaml
|
||||
@$(KUBECTL) apply --force-conflicts --server-side=true -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.crds.yaml
|
||||
@@ -160,6 +158,26 @@ dev-setup:
|
||||
./charts/capsule
|
||||
$(KUBECTL) -n capsule-system scale deployment capsule-controller-manager --replicas=0 || true
|
||||
|
||||
setup-monitoring: dev-setup-fluxcd
|
||||
@$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/monitoring | envsubst | kubectl apply -f -
|
||||
@$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/monitoring/dashboards | kubectl apply -f -
|
||||
@$(MAKE) wait-for-helmreleases
|
||||
@printf "\n\033[32mAccess Grafana:\033[0m\n\n"
|
||||
@printf " \033[1mkubectl port-forward svc/kube-prometheus-stack-grafana 9090:80 -n monitoring-system\033[0m\n\n"
|
||||
|
||||
dev-setup-monitoring: setup-monitoring
|
||||
@$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/host-proxy | envsubst | kubectl apply -f -
|
||||
|
||||
dev-setup-fluxcd:
|
||||
@$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/fluxcd | envsubst | kubectl apply -f -
|
||||
|
||||
wait-for-helmreleases:
|
||||
@ echo "Waiting for all HelmReleases to have observedGeneration >= 0..."
|
||||
@while [ "$$($(KUBECTL) get helmrelease -A -o jsonpath='{range .items[?(@.status.observedGeneration<0)]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}' | wc -l)" -ne 0 ]; do \
|
||||
sleep 5; \
|
||||
done
|
||||
|
||||
|
||||
####################
|
||||
# -- Docker
|
||||
####################
|
||||
|
||||
@@ -11,8 +11,15 @@ import (
|
||||
|
||||
// CapsuleConfigurationSpec defines the Capsule configuration.
|
||||
type CapsuleConfigurationSpec struct {
|
||||
// Define entities which are considered part of the Capsule construct
|
||||
// Users not mentioned here will be ignored by Capsule
|
||||
Users api.UserListSpec `json:"users,omitempty"`
|
||||
// Deprecated: use users property instead (https://projectcapsule.dev/docs/operating/setup/configuration/#users)
|
||||
//
|
||||
// Names of the users considered as Capsule users.
|
||||
UserNames []string `json:"userNames,omitempty"`
|
||||
// Deprecated: use users property instead (https://projectcapsule.dev/docs/operating/setup/configuration/#users)
|
||||
//
|
||||
// Names of the groups considered as Capsule users.
|
||||
// +kubebuilder:default={capsule.clastix.io}
|
||||
UserGroups []string `json:"userGroups,omitempty"`
|
||||
|
||||
@@ -11,8 +11,9 @@ type NamespaceOptions struct {
|
||||
// +kubebuilder:validation:Minimum=1
|
||||
// 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.
|
||||
Quota *int32 `json:"quota,omitempty"`
|
||||
// Deprecated: Use additionalMetadataList instead (https://projectcapsule.dev/docs/tenants/metadata/#additionalmetadatalist)
|
||||
//
|
||||
// Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
|
||||
// Deprecated: Use additionalMetadataList instead
|
||||
AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
|
||||
// Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant via a list. Optional.
|
||||
AdditionalMetadataList []api.AdditionalMetadataSelectorSpec `json:"additionalMetadataList,omitempty"`
|
||||
|
||||
@@ -37,11 +37,13 @@ type TenantSpec struct {
|
||||
ContainerRegistries *api.AllowedListSpec `json:"containerRegistries,omitempty"`
|
||||
// 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.
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
// Deprecated: Use Tenant Replications instead (https://projectcapsule.dev/docs/replications/)
|
||||
//
|
||||
// Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
// Deprecated: Use Tenant Replications instead (https://projectcapsule.dev/docs/replications/)
|
||||
NetworkPolicies api.NetworkPolicySpec `json:"networkPolicies,omitempty"`
|
||||
// Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
|
||||
// Deprecated: Use Tenant Replications instead (https://projectcapsule.dev/docs/replications/)
|
||||
//
|
||||
// Specifies the resource min/max usage restrictions to the Tenant. The assigned values are inherited by any namespace created in the Tenant. Optional.
|
||||
LimitRanges api.LimitRangesSpec `json:"limitRanges,omitempty"`
|
||||
// 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.
|
||||
ResourceQuota api.ResourceQuotaSpec `json:"resourceQuotas,omitempty"`
|
||||
|
||||
@@ -98,6 +98,11 @@ func (in *CapsuleConfigurationList) DeepCopyObject() runtime.Object {
|
||||
// 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.Users != nil {
|
||||
in, out := &in.Users, &out.Users
|
||||
*out = make(api.UserListSpec, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.UserNames != nil {
|
||||
in, out := &in.UserNames, &out.UserNames
|
||||
*out = make([]string, len(*in))
|
||||
|
||||
@@ -116,7 +116,7 @@ The following Values have changed key or Value:
|
||||
| manager.options.allowServiceAccountPromotion | bool | `false` | ServiceAccounts within tenant namespaces can be promoted to owners of the given tenant this can be achieved by labeling the serviceaccount and then they are considered owners. This can only be done by other owners of the tenant. However ServiceAccounts which have been promoted to owner can not promote further serviceAccounts. |
|
||||
| manager.options.annotations | object | `{}` | Additional annotations to add to the CapsuleConfiguration resource |
|
||||
| manager.options.capsuleConfiguration | string | `"default"` | Change the default name of the capsule configuration name |
|
||||
| manager.options.capsuleUserGroups | list | `["projectcapsule.dev"]` | Names of the groups considered as Capsule users. |
|
||||
| manager.options.capsuleUserGroups | list | `[]` | DEPRECATED: use users properties. Names of the users considered as Capsule users. |
|
||||
| manager.options.createConfiguration | bool | `true` | Create Configuration |
|
||||
| manager.options.forceTenantPrefix | bool | `false` | Boolean, enforces the Tenant owner, during Namespace creation, to name it using the selected Tenant name as prefix, separated by a dash |
|
||||
| manager.options.generateCertificates | bool | `true` | Specifies whether capsule webhooks certificates should be generated by capsule operator |
|
||||
@@ -125,7 +125,8 @@ The following Values have changed key or Value:
|
||||
| manager.options.logLevel | string | `"3"` | Set the log verbosity of the capsule with a value from 1 to 5 |
|
||||
| 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.options.userNames | list | `[]` | Names of the users considered as Capsule users. |
|
||||
| manager.options.userNames | list | `[]` | DEPRECATED: use users properties. Names of the users considered as Capsule users. |
|
||||
| manager.options.users | list | `[{"kind":"Group","name":"projectcapsule.dev"}]` | Define entities which are considered part of the Capsule construct. Users not mentioned here will be ignored by Capsule |
|
||||
| manager.options.workers | int | `1` | Workers (MaxConcurrentReconciles) is the maximum number of concurrent Reconciles which can be run (ALPHA). |
|
||||
| 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. |
|
||||
@@ -166,6 +167,13 @@ The following Values have changed key or Value:
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| webhooks.exclusive | bool | `false` | When `crds.exclusive` is `true` the webhooks will be installed |
|
||||
| webhooks.hooks.config.enabled | bool | `true` | Enable the Hook |
|
||||
| webhooks.hooks.config.failurePolicy | string | `"Ignore"` | [FailurePolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy) |
|
||||
| webhooks.hooks.config.matchConditions | list | `[]` | [MatchConditions](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy) |
|
||||
| webhooks.hooks.config.matchPolicy | string | `"Exact"` | [MatchPolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy) |
|
||||
| webhooks.hooks.config.namespaceSelector | object | `{}` | [NamespaceSelector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector) |
|
||||
| webhooks.hooks.config.objectSelector | object | `{}` | [ObjectSelector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-objectselector) |
|
||||
| webhooks.hooks.config.reinvocationPolicy | string | `"Never"` | [ReinvocationPolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#reinvocation-policy) |
|
||||
| webhooks.hooks.cordoning.enabled | bool | `true` | Enable the Hook |
|
||||
| webhooks.hooks.cordoning.failurePolicy | string | `"Fail"` | [FailurePolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy) |
|
||||
| webhooks.hooks.cordoning.matchConditions | list | `[]` | [MatchConditions](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy) |
|
||||
|
||||
@@ -158,15 +158,43 @@ spec:
|
||||
userGroups:
|
||||
default:
|
||||
- capsule.clastix.io
|
||||
description: Names of the groups considered as Capsule users.
|
||||
description: |-
|
||||
Deprecated: use users property instead (https://projectcapsule.dev/docs/operating/setup/configuration/#users)
|
||||
|
||||
Names of the groups considered as Capsule users.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
userNames:
|
||||
description: Names of the users considered as Capsule users.
|
||||
description: |-
|
||||
Deprecated: use users property instead (https://projectcapsule.dev/docs/operating/setup/configuration/#users)
|
||||
|
||||
Names of the users considered as Capsule users.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
users:
|
||||
description: |-
|
||||
Define entities which are considered part of the Capsule construct
|
||||
Users not mentioned here will be ignored by Capsule
|
||||
items:
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of entity. Possible values are "User", "Group",
|
||||
and "ServiceAccount"
|
||||
enum:
|
||||
- User
|
||||
- Group
|
||||
- ServiceAccount
|
||||
type: string
|
||||
name:
|
||||
description: Name of the entity.
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- enableTLSReconciler
|
||||
type: object
|
||||
|
||||
@@ -129,7 +129,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
type: object
|
||||
imagePullPolicies:
|
||||
@@ -160,7 +163,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
type: object
|
||||
allowedHostnames:
|
||||
@@ -176,7 +182,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
type: object
|
||||
hostnameCollisionScope:
|
||||
@@ -855,7 +864,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
type: object
|
||||
resourceQuotas:
|
||||
@@ -1030,7 +1042,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
@@ -1185,7 +1200,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
type: object
|
||||
cordoned:
|
||||
@@ -1203,7 +1221,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements.
|
||||
@@ -1271,7 +1292,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
default:
|
||||
type: string
|
||||
@@ -1352,7 +1376,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
default:
|
||||
type: string
|
||||
@@ -1412,7 +1439,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
type: object
|
||||
hostnameCollisionScope:
|
||||
@@ -1436,8 +1466,9 @@ spec:
|
||||
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.
|
||||
Deprecated: Use Tenant Replications instead (https://projectcapsule.dev/docs/replications/)
|
||||
|
||||
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:
|
||||
@@ -1527,8 +1558,9 @@ spec:
|
||||
properties:
|
||||
additionalMetadata:
|
||||
description: |-
|
||||
Deprecated: Use additionalMetadataList instead (https://projectcapsule.dev/docs/tenants/metadata/#additionalmetadatalist)
|
||||
|
||||
Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
|
||||
Deprecated: Use additionalMetadataList instead
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
@@ -1642,8 +1674,9 @@ spec:
|
||||
type: object
|
||||
networkPolicies:
|
||||
description: |-
|
||||
Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
Deprecated: Use Tenant Replications instead (https://projectcapsule.dev/docs/replications/)
|
||||
|
||||
Specifies the NetworkPolicies assigned to the Tenant. The assigned NetworkPolicies are inherited by any namespace created in the Tenant. Optional.
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
@@ -2292,7 +2325,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
default:
|
||||
type: string
|
||||
@@ -2439,7 +2475,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
default:
|
||||
type: string
|
||||
@@ -2572,7 +2611,10 @@ spec:
|
||||
type: string
|
||||
type: array
|
||||
allowedRegex:
|
||||
description: Match elements by regex (DEPRECATED)
|
||||
description: |-
|
||||
Deprecated: will be removed in a future release
|
||||
|
||||
Match elements by regex.
|
||||
type: string
|
||||
default:
|
||||
type: string
|
||||
|
||||
@@ -14,6 +14,10 @@ metadata:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
administrators:
|
||||
{{- toYaml .Values.manager.options.administrators | nindent 4 }}
|
||||
users:
|
||||
{{- toYaml .Values.manager.options.users | nindent 4 }}
|
||||
enableTLSReconciler: {{ .Values.tls.enableController }}
|
||||
overrides:
|
||||
mutatingWebhookConfigurationName: {{ include "capsule.fullname" . }}-mutating-webhook-configuration
|
||||
|
||||
@@ -87,7 +87,7 @@ spec:
|
||||
# piping stderr to stdout means kubectl's errors are surfaced
|
||||
# in the pod's logs.
|
||||
|
||||
kubectl apply --server-side=true --overwrite=true --force-conflicts=true -f /data/ 2>&1
|
||||
kubectl apply --server-side=true --overwrite=true --force-conflicts=true --field-manager='capsule/crd-lifecycle' -f /data/ 2>&1
|
||||
volumeMounts:
|
||||
{{- range $path, $_ := .Files.Glob "crds/**.yaml" }}
|
||||
- name: {{ $path | base | trimSuffix ".yaml" | regexFind "[^_]+$" }}
|
||||
|
||||
@@ -603,4 +603,40 @@ webhooks:
|
||||
timeoutSeconds: {{ $.Values.webhooks.validatingWebhooksTimeoutSeconds }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.webhooks.hooks.config }}
|
||||
{{- if .enabled }}
|
||||
- name: config.projectcapsule.dev
|
||||
admissionReviewVersions:
|
||||
- v1
|
||||
- v1beta1
|
||||
clientConfig:
|
||||
{{- include "capsule.webhooks.service" (dict "path" "/config/validating" "ctx" $) | nindent 4 }}
|
||||
failurePolicy: {{ .failurePolicy }}
|
||||
matchPolicy: {{ .matchPolicy }}
|
||||
{{- with .namespaceSelector }}
|
||||
namespaceSelector:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .objectSelector }}
|
||||
objectSelector:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .matchConditions }}
|
||||
matchConditions:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- capsule.clastix.io
|
||||
apiVersions:
|
||||
- v1beta2
|
||||
operations:
|
||||
- UPDATE
|
||||
resources:
|
||||
- capsuleconfigurations
|
||||
scope: 'Cluster'
|
||||
sideEffects: None
|
||||
timeoutSeconds: {{ $.Values.webhooks.validatingWebhooksTimeoutSeconds }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -336,11 +336,8 @@
|
||||
"type": "string"
|
||||
},
|
||||
"capsuleUserGroups": {
|
||||
"description": "Names of the groups considered as Capsule users.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
"description": "DEPRECATED: use users properties. Names of the users considered as Capsule users.",
|
||||
"type": "array"
|
||||
},
|
||||
"createConfiguration": {
|
||||
"description": "Create Configuration",
|
||||
@@ -399,9 +396,24 @@
|
||||
"type": "string"
|
||||
},
|
||||
"userNames": {
|
||||
"description": "Names of the users considered as Capsule users.",
|
||||
"description": "DEPRECATED: use users properties. Names of the users considered as Capsule users.",
|
||||
"type": "array"
|
||||
},
|
||||
"users": {
|
||||
"description": "Define entities which are considered part of the Capsule construct. Users not mentioned here will be ignored by Capsule",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"workers": {
|
||||
"description": "Workers (MaxConcurrentReconciles) is the maximum number of concurrent Reconciles which can be run (ALPHA).",
|
||||
"type": "integer"
|
||||
@@ -739,6 +751,39 @@
|
||||
"hooks": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"description": "Enable the Hook",
|
||||
"type": "boolean"
|
||||
},
|
||||
"failurePolicy": {
|
||||
"description": "[FailurePolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy)",
|
||||
"type": "string"
|
||||
},
|
||||
"matchConditions": {
|
||||
"description": "[MatchConditions](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy)",
|
||||
"type": "array"
|
||||
},
|
||||
"matchPolicy": {
|
||||
"description": "[MatchPolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy)",
|
||||
"type": "string"
|
||||
},
|
||||
"namespaceSelector": {
|
||||
"description": "[NamespaceSelector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector)",
|
||||
"type": "object"
|
||||
},
|
||||
"objectSelector": {
|
||||
"description": "[ObjectSelector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-objectselector)",
|
||||
"type": "object"
|
||||
},
|
||||
"reinvocationPolicy": {
|
||||
"description": "[ReinvocationPolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#reinvocation-policy)",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cordoning": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -178,6 +178,11 @@ manager:
|
||||
workers: 1
|
||||
# -- Set the log verbosity of the capsule with a value from 1 to 5
|
||||
logLevel: '3'
|
||||
# -- Define entities which are considered part of the Capsule construct.
|
||||
# Users not mentioned here will be ignored by Capsule
|
||||
users:
|
||||
- kind: "Group"
|
||||
name: "projectcapsule.dev"
|
||||
# -- Define entities which can act as Administrators in the capsule construct
|
||||
# These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label
|
||||
# for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefor
|
||||
@@ -185,10 +190,6 @@ manager:
|
||||
administrators: []
|
||||
# - kind: User
|
||||
# name: alice
|
||||
# -- Names of the users considered as Capsule users.
|
||||
userNames: []
|
||||
# -- Names of the groups considered as Capsule users.
|
||||
capsuleUserGroups: ["projectcapsule.dev"]
|
||||
# -- Define groups which when found in the request of a user will be ignored by the Capsule
|
||||
# this might be useful if you have one group where all the users are in, but you want to separate administrators from normal users with additional groups.
|
||||
ignoreUserWithGroups: []
|
||||
@@ -210,6 +211,13 @@ manager:
|
||||
forbiddenAnnotations:
|
||||
denied: []
|
||||
deniedRegex: ""
|
||||
# -- DEPRECATED: use users properties.
|
||||
# Names of the users considered as Capsule users.
|
||||
userNames: []
|
||||
# -- DEPRECATED: use users properties.
|
||||
# Names of the users considered as Capsule users.
|
||||
capsuleUserGroups: []
|
||||
|
||||
|
||||
# -- A list of extra arguments for the capsule controller
|
||||
extraArgs:
|
||||
@@ -654,6 +662,22 @@ webhooks:
|
||||
# -- [ReinvocationPolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#reinvocation-policy)
|
||||
reinvocationPolicy: Never
|
||||
|
||||
config:
|
||||
# -- Enable the Hook
|
||||
enabled: true
|
||||
# -- [FailurePolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy)
|
||||
failurePolicy: Ignore
|
||||
# -- [MatchPolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy)
|
||||
matchPolicy: Exact
|
||||
# -- [ObjectSelector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-objectselector)
|
||||
objectSelector: {}
|
||||
# -- [NamespaceSelector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector)
|
||||
namespaceSelector: {}
|
||||
# -- [MatchConditions](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy)
|
||||
matchConditions: []
|
||||
# -- [ReinvocationPolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#reinvocation-policy)
|
||||
reinvocationPolicy: Never
|
||||
|
||||
tenantResourceObjects:
|
||||
# -- Enable the Hook
|
||||
enabled: true
|
||||
|
||||
19
cmd/main.go
19
cmd/main.go
@@ -43,6 +43,7 @@ import (
|
||||
utilscontroller "github.com/projectcapsule/capsule/internal/controllers/utils"
|
||||
"github.com/projectcapsule/capsule/internal/metrics"
|
||||
"github.com/projectcapsule/capsule/internal/webhook"
|
||||
cfgvalidation "github.com/projectcapsule/capsule/internal/webhook/cfg"
|
||||
"github.com/projectcapsule/capsule/internal/webhook/defaults"
|
||||
"github.com/projectcapsule/capsule/internal/webhook/dra"
|
||||
"github.com/projectcapsule/capsule/internal/webhook/gateway"
|
||||
@@ -88,11 +89,11 @@ func printVersion() {
|
||||
setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH))
|
||||
}
|
||||
|
||||
//nolint:maintidx
|
||||
//nolint:maintidx,cyclop
|
||||
func main() {
|
||||
controllerConfig := utilscontroller.ControllerOptions{}
|
||||
|
||||
var enableLeaderElection, version bool
|
||||
var enableLeaderElection, enablePprof, version bool
|
||||
|
||||
var metricsAddr, ns string
|
||||
|
||||
@@ -108,6 +109,7 @@ func main() {
|
||||
"Enabling this will ensure there is only one active controller manager.")
|
||||
flag.BoolVar(&version, "version", false, "Print the Capsule version and exit")
|
||||
flag.StringVar(&controllerConfig.ConfigurationName, "configuration-name", "default", "The CapsuleConfiguration resource name to use")
|
||||
flag.BoolVar(&enablePprof, "enable-pprof", false, "Enables Pprof endpoint for profiling (not recommend in production)")
|
||||
|
||||
opts := zap.Options{
|
||||
EncoderConfigOptions: append([]zap.EncoderConfigOption{}, func(config *zapcore.EncoderConfig) {
|
||||
@@ -139,7 +141,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
ctrlOpts := ctrl.Options{
|
||||
Scheme: scheme,
|
||||
Metrics: metricsserver.Options{
|
||||
BindAddress: metricsAddr,
|
||||
@@ -155,7 +157,13 @@ func main() {
|
||||
|
||||
return client.New(config, options)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if enablePprof {
|
||||
ctrlOpts.PprofBindAddress = ":8082"
|
||||
}
|
||||
|
||||
manager, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrlOpts)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
os.Exit(1)
|
||||
@@ -311,6 +319,9 @@ func main() {
|
||||
route.TenantAssignment(
|
||||
misc.TenantAssignmentHandler(),
|
||||
),
|
||||
route.ConfigValidation(
|
||||
cfgvalidation.WarningHandler(),
|
||||
),
|
||||
)
|
||||
|
||||
nodeWebhookSupported, _ := utils.NodeWebhookSupported(kubeVersion)
|
||||
|
||||
@@ -69,7 +69,9 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
|
||||
|
||||
It("should fail using a User non matching the capsule-user-group flag", func() {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
|
||||
configuration.Spec.UserGroups = []string{"test"}
|
||||
configuration.Spec.UserNames = []string{}
|
||||
configuration.Spec.UserGroups = []string{}
|
||||
configuration.Spec.Users = []api.UserSpec{{Kind: api.GroupOwner, Name: "test"}}
|
||||
})
|
||||
|
||||
ns := NewNamespace("")
|
||||
@@ -78,7 +80,9 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
|
||||
|
||||
It("should succeed and be available in Tenant namespaces list with multiple groups", func() {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
|
||||
configuration.Spec.UserGroups = []string{"test", "alice"}
|
||||
configuration.Spec.UserNames = []string{}
|
||||
configuration.Spec.UserGroups = []string{}
|
||||
configuration.Spec.Users = []api.UserSpec{{Kind: api.UserOwner, Name: "alice"}, {Kind: api.GroupOwner, Name: "test"}}
|
||||
})
|
||||
|
||||
ns := NewNamespace("")
|
||||
@@ -89,7 +93,9 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
|
||||
|
||||
It("should succeed and be available in Tenant namespaces list with default single group", func() {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
|
||||
configuration.Spec.UserGroups = []string{"projectcapsule.dev"}
|
||||
configuration.Spec.UserNames = []string{}
|
||||
configuration.Spec.UserGroups = []string{}
|
||||
configuration.Spec.Users = []api.UserSpec{{Kind: api.GroupOwner, Name: "projectcapsule.dev"}}
|
||||
})
|
||||
|
||||
ns := NewNamespace("")
|
||||
@@ -100,7 +106,9 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
|
||||
|
||||
It("should fail when group is ignored", func() {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
|
||||
configuration.Spec.UserGroups = []string{"projectcapsule.dev"}
|
||||
configuration.Spec.UserNames = []string{}
|
||||
configuration.Spec.UserGroups = []string{}
|
||||
configuration.Spec.Users = []api.UserSpec{{Kind: api.GroupOwner, Name: "projectcapsule.dev"}}
|
||||
configuration.Spec.IgnoreUserWithGroups = []string{"projectcapsule.dev"}
|
||||
})
|
||||
|
||||
@@ -111,9 +119,10 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
|
||||
|
||||
It("should succeed and be available in Tenant namespaces list with default single user", func() {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
|
||||
configuration.Spec.UserNames = []string{}
|
||||
configuration.Spec.UserGroups = []string{}
|
||||
configuration.Spec.Users = []api.UserSpec{{Kind: api.UserOwner, Name: tnt.Spec.Owners[0].Name}}
|
||||
configuration.Spec.IgnoreUserWithGroups = []string{}
|
||||
configuration.Spec.UserNames = []string{tnt.Spec.Owners[0].Name}
|
||||
})
|
||||
|
||||
ns := NewNamespace("")
|
||||
@@ -123,9 +132,10 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
|
||||
|
||||
It("should succeed and be available in Tenant namespaces list with default single user", func() {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
|
||||
configuration.Spec.UserNames = []string{}
|
||||
configuration.Spec.UserGroups = []string{}
|
||||
configuration.Spec.IgnoreUserWithGroups = []string{}
|
||||
configuration.Spec.UserNames = []string{tnt.Spec.Owners[0].Name}
|
||||
configuration.Spec.Users = []api.UserSpec{{Kind: api.UserOwner, Name: tnt.Spec.Owners[0].Name}}
|
||||
})
|
||||
|
||||
ns := NewNamespace("")
|
||||
@@ -135,8 +145,9 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
|
||||
|
||||
It("should fail when group is ignored", func() {
|
||||
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) {
|
||||
configuration.Spec.UserNames = []string{}
|
||||
configuration.Spec.UserGroups = []string{}
|
||||
configuration.Spec.UserNames = []string{tnt.Spec.Owners[0].Name}
|
||||
configuration.Spec.Users = []api.UserSpec{{Kind: api.UserOwner, Name: tnt.Spec.Owners[0].Name}}
|
||||
configuration.Spec.IgnoreUserWithGroups = []string{"projectcapsule.dev"}
|
||||
})
|
||||
|
||||
|
||||
33
hack/distro/fluxcd/kustomization.yaml
Normal file
33
hack/distro/fluxcd/kustomization.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- https://github.com/fluxcd/flux2/releases/download/v2.4.0/install.yaml
|
||||
patches:
|
||||
- patch: |
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/args/-
|
||||
value: --no-cross-namespace-refs=true
|
||||
target:
|
||||
kind: Deployment
|
||||
name: "(kustomize-controller|helm-controller|notification-controller|image-reflector-controller|image-automation-controller)"
|
||||
- patch: |
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/args/-
|
||||
value: --no-remote-bases=true
|
||||
target:
|
||||
kind: Deployment
|
||||
name: "kustomize-controller"
|
||||
- patch: |
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/args/-
|
||||
value: --default-service-account=default
|
||||
target:
|
||||
kind: Deployment
|
||||
name: "(kustomize-controller|helm-controller)"
|
||||
- patch: |
|
||||
- op: replace
|
||||
path: /spec/replicas
|
||||
value: 0
|
||||
target:
|
||||
kind: Deployment
|
||||
name: "(notification-controller|image-reflector-controller|image-automation-controller)"
|
||||
43
hack/distro/host-proxy/deploy.yaml
Normal file
43
hack/distro/host-proxy/deploy.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: capsule
|
||||
namespace: monitoring-system
|
||||
labels:
|
||||
app: capsule
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: capsule
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: capsule
|
||||
annotations:
|
||||
prometheus.io/scrape: 'true'
|
||||
prometheus.io/path: '/metrics'
|
||||
prometheus.io/port: '8080'
|
||||
profiles.grafana.com/memory.scrape: "true"
|
||||
profiles.grafana.com/memory.port: "8082"
|
||||
profiles.grafana.com/cpu.scrape: "true"
|
||||
profiles.grafana.com/cpu.port: "8082"
|
||||
profiles.grafana.com/goroutine.scrape: "true"
|
||||
profiles.grafana.com/goroutine.port: "8082"
|
||||
spec:
|
||||
containers:
|
||||
- name: tcp-proxy
|
||||
image: alpine/socat
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
- containerPort: 8082
|
||||
command: ["sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
set -e
|
||||
echo "Starting TCP proxy to ${LAPTOP_HOST_IP}..."
|
||||
# Forward 8080 -> TARGET_HOST:8080
|
||||
socat TCP-LISTEN:8080,fork,reuseaddr TCP:${LAPTOP_HOST_IP}:8080 &
|
||||
# Forward 8082 -> TARGET_HOST:8082
|
||||
socat TCP-LISTEN:8082,fork,reuseaddr TCP:${LAPTOP_HOST_IP}:8082 &
|
||||
wait
|
||||
5
hack/distro/host-proxy/kustomization.yaml
Normal file
5
hack/distro/host-proxy/kustomization.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- deploy.yaml
|
||||
- servicemonitor.yaml
|
||||
33
hack/distro/host-proxy/servicemonitor.yaml
Normal file
33
hack/distro/host-proxy/servicemonitor.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: capsule
|
||||
namespace: monitoring-system
|
||||
labels:
|
||||
app: capsule
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
app: capsule
|
||||
ports:
|
||||
- name: metrics
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: capsule
|
||||
namespace: monitoring-system
|
||||
labels:
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: capsule
|
||||
endpoints:
|
||||
- port: metrics
|
||||
path: /metrics
|
||||
interval: 5s
|
||||
515
hack/distro/monitoring/dashboards/ctrl-runtime-admission.json
Normal file
515
hack/distro/monitoring/dashboards/ctrl-runtime-admission.json
Normal file
@@ -0,0 +1,515 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"target": {
|
||||
"limit": 100,
|
||||
"matchAny": false,
|
||||
"tags": [],
|
||||
"type": "dashboard"
|
||||
},
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Profiling performance-related metrics based on controller-runtime.",
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": 10,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 6,
|
||||
"panels": [],
|
||||
"title": "Overview",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": true,
|
||||
"expr": "controller_runtime_active_workers{job=~\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "{{controller}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Active Workers",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "normal"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 1
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": true,
|
||||
"expr": "sum by (result) (rate(controller_runtime_reconcile_total{job=~\"$job\"}[$__rate_interval]))",
|
||||
"interval": "",
|
||||
"legendFormat": "{{result}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Reoncile Rate",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 10
|
||||
},
|
||||
"id": 8,
|
||||
"panels": [],
|
||||
"repeat": "Webhook",
|
||||
"title": "Webhook \"$Webhook\" Status",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 11
|
||||
},
|
||||
"id": 10,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": true,
|
||||
"expr": "rate(controller_runtime_webhook_latency_seconds_count{job=~\"$job\", webhook=\"$Webhook\"}[$__rate_interval])",
|
||||
"interval": "",
|
||||
"legendFormat": "Request Rate",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Webhook Request Rate $Webhook",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 20
|
||||
},
|
||||
"id": 12,
|
||||
"options": {
|
||||
"calculate": false,
|
||||
"calculation": {},
|
||||
"cellGap": 2,
|
||||
"cellValues": {},
|
||||
"color": {
|
||||
"exponent": 0.5,
|
||||
"fill": "#b4ff00",
|
||||
"mode": "scheme",
|
||||
"reverse": false,
|
||||
"scale": "exponential",
|
||||
"scheme": "Oranges",
|
||||
"steps": 128
|
||||
},
|
||||
"exemplars": {
|
||||
"color": "rgba(255,0,255,0.7)"
|
||||
},
|
||||
"filterValues": {
|
||||
"le": 1e-9
|
||||
},
|
||||
"legend": {
|
||||
"show": false
|
||||
},
|
||||
"rowsFrame": {
|
||||
"layout": "auto"
|
||||
},
|
||||
"showValue": "never",
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"showColorScale": false,
|
||||
"yHistogram": false
|
||||
},
|
||||
"yAxis": {
|
||||
"axisPlacement": "left",
|
||||
"reverse": false,
|
||||
"unit": "short"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": true,
|
||||
"expr": "rate(controller_runtime_webhook_latency_seconds_bucket{job=~\"$job\", webhook=\"$Webhook\"}[$__rate_interval])",
|
||||
"format": "heatmap",
|
||||
"interval": "",
|
||||
"legendFormat": "{{le}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Reconcile Time Buckets",
|
||||
"type": "heatmap"
|
||||
}
|
||||
],
|
||||
"preload": false,
|
||||
"refresh": "30s",
|
||||
"schemaVersion": 42,
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"text": [
|
||||
"capsule"
|
||||
],
|
||||
"value": [
|
||||
"capsule"
|
||||
]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"definition": "label_values(controller_runtime_webhook_requests_total,job)",
|
||||
"includeAll": true,
|
||||
"label": "Job",
|
||||
"multi": true,
|
||||
"name": "job",
|
||||
"options": [],
|
||||
"query": {
|
||||
"qryType": 1,
|
||||
"query": "label_values(controller_runtime_webhook_requests_total,job)",
|
||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
||||
},
|
||||
"refresh": 2,
|
||||
"regex": "",
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": [
|
||||
"All"
|
||||
],
|
||||
"value": [
|
||||
"$__all"
|
||||
]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"definition": "label_values(controller_runtime_webhook_requests_total{job=~\"$job\"},webhook)",
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "Webhook",
|
||||
"options": [],
|
||||
"query": {
|
||||
"qryType": 1,
|
||||
"query": "label_values(controller_runtime_webhook_requests_total{job=~\"$job\"},webhook)",
|
||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"type": "query"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Controller Runtime Webhooks Detail",
|
||||
"uid": "0L6Y8KEnk",
|
||||
"version": 1
|
||||
}
|
||||
546
hack/distro/monitoring/dashboards/ctrl-runtime.json
Normal file
546
hack/distro/monitoring/dashboards/ctrl-runtime.json
Normal file
@@ -0,0 +1,546 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"target": {
|
||||
"limit": 100,
|
||||
"matchAny": false,
|
||||
"tags": [],
|
||||
"type": "dashboard"
|
||||
},
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Profiling performance-related metrics based on controller-runtime.",
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": 22,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 6,
|
||||
"panels": [],
|
||||
"title": "Overview",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 1
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "right",
|
||||
"showLegend": true,
|
||||
"sortBy": "Last *",
|
||||
"sortDesc": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": true,
|
||||
"expr": "controller_runtime_active_workers{job=~\"$job\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "{{controller}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum (controller_runtime_active_workers{job=~\"$job\"})",
|
||||
"hide": false,
|
||||
"instant": false,
|
||||
"legendFormat": "Total",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Active Workers",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "normal"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 1
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "right",
|
||||
"showLegend": true,
|
||||
"sortBy": "Last *",
|
||||
"sortDesc": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum by (result) (\n increase(controller_runtime_reconcile_total{job=~\"$job\"}[$__rate_interval])\n)",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
"legendFormat": "{{result}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(\n increase(controller_runtime_reconcile_total{job=~\"$job\"}[$__rate_interval])\n)",
|
||||
"hide": false,
|
||||
"instant": false,
|
||||
"legendFormat": "Total",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Reoncile Rate",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"collapsed": false,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 10
|
||||
},
|
||||
"id": 8,
|
||||
"panels": [],
|
||||
"repeat": "Controller",
|
||||
"title": "Controller \"$Controller\" Status",
|
||||
"type": "row"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 11
|
||||
},
|
||||
"id": 10,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": true,
|
||||
"expr": "sum by (result) (\n increase(controller_runtime_reconcile_total{job=~\"$job\", controller=~\"$Controller\"}[$__rate_interval])\n)\n",
|
||||
"interval": "",
|
||||
"legendFormat": "{{result}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Reconcile Rate",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 20
|
||||
},
|
||||
"id": 12,
|
||||
"options": {
|
||||
"calculate": false,
|
||||
"calculation": {},
|
||||
"cellGap": 2,
|
||||
"cellValues": {},
|
||||
"color": {
|
||||
"exponent": 0.5,
|
||||
"fill": "#b4ff00",
|
||||
"mode": "scheme",
|
||||
"reverse": false,
|
||||
"scale": "exponential",
|
||||
"scheme": "Oranges",
|
||||
"steps": 128
|
||||
},
|
||||
"exemplars": {
|
||||
"color": "rgba(255,0,255,0.7)"
|
||||
},
|
||||
"filterValues": {
|
||||
"le": 1e-9
|
||||
},
|
||||
"legend": {
|
||||
"show": false
|
||||
},
|
||||
"rowsFrame": {
|
||||
"layout": "auto"
|
||||
},
|
||||
"showValue": "never",
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"showColorScale": false,
|
||||
"yHistogram": false
|
||||
},
|
||||
"yAxis": {
|
||||
"axisPlacement": "left",
|
||||
"reverse": false,
|
||||
"unit": "short"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": true,
|
||||
"expr": "rate(controller_runtime_reconcile_time_seconds_bucket{job=~'$job', controller=~'$Controller'}[$__rate_interval])",
|
||||
"format": "heatmap",
|
||||
"interval": "",
|
||||
"legendFormat": "{{le}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Reconcile Time Buckets",
|
||||
"type": "heatmap"
|
||||
}
|
||||
],
|
||||
"preload": false,
|
||||
"refresh": "10s",
|
||||
"schemaVersion": 42,
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"text": "All",
|
||||
"value": [
|
||||
"$__all"
|
||||
]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"definition": "label_values(controller_runtime_active_workers,job)",
|
||||
"includeAll": true,
|
||||
"label": "Job",
|
||||
"multi": true,
|
||||
"name": "job",
|
||||
"options": [],
|
||||
"query": {
|
||||
"qryType": 1,
|
||||
"query": "label_values(controller_runtime_active_workers,job)",
|
||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
||||
},
|
||||
"refresh": 2,
|
||||
"regex": "",
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": "All",
|
||||
"value": [
|
||||
"$__all"
|
||||
]
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"definition": "label_values(controller_runtime_active_workers{job=~\"$job\"},controller)",
|
||||
"includeAll": true,
|
||||
"multi": true,
|
||||
"name": "Controller",
|
||||
"options": [],
|
||||
"query": {
|
||||
"qryType": 1,
|
||||
"query": "label_values(controller_runtime_active_workers{job=~\"$job\"},controller)",
|
||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"type": "query"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-15m",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Controller Runtime Controllers Detail",
|
||||
"uid": "5J4pyKEnk",
|
||||
"version": 1
|
||||
}
|
||||
22
hack/distro/monitoring/dashboards/kustomization.yaml
Normal file
22
hack/distro/monitoring/dashboards/kustomization.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: monitoring-system
|
||||
commonLabels:
|
||||
release: prometheus
|
||||
generatorOptions:
|
||||
annotations:
|
||||
k8s-sidecar-target-directory: /tmp/dashboards/Controller Development
|
||||
labels:
|
||||
grafana_dashboard: "1"
|
||||
disableNameSuffixHash: true
|
||||
configMapGenerator:
|
||||
- name: ctrl-runtime
|
||||
files:
|
||||
- ctrl-runtime.json
|
||||
- name: ctrl-runtime-admission
|
||||
files:
|
||||
- ctrl-runtime-admission.json
|
||||
- name: perf
|
||||
files:
|
||||
- performance.json
|
||||
4466
hack/distro/monitoring/dashboards/performance.json
Normal file
4466
hack/distro/monitoring/dashboards/performance.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- release.flux.yaml
|
||||
100
hack/distro/monitoring/kube-prometheus-stack/release.flux.yaml
Normal file
100
hack/distro/monitoring/kube-prometheus-stack/release.flux.yaml
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: kube-prometheus-stack
|
||||
namespace: flux-system
|
||||
spec:
|
||||
serviceAccountName: kustomize-controller
|
||||
interval: 30s
|
||||
timeout: 10m
|
||||
targetNamespace: monitoring-system
|
||||
releaseName: "kube-prometheus-stack"
|
||||
chart:
|
||||
spec:
|
||||
chart: kube-prometheus-stack
|
||||
version: "79.11.0"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: kube-prometheus-stack
|
||||
interval: 24h
|
||||
install:
|
||||
createNamespace: true
|
||||
remediation:
|
||||
retries: -1
|
||||
upgrade:
|
||||
remediation:
|
||||
remediateLastFailure: true
|
||||
retries: -1
|
||||
driftDetection:
|
||||
mode: enabled
|
||||
values:
|
||||
grafana:
|
||||
additionalDataSources:
|
||||
- name: Pyroscope
|
||||
type: grafana-pyroscope-datasource
|
||||
uid: pyroscope
|
||||
url: http://pyroscope.{{ $.Release.Namespace }}.svc.cluster.local.:4040/
|
||||
adminPassword: admin
|
||||
global:
|
||||
dnsService: "kube-dns"
|
||||
dnsNamespace: "kube-system"
|
||||
assertNoLeakedSecrets: false
|
||||
deploymentStrategy:
|
||||
type: Recreate
|
||||
persistence:
|
||||
enabled: false
|
||||
initChownData:
|
||||
enabled: false
|
||||
plugins:
|
||||
- grafana-llm-app
|
||||
- grafana-resourcesexporter-app
|
||||
- grafana-pyroscope-app
|
||||
- grafana-exploretraces-app
|
||||
env:
|
||||
GF_AUTH_ANONYMOUS_ENABLED: "true"
|
||||
GF_AUTH_ANONYMOUS_ORG_ROLE: "Admin"
|
||||
GF_DIAGNOSTICS_PROFILING_ENABLED: "true"
|
||||
GF_DIAGNOSTICS_PROFILING_ADDR: "0.0.0.0"
|
||||
GF_DIAGNOSTICS_PROFILING_PORT: "9094"
|
||||
sidecar:
|
||||
enableUniqueFilenames: true
|
||||
datasources:
|
||||
enabled: true
|
||||
dashboards:
|
||||
enabled: true
|
||||
folderAnnotation: "k8s-sidecar-target-directory"
|
||||
annotations:
|
||||
k8s-sidecar-target-directory: /tmp/dashboards/Kube Prometheus Stack
|
||||
provider:
|
||||
foldersFromFilesStructure: true
|
||||
# https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/
|
||||
grafana.ini:
|
||||
analytics:
|
||||
reporting_enabled: false
|
||||
check_for_updates: false
|
||||
check_for_plugin_updates: false
|
||||
security:
|
||||
disable_gravatar: true
|
||||
cookie_secure: true
|
||||
cookie_samesite: lax
|
||||
strict_transport_security: true
|
||||
strict_transport_security_preload: true
|
||||
strict_transport_security_subdomains: true
|
||||
content_security_policy: true
|
||||
auth:
|
||||
disable_login_form: false
|
||||
users:
|
||||
allow_sign_up: true
|
||||
auto_assign_org: true
|
||||
server:
|
||||
root_url: "http://localhost:9090"
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: kube-prometheus-stack
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 24h0m0s
|
||||
url: https://prometheus-community.github.io/helm-charts
|
||||
6
hack/distro/monitoring/kustomization.yaml
Normal file
6
hack/distro/monitoring/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ns.yaml
|
||||
- kube-prometheus-stack/
|
||||
- pyroscope/
|
||||
4
hack/distro/monitoring/ns.yaml
Normal file
4
hack/distro/monitoring/ns.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring-system
|
||||
4
hack/distro/monitoring/pyroscope/kustomization.yaml
Normal file
4
hack/distro/monitoring/pyroscope/kustomization.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- release.flux.yaml
|
||||
57
hack/distro/monitoring/pyroscope/release.flux.yaml
Normal file
57
hack/distro/monitoring/pyroscope/release.flux.yaml
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: pyroscope
|
||||
namespace: flux-system
|
||||
spec:
|
||||
serviceAccountName: kustomize-controller
|
||||
interval: 30s
|
||||
timeout: 10m
|
||||
targetNamespace: monitoring-system
|
||||
releaseName: "pyroscope"
|
||||
chart:
|
||||
spec:
|
||||
chart: pyroscope
|
||||
version: "1.16.0"
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: pyroscope
|
||||
interval: 24h
|
||||
install:
|
||||
createNamespace: true
|
||||
remediation:
|
||||
retries: -1
|
||||
upgrade:
|
||||
remediation:
|
||||
remediateLastFailure: true
|
||||
retries: -1
|
||||
driftDetection:
|
||||
mode: enabled
|
||||
values:
|
||||
global:
|
||||
dnsService: "kube-dns"
|
||||
dnsNamespace: "kube-system"
|
||||
clusterLabelOverride: "kind"
|
||||
pyroscope:
|
||||
persistence:
|
||||
size: 10Gi
|
||||
podAnnotations:
|
||||
profiles.grafana.com/memory.scrape: "false"
|
||||
profiles.grafana.com/goroutine.scrape: "false"
|
||||
profiles.grafana.com/cpu.scrape: "false"
|
||||
alloy:
|
||||
controller:
|
||||
podAnnotations:
|
||||
profiles.grafana.com/memory.scrape: "false"
|
||||
profiles.grafana.com/goroutine.scrape: "false"
|
||||
profiles.grafana.com/cpu.scrape: "false"
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: pyroscope
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 24h0m0s
|
||||
url: https://grafana.github.io/helm-charts
|
||||
@@ -21,8 +21,9 @@ import (
|
||||
// Ensuring all the LimitRange are applied to each Namespace handled by the Tenant.
|
||||
func (r *Manager) syncLimitRanges(ctx context.Context, tenant *capsulev1beta2.Tenant) error { //nolint:dupl
|
||||
// getting requested LimitRange keys
|
||||
keys := make([]string, 0, len(tenant.Spec.LimitRanges.Items))
|
||||
keys := make([]string, 0, len(tenant.Spec.LimitRanges.Items)) //nolint:staticcheck
|
||||
|
||||
//nolint:staticcheck
|
||||
for i := range tenant.Spec.LimitRanges.Items {
|
||||
keys = append(keys, strconv.Itoa(i))
|
||||
}
|
||||
@@ -45,6 +46,7 @@ func (r *Manager) syncLimitRange(ctx context.Context, tenant *capsulev1beta2.Ten
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
for i, spec := range tenant.Spec.LimitRanges.Items { //nolint:dupl
|
||||
target := &corev1.LimitRange{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
||||
@@ -285,14 +285,16 @@ func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ct
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Ensuring LimitRange resources
|
||||
r.Log.V(4).Info("Starting processing of Limit Ranges", "items", len(instance.Spec.LimitRanges.Items))
|
||||
r.Log.V(4).Info("Starting processing of Limit Ranges", "items", len(instance.Spec.LimitRanges.Items)) //nolint:staticcheck
|
||||
|
||||
if err = r.syncLimitRanges(ctx, instance); err != nil {
|
||||
err = fmt.Errorf("cannot sync limitrange items: %w", err)
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Ensuring ResourceQuota resources
|
||||
r.Log.V(4).Info("Starting processing of Resource Quotas", "items", len(instance.Spec.ResourceQuota.Items))
|
||||
|
||||
@@ -301,6 +303,7 @@ func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ct
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Ensuring RoleBinding resources
|
||||
r.Log.V(4).Info("Ensuring RoleBindings for Owners and Tenant")
|
||||
|
||||
|
||||
@@ -21,8 +21,9 @@ import (
|
||||
// Ensuring all the NetworkPolicies are applied to each Namespace handled by the Tenant.
|
||||
func (r *Manager) syncNetworkPolicies(ctx context.Context, tenant *capsulev1beta2.Tenant) error { //nolint:dupl
|
||||
// getting requested NetworkPolicy keys
|
||||
keys := make([]string, 0, len(tenant.Spec.NetworkPolicies.Items))
|
||||
keys := make([]string, 0, len(tenant.Spec.NetworkPolicies.Items)) //nolint:staticcheck
|
||||
|
||||
//nolint:staticcheck
|
||||
for i := range tenant.Spec.NetworkPolicies.Items {
|
||||
keys = append(keys, strconv.Itoa(i))
|
||||
}
|
||||
@@ -45,6 +46,7 @@ func (r *Manager) syncNetworkPolicy(ctx context.Context, tenant *capsulev1beta2.
|
||||
return err
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
for i, spec := range tenant.Spec.NetworkPolicies.Items { //nolint:dupl
|
||||
target := &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
||||
71
internal/webhook/cfg/warnings.go
Normal file
71
internal/webhook/cfg/warnings.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cfg
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
capsulewebhook "github.com/projectcapsule/capsule/internal/webhook"
|
||||
"github.com/projectcapsule/capsule/internal/webhook/utils"
|
||||
)
|
||||
|
||||
type warningHandler struct{}
|
||||
|
||||
func WarningHandler() capsulewebhook.Handler {
|
||||
return &warningHandler{}
|
||||
}
|
||||
|
||||
func (h *warningHandler) OnCreate(_ client.Client, decoder admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
|
||||
return func(_ context.Context, req admission.Request) *admission.Response {
|
||||
return h.handle(decoder, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *warningHandler) OnDelete(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func {
|
||||
return func(context.Context, admission.Request) *admission.Response {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *warningHandler) OnUpdate(_ client.Client, decoder admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
|
||||
return func(_ context.Context, req admission.Request) *admission.Response {
|
||||
return h.handle(decoder, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *warningHandler) handle(decoder admission.Decoder, req admission.Request) *admission.Response {
|
||||
config := &capsulev1beta2.CapsuleConfiguration{}
|
||||
if err := decoder.Decode(req, config); err != nil {
|
||||
return utils.ErroredResponse(err)
|
||||
}
|
||||
|
||||
response := &admission.Response{
|
||||
AdmissionResponse: admissionv1.AdmissionResponse{
|
||||
UID: req.UID,
|
||||
Allowed: true,
|
||||
},
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if len(config.Spec.UserGroups) > 0 {
|
||||
response.Warnings = append(response.Warnings,
|
||||
"The field `userGroups` is deprecated and will be removed in a future release. Please migrate to the `users` field. See: https://projectcapsule.dev/docs/operating/setup/configuration/#users.",
|
||||
)
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if len(config.Spec.UserNames) > 0 {
|
||||
response.Warnings = append(response.Warnings,
|
||||
"The field `userNames` is deprecated and will be removed in a future release. Please migrate to the `users` field. See: https://projectcapsule.dev/docs/operating/setup/configuration/#users.",
|
||||
)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
@@ -108,6 +108,7 @@ func appendHostnameError(spec api.AllowedListSpec) (append string) {
|
||||
append = fmt.Sprintf(", specify one of the following (%s)", strings.Join(spec.Exact, ", "))
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if len(spec.Regex) > 0 {
|
||||
append += fmt.Sprintf(", or matching the regex %s", spec.Regex)
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ func (r *hostnames) validateHostnames(tenant capsulev1beta2.Tenant, hostnames se
|
||||
|
||||
var notMatchingHostnames []string
|
||||
|
||||
//nolint:staticcheck
|
||||
if allowedRegex := tenant.Spec.IngressOptions.AllowedHostnames.Regex; len(allowedRegex) > 0 {
|
||||
for currentHostname := range hostnames {
|
||||
matched, _ = regexp.MatchString(allowedRegex, currentHostname)
|
||||
|
||||
@@ -43,6 +43,7 @@ func (f registryClassForbiddenError) Error() (err string) {
|
||||
extra = append(extra, fmt.Sprintf("use one from the following list (%s)", strings.Join(f.spec.Exact, ", ")))
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if len(f.spec.Regex) > 0 {
|
||||
extra = append(extra, fmt.Sprintf(" use one matching the following regex (%s)", f.spec.Regex))
|
||||
}
|
||||
|
||||
24
internal/webhook/route/config.go
Normal file
24
internal/webhook/route/config.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
capsulewebhook "github.com/projectcapsule/capsule/internal/webhook"
|
||||
)
|
||||
|
||||
type configValidating struct {
|
||||
handlers []capsulewebhook.Handler
|
||||
}
|
||||
|
||||
func ConfigValidation(handler ...capsulewebhook.Handler) capsulewebhook.Webhook {
|
||||
return &configValidating{handlers: handler}
|
||||
}
|
||||
|
||||
func (w *configValidating) GetHandlers() []capsulewebhook.Handler {
|
||||
return w.handlers
|
||||
}
|
||||
|
||||
func (w *configValidating) GetPath() string {
|
||||
return "/config/validating"
|
||||
}
|
||||
@@ -49,6 +49,7 @@ func (h *containerRegistryRegexHandler) OnUpdate(_ client.Client, decoder admiss
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
func (h *containerRegistryRegexHandler) validate(decoder admission.Decoder, req admission.Request) *admission.Response {
|
||||
tenant := &capsulev1beta2.Tenant{}
|
||||
if err := decoder.Decode(req, tenant); err != nil {
|
||||
|
||||
@@ -49,6 +49,7 @@ func (h *hostnameRegexHandler) OnUpdate(_ client.Client, decoder admission.Decod
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
func (h *hostnameRegexHandler) validate(decoder admission.Decoder, req admission.Request) *admission.Response {
|
||||
tenant := &capsulev1beta2.Tenant{}
|
||||
if err := decoder.Decode(req, tenant); err != nil {
|
||||
|
||||
@@ -49,6 +49,7 @@ func (h *ingressClassRegexHandler) OnUpdate(_ client.Client, decoder admission.D
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
func (h *ingressClassRegexHandler) validate(decoder admission.Decoder, req admission.Request) *admission.Response {
|
||||
tenant := &capsulev1beta2.Tenant{}
|
||||
if err := decoder.Decode(req, tenant); err != nil {
|
||||
|
||||
@@ -49,6 +49,7 @@ func (h *storageClassRegexHandler) OnUpdate(_ client.Client, decoder admission.D
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
func (h *storageClassRegexHandler) validate(decoder admission.Decoder, req admission.Request) *admission.Response {
|
||||
tenant := &capsulev1beta2.Tenant{}
|
||||
if err := decoder.Decode(req, tenant); err != nil {
|
||||
|
||||
@@ -63,32 +63,53 @@ func (h *warningHandler) handle(tnt *capsulev1beta2.Tenant, decoder admission.De
|
||||
},
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if len(tnt.Spec.LimitRanges.Items) > 0 {
|
||||
response.Warnings = append(response.Warnings, "Limitranges are deprecated and will be removed int the future. You need to consider to migrate to TenantReplications: https://projectcapsule.dev/docs/tenants/enforcement/#limitrange-distribution-with-tenantreplications.")
|
||||
response.Warnings = append(response.Warnings,
|
||||
"The field `limitRanges` is deprecated and will be removed in a future release. Please migrate to TenantReplications. See: https://projectcapsule.dev/docs/tenants/enforcement/#limitrange-distribution-with-tenantreplications.",
|
||||
)
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if len(tnt.Spec.NetworkPolicies.Items) > 0 {
|
||||
response.Warnings = append(response.Warnings, "NetworkPolicies are deprecated and will be removed int the future. You need to consider to migrate to TenantReplications: https://projectcapsule.dev/docs/tenants/enforcement/#networkpolicy-distribution-with-tenantreplications.")
|
||||
response.Warnings = append(response.Warnings,
|
||||
"The field `networkPolicies` is deprecated and will be removed in a future release. Please migrate to TenantReplications. See: https://projectcapsule.dev/docs/tenants/enforcement/#networkpolicy-distribution-with-tenantreplications.",
|
||||
)
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if tnt.Spec.NamespaceOptions != nil && tnt.Spec.NamespaceOptions.AdditionalMetadata != nil {
|
||||
response.Warnings = append(response.Warnings, "additionalMetadata is deprecated and will be removed int the future. You need to consider to migrate to AdditionalMetadataList: https://projectcapsule.dev/docs/tenants/enforcement/#additionalmetadatalist.")
|
||||
response.Warnings = append(response.Warnings,
|
||||
"The field `additionalMetadata` is deprecated and will be removed in a future release. Please migrate to `additionalMetadataList`. See: https://projectcapsule.dev/docs/tenants/metadata/#additionalmetadatalist.",
|
||||
)
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if tnt.Spec.StorageClasses != nil && tnt.Spec.StorageClasses.Regex != "" {
|
||||
response.Warnings = append(response.Warnings, "Using the regex property to select StorageClasses is deprecated and will be removed int the future.")
|
||||
response.Warnings = append(response.Warnings,
|
||||
"The `regex` selector for StorageClasses is deprecated and will be removed in a future release.",
|
||||
)
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if tnt.Spec.GatewayOptions.AllowedClasses != nil && tnt.Spec.GatewayOptions.AllowedClasses.Regex != "" {
|
||||
response.Warnings = append(response.Warnings, "Using the regex property to select GatewayClasses is deprecated and will be removed int the future.")
|
||||
response.Warnings = append(response.Warnings,
|
||||
"The `regex` selector for GatewayClasses is deprecated and will be removed in a future release.",
|
||||
)
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if tnt.Spec.PriorityClasses != nil && tnt.Spec.PriorityClasses.Regex != "" {
|
||||
response.Warnings = append(response.Warnings, "Using the regex property to select PriorityClasses is deprecated and will be removed int the future.")
|
||||
response.Warnings = append(response.Warnings,
|
||||
"The `regex` selector for PriorityClasses is deprecated and will be removed in a future release.",
|
||||
)
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if tnt.Spec.RuntimeClasses != nil && tnt.Spec.RuntimeClasses.Regex != "" {
|
||||
response.Warnings = append(response.Warnings, "Using the regex property to select RuntimeClasses is deprecated and will be removed int the future.")
|
||||
response.Warnings = append(response.Warnings,
|
||||
"The `regex` selector for RuntimeClasses is deprecated and will be removed in a future release.",
|
||||
)
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
@@ -29,6 +29,7 @@ func AllowedValuesErrorMessage(allowed api.SelectorAllowedListSpec, err string)
|
||||
extra = append(extra, fmt.Sprintf("use one from the following list (%s)", strings.Join(allowed.Exact, ", ")))
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if len(allowed.Regex) > 0 {
|
||||
extra = append(extra, fmt.Sprintf("use one matching the following regex (%s)", allowed.Regex))
|
||||
}
|
||||
|
||||
@@ -58,7 +58,9 @@ func (in *SelectorAllowedListSpec) SelectorMatch(obj client.Object) bool {
|
||||
type AllowedListSpec struct {
|
||||
// Match exact elements which are allowed as class names within this tenant
|
||||
Exact []string `json:"allowed,omitempty"`
|
||||
// Match elements by regex (DEPRECATED)
|
||||
// Deprecated: will be removed in a future release
|
||||
//
|
||||
// Match elements by regex.
|
||||
Regex string `json:"allowedRegex,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ func (o OwnerStatusListSpec) IsOwner(name string, groups []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
//nolint:dupl
|
||||
func (o OwnerStatusListSpec) FindOwner(name string, kind OwnerKind) (CoreOwnerSpec, bool) {
|
||||
// Sort in-place by (Kind.String(), Name).
|
||||
sort.Sort(GetByKindAndName(o))
|
||||
|
||||
@@ -8,39 +8,84 @@ import (
|
||||
)
|
||||
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
type UserListSpec []UserSpec
|
||||
|
||||
func (u UserListSpec) IsPresent(name string, groups []string) bool {
|
||||
groupSet := make(map[string]struct{}, len(groups))
|
||||
for _, g := range groups {
|
||||
groupSet[g] = struct{}{}
|
||||
}
|
||||
|
||||
for _, user := range u {
|
||||
switch user.Kind {
|
||||
case UserOwner, ServiceAccountOwner:
|
||||
if name == user.Name {
|
||||
if user.Name == name {
|
||||
return true
|
||||
}
|
||||
case GroupOwner:
|
||||
for _, group := range groups {
|
||||
if group == user.Name {
|
||||
if _, ok := groupSet[user.Name]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (o UserListSpec) FindUser(name string, kind OwnerKind) (owner UserSpec) {
|
||||
//nolint:dupl
|
||||
func (o UserListSpec) FindUser(name string, kind OwnerKind) (UserSpec, bool) {
|
||||
sort.Sort(ByKindName(o))
|
||||
i := sort.Search(len(o), func(i int) bool {
|
||||
return o[i].Kind >= kind && o[i].Name >= name
|
||||
|
||||
targetKind := kind.String()
|
||||
n := len(o)
|
||||
|
||||
idx := sort.Search(n, func(i int) bool {
|
||||
ki := o[i].Kind.String()
|
||||
|
||||
switch {
|
||||
case ki > targetKind:
|
||||
return true
|
||||
case ki < targetKind:
|
||||
return false
|
||||
default:
|
||||
return o[i].Name >= name
|
||||
}
|
||||
})
|
||||
|
||||
if i < len(o) && o[i].Kind == kind && o[i].Name == name {
|
||||
return o[i]
|
||||
if idx < n &&
|
||||
o[idx].Kind.String() == targetKind &&
|
||||
o[idx].Name == name {
|
||||
return o[idx], true
|
||||
}
|
||||
|
||||
return owner
|
||||
return UserSpec{}, false
|
||||
}
|
||||
|
||||
func (o UserListSpec) GetByKinds(kinds []OwnerKind) []string {
|
||||
if len(o) == 0 || len(kinds) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
kindSet := make(map[OwnerKind]struct{}, len(kinds))
|
||||
for _, k := range kinds {
|
||||
kindSet[k] = struct{}{}
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(o))
|
||||
|
||||
for _, u := range o {
|
||||
if _, ok := kindSet[u.Kind]; ok {
|
||||
names = append(names, u.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
type ByKindName UserListSpec
|
||||
|
||||
325
pkg/api/users_list_test.go
Normal file
325
pkg/api/users_list_test.go
Normal file
@@ -0,0 +1,325 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
func linearFindUser(list api.UserListSpec, name string, kind api.OwnerKind) (api.UserSpec, bool) {
|
||||
for _, u := range list {
|
||||
if u.Kind == kind && u.Name == name {
|
||||
return u, true
|
||||
}
|
||||
}
|
||||
return api.UserSpec{}, false
|
||||
}
|
||||
|
||||
func slowIsPresent(u api.UserListSpec, name string, groups []string) bool {
|
||||
for _, user := range u {
|
||||
switch user.Kind {
|
||||
case api.UserOwner, api.ServiceAccountOwner:
|
||||
if name == user.Name {
|
||||
return true
|
||||
}
|
||||
case api.GroupOwner:
|
||||
for _, group := range groups {
|
||||
if group == user.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestByKindNameOrdering_UserListSpec(t *testing.T) {
|
||||
u := api.UserListSpec{
|
||||
api.UserSpec{Name: "b", Kind: api.ServiceAccountOwner},
|
||||
api.UserSpec{Name: "z", Kind: api.UserOwner},
|
||||
api.UserSpec{Name: "a", Kind: api.GroupOwner},
|
||||
api.UserSpec{Name: "a", Kind: api.UserOwner},
|
||||
}
|
||||
|
||||
// Sort using production ordering
|
||||
got := append(api.UserListSpec(nil), u...)
|
||||
sort.Sort(api.ByKindName(got))
|
||||
|
||||
// Manually sorted expectation using the same logic.
|
||||
want := append(api.UserListSpec(nil), u...)
|
||||
sort.Slice(want, func(i, j int) bool {
|
||||
if want[i].Kind.String() != want[j].Kind.String() {
|
||||
return want[i].Kind.String() < want[j].Kind.String()
|
||||
}
|
||||
return want[i].Name < want[j].Name
|
||||
})
|
||||
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("length mismatch: got %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i := range got {
|
||||
if !reflect.DeepEqual(got[i], want[i]) {
|
||||
t.Fatalf("ordering mismatch at %d: got %+v, want %+v", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindUser_Randomized(t *testing.T) {
|
||||
rnd := rand.New(rand.NewSource(42))
|
||||
|
||||
ownerKinds := []api.OwnerKind{
|
||||
api.GroupOwner,
|
||||
api.UserOwner,
|
||||
api.ServiceAccountOwner,
|
||||
}
|
||||
|
||||
const (
|
||||
numLists = 200
|
||||
maxLength = 40
|
||||
numLookupsPerList = 80
|
||||
)
|
||||
|
||||
for listIdx := 0; listIdx < numLists; listIdx++ {
|
||||
var list api.UserListSpec
|
||||
n := rnd.Intn(maxLength)
|
||||
for i := 0; i < n; i++ {
|
||||
k := ownerKinds[rnd.Intn(len(ownerKinds))]
|
||||
list = append(list, api.UserSpec{
|
||||
Name: randomName(rnd, 3+rnd.Intn(4)), // length 3–6
|
||||
Kind: k,
|
||||
})
|
||||
}
|
||||
|
||||
for lookupIdx := 0; lookupIdx < numLookupsPerList; lookupIdx++ {
|
||||
var qName string
|
||||
var qKind api.OwnerKind
|
||||
|
||||
if len(list) > 0 && rnd.Float64() < 0.6 {
|
||||
// 60% of lookups: pick a real element, must be found
|
||||
pick := list[rnd.Intn(len(list))]
|
||||
qName = pick.Name
|
||||
qKind = pick.Kind
|
||||
} else {
|
||||
// 40%: random query, may or may not exist
|
||||
qName = randomName(rnd, 3+rnd.Intn(4))
|
||||
qKind = ownerKinds[rnd.Intn(len(ownerKinds))]
|
||||
}
|
||||
|
||||
listCopy := append(api.UserListSpec(nil), list...) // FindUser sorts in-place
|
||||
gotUser, gotFound := listCopy.FindUser(qName, qKind)
|
||||
wantUser, wantFound := linearFindUser(list, qName, qKind)
|
||||
|
||||
if gotFound != wantFound {
|
||||
t.Fatalf("list=%d lookup=%d: found mismatch for (%q,%v): got=%v, want=%v",
|
||||
listIdx, lookupIdx, qName, qKind, gotFound, wantFound)
|
||||
}
|
||||
if gotFound && !reflect.DeepEqual(gotUser, wantUser) {
|
||||
t.Fatalf("list=%d lookup=%d: user mismatch for (%q,%v):\n got= %+v\nwant= %+v",
|
||||
listIdx, lookupIdx, qName, qKind, gotUser, wantUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPresent_RandomizedMatchesSlowImplementation(t *testing.T) {
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
ownerKinds := []api.OwnerKind{
|
||||
api.UserOwner,
|
||||
api.GroupOwner,
|
||||
api.ServiceAccountOwner,
|
||||
}
|
||||
|
||||
const (
|
||||
numLists = 200
|
||||
maxOwnersPerList = 30
|
||||
numLookupsPerList = 80
|
||||
maxGroupsPerUser = 10
|
||||
)
|
||||
|
||||
for listIdx := 0; listIdx < numLists; listIdx++ {
|
||||
// Generate a random user list (possibly with duplicates).
|
||||
var users api.UserListSpec
|
||||
nOwners := rnd.Intn(maxOwnersPerList)
|
||||
for i := 0; i < nOwners; i++ {
|
||||
kind := ownerKinds[rnd.Intn(len(ownerKinds))]
|
||||
users = append(users, api.UserSpec{
|
||||
Name: randomName(rnd, 3+rnd.Intn(4)), // length 3–6
|
||||
Kind: kind,
|
||||
})
|
||||
}
|
||||
|
||||
for lookupIdx := 0; lookupIdx < numLookupsPerList; lookupIdx++ {
|
||||
// Generate a random userName and groups,
|
||||
// sometimes biased to hit existing owners/groups.
|
||||
var userName string
|
||||
var groups []string
|
||||
|
||||
// 50% of the time: pick an existing owner name as userName
|
||||
if len(users) > 0 && rnd.Float64() < 0.5 {
|
||||
pick := users[rnd.Intn(len(users))]
|
||||
userName = pick.Name
|
||||
} else {
|
||||
userName = randomName(rnd, 3+rnd.Intn(4))
|
||||
}
|
||||
|
||||
// Random groups, sometimes including owner names
|
||||
nGroups := rnd.Intn(maxGroupsPerUser)
|
||||
for i := 0; i < nGroups; i++ {
|
||||
if len(users) > 0 && rnd.Float64() < 0.5 {
|
||||
pick := users[rnd.Intn(len(users))]
|
||||
groups = append(groups, pick.Name)
|
||||
} else {
|
||||
groups = append(groups, randomName(rnd, 3+rnd.Intn(4)))
|
||||
}
|
||||
}
|
||||
|
||||
got := users.IsPresent(userName, groups)
|
||||
want := slowIsPresent(users, userName, groups)
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("list=%d lookup=%d: mismatch\n users=%v\n user=%q\n groups=%v\n optimized=%v\n slow=%v",
|
||||
listIdx, lookupIdx, users, userName, groups, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByKinds_Basic(t *testing.T) {
|
||||
users := api.UserListSpec{
|
||||
api.UserSpec{Name: "alice", Kind: api.UserOwner},
|
||||
api.UserSpec{Name: "svc-1", Kind: api.ServiceAccountOwner},
|
||||
api.UserSpec{Name: "team-a", Kind: api.GroupOwner},
|
||||
api.UserSpec{Name: "bob", Kind: api.UserOwner},
|
||||
api.UserSpec{Name: "team-b", Kind: api.GroupOwner},
|
||||
}
|
||||
|
||||
eqStrings := func(got, want []string) bool {
|
||||
if got == nil && want == nil {
|
||||
return true
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
return false
|
||||
}
|
||||
sort.Strings(got)
|
||||
sort.Strings(want)
|
||||
for i := range got {
|
||||
if got[i] != want[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Single kind: UserOwner
|
||||
gotUsers := users.GetByKinds([]api.OwnerKind{api.UserOwner})
|
||||
wantUsers := []string{"alice", "bob"}
|
||||
if !eqStrings(gotUsers, wantUsers) {
|
||||
t.Fatalf("GetByKinds([UserOwner]) = %v, want %v", gotUsers, wantUsers)
|
||||
}
|
||||
|
||||
// Single kind: GroupOwner
|
||||
gotGroups := users.GetByKinds([]api.OwnerKind{api.GroupOwner})
|
||||
wantGroups := []string{"team-a", "team-b"}
|
||||
if !eqStrings(gotGroups, wantGroups) {
|
||||
t.Fatalf("GetByKinds([GroupOwner]) = %v, want %v", gotGroups, wantGroups)
|
||||
}
|
||||
|
||||
// Multiple kinds: UserOwner + ServiceAccountOwner
|
||||
gotUsersAndSAs := users.GetByKinds([]api.OwnerKind{api.UserOwner, api.ServiceAccountOwner})
|
||||
wantUsersAndSAs := []string{"alice", "bob", "svc-1"}
|
||||
if !eqStrings(gotUsersAndSAs, wantUsersAndSAs) {
|
||||
t.Fatalf("GetByKinds([UserOwner,ServiceAccountOwner]) = %v, want %v",
|
||||
gotUsersAndSAs, wantUsersAndSAs)
|
||||
}
|
||||
|
||||
// No kinds → nil
|
||||
gotNone := users.GetByKinds(nil)
|
||||
if gotNone != nil {
|
||||
t.Fatalf("GetByKinds(nil) = %v, want nil", gotNone)
|
||||
}
|
||||
|
||||
// Kind not present at all
|
||||
gotUnknown := users.GetByKinds([]api.OwnerKind{api.OwnerKind("does-not-exist")})
|
||||
if gotUnknown != nil {
|
||||
t.Fatalf("GetByKinds([unknown]) = %v, want nil", gotUnknown)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByKinds_Randomized(t *testing.T) {
|
||||
rnd := rand.New(rand.NewSource(123))
|
||||
|
||||
ownerKinds := []api.OwnerKind{
|
||||
api.UserOwner,
|
||||
api.GroupOwner,
|
||||
api.ServiceAccountOwner,
|
||||
}
|
||||
|
||||
const (
|
||||
numLists = 200
|
||||
maxOwnersPerList = 50
|
||||
)
|
||||
|
||||
for listIdx := 0; listIdx < numLists; listIdx++ {
|
||||
var users api.UserListSpec
|
||||
n := rnd.Intn(maxOwnersPerList)
|
||||
for i := 0; i < n; i++ {
|
||||
k := ownerKinds[rnd.Intn(len(ownerKinds))]
|
||||
users = append(users, api.UserSpec{
|
||||
Name: randomName(rnd, 3+rnd.Intn(4)), // reuse your helper
|
||||
Kind: k,
|
||||
})
|
||||
}
|
||||
|
||||
// Try several random kind-subsets per list
|
||||
for subsetIdx := 0; subsetIdx < 10; subsetIdx++ {
|
||||
// Build a random subset of kinds
|
||||
var kinds []api.OwnerKind
|
||||
for _, k := range ownerKinds {
|
||||
if rnd.Float64() < 0.5 {
|
||||
kinds = append(kinds, k)
|
||||
}
|
||||
}
|
||||
|
||||
got := users.GetByKinds(kinds)
|
||||
|
||||
// Reference implementation: filter + sort
|
||||
kindSet := make(map[api.OwnerKind]struct{}, len(kinds))
|
||||
for _, k := range kinds {
|
||||
kindSet[k] = struct{}{}
|
||||
}
|
||||
|
||||
var want []string
|
||||
if len(kinds) > 0 {
|
||||
for _, u := range users {
|
||||
if _, ok := kindSet[u.Kind]; ok {
|
||||
want = append(want, u.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(want) == 0 {
|
||||
if got != nil {
|
||||
t.Fatalf("list=%d subset=%d: expected nil, got %v (kinds=%v, users=%v)",
|
||||
listIdx, subsetIdx, got, kinds, users)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
sort.Strings(want)
|
||||
sort.Strings(got)
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("list=%d subset=%d: GetByKinds mismatch\n kinds=%v\n got= %v\n want= %v",
|
||||
listIdx, subsetIdx, kinds, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func NewCapsuleConfiguration(ctx context.Context, client client.Client, name str
|
||||
if apierrors.IsNotFound(err) {
|
||||
return &capsulev1beta2.CapsuleConfiguration{
|
||||
Spec: capsulev1beta2.CapsuleConfigurationSpec{
|
||||
UserGroups: []string{"projectcapsule.dev"},
|
||||
Users: []capsuleapi.UserSpec{{Name: "projectcapsule.dev", Kind: capsuleapi.GroupOwner}},
|
||||
ForceTenantPrefix: false,
|
||||
ProtectedNamespaceRegexpString: "",
|
||||
},
|
||||
@@ -86,12 +86,14 @@ func (c *capsuleConfiguration) ValidatingWebhookConfigurationName() (name string
|
||||
return c.retrievalFn().Spec.CapsuleResources.ValidatingWebhookConfigurationName
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
func (c *capsuleConfiguration) UserGroups() []string {
|
||||
return c.retrievalFn().Spec.UserGroups
|
||||
return append(c.retrievalFn().Spec.UserGroups, c.retrievalFn().Spec.Users.GetByKinds([]capsuleapi.OwnerKind{capsuleapi.GroupOwner})...)
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
func (c *capsuleConfiguration) UserNames() []string {
|
||||
return c.retrievalFn().Spec.UserNames
|
||||
return append(c.retrievalFn().Spec.UserNames, c.retrievalFn().Spec.Users.GetByKinds([]capsuleapi.OwnerKind{capsuleapi.UserOwner, capsuleapi.ServiceAccountOwner})...)
|
||||
}
|
||||
|
||||
func (c *capsuleConfiguration) IgnoreUserWithGroups() []string {
|
||||
|
||||
@@ -73,6 +73,7 @@ func BuildNamespaceMetadataForTenant(ns *corev1.Namespace, tnt *capsulev1beta2.T
|
||||
func BuildNamespaceAnnotationsForTenant(tnt *capsulev1beta2.Tenant) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
|
||||
//nolint:staticcheck
|
||||
if md := tnt.Spec.NamespaceOptions; md != nil && md.AdditionalMetadata != nil {
|
||||
maps.Copy(annotations, md.AdditionalMetadata.Annotations)
|
||||
}
|
||||
@@ -86,6 +87,7 @@ func BuildNamespaceAnnotationsForTenant(tnt *capsulev1beta2.Tenant) map[string]s
|
||||
annotations[meta.AvailableIngressClassesAnnotation] = strings.Join(ic.Exact, ",")
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if len(ic.Regex) > 0 {
|
||||
annotations[meta.AvailableIngressClassesRegexpAnnotation] = ic.Regex
|
||||
}
|
||||
@@ -96,6 +98,7 @@ func BuildNamespaceAnnotationsForTenant(tnt *capsulev1beta2.Tenant) map[string]s
|
||||
annotations[meta.AvailableStorageClassesAnnotation] = strings.Join(sc.Exact, ",")
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if len(sc.Regex) > 0 {
|
||||
annotations[meta.AvailableStorageClassesRegexpAnnotation] = sc.Regex
|
||||
}
|
||||
@@ -106,6 +109,7 @@ func BuildNamespaceAnnotationsForTenant(tnt *capsulev1beta2.Tenant) map[string]s
|
||||
annotations[meta.AllowedRegistriesAnnotation] = strings.Join(cr.Exact, ",")
|
||||
}
|
||||
|
||||
//nolint:staticcheck
|
||||
if len(cr.Regex) > 0 {
|
||||
annotations[meta.AllowedRegistriesRegexpAnnotation] = cr.Regex
|
||||
}
|
||||
@@ -128,6 +132,7 @@ func BuildNamespaceAnnotationsForTenant(tnt *capsulev1beta2.Tenant) map[string]s
|
||||
func BuildNamespaceLabelsForTenant(tnt *capsulev1beta2.Tenant) map[string]string {
|
||||
labels := make(map[string]string)
|
||||
|
||||
//nolint:staticcheck
|
||||
if md := tnt.Spec.NamespaceOptions; md != nil && md.AdditionalMetadata != nil {
|
||||
maps.Copy(labels, md.AdditionalMetadata.Labels)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user