mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-14 18:09:58 +00:00
Migrating from OperatorSDK 0.18 to 0.19 (#23)
This commit is contained in:
committed by
GitHub
parent
aab0d9b657
commit
5d20d515a7
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -35,7 +35,7 @@ A clear and concise description of what you expected to happen.
|
||||
If applicable, please provide logs of `capsule`.
|
||||
|
||||
In a standard stand-alone installation of Capsule,
|
||||
you'd get this by running `kubectl -n capsule-system logs deploy/capsule`.
|
||||
you'd get this by running `kubectl -n capsule-system logs deploy/capsule-controller-manager`.
|
||||
|
||||
# Additional context
|
||||
|
||||
|
||||
86
.gitignore
vendored
86
.gitignore
vendored
@@ -1,78 +1,26 @@
|
||||
# Temporary Build Files
|
||||
build/_output
|
||||
build/_test
|
||||
# Created by https://www.gitignore.io/api/go,vim,emacs,visualstudiocode
|
||||
### Emacs ###
|
||||
# -*- mode: gitignore; -*-
|
||||
*~
|
||||
\#*\#
|
||||
/.emacs.desktop
|
||||
/.emacs.desktop.lock
|
||||
*.elc
|
||||
auto-save-list
|
||||
tramp
|
||||
.\#*
|
||||
# Org-mode
|
||||
.org-id-locations
|
||||
*_archive
|
||||
# flymake-mode
|
||||
*_flymake.*
|
||||
# eshell files
|
||||
/eshell/history
|
||||
/eshell/lastdir
|
||||
# elpa packages
|
||||
/elpa/
|
||||
# reftex files
|
||||
*.rel
|
||||
# AUCTeX auto folder
|
||||
/auto/
|
||||
# cask packages
|
||||
.cask/
|
||||
dist/
|
||||
# Flycheck
|
||||
flycheck_*.el
|
||||
# server auth directory
|
||||
/server/
|
||||
# projectiles files
|
||||
.projectile
|
||||
projectile-bookmarks.eld
|
||||
# directory configuration
|
||||
.dir-locals.el
|
||||
# saveplace
|
||||
places
|
||||
# url cache
|
||||
url/cache/
|
||||
# cedet
|
||||
ede-projects.el
|
||||
# smex
|
||||
smex-items
|
||||
# company-statistics
|
||||
company-statistics-cache.el
|
||||
# anaconda-mode
|
||||
anaconda-mode/
|
||||
### Go ###
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
# Test binary, build with 'go test -c'
|
||||
bin
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
### Vim ###
|
||||
# swap
|
||||
.sw[a-p]
|
||||
.*.sw[a-p]
|
||||
# session
|
||||
Session.vim
|
||||
# temporary
|
||||
.netrwhist
|
||||
# auto-generated tag files
|
||||
tags
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
.history
|
||||
# End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode
|
||||
.idea
|
||||
|
||||
# Kubernetes Generated files - skip generated files, except for vendored files
|
||||
|
||||
!vendor/**/zz_generated.*
|
||||
|
||||
# editor and IDE paraphernalia
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
hack/*.kubeconfig
|
||||
|
||||
31
Dockerfile
Normal file
31
Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
# Build the manager binary
|
||||
FROM golang:1.13 as builder
|
||||
|
||||
WORKDIR /workspace
|
||||
# Copy the Go Modules manifests
|
||||
COPY go.mod go.mod
|
||||
COPY go.sum go.sum
|
||||
# cache deps before building and copying source so that we don't need to re-download as much
|
||||
# and so that source changes don't invalidate our downloaded layer
|
||||
RUN go mod download
|
||||
|
||||
# Copy the go source
|
||||
COPY main.go main.go
|
||||
COPY api/ api/
|
||||
COPY controllers/ controllers/
|
||||
COPY pkg/ pkg/
|
||||
COPY version/ version/
|
||||
|
||||
ARG VERSION
|
||||
|
||||
# Build
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -ldflags "-X github.com/clastix/capsule/version.Version=${VERSION}" -o manager main.go
|
||||
|
||||
# Use distroless as minimal base image to package the manager binary
|
||||
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
WORKDIR /
|
||||
COPY --from=builder /workspace/manager .
|
||||
USER nonroot:nonroot
|
||||
|
||||
ENTRYPOINT ["/manager"]
|
||||
129
Makefile
129
Makefile
@@ -1,19 +1,118 @@
|
||||
.PHONY: k8s
|
||||
k8s:
|
||||
operator-sdk generate k8s
|
||||
# Current Operator version
|
||||
VERSION ?= 0.0.1
|
||||
|
||||
.PHONY: crds
|
||||
crds:
|
||||
operator-sdk generate crds
|
||||
# Default bundle image tag
|
||||
BUNDLE_IMG ?= quay.io/clastix/capsule:$(VERSION)-bundle
|
||||
# Options for 'bundle-build'
|
||||
ifneq ($(origin CHANNELS), undefined)
|
||||
BUNDLE_CHANNELS := --channels=$(CHANNELS)
|
||||
endif
|
||||
ifneq ($(origin DEFAULT_CHANNEL), undefined)
|
||||
BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL)
|
||||
endif
|
||||
BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
|
||||
|
||||
.PHONY: docker-image
|
||||
docker-image:
|
||||
operator-sdk build quay.io/clastix/capsule:latest
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= quay.io/clastix/capsule:latest
|
||||
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
||||
CRD_OPTIONS ?= "crd:trivialVersions=true"
|
||||
|
||||
.PHONY: goimports
|
||||
goimports:
|
||||
goimports -w -l -local "github.com/clastix/capsule" .
|
||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||
ifeq (,$(shell go env GOBIN))
|
||||
GOBIN=$(shell go env GOPATH)/bin
|
||||
else
|
||||
GOBIN=$(shell go env GOBIN)
|
||||
endif
|
||||
|
||||
.PHONY: golint
|
||||
golint:
|
||||
golangci-lint run
|
||||
all: manager
|
||||
|
||||
# Run tests
|
||||
test: generate fmt vet manifests
|
||||
go test ./... -coverprofile cover.out
|
||||
|
||||
# Build manager binary
|
||||
manager: generate fmt vet
|
||||
go build -o bin/manager main.go
|
||||
|
||||
# Run against the configured Kubernetes cluster in ~/.kube/config
|
||||
run: generate fmt vet manifests
|
||||
go run ./main.go
|
||||
|
||||
# Install CRDs into a cluster
|
||||
install: manifests kustomize
|
||||
$(KUSTOMIZE) build config/crd | kubectl apply -f -
|
||||
|
||||
# Uninstall CRDs from a cluster
|
||||
uninstall: manifests kustomize
|
||||
$(KUSTOMIZE) build config/crd | kubectl delete -f -
|
||||
|
||||
# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
|
||||
deploy: manifests kustomize
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
||||
$(KUSTOMIZE) build config/default | kubectl apply -f -
|
||||
|
||||
# Generate manifests e.g. CRD, RBAC etc.
|
||||
manifests: controller-gen
|
||||
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
|
||||
# Run go fmt against code
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
# Run go vet against code
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
# Generate code
|
||||
generate: controller-gen
|
||||
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
|
||||
|
||||
# Build the docker image
|
||||
docker-build: test
|
||||
docker build . --build-arg=VERSION=${VERSION} -t ${IMG}
|
||||
|
||||
# Push the docker image
|
||||
docker-push:
|
||||
docker push ${IMG}
|
||||
|
||||
# find or download controller-gen
|
||||
# download controller-gen if necessary
|
||||
controller-gen:
|
||||
ifeq (, $(shell which controller-gen))
|
||||
@{ \
|
||||
set -e ;\
|
||||
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$CONTROLLER_GEN_TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.3.0 ;\
|
||||
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
|
||||
}
|
||||
CONTROLLER_GEN=$(GOBIN)/controller-gen
|
||||
else
|
||||
CONTROLLER_GEN=$(shell which controller-gen)
|
||||
endif
|
||||
|
||||
kustomize:
|
||||
ifeq (, $(shell which kustomize))
|
||||
@{ \
|
||||
set -e ;\
|
||||
KUSTOMIZE_GEN_TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$KUSTOMIZE_GEN_TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
go get sigs.k8s.io/kustomize/kustomize/v3@v3.5.4 ;\
|
||||
rm -rf $$KUSTOMIZE_GEN_TMP_DIR ;\
|
||||
}
|
||||
KUSTOMIZE=$(GOBIN)/kustomize
|
||||
else
|
||||
KUSTOMIZE=$(shell which kustomize)
|
||||
endif
|
||||
|
||||
# Generate bundle manifests and metadata, then validate generated files.
|
||||
bundle: manifests
|
||||
operator-sdk generate kustomize manifests -q
|
||||
kustomize build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
|
||||
operator-sdk bundle validate ./bundle
|
||||
|
||||
# Build the bundle image.
|
||||
bundle-build:
|
||||
docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) .
|
||||
|
||||
10
PROJECT
Normal file
10
PROJECT
Normal file
@@ -0,0 +1,10 @@
|
||||
domain: github.com/clastix/capsule
|
||||
layout: go.kubebuilder.io/v2
|
||||
repo: github.com/clastix/capsule
|
||||
resources:
|
||||
- group: capsule.clastix.io
|
||||
kind: Tenant
|
||||
version: v1alpha1
|
||||
version: 3-alpha
|
||||
plugins:
|
||||
go.operator-sdk.io/v2-alpha: {}
|
||||
42
README.md
42
README.md
@@ -8,24 +8,30 @@ _Container-as-a-Service_ (CaaS) platforms.
|
||||
|
||||
# tl;dr; How to install
|
||||
|
||||
As a Cluster Admin, ensure the `capsule-system` Namespace is already there.
|
||||
Ensure you have [`kustomize`](https://github.com/kubernetes-sigs/kustomize)
|
||||
installed in your `PATH`:
|
||||
|
||||
```
|
||||
# kubectl apply -f deploy
|
||||
mutatingwebhookconfiguration.admissionregistration.k8s.io/capsule created
|
||||
clusterrole.rbac.authorization.k8s.io/namespace:deleter created
|
||||
clusterrole.rbac.authorization.k8s.io/namespace:provisioner created
|
||||
clusterrolebinding.rbac.authorization.k8s.io/namespace:provisioner created
|
||||
deployment.apps/capsule created
|
||||
clusterrole.rbac.authorization.k8s.io/capsule created
|
||||
clusterrolebinding.rbac.authorization.k8s.io/capsule-cluster-admin created
|
||||
clusterrolebinding.rbac.authorization.k8s.io/capsule created
|
||||
secret/capsule-ca created
|
||||
secret/capsule-tls created
|
||||
service/capsule created
|
||||
serviceaccount/capsule created
|
||||
# kubectl apply -f deploy/crds/capsule.clastix.io_tenants_crd.yaml
|
||||
customresourcedefinition.apiextensions.k8s.io/tenants.capsule.clastix.io created
|
||||
make deploy
|
||||
# /home/prometherion/go/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
# cd config/manager && /usr/local/bin/kustomize edit set image controller=quay.io/clastix/capsule:latest
|
||||
# /usr/local/bin/kustomize build config/default | kubectl apply -f -
|
||||
# namespace/capsule-system created
|
||||
# customresourcedefinition.apiextensions.k8s.io/tenants.capsule.clastix.io created
|
||||
# clusterrole.rbac.authorization.k8s.io/capsule-namespace:deleter created
|
||||
# clusterrole.rbac.authorization.k8s.io/capsule-namespace:provisioner created
|
||||
# clusterrole.rbac.authorization.k8s.io/capsule-proxy-role created
|
||||
# clusterrole.rbac.authorization.k8s.io/capsule-metrics-reader created
|
||||
# clusterrolebinding.rbac.authorization.k8s.io/capsule-manager-rolebinding created
|
||||
# clusterrolebinding.rbac.authorization.k8s.io/capsule-namespace:provisioner created
|
||||
# clusterrolebinding.rbac.authorization.k8s.io/capsule-proxy-rolebinding created
|
||||
# secret/capsule-ca created
|
||||
# secret/capsule-tls created
|
||||
# service/capsule-controller-manager-metrics-service created
|
||||
# service/capsule-webhook-service created
|
||||
# deployment.apps/capsule-controller-manager created
|
||||
# mutatingwebhookconfiguration.admissionregistration.k8s.io/capsule-mutating-webhook-configuration created
|
||||
# validatingwebhookconfiguration.admissionregistration.k8s.io/capsule-validating-webhook-configuration created
|
||||
```
|
||||
|
||||
## Webhooks and CA Bundle
|
||||
@@ -64,11 +70,11 @@ All Tenant owner needs to be granted with a X.509 certificate with
|
||||
|
||||
## How to create a Tenant
|
||||
|
||||
Use the [scaffold Tenant](deploy/crds/capsule.clastix.io_v1alpha1_tenant_cr.yaml)
|
||||
Use the [scaffold Tenant](config/samples/capsule_v1alpha1_tenant.yaml)
|
||||
and simply apply as Cluster Admin.
|
||||
|
||||
```
|
||||
# kubectl apply -f deploy/crds/capsule.clastix.io_v1alpha1_tenant_cr.yaml
|
||||
# kubectl apply -f config/samples/capsule_v1alpha1_tenant.yaml
|
||||
tenant.capsule.clastix.io/oil created
|
||||
```
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -11,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
package domain
|
||||
|
||||
type SearchIn interface {
|
||||
IsStringInList(value string) bool
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -11,10 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// NOTE: Boilerplate only. Ignore this file.
|
||||
|
||||
// Package v1alpha1 contains API Schema definitions for the capsule v1alpha1 API group
|
||||
// +k8s:deepcopy-gen=package,register
|
||||
// Package v1alpha1 contains API Schema definitions for the capsule.clastix.io v1alpha1 API group
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=capsule.clastix.io
|
||||
package v1alpha1
|
||||
|
||||
@@ -24,9 +25,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
SchemeGroupVersion = schema.GroupVersion{Group: "capsule.clastix.io", Version: "v1alpha1"}
|
||||
// GroupVersion is group version used to register these objects
|
||||
GroupVersion = schema.GroupVersion{Group: "capsule.clastix.io", Version: "v1alpha1"}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||
|
||||
// AddToScheme adds the types in this group-version to the given scheme.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -45,12 +48,13 @@ type TenantStatus struct {
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// Tenant is the Schema for the tenants API
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:path=tenants,scope=Cluster
|
||||
// +kubebuilder:resource:scope=Cluster
|
||||
// +kubebuilder:printcolumn:name="Namespace quota",type="integer",JSONPath=".spec.namespaceQuota",description="The max amount of Namespaces can be created"
|
||||
// +kubebuilder:printcolumn:name="Namespace count",type="integer",JSONPath=".status.size",description="The total amount of Namespaces in use"
|
||||
|
||||
// Tenant is the Schema for the tenants API
|
||||
type Tenant struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
@@ -59,7 +63,7 @@ type Tenant struct {
|
||||
Status TenantStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// TenantList contains a list of Tenant
|
||||
type TenantList struct {
|
||||
@@ -1,13 +1,29 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Code generated by operator-sdk. DO NOT EDIT.
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/networking/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/api/networking/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
@@ -16,7 +32,6 @@ func (in IngressClassList) DeepCopyInto(out *IngressClassList) {
|
||||
in := &in
|
||||
*out = make(IngressClassList, len(*in))
|
||||
copy(*out, *in)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +51,6 @@ func (in NamespaceList) DeepCopyInto(out *NamespaceList) {
|
||||
in := &in
|
||||
*out = make(NamespaceList, len(*in))
|
||||
copy(*out, *in)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +70,6 @@ func (in StorageClassList) DeepCopyInto(out *StorageClassList) {
|
||||
in := &in
|
||||
*out = make(StorageClassList, len(*in))
|
||||
copy(*out, *in)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +90,6 @@ func (in *Tenant) DeepCopyInto(out *Tenant) {
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tenant.
|
||||
@@ -110,7 +122,6 @@ func (in *TenantList) DeepCopyInto(out *TenantList) {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantList.
|
||||
@@ -172,7 +183,6 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec.
|
||||
@@ -203,7 +213,6 @@ func (in *TenantStatus) DeepCopyInto(out *TenantStatus) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantStatus.
|
||||
@@ -1,15 +0,0 @@
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:latest
|
||||
|
||||
ENV OPERATOR=/usr/local/bin/capsule \
|
||||
USER_UID=0 \
|
||||
USER_NAME=capsule
|
||||
|
||||
# install operator binary
|
||||
COPY build/_output/bin/capsule ${OPERATOR}
|
||||
|
||||
COPY build/bin /usr/local/bin
|
||||
RUN /usr/local/bin/user_setup
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint"]
|
||||
|
||||
USER ${USER_UID}
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
exec ${OPERATOR} $@
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -x
|
||||
|
||||
# ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be)
|
||||
echo "${USER_NAME}:x:${USER_UID}:0:${USER_NAME} user:${HOME}:/sbin/nologin" >> /etc/passwd
|
||||
mkdir -p "${HOME}"
|
||||
chown "${USER_UID}:0" "${HOME}"
|
||||
chmod ug+rwx "${HOME}"
|
||||
|
||||
# no need for this script to remain in the image after running
|
||||
rm "$0"
|
||||
@@ -1,244 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
|
||||
kubemetrics "github.com/operator-framework/operator-sdk/pkg/kube-metrics"
|
||||
"github.com/operator-framework/operator-sdk/pkg/leader"
|
||||
"github.com/operator-framework/operator-sdk/pkg/log/zap"
|
||||
"github.com/operator-framework/operator-sdk/pkg/metrics"
|
||||
sdkVersion "github.com/operator-framework/operator-sdk/version"
|
||||
"github.com/spf13/pflag"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
|
||||
|
||||
"github.com/clastix/capsule/pkg/apis"
|
||||
"github.com/clastix/capsule/pkg/controller"
|
||||
"github.com/clastix/capsule/pkg/indexer"
|
||||
"github.com/clastix/capsule/pkg/webhook"
|
||||
"github.com/clastix/capsule/version"
|
||||
)
|
||||
|
||||
// Change below variables to serve metrics on different host or port.
|
||||
var (
|
||||
metricsHost = "0.0.0.0"
|
||||
metricsPort int32 = 8383
|
||||
operatorMetricsPort int32 = 8686
|
||||
)
|
||||
var log = logf.Log.WithName("cmd")
|
||||
|
||||
func printVersion() {
|
||||
log.Info(fmt.Sprintf("Operator Version: %s", version.Version))
|
||||
log.Info(fmt.Sprintf("Go Version: %s", runtime.Version()))
|
||||
log.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH))
|
||||
log.Info(fmt.Sprintf("Version of operator-sdk: %v", sdkVersion.Version))
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Add the zap logger flag set to the CLI. The flag set must
|
||||
// be added before calling pflag.Parse().
|
||||
pflag.CommandLine.AddFlagSet(zap.FlagSet())
|
||||
|
||||
// Add flags registered by imported packages (e.g. glog and
|
||||
// controller-runtime)
|
||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||
|
||||
var v bool
|
||||
pflag.BoolVarP(&v, "version", "v", false, "Print the Capsule version and exit")
|
||||
|
||||
pflag.Parse()
|
||||
|
||||
// Use a zap logr.Logger implementation. If none of the zap
|
||||
// flags are configured (or if the zap flag set is not being
|
||||
// used), this defaults to a production zap logger.
|
||||
//
|
||||
// The logger instantiated here can be changed to any logger
|
||||
// implementing the logr.Logger interface. This logger will
|
||||
// be propagated through the whole operator, generating
|
||||
// uniform and structured logs.
|
||||
logf.SetLogger(zap.Logger())
|
||||
|
||||
printVersion()
|
||||
if v {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
namespace, err := k8sutil.GetWatchNamespace()
|
||||
if err != nil {
|
||||
log.Error(err, "Failed to get watch namespace")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Get a config to talk to the apiserver
|
||||
cfg, err := config.GetConfig()
|
||||
if err != nil {
|
||||
log.Error(err, "")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
// Become the leader before proceeding
|
||||
err = leader.Become(ctx, "capsule-lock")
|
||||
if err != nil {
|
||||
log.Error(err, "")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Set default manager options
|
||||
options := manager.Options{
|
||||
Namespace: namespace,
|
||||
MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort),
|
||||
}
|
||||
|
||||
// Add support for MultiNamespace set in WATCH_NAMESPACE (e.g ns1,ns2)
|
||||
// Note that this is not intended to be used for excluding namespaces, this is better done via a Predicate
|
||||
// Also note that you may face performance issues when using this with a high number of namespaces.
|
||||
// More Info: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/cache#MultiNamespacedCacheBuilder
|
||||
if strings.Contains(namespace, ",") {
|
||||
options.Namespace = ""
|
||||
options.NewCache = cache.MultiNamespacedCacheBuilder(strings.Split(namespace, ","))
|
||||
}
|
||||
|
||||
stop := signals.SetupSignalHandler()
|
||||
|
||||
// Create a new manager to provide shared dependencies and start components
|
||||
mgr, err := manager.New(cfg, options)
|
||||
if err != nil {
|
||||
log.Error(err, "")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Info("Registering Components.")
|
||||
|
||||
// Setup Scheme for all resources
|
||||
if err := apis.AddToScheme(mgr.GetScheme()); err != nil {
|
||||
log.Error(err, "")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Setup all Controllers
|
||||
if err := controller.AddToManager(mgr); err != nil {
|
||||
log.Error(err, "")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Setup all Webhooks
|
||||
if err := webhook.AddToServer(mgr); err != nil {
|
||||
log.Error(err, "")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Setup all Custom Indexers
|
||||
if err := indexer.AddToManager(mgr); err != nil {
|
||||
log.Error(err, "")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Add the Metrics Service
|
||||
addMetrics(ctx, cfg)
|
||||
|
||||
log.Info("Starting the Cmd.")
|
||||
|
||||
// Start the Cmd
|
||||
if err := mgr.Start(stop); err != nil {
|
||||
log.Error(err, "Manager exited non-zero")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// addMetrics will create the Services and Service Monitors to allow the operator export the metrics by using
|
||||
// the Prometheus operator
|
||||
func addMetrics(ctx context.Context, cfg *rest.Config) {
|
||||
// Get the namespace the operator is currently deployed in.
|
||||
operatorNs, err := k8sutil.GetOperatorNamespace()
|
||||
if err != nil {
|
||||
if errors.Is(err, k8sutil.ErrRunLocal) {
|
||||
log.Info("Skipping CR metrics server creation; not running in a cluster.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := serveCRMetrics(cfg, operatorNs); err != nil {
|
||||
log.Info("Could not generate and serve custom resource metrics", "error", err.Error())
|
||||
}
|
||||
|
||||
// Add to the below struct any other metrics ports you want to expose.
|
||||
servicePorts := []v1.ServicePort{
|
||||
{Port: metricsPort, Name: metrics.OperatorPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: metricsPort}},
|
||||
{Port: operatorMetricsPort, Name: metrics.CRPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: operatorMetricsPort}},
|
||||
}
|
||||
|
||||
// Create Service object to expose the metrics port(s).
|
||||
service, err := metrics.CreateMetricsService(ctx, cfg, servicePorts)
|
||||
if err != nil {
|
||||
log.Info("Could not create metrics Service", "error", err.Error())
|
||||
}
|
||||
|
||||
// CreateServiceMonitors will automatically create the prometheus-operator ServiceMonitor resources
|
||||
// necessary to configure Prometheus to scrape metrics from this operator.
|
||||
services := []*v1.Service{service}
|
||||
|
||||
// The ServiceMonitor is created in the same namespace where the operator is deployed
|
||||
_, err = metrics.CreateServiceMonitors(cfg, operatorNs, services)
|
||||
if err != nil {
|
||||
log.Info("Could not create ServiceMonitor object", "error", err.Error())
|
||||
// If this operator is deployed to a cluster without the prometheus-operator running, it will return
|
||||
// ErrServiceMonitorNotPresent, which can be used to safely skip ServiceMonitor creation.
|
||||
if err == metrics.ErrServiceMonitorNotPresent {
|
||||
log.Info("Install prometheus-operator in your cluster to create ServiceMonitor objects", "error", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// serveCRMetrics gets the Operator/CustomResource GVKs and generates metrics based on those types.
|
||||
// It serves those metrics on "http://metricsHost:operatorMetricsPort".
|
||||
func serveCRMetrics(cfg *rest.Config, operatorNs string) error {
|
||||
// The function below returns a list of filtered operator/CR specific GVKs. For more control, override the GVK list below
|
||||
// with your own custom logic. Note that if you are adding third party API schemas, probably you will need to
|
||||
// customize this implementation to avoid permissions issues.
|
||||
filteredGVK, err := k8sutil.GetGVKsFromAddToScheme(apis.AddToScheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The metrics will be generated from the namespaces which are returned here.
|
||||
// NOTE that passing nil or an empty list of namespaces in GenerateAndServeCRMetrics will result in an error.
|
||||
ns, err := kubemetrics.GetNamespacesForMetrics(operatorNs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate and serve custom resource specific metrics.
|
||||
err = kubemetrics.GenerateAndServeCRMetrics(cfg, ns, filteredGVK, metricsHost, operatorMetricsPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
702
config/crd/bases/capsule.clastix.io_tenants.yaml
Normal file
702
config/crd/bases/capsule.clastix.io_tenants.yaml
Normal file
@@ -0,0 +1,702 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.5
|
||||
creationTimestamp: null
|
||||
name: tenants.capsule.clastix.io
|
||||
spec:
|
||||
additionalPrinterColumns:
|
||||
- JSONPath: .spec.namespaceQuota
|
||||
description: The max amount of Namespaces can be created
|
||||
name: Namespace quota
|
||||
type: integer
|
||||
- JSONPath: .status.size
|
||||
description: The total amount of Namespaces in use
|
||||
name: Namespace count
|
||||
type: integer
|
||||
group: capsule.clastix.io
|
||||
names:
|
||||
kind: Tenant
|
||||
listKind: TenantList
|
||||
plural: tenants
|
||||
singular: tenant
|
||||
scope: Cluster
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: Tenant is the Schema for the tenants API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: TenantSpec defines the desired state of Tenant
|
||||
properties:
|
||||
ingressClasses:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
limitRanges:
|
||||
items:
|
||||
description: LimitRangeSpec defines a min/max usage limit for resources
|
||||
that match on kind.
|
||||
properties:
|
||||
limits:
|
||||
description: Limits is the list of LimitRangeItem objects that
|
||||
are enforced.
|
||||
items:
|
||||
description: LimitRangeItem defines a min/max usage limit for
|
||||
any resource that matches on kind.
|
||||
properties:
|
||||
default:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: Default resource requirement limit value by
|
||||
resource name if resource limit is omitted.
|
||||
type: object
|
||||
defaultRequest:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: DefaultRequest is the default resource requirement
|
||||
request value by resource name if resource request is
|
||||
omitted.
|
||||
type: object
|
||||
max:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: Max usage constraints on this kind by resource
|
||||
name.
|
||||
type: object
|
||||
maxLimitRequestRatio:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: MaxLimitRequestRatio if specified, the named
|
||||
resource must have a request and limit that are both non-zero
|
||||
where limit divided by request is less than or equal to
|
||||
the enumerated value; this represents the max burst for
|
||||
the named resource.
|
||||
type: object
|
||||
min:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: Min usage constraints on this kind by resource
|
||||
name.
|
||||
type: object
|
||||
type:
|
||||
description: Type of resource that this limit applies to.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- limits
|
||||
type: object
|
||||
type: array
|
||||
namespaceQuota:
|
||||
minimum: 1
|
||||
type: integer
|
||||
networkPolicies:
|
||||
items:
|
||||
description: NetworkPolicySpec provides the specification of a NetworkPolicy
|
||||
properties:
|
||||
egress:
|
||||
description: List of egress rules to be applied to the selected
|
||||
pods. Outgoing traffic is allowed if there are no NetworkPolicies
|
||||
selecting the pod (and cluster policy otherwise allows the traffic),
|
||||
OR if the traffic matches at least one egress rule across all
|
||||
of the NetworkPolicy objects whose podSelector matches the pod.
|
||||
If this field is empty then this NetworkPolicy limits all outgoing
|
||||
traffic (and serves solely to ensure that the pods it selects
|
||||
are isolated by default). This field is beta-level in 1.8
|
||||
items:
|
||||
description: NetworkPolicyEgressRule describes a particular
|
||||
set of traffic that is allowed out of pods matched by a NetworkPolicySpec's
|
||||
podSelector. The traffic must match both ports and to. This
|
||||
type is beta-level in 1.8
|
||||
properties:
|
||||
ports:
|
||||
description: List of destination ports for outgoing traffic.
|
||||
Each item in this list is combined using a logical OR.
|
||||
If this field is empty or missing, this rule matches all
|
||||
ports (traffic not restricted by port). If this field
|
||||
is present and contains at least one item, then this rule
|
||||
allows traffic only if the traffic matches at least one
|
||||
port in the list.
|
||||
items:
|
||||
description: NetworkPolicyPort describes a port to allow
|
||||
traffic on
|
||||
properties:
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: The port on the given protocol. This
|
||||
can either be a numerical or named port on a pod.
|
||||
If this field is not provided, this matches all
|
||||
port names and numbers.
|
||||
x-kubernetes-int-or-string: true
|
||||
protocol:
|
||||
description: The protocol (TCP, UDP, or SCTP) which
|
||||
traffic must match. If not specified, this field
|
||||
defaults to TCP.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
to:
|
||||
description: List of destinations for outgoing traffic of
|
||||
pods selected for this rule. Items in this list are combined
|
||||
using a logical OR operation. If this field is empty or
|
||||
missing, this rule matches all destinations (traffic not
|
||||
restricted by destination). If this field is present and
|
||||
contains at least one item, this rule allows traffic only
|
||||
if the traffic matches at least one item in the to list.
|
||||
items:
|
||||
description: NetworkPolicyPeer describes a peer to allow
|
||||
traffic from. Only certain combinations of fields are
|
||||
allowed
|
||||
properties:
|
||||
ipBlock:
|
||||
description: IPBlock defines policy on a particular
|
||||
IPBlock. If this field is set then neither of the
|
||||
other fields can be.
|
||||
properties:
|
||||
cidr:
|
||||
description: CIDR is a string representing the
|
||||
IP Block Valid examples are "192.168.1.1/24"
|
||||
or "2001:db9::/64"
|
||||
type: string
|
||||
except:
|
||||
description: Except is a slice of CIDRs that should
|
||||
not be included within an IP Block Valid examples
|
||||
are "192.168.1.1/24" or "2001:db9::/64" Except
|
||||
values will be rejected if they are outside
|
||||
the CIDR range
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- cidr
|
||||
type: object
|
||||
namespaceSelector:
|
||||
description: "Selects Namespaces using cluster-scoped
|
||||
labels. This field follows standard label selector
|
||||
semantics; if present but empty, it selects all
|
||||
namespaces. \n If PodSelector is also set, then
|
||||
the NetworkPolicyPeer as a whole selects the Pods
|
||||
matching PodSelector in the Namespaces selected
|
||||
by NamespaceSelector. Otherwise it selects all Pods
|
||||
in the Namespaces selected by NamespaceSelector."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label
|
||||
selector requirements. The requirements are
|
||||
ANDed.
|
||||
items:
|
||||
description: A label selector requirement is
|
||||
a selector that contains values, a key, and
|
||||
an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the
|
||||
selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's
|
||||
relationship to a set of values. Valid
|
||||
operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string
|
||||
values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If
|
||||
the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array
|
||||
is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value}
|
||||
pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions,
|
||||
whose key field is "key", the operator is "In",
|
||||
and the values array contains only "value".
|
||||
The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
podSelector:
|
||||
description: "This is a label selector which selects
|
||||
Pods. This field follows standard label selector
|
||||
semantics; if present but empty, it selects all
|
||||
pods. \n If NamespaceSelector is also set, then
|
||||
the NetworkPolicyPeer as a whole selects the Pods
|
||||
matching PodSelector in the Namespaces selected
|
||||
by NamespaceSelector. Otherwise it selects the Pods
|
||||
matching PodSelector in the policy's own Namespace."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label
|
||||
selector requirements. The requirements are
|
||||
ANDed.
|
||||
items:
|
||||
description: A label selector requirement is
|
||||
a selector that contains values, a key, and
|
||||
an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the
|
||||
selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's
|
||||
relationship to a set of values. Valid
|
||||
operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string
|
||||
values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If
|
||||
the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array
|
||||
is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value}
|
||||
pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions,
|
||||
whose key field is "key", the operator is "In",
|
||||
and the values array contains only "value".
|
||||
The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
ingress:
|
||||
description: List of ingress rules to be applied to the selected
|
||||
pods. Traffic is allowed to a pod if there are no NetworkPolicies
|
||||
selecting the pod (and cluster policy otherwise allows the traffic),
|
||||
OR if the traffic source is the pod's local node, OR if the
|
||||
traffic matches at least one ingress rule across all of the
|
||||
NetworkPolicy objects whose podSelector matches the pod. If
|
||||
this field is empty then this NetworkPolicy does not allow any
|
||||
traffic (and serves solely to ensure that the pods it selects
|
||||
are isolated by default)
|
||||
items:
|
||||
description: NetworkPolicyIngressRule describes a particular
|
||||
set of traffic that is allowed to the pods matched by a NetworkPolicySpec's
|
||||
podSelector. The traffic must match both ports and from.
|
||||
properties:
|
||||
from:
|
||||
description: List of sources which should be able to access
|
||||
the pods selected for this rule. Items in this list are
|
||||
combined using a logical OR operation. If this field is
|
||||
empty or missing, this rule matches all sources (traffic
|
||||
not restricted by source). If this field is present and
|
||||
contains at least one item, this rule allows traffic only
|
||||
if the traffic matches at least one item in the from list.
|
||||
items:
|
||||
description: NetworkPolicyPeer describes a peer to allow
|
||||
traffic from. Only certain combinations of fields are
|
||||
allowed
|
||||
properties:
|
||||
ipBlock:
|
||||
description: IPBlock defines policy on a particular
|
||||
IPBlock. If this field is set then neither of the
|
||||
other fields can be.
|
||||
properties:
|
||||
cidr:
|
||||
description: CIDR is a string representing the
|
||||
IP Block Valid examples are "192.168.1.1/24"
|
||||
or "2001:db9::/64"
|
||||
type: string
|
||||
except:
|
||||
description: Except is a slice of CIDRs that should
|
||||
not be included within an IP Block Valid examples
|
||||
are "192.168.1.1/24" or "2001:db9::/64" Except
|
||||
values will be rejected if they are outside
|
||||
the CIDR range
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- cidr
|
||||
type: object
|
||||
namespaceSelector:
|
||||
description: "Selects Namespaces using cluster-scoped
|
||||
labels. This field follows standard label selector
|
||||
semantics; if present but empty, it selects all
|
||||
namespaces. \n If PodSelector is also set, then
|
||||
the NetworkPolicyPeer as a whole selects the Pods
|
||||
matching PodSelector in the Namespaces selected
|
||||
by NamespaceSelector. Otherwise it selects all Pods
|
||||
in the Namespaces selected by NamespaceSelector."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label
|
||||
selector requirements. The requirements are
|
||||
ANDed.
|
||||
items:
|
||||
description: A label selector requirement is
|
||||
a selector that contains values, a key, and
|
||||
an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the
|
||||
selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's
|
||||
relationship to a set of values. Valid
|
||||
operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string
|
||||
values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If
|
||||
the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array
|
||||
is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value}
|
||||
pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions,
|
||||
whose key field is "key", the operator is "In",
|
||||
and the values array contains only "value".
|
||||
The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
podSelector:
|
||||
description: "This is a label selector which selects
|
||||
Pods. This field follows standard label selector
|
||||
semantics; if present but empty, it selects all
|
||||
pods. \n If NamespaceSelector is also set, then
|
||||
the NetworkPolicyPeer as a whole selects the Pods
|
||||
matching PodSelector in the Namespaces selected
|
||||
by NamespaceSelector. Otherwise it selects the Pods
|
||||
matching PodSelector in the policy's own Namespace."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label
|
||||
selector requirements. The requirements are
|
||||
ANDed.
|
||||
items:
|
||||
description: A label selector requirement is
|
||||
a selector that contains values, a key, and
|
||||
an operator that relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the
|
||||
selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's
|
||||
relationship to a set of values. Valid
|
||||
operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string
|
||||
values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If
|
||||
the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array
|
||||
is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value}
|
||||
pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions,
|
||||
whose key field is "key", the operator is "In",
|
||||
and the values array contains only "value".
|
||||
The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
ports:
|
||||
description: List of ports which should be made accessible
|
||||
on the pods selected for this rule. Each item in this
|
||||
list is combined using a logical OR. If this field is
|
||||
empty or missing, this rule matches all ports (traffic
|
||||
not restricted by port). If this field is present and
|
||||
contains at least one item, then this rule allows traffic
|
||||
only if the traffic matches at least one port in the list.
|
||||
items:
|
||||
description: NetworkPolicyPort describes a port to allow
|
||||
traffic on
|
||||
properties:
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: The port on the given protocol. This
|
||||
can either be a numerical or named port on a pod.
|
||||
If this field is not provided, this matches all
|
||||
port names and numbers.
|
||||
x-kubernetes-int-or-string: true
|
||||
protocol:
|
||||
description: The protocol (TCP, UDP, or SCTP) which
|
||||
traffic must match. If not specified, this field
|
||||
defaults to TCP.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
podSelector:
|
||||
description: Selects the pods to which this NetworkPolicy object
|
||||
applies. The array of ingress rules is applied to any pods selected
|
||||
by this field. Multiple network policies can select the same
|
||||
set of pods. In this case, the ingress rules for each are combined
|
||||
additively. This field is NOT optional and follows standard
|
||||
label selector semantics. An empty podSelector matches all pods
|
||||
in this namespace.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector
|
||||
requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector
|
||||
that contains values, a key, and an operator that relates
|
||||
the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector
|
||||
applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are In, NotIn,
|
||||
Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If
|
||||
the operator is In or NotIn, the values array must
|
||||
be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced
|
||||
during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A
|
||||
single {key,value} in the matchLabels map is equivalent
|
||||
to an element of matchExpressions, whose key field is "key",
|
||||
the operator is "In", and the values array contains only
|
||||
"value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
policyTypes:
|
||||
description: List of rule types that the NetworkPolicy relates
|
||||
to. Valid options are "Ingress", "Egress", or "Ingress,Egress".
|
||||
If this field is not specified, it will default based on the
|
||||
existence of Ingress or Egress rules; policies that contain
|
||||
an Egress section are assumed to affect Egress, and all policies
|
||||
(whether or not they contain an Ingress section) are assumed
|
||||
to affect Ingress. If you want to write an egress-only policy,
|
||||
you must explicitly specify policyTypes [ "Egress" ]. Likewise,
|
||||
if you want to write a policy that specifies that no egress
|
||||
is allowed, you must specify a policyTypes value that include
|
||||
"Egress" (since such a policy would not include an Egress section
|
||||
and would otherwise default to just [ "Ingress" ]). This field
|
||||
is beta-level in 1.8
|
||||
items:
|
||||
description: Policy Type string describes the NetworkPolicy
|
||||
type This type is beta-level in 1.8
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- podSelector
|
||||
type: object
|
||||
type: array
|
||||
nodeSelector:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
owner:
|
||||
type: string
|
||||
resourceQuotas:
|
||||
items:
|
||||
description: ResourceQuotaSpec defines the desired hard limits to
|
||||
enforce for Quota.
|
||||
properties:
|
||||
hard:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: 'hard is the set of desired hard limits for each
|
||||
named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/'
|
||||
type: object
|
||||
scopeSelector:
|
||||
description: scopeSelector is also a collection of filters like
|
||||
scopes that must match each object tracked by a quota but expressed
|
||||
using ScopeSelectorOperator in combination with possible values.
|
||||
For a resource to match, both scopes AND scopeSelector (if specified
|
||||
in spec), must be matched.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: A list of scope selector requirements by scope
|
||||
of the resources.
|
||||
items:
|
||||
description: A scoped-resource selector requirement is a
|
||||
selector that contains values, a scope name, and an operator
|
||||
that relates the scope name and values.
|
||||
properties:
|
||||
operator:
|
||||
description: Represents a scope's relationship to a
|
||||
set of values. Valid operators are In, NotIn, Exists,
|
||||
DoesNotExist.
|
||||
type: string
|
||||
scopeName:
|
||||
description: The name of the scope that the selector
|
||||
applies to.
|
||||
type: string
|
||||
values:
|
||||
description: An array of string values. If the operator
|
||||
is In or NotIn, the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist, the values
|
||||
array must be empty. This array is replaced during
|
||||
a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- operator
|
||||
- scopeName
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
scopes:
|
||||
description: A collection of filters that must match each object
|
||||
tracked by a quota. If not specified, the quota matches all
|
||||
objects.
|
||||
items:
|
||||
description: A ResourceQuotaScope defines a filter that must
|
||||
match each object tracked by a quota
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
storageClasses:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- ingressClasses
|
||||
- limitRanges
|
||||
- namespaceQuota
|
||||
- owner
|
||||
- storageClasses
|
||||
type: object
|
||||
status:
|
||||
description: TenantStatus defines the observed state of Tenant
|
||||
properties:
|
||||
groups:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
size:
|
||||
type: integer
|
||||
users:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- size
|
||||
type: object
|
||||
type: object
|
||||
version: v1alpha1
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
10
config/crd/kustomization.yaml
Normal file
10
config/crd/kustomization.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
# This kustomization.yaml is not intended to be run by itself,
|
||||
# since it depends on service name and namespace that are out of this kustomize package.
|
||||
# It should be run by config/default
|
||||
resources:
|
||||
- bases/capsule.clastix.io_tenants.yaml
|
||||
# +kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
||||
configurations:
|
||||
- kustomizeconfig.yaml
|
||||
17
config/crd/kustomizeconfig.yaml
Normal file
17
config/crd/kustomizeconfig.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# This file is for teaching kustomize how to substitute name and namespace reference in CRD
|
||||
nameReference:
|
||||
- kind: Service
|
||||
version: v1
|
||||
fieldSpecs:
|
||||
- kind: CustomResourceDefinition
|
||||
group: apiextensions.k8s.io
|
||||
path: spec/conversion/webhookClientConfig/service/name
|
||||
|
||||
namespace:
|
||||
- kind: CustomResourceDefinition
|
||||
group: apiextensions.k8s.io
|
||||
path: spec/conversion/webhookClientConfig/service/namespace
|
||||
create: false
|
||||
|
||||
varReference:
|
||||
- path: metadata/annotations
|
||||
30
config/default/kustomization.yaml
Normal file
30
config/default/kustomization.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
# Adds namespace to all resources.
|
||||
namespace: capsule-system
|
||||
|
||||
# Value of this field is prepended to the
|
||||
# names of all resources, e.g. a deployment named
|
||||
# "wordpress" becomes "alices-wordpress".
|
||||
# Note that it should also match with the prefix (text before '-') of the namespace
|
||||
# field above.
|
||||
namePrefix: capsule-
|
||||
|
||||
# Labels to add to all resources and selectors.
|
||||
#commonLabels:
|
||||
# someName: someValue
|
||||
|
||||
bases:
|
||||
- ../crd
|
||||
- ../rbac
|
||||
- ../manager
|
||||
- ../secret
|
||||
- ../webhook
|
||||
- ../tenants
|
||||
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
|
||||
#- ../prometheus
|
||||
|
||||
patchesStrategicMerge:
|
||||
# Protect the /metrics endpoint by putting it behind auth.
|
||||
# If you want your controller-manager to expose the /metrics
|
||||
# endpoint w/o any authn/z, please comment the following line.
|
||||
- manager_auth_proxy_patch.yaml
|
||||
- manager_webhook_patch.yaml
|
||||
25
config/default/manager_auth_proxy_patch.yaml
Normal file
25
config/default/manager_auth_proxy_patch.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
# This patch inject a sidecar container which is a HTTP proxy for the
|
||||
# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: kube-rbac-proxy
|
||||
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0
|
||||
args:
|
||||
- "--secure-listen-address=0.0.0.0:8443"
|
||||
- "--upstream=http://127.0.0.1:8080/"
|
||||
- "--logtostderr=true"
|
||||
- "--v=10"
|
||||
ports:
|
||||
- containerPort: 8443
|
||||
name: https
|
||||
- name: manager
|
||||
args:
|
||||
- "--metrics-addr=127.0.0.1:8080"
|
||||
- "--enable-leader-election"
|
||||
23
config/default/manager_webhook_patch.yaml
Normal file
23
config/default/manager_webhook_patch.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: manager
|
||||
ports:
|
||||
- containerPort: 9443
|
||||
name: webhook-server
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- mountPath: /tmp/k8s-webhook-server/serving-certs
|
||||
name: cert
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: cert
|
||||
secret:
|
||||
defaultMode: 420
|
||||
secretName: capsule-tls
|
||||
8
config/manager/kustomization.yaml
Normal file
8
config/manager/kustomization.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
resources:
|
||||
- manager.yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
images:
|
||||
- name: controller
|
||||
newName: quay.io/clastix/capsule
|
||||
newTag: latest
|
||||
40
config/manager/manager.yaml
Normal file
40
config/manager/manager.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
name: system
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
control-plane: controller-manager
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- /manager
|
||||
args:
|
||||
- --enable-leader-election
|
||||
image: quay.io/clastix/capsule:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: manager
|
||||
resources:
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
terminationGracePeriodSeconds: 10
|
||||
2
config/prometheus/kustomization.yaml
Normal file
2
config/prometheus/kustomization.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
resources:
|
||||
- monitor.yaml
|
||||
16
config/prometheus/monitor.yaml
Normal file
16
config/prometheus/monitor.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
# Prometheus Monitor Service (Metrics)
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
name: controller-manager-metrics-monitor
|
||||
namespace: system
|
||||
spec:
|
||||
endpoints:
|
||||
- path: /metrics
|
||||
port: https
|
||||
selector:
|
||||
matchLabels:
|
||||
control-plane: controller-manager
|
||||
7
config/rbac/auth_proxy_client_clusterrole.yaml
Normal file
7
config/rbac/auth_proxy_client_clusterrole.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: metrics-reader
|
||||
rules:
|
||||
- nonResourceURLs: ["/metrics"]
|
||||
verbs: ["get"]
|
||||
13
config/rbac/auth_proxy_role.yaml
Normal file
13
config/rbac/auth_proxy_role.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: proxy-role
|
||||
rules:
|
||||
- apiGroups: ["authentication.k8s.io"]
|
||||
resources:
|
||||
- tokenreviews
|
||||
verbs: ["create"]
|
||||
- apiGroups: ["authorization.k8s.io"]
|
||||
resources:
|
||||
- subjectaccessreviews
|
||||
verbs: ["create"]
|
||||
12
config/rbac/auth_proxy_role_binding.yaml
Normal file
12
config/rbac/auth_proxy_role_binding.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: proxy-rolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: proxy-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: default
|
||||
namespace: system
|
||||
14
config/rbac/auth_proxy_service.yaml
Normal file
14
config/rbac/auth_proxy_service.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
name: controller-manager-metrics-service
|
||||
namespace: system
|
||||
spec:
|
||||
ports:
|
||||
- name: https
|
||||
port: 8443
|
||||
targetPort: https
|
||||
selector:
|
||||
control-plane: controller-manager
|
||||
9
config/rbac/kustomization.yaml
Normal file
9
config/rbac/kustomization.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
resources:
|
||||
- role_binding.yaml
|
||||
# Comment the following 4 lines if you want to disable
|
||||
# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
|
||||
# which protects your /metrics endpoint.
|
||||
- auth_proxy_service.yaml
|
||||
- auth_proxy_role.yaml
|
||||
- auth_proxy_role_binding.yaml
|
||||
- auth_proxy_client_clusterrole.yaml
|
||||
12
config/rbac/role_binding.yaml
Normal file
12
config/rbac/role_binding.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: manager-rolebinding
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: cluster-admin
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: default
|
||||
namespace: system
|
||||
3
config/samples/kustomization.yaml
Normal file
3
config/samples/kustomization.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
## This file is auto-generated, do not modify ##
|
||||
resources:
|
||||
- capsule_v1alpha1_tenant.yaml
|
||||
3
config/secret/kustomization.yaml
Normal file
3
config/secret/kustomization.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
resources:
|
||||
- secret-ca.yaml
|
||||
- secret-tls.yaml
|
||||
4
config/secret/secret-ca.yaml
Normal file
4
config/secret/secret-ca.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ca
|
||||
4
config/secret/secret-tls.yaml
Normal file
4
config/secret/secret-tls.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: tls
|
||||
3
config/tenants/kustomization.yaml
Normal file
3
config/tenants/kustomization.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
resources:
|
||||
- namespace-deleter.yaml
|
||||
- namespace-provisioner.yaml
|
||||
6
config/webhook/kustomization.yaml
Normal file
6
config/webhook/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
resources:
|
||||
- manifests.yaml
|
||||
- service.yaml
|
||||
|
||||
configurations:
|
||||
- kustomizeconfig.yaml
|
||||
25
config/webhook/kustomizeconfig.yaml
Normal file
25
config/webhook/kustomizeconfig.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
# the following config is for teaching kustomize where to look at when substituting vars.
|
||||
# It requires kustomize v2.1.0 or newer to work properly.
|
||||
nameReference:
|
||||
- kind: Service
|
||||
version: v1
|
||||
fieldSpecs:
|
||||
- kind: MutatingWebhookConfiguration
|
||||
group: admissionregistration.k8s.io
|
||||
path: webhooks/clientConfig/service/name
|
||||
- kind: ValidatingWebhookConfiguration
|
||||
group: admissionregistration.k8s.io
|
||||
path: webhooks/clientConfig/service/name
|
||||
|
||||
namespace:
|
||||
- kind: MutatingWebhookConfiguration
|
||||
group: admissionregistration.k8s.io
|
||||
path: webhooks/clientConfig/service/namespace
|
||||
create: true
|
||||
- kind: ValidatingWebhookConfiguration
|
||||
group: admissionregistration.k8s.io
|
||||
path: webhooks/clientConfig/service/namespace
|
||||
create: true
|
||||
|
||||
varReference:
|
||||
- path: metadata/annotations
|
||||
122
config/webhook/manifests.yaml
Normal file
122
config/webhook/manifests.yaml
Normal file
@@ -0,0 +1,122 @@
|
||||
|
||||
---
|
||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||
kind: MutatingWebhookConfiguration
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: mutating-webhook-configuration
|
||||
webhooks:
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /mutate-v1-namespace-owner-reference
|
||||
failurePolicy: Fail
|
||||
name: owner.namespace.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- namespaces
|
||||
|
||||
---
|
||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||
kind: ValidatingWebhookConfiguration
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: validating-webhook-configuration
|
||||
webhooks:
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-extensions-ingress
|
||||
failurePolicy: Fail
|
||||
name: extensions.ingress.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- extensions
|
||||
apiVersions:
|
||||
- v1beta1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- ingresses
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-networking-ingress
|
||||
failurePolicy: Fail
|
||||
name: networking.ingress.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
apiVersions:
|
||||
- v1beta1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- ingresses
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validate-v1-namespace-quota
|
||||
failurePolicy: Fail
|
||||
name: quota.namespace.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- namespaces
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-network-policy
|
||||
failurePolicy: Fail
|
||||
name: validating.network-policy.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
- DELETE
|
||||
resources:
|
||||
- networkpolicies
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: webhook-service
|
||||
namespace: system
|
||||
path: /validating-v1-pvc
|
||||
failurePolicy: Fail
|
||||
name: pvc.capsule.clastix.io
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
apiVersions:
|
||||
- v1
|
||||
operations:
|
||||
- CREATE
|
||||
resources:
|
||||
- persistentvolumeclaims
|
||||
11
config/webhook/service.yaml
Normal file
11
config/webhook/service.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: webhook-service
|
||||
spec:
|
||||
ports:
|
||||
- port: 443
|
||||
targetPort: 9443
|
||||
selector:
|
||||
control-plane: controller-manager
|
||||
154
contributing.md
154
contributing.md
@@ -9,7 +9,8 @@ The first step is to setup your local development environment
|
||||
The following dependencies are mandatory:
|
||||
|
||||
- [Go 1.13.8](https://golang.org/dl/)
|
||||
- [OperatorSDK 1.8](https://github.com/operator-framework/operator-sdk)
|
||||
- [OperatorSDK 1.9](https://github.com/operator-framework/operator-sdk)
|
||||
- [Kubebuilder](https://github.com/kubernetes-sigs/kubebuilder)
|
||||
- [KinD](https://github.com/kubernetes-sigs/kind)
|
||||
- [ngrok](https://ngrok.com/) (if you want to run locally)
|
||||
- [golangci-lint](https://github.com/golangci/golangci-lint)
|
||||
@@ -29,6 +30,13 @@ Some operations, like the Docker image build process or the code-generation of
|
||||
the CRDs manifests, as well the deep copy functions, require _Operator SDK_:
|
||||
the binary has to be installed into your `PATH`.
|
||||
|
||||
### Installing Kubebuilder
|
||||
|
||||
With the latest release of OperatorSDK there's a more tightly integration with
|
||||
Kubebuilder and its opinionated testing suite: ensure to download the latest
|
||||
binaries available from the _Releases_ GitHub page and place them into the
|
||||
`/usr/local/kubebuilder/bin` folder, ensuring this is also in your `PATH`.
|
||||
|
||||
### Installing KinD
|
||||
|
||||
Capsule is able to run on any certified Kubernetes installation and locally
|
||||
@@ -68,37 +76,63 @@ certificates and the context changed to the just born Kubernetes cluster.
|
||||
From the root path, issue the _make_ recipe:
|
||||
|
||||
```
|
||||
# make docker-image
|
||||
operator-sdk build quay.io/clastix/capsule:latest
|
||||
INFO[0001] Building OCI image quay.io/clastix/capsule:latest
|
||||
Sending build context to Docker daemon 89.26MB
|
||||
Step 1/7 : FROM registry.access.redhat.com/ubi8/ubi-minimal:latest
|
||||
---> 75a64ccf990b
|
||||
Step 2/7 : ENV OPERATOR=/usr/local/bin/capsule USER_UID=0 USER_NAME=capsule
|
||||
# make docker-build
|
||||
/home/prometherion/go/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
|
||||
go fmt ./...
|
||||
main.go
|
||||
go vet ./...
|
||||
/home/prometherion/go/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
go test ./... -coverprofile cover.out
|
||||
...
|
||||
docker build . -t quay.io/clastix/capsule:latest
|
||||
Sending build context to Docker daemon 43.21MB
|
||||
Step 1/15 : FROM golang:1.13 as builder
|
||||
---> 67d10cb69049
|
||||
Step 2/15 : WORKDIR /workspace
|
||||
---> Using cache
|
||||
---> e4610bd8596f
|
||||
Step 3/7 : COPY build/_output/bin/capsule ${OPERATOR}
|
||||
---> d783cc2b7c33
|
||||
Step 3/15 : COPY go.mod go.mod
|
||||
---> Using cache
|
||||
---> 1f6196485c28
|
||||
Step 4/7 : COPY build/bin /usr/local/bin
|
||||
---> 0fec3ca39e50
|
||||
Step 4/15 : COPY go.sum go.sum
|
||||
---> Using cache
|
||||
---> b517a62ca352
|
||||
Step 5/7 : RUN /usr/local/bin/user_setup
|
||||
---> de15be20dbe7
|
||||
Step 5/15 : RUN go mod download
|
||||
---> Using cache
|
||||
---> e879394010d5
|
||||
Step 6/7 : ENTRYPOINT ["/usr/local/bin/entrypoint"]
|
||||
---> b525cd9abc67
|
||||
Step 6/15 : COPY main.go main.go
|
||||
---> 67d9d6538ffc
|
||||
Step 7/15 : COPY api/ api/
|
||||
---> 6243b250d170
|
||||
Step 8/15 : COPY controllers/ controllers/
|
||||
---> 4abf8ce85484
|
||||
Step 9/15 : COPY pkg/ pkg/
|
||||
---> 2cd289b1d496
|
||||
Step 10/15 : RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go
|
||||
---> Running in dac9a1e3b23f
|
||||
Removing intermediate container dac9a1e3b23f
|
||||
---> bb650a8efcb2
|
||||
Step 11/15 : FROM gcr.io/distroless/static:nonroot
|
||||
---> 131713291b92
|
||||
Step 12/15 : WORKDIR /
|
||||
---> Using cache
|
||||
---> 6e740290e0e4
|
||||
Step 7/7 : USER ${USER_UID}
|
||||
---> Using cache
|
||||
---> ebb8f640dda1
|
||||
Successfully built ebb8f640dda1
|
||||
---> 677a73ab94d3
|
||||
Step 13/15 : COPY --from=builder /workspace/manager .
|
||||
---> 6ecb58a82c0a
|
||||
Step 14/15 : USER nonroot:nonroot
|
||||
---> Running in a0b8c95f85d4
|
||||
Removing intermediate container a0b8c95f85d4
|
||||
---> c4897d60a094
|
||||
Step 15/15 : ENTRYPOINT ["/manager"]
|
||||
---> Running in 1a42bab52aa7
|
||||
Removing intermediate container 1a42bab52aa7
|
||||
---> 37d2adbe2669
|
||||
Successfully built 37d2adbe2669
|
||||
Successfully tagged quay.io/clastix/capsule:latest
|
||||
INFO[0004] Operator build complete.
|
||||
```
|
||||
|
||||
The image `quay.io/clastix/capsule:latest` will be available locally, you just
|
||||
need to push it to kind with the following command.
|
||||
need to push it to _KinD_ with the following command.
|
||||
|
||||
```
|
||||
# kind load docker-image --nodes capsule-control-plane --name capsule quay.io/clastix/capsule:latest
|
||||
@@ -107,45 +141,34 @@ Image: "quay.io/clastix/capsule:latest" with ID "sha256:ebb8f640dda129a795ddc68b
|
||||
|
||||
### Deploy the Kubernetes manifests
|
||||
|
||||
With the current `kind-capsule` context enabled, create the `capsule-system`
|
||||
Namespace that will contain all the Kubernetes resources.
|
||||
With the current `kind-capsule` context enabled, deploy all the required
|
||||
manifests issuing the following command:
|
||||
|
||||
```
|
||||
# kubectl create namespace capsule-system
|
||||
namespace/capsule-system created
|
||||
make deploy
|
||||
```
|
||||
|
||||
Now it's time to install the _Custom Resource Definition_:
|
||||
This will install all the required Kubernetes resources, automatically.
|
||||
|
||||
You can check if Capsule is running tailing the logs:
|
||||
|
||||
```
|
||||
# kubectl apply -f deploy/crds/capsule.clastix.io_tenants_crd.yaml
|
||||
customresourcedefinition.apiextensions.k8s.io/tenants.capsule.clastix.io created
|
||||
```
|
||||
|
||||
Finally, install the required manifests issuing the following command:
|
||||
|
||||
```
|
||||
# kubectl apply -f deploy
|
||||
mutatingwebhookconfiguration.admissionregistration.k8s.io/capsule created
|
||||
clusterrole.rbac.authorization.k8s.io/namespace:deleter created
|
||||
clusterrole.rbac.authorization.k8s.io/namespace:provisioner created
|
||||
clusterrolebinding.rbac.authorization.k8s.io/namespace:provisioner created
|
||||
deployment.apps/capsule created
|
||||
clusterrole.rbac.authorization.k8s.io/capsule created
|
||||
clusterrolebinding.rbac.authorization.k8s.io/capsule-cluster-admin created
|
||||
clusterrolebinding.rbac.authorization.k8s.io/capsule created
|
||||
secret/capsule-ca created
|
||||
secret/capsule-tls created
|
||||
service/capsule created
|
||||
serviceaccount/capsule created
|
||||
```
|
||||
|
||||
You can check if Capsule is running checking the logs:
|
||||
|
||||
```
|
||||
# kubectl -n capsule-system logs -f -l name=capsule
|
||||
# kubectl -n capsule-system logs --all-containers -f -l control-plane=controller-manager
|
||||
...
|
||||
{"level":"info","ts":1596125071.5951712,"logger":"controller-runtime.controller","msg":"Starting workers","controller":"tenant-controller","worker count":1}
|
||||
2020-08-03T15:37:44.031Z INFO controllers.Tenant Role Binding sync result: unchanged {"Request.Name": "oil", "name": "namespace:deleter", "namespace": "oil-dev"}
|
||||
2020-08-03T15:37:44.032Z INFO controllers.Tenant Role Binding sync result: unchanged {"Request.Name": "oil", "name": "namespace:admin", "namespace": "oil-production"}
|
||||
2020-08-03T15:37:44.032Z INFO controllers.Tenant Role Binding sync result: unchanged {"Request.Name": "oil", "name": "namespace:deleter", "namespace": "oil-production"}
|
||||
2020-08-03T15:37:44.032Z INFO controllers.Tenant Tenant reconciling completed {"Request.Name": "oil"}
|
||||
2020-08-03T15:37:44.032Z DEBUG controller-runtime.controller Successfully Reconciled {"controller": "tenant", "request": "/oil"}
|
||||
2020-08-03T15:37:46.945Z INFO controllers.Namespace Reconciling Namespace {"Request.Name": "oil-staging"}
|
||||
2020-08-03T15:37:46.953Z INFO controllers.Namespace Namespace reconciliation processed {"Request.Name": "oil-staging"}
|
||||
2020-08-03T15:37:46.953Z DEBUG controller-runtime.controller Successfully Reconciled {"controller": "namespace", "request": "/oil-staging"}
|
||||
2020-08-03T15:37:46.957Z INFO controllers.Namespace Reconciling Namespace {"Request.Name": "oil-staging"}
|
||||
2020-08-03T15:37:46.957Z DEBUG controller-runtime.controller Successfully Reconciled {"controller": "namespace", "request": "/oil-staging"}
|
||||
I0803 15:16:01.763606 1 main.go:186] Valid token audiences:
|
||||
I0803 15:16:01.763689 1 main.go:232] Generating self signed cert as no cert is provided
|
||||
I0803 15:16:02.042022 1 main.go:281] Starting TCP socket on 0.0.0.0:8443
|
||||
I0803 15:16:02.042364 1 main.go:288] Listening securely on 0.0.0.0:8443
|
||||
```
|
||||
|
||||
Since Capsule is built using _OperatorSDK_, logging is handled by the zap
|
||||
@@ -169,8 +192,8 @@ access to the Kubernetes API Server.
|
||||
First, ensure the Capsule pod is not running scaling down the Deployment.
|
||||
|
||||
```
|
||||
# kubectl -n capsule-system scale deployment capsule --replicas=0
|
||||
deployment.apps/capsule scaled
|
||||
# kubectl -n capsule-system scale deployment capsule-controller-manager --replicas=0
|
||||
deployment.apps/capsule-controller-manager scaled
|
||||
```
|
||||
|
||||
> This is mandatory since Capsule uses Leader Election
|
||||
@@ -197,7 +220,7 @@ In another session we need a `ngrok` session, mandatory to debug also webhooks
|
||||
(YMMV).
|
||||
|
||||
```
|
||||
# ngrok http localhost:443
|
||||
# ngrok http https://localhost:9443
|
||||
ngrok by @inconshreveable
|
||||
|
||||
Session Status online
|
||||
@@ -205,8 +228,8 @@ Account Dario Tranchitella (Plan: Free)
|
||||
Version 2.3.35
|
||||
Region United States (us)
|
||||
Web Interface http://127.0.01:4040
|
||||
Forwarding http://cdb72b99348c.ngrok.io -> https://localhost:443
|
||||
Forwarding https://cdb72b99348c.ngrok.io -> https://localhost:443
|
||||
Forwarding http://cdb72b99348c.ngrok.io -> https://localhost:9443
|
||||
Forwarding https://cdb72b99348c.ngrok.io -> https://localhost:9443
|
||||
Connections ttl opn rt1 rt5 p50 p90
|
||||
0 0 0.00 0.00 0.00 0.00
|
||||
```
|
||||
@@ -217,14 +240,15 @@ _Dynamic Admissions Control Webhooks_.
|
||||
|
||||
#### Patching the MutatingWebhookConfiguration
|
||||
|
||||
Now it's time to patch the _MutatingWebhookConfiguration_, adding the said
|
||||
`ngrok` URL as base for each defined webhook, as following:
|
||||
Now it's time to patch the _MutatingWebhookConfiguration_ and the
|
||||
_ValidatingWebhookConfiguration_ too, adding the said `ngrok` URL as base for
|
||||
each defined webhook, as following:
|
||||
|
||||
```diff
|
||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||
kind: MutatingWebhookConfiguration
|
||||
metadata:
|
||||
name: capsule
|
||||
name: capsule-mutating-webhook-configuration
|
||||
webhooks:
|
||||
- name: owner.namespace.capsule.clastix.io
|
||||
failurePolicy: Fail
|
||||
@@ -237,7 +261,7 @@ webhooks:
|
||||
+ url: https://cdb72b99348c.ngrok.io/mutate-v1-namespace-owner-reference
|
||||
- caBundle:
|
||||
- service:
|
||||
- namespace: capsule-system
|
||||
- namespace: system
|
||||
- name: capsule
|
||||
- path: /mutate-v1-namespace-owner-reference
|
||||
...
|
||||
@@ -249,7 +273,7 @@ Finally, it's time to run locally Capsule using your preferred IDE (or not):
|
||||
from the project root path you can issue the following command.
|
||||
|
||||
```
|
||||
WATCH_NAMESPACE= KUBECONFIG=/path/to/your/kubeconfig OPERATOR_NAME=capsule go run cmd/manager/main.go
|
||||
make run
|
||||
```
|
||||
|
||||
All the logs will start to flow in your standard output, feel free to attach
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -11,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package namespace
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -24,106 +27,81 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
"github.com/clastix/capsule/pkg/apis/capsule/v1alpha1"
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
)
|
||||
|
||||
// Add creates a new Namespace Controller and adds it to the Manager. The Manager will set fields on the Controller
|
||||
// and Start it when the Manager is Started.
|
||||
func Add(mgr manager.Manager) error {
|
||||
return add(mgr, newReconciler(mgr))
|
||||
type NamespaceReconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// newReconciler returns a new reconcile.Reconciler
|
||||
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
|
||||
return &ReconcileNamespace{
|
||||
client: mgr.GetClient(),
|
||||
scheme: mgr.GetScheme(),
|
||||
}
|
||||
func (r *NamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&corev1.Namespace{}, builder.WithPredicates(predicate.Funcs{
|
||||
CreateFunc: func(event event.CreateEvent) (ok bool) {
|
||||
ok, _ = getCapsuleReference(event.Meta.GetOwnerReferences())
|
||||
return
|
||||
},
|
||||
DeleteFunc: func(deleteEvent event.DeleteEvent) (ok bool) {
|
||||
ok, _ = getCapsuleReference(deleteEvent.Meta.GetOwnerReferences())
|
||||
return
|
||||
},
|
||||
UpdateFunc: func(updateEvent event.UpdateEvent) (ok bool) {
|
||||
ok, _ = getCapsuleReference(updateEvent.MetaNew.GetOwnerReferences())
|
||||
return
|
||||
},
|
||||
GenericFunc: func(genericEvent event.GenericEvent) (ok bool) {
|
||||
ok, _ = getCapsuleReference(genericEvent.Meta.GetOwnerReferences())
|
||||
return
|
||||
},
|
||||
})).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func getCapsuleReference(refs []v1.OwnerReference) (ok bool, reference *v1.OwnerReference) {
|
||||
for _, r := range refs {
|
||||
if r.APIVersion == v1alpha1.SchemeGroupVersion.String() {
|
||||
if r.APIVersion == v1alpha1.GroupVersion.String() {
|
||||
return true, r.DeepCopy()
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// add adds a new Controller to mgr with r as the reconcile.Reconciler
|
||||
func add(mgr manager.Manager, r reconcile.Reconciler) error {
|
||||
// Create a new controller
|
||||
c, err := controller.New("namespace-controller", mgr, controller.Options{Reconciler: r})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Watch for changes to primary resource Namespace
|
||||
err = c.Watch(&source.Kind{Type: &corev1.Namespace{}}, &handler.EnqueueRequestForObject{}, predicate.Funcs{
|
||||
CreateFunc: func(event event.CreateEvent) (ok bool) {
|
||||
ok, _ = getCapsuleReference(event.Meta.GetOwnerReferences())
|
||||
return
|
||||
},
|
||||
DeleteFunc: func(deleteEvent event.DeleteEvent) (ok bool) {
|
||||
ok, _ = getCapsuleReference(deleteEvent.Meta.GetOwnerReferences())
|
||||
return
|
||||
},
|
||||
UpdateFunc: func(updateEvent event.UpdateEvent) (ok bool) {
|
||||
ok, _ = getCapsuleReference(updateEvent.MetaNew.GetOwnerReferences())
|
||||
return
|
||||
},
|
||||
GenericFunc: func(genericEvent event.GenericEvent) (ok bool) {
|
||||
ok, _ = getCapsuleReference(genericEvent.Meta.GetOwnerReferences())
|
||||
return
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReconcileNamespace reconciles a Namespace object
|
||||
type ReconcileNamespace struct {
|
||||
client client.Client
|
||||
scheme *runtime.Scheme
|
||||
logger logr.Logger
|
||||
}
|
||||
|
||||
func (r *ReconcileNamespace) removeNamespace(name string, tenant *v1alpha1.Tenant) {
|
||||
func (r *NamespaceReconciler) removeNamespace(name string, tenant *v1alpha1.Tenant) {
|
||||
c := tenant.Status.Namespaces.DeepCopy()
|
||||
sort.Sort(c)
|
||||
i := sort.SearchStrings(c, name)
|
||||
// namespace already removed, do nothing
|
||||
if i > c.Len() || i == c.Len() {
|
||||
r.Log.Info("Namespace has been already removed")
|
||||
return
|
||||
}
|
||||
// namespace is there, removing it
|
||||
r.Log.Info("Removing Namespace from Tenant status")
|
||||
tenant.Status.Namespaces = []string{}
|
||||
tenant.Status.Namespaces = append(tenant.Status.Namespaces, c[:i]...)
|
||||
tenant.Status.Namespaces = append(tenant.Status.Namespaces, c[i+1:]...)
|
||||
}
|
||||
|
||||
func (r *ReconcileNamespace) addNamespace(name string, tenant *v1alpha1.Tenant) {
|
||||
func (r *NamespaceReconciler) addNamespace(name string, tenant *v1alpha1.Tenant) {
|
||||
c := tenant.Status.Namespaces.DeepCopy()
|
||||
sort.Sort(c)
|
||||
i := sort.SearchStrings(c, name)
|
||||
// namespace already there, nothing to do
|
||||
if i < c.Len() && c[i] == name {
|
||||
r.Log.Info("Namespace has been already added")
|
||||
return
|
||||
}
|
||||
// missing namespace, let's append it
|
||||
r.Log.Info("Adding Namespace to Tenant status")
|
||||
if i == 0 {
|
||||
tenant.Status.Namespaces = []string{name}
|
||||
} else {
|
||||
@@ -134,21 +112,20 @@ func (r *ReconcileNamespace) addNamespace(name string, tenant *v1alpha1.Tenant)
|
||||
tenant.Status.Namespaces = append(tenant.Status.Namespaces, c[i:]...)
|
||||
}
|
||||
|
||||
func (r *ReconcileNamespace) updateNamespaceCount(tenant *v1alpha1.Tenant) error {
|
||||
tenant.Status.Size = uint(len(tenant.Status.Namespaces))
|
||||
|
||||
func (r *NamespaceReconciler) updateNamespaceCount(tenant *v1alpha1.Tenant) error {
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
return r.client.Status().Update(context.TODO(), tenant, &client.UpdateOptions{})
|
||||
tenant.Status.Size = uint(len(tenant.Status.Namespaces))
|
||||
return r.Client.Status().Update(context.TODO(), tenant, &client.UpdateOptions{})
|
||||
})
|
||||
}
|
||||
|
||||
func (r *ReconcileNamespace) Reconcile(request reconcile.Request) (res reconcile.Result, err error) {
|
||||
r.logger = log.Log.WithName("controller_namespace").WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
|
||||
r.logger.Info("Reconciling Namespace")
|
||||
func (r NamespaceReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) {
|
||||
r.Log = r.Log.WithValues("Request.Name", request.Name)
|
||||
r.Log.Info("Reconciling Namespace")
|
||||
|
||||
// Fetch the Namespace instance
|
||||
ns := &corev1.Namespace{}
|
||||
if err := r.client.Get(context.TODO(), request.NamespacedName, ns); err != nil {
|
||||
if err := r.Get(context.TODO(), request.NamespacedName, ns); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
// Request object not found, could have been deleted after reconcile request.
|
||||
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
|
||||
@@ -161,27 +138,27 @@ func (r *ReconcileNamespace) Reconcile(request reconcile.Request) (res reconcile
|
||||
|
||||
_, or := getCapsuleReference(ns.OwnerReferences)
|
||||
t := &v1alpha1.Tenant{}
|
||||
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: or.Name}, t); err != nil {
|
||||
if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: or.Name}, t); err != nil {
|
||||
// Error reading the object - requeue the request.
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
if err := r.ensureLabel(ns, t.Name); err != nil {
|
||||
r.logger.Error(err, "cannot update Namespace label")
|
||||
r.Log.Error(err, "cannot update Namespace label")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
r.updateTenantStatus(ns, t)
|
||||
|
||||
if err := r.updateNamespaceCount(t); err != nil {
|
||||
r.logger.Error(err, "cannot update Namespace list", "tenant", t.Name)
|
||||
r.Log.Error(err, "cannot update Namespace list", "tenant", t.Name)
|
||||
}
|
||||
|
||||
r.logger.Info("Namespace reconciliation processed")
|
||||
r.Log.Info("Namespace reconciliation processed")
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *ReconcileNamespace) ensureLabel(ns *corev1.Namespace, tenantName string) error {
|
||||
func (r *NamespaceReconciler) ensureLabel(ns *corev1.Namespace, tenantName string) error {
|
||||
capsuleLabel, err := v1alpha1.GetTypeLabel(&v1alpha1.Tenant{})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -191,15 +168,15 @@ func (r *ReconcileNamespace) ensureLabel(ns *corev1.Namespace, tenantName string
|
||||
}
|
||||
tl, ok := ns.Labels[capsuleLabel]
|
||||
if !ok || tl != tenantName {
|
||||
ns.Labels[capsuleLabel] = tenantName
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
return r.client.Update(context.TODO(), ns, &client.UpdateOptions{})
|
||||
ns.Labels[capsuleLabel] = tenantName
|
||||
return r.Client.Update(context.TODO(), ns, &client.UpdateOptions{})
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReconcileNamespace) updateTenantStatus(ns *corev1.Namespace, tenant *v1alpha1.Tenant) {
|
||||
func (r *NamespaceReconciler) updateTenantStatus(ns *corev1.Namespace, tenant *v1alpha1.Tenant) {
|
||||
switch ns.Status.Phase {
|
||||
case corev1.NamespaceTerminating:
|
||||
r.removeNamespace(ns.Name, tenant)
|
||||
177
controllers/secret/ca.go
Normal file
177
controllers/secret/ca.go
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package secret
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/clastix/capsule/pkg/cert"
|
||||
)
|
||||
|
||||
type CaReconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
func (r *CaReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&corev1.Secret{}, forOptionPerInstanceName(caSecretName)).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r CaReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) {
|
||||
var err error
|
||||
|
||||
r.Log = log.Log.WithName("controller_ca").WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
|
||||
r.Log.Info("Reconciling CA Secret")
|
||||
|
||||
// Fetch the CA instance
|
||||
instance := &corev1.Secret{}
|
||||
err = r.Client.Get(context.TODO(), request.NamespacedName, instance)
|
||||
if err != nil {
|
||||
// Error reading the object - requeue the request.
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
var ca cert.Ca
|
||||
var rq time.Duration
|
||||
ca, err = getCertificateAuthority(r.Client)
|
||||
if err != nil && errors.Is(err, MissingCaError{}) {
|
||||
ca, err = cert.GenerateCertificateAuthority()
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
r.Log.Info("Handling CA Secret")
|
||||
|
||||
rq, err = ca.ExpiresIn(time.Now())
|
||||
if err != nil {
|
||||
r.Log.Info("CA is expired, cleaning to obtain a new one")
|
||||
instance.Data = map[string][]byte{}
|
||||
} else {
|
||||
r.Log.Info("Updating CA secret with new PEM and RSA")
|
||||
|
||||
var crt *bytes.Buffer
|
||||
var key *bytes.Buffer
|
||||
crt, _ = ca.CaCertificatePem()
|
||||
key, _ = ca.CaPrivateKeyPem()
|
||||
|
||||
instance.Data = map[string][]byte{
|
||||
certSecretKey: crt.Bytes(),
|
||||
privateKeySecretKey: key.Bytes(),
|
||||
}
|
||||
// Updating ValidatingWebhookConfiguration CA bundle
|
||||
err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
vw := &v1.ValidatingWebhookConfiguration{}
|
||||
err = r.Get(context.TODO(), types.NamespacedName{Name: "capsule-validating-webhook-configuration"}, vw)
|
||||
if err != nil {
|
||||
r.Log.Error(err, "cannot retrieve ValidatingWebhookConfiguration")
|
||||
return err
|
||||
}
|
||||
for i, w := range vw.Webhooks {
|
||||
// Updating CABundle only in case of an internal service reference
|
||||
if w.ClientConfig.Service != nil {
|
||||
vw.Webhooks[i].ClientConfig.CABundle = instance.Data[certSecretKey]
|
||||
}
|
||||
}
|
||||
err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
return r.Update(context.TODO(), vw, &client.UpdateOptions{})
|
||||
})
|
||||
if err != nil {
|
||||
r.Log.Error(err, "cannot update MutatingWebhookConfiguration webhooks CA bundle")
|
||||
return err
|
||||
}
|
||||
return r.Update(context.TODO(), vw, &client.UpdateOptions{})
|
||||
})
|
||||
// Updating MutatingWebhookConfiguration CA bundle
|
||||
err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
mw := &v1.MutatingWebhookConfiguration{}
|
||||
err = r.Get(context.TODO(), types.NamespacedName{Name: "capsule-mutating-webhook-configuration"}, mw)
|
||||
if err != nil {
|
||||
r.Log.Error(err, "cannot retrieve MutatingWebhookConfiguration")
|
||||
return err
|
||||
}
|
||||
for i, w := range mw.Webhooks {
|
||||
// Updating CABundle only in case of an internal service reference
|
||||
if w.ClientConfig.Service != nil {
|
||||
mw.Webhooks[i].ClientConfig.CABundle = instance.Data[certSecretKey]
|
||||
}
|
||||
}
|
||||
return r.Update(context.TODO(), mw, &client.UpdateOptions{})
|
||||
})
|
||||
if err != nil {
|
||||
r.Log.Error(err, "cannot update MutatingWebhookConfiguration webhooks CA bundle")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
var res controllerutil.OperationResult
|
||||
t := &corev1.Secret{ObjectMeta: instance.ObjectMeta}
|
||||
res, err = controllerutil.CreateOrUpdate(context.TODO(), r.Client, t, func() error {
|
||||
t.Data = instance.Data
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
r.Log.Error(err, "cannot update Capsule TLS")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
if res == controllerutil.OperationResultUpdated {
|
||||
r.Log.Info("Capsule CA has been updated, we need to trigger TLS update too")
|
||||
tls := &corev1.Secret{}
|
||||
err = r.Get(context.TODO(), types.NamespacedName{
|
||||
Namespace: "capsule-system",
|
||||
Name: tlsSecretName,
|
||||
}, tls)
|
||||
if err != nil {
|
||||
r.Log.Error(err, "Capsule TLS Secret missing")
|
||||
}
|
||||
err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
_, err = controllerutil.CreateOrUpdate(context.TODO(), r.Client, tls, func() error {
|
||||
tls.Data = map[string][]byte{}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
r.Log.Error(err, "Cannot clean Capsule TLS Secret due to CA update")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
r.Log.Info("Reconciliation completed, processing back in " + rq.String())
|
||||
return reconcile.Result{Requeue: true, RequeueAfter: rq}, nil
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -14,9 +17,9 @@ limitations under the License.
|
||||
package secret
|
||||
|
||||
const (
|
||||
Cert = "tls.crt"
|
||||
PrivateKey = "tls.key"
|
||||
certSecretKey = "tls.crt"
|
||||
privateKeySecretKey = "tls.key"
|
||||
|
||||
CaSecretName = "capsule-ca"
|
||||
TlsSecretName = "capsule-tls"
|
||||
caSecretName = "capsule-ca"
|
||||
tlsSecretName = "capsule-tls"
|
||||
)
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -11,12 +14,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
package secret
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/webhook/pvc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
AddToWebhookServer = append(AddToWebhookServer, pvc.Add)
|
||||
type MissingCaError struct {
|
||||
}
|
||||
|
||||
func (MissingCaError) Error() string {
|
||||
return "CA has not been created yet, please generate a new"
|
||||
}
|
||||
75
controllers/secret/reconciler.go
Normal file
75
controllers/secret/reconciler.go
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
|
||||
"github.com/clastix/capsule/pkg/cert"
|
||||
)
|
||||
|
||||
func getCertificateAuthority(client client.Client) (ca cert.Ca, err error) {
|
||||
instance := &corev1.Secret{}
|
||||
|
||||
err = client.Get(context.TODO(), types.NamespacedName{
|
||||
Namespace: "capsule-system",
|
||||
Name: caSecretName,
|
||||
}, instance)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("missing secret %s, cannot reconcile", caSecretName)
|
||||
}
|
||||
|
||||
if instance.Data == nil {
|
||||
return nil, MissingCaError{}
|
||||
}
|
||||
|
||||
ca, err = cert.NewCertificateAuthorityFromBytes(instance.Data[certSecretKey], instance.Data[privateKeySecretKey])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func forOptionPerInstanceName(instanceName string) builder.ForOption {
|
||||
return builder.WithPredicates(predicate.Funcs{
|
||||
CreateFunc: func(event event.CreateEvent) bool {
|
||||
return filterByName(event.Meta.GetName(), instanceName)
|
||||
},
|
||||
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
|
||||
return filterByName(deleteEvent.Meta.GetName(), instanceName)
|
||||
},
|
||||
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
|
||||
return filterByName(updateEvent.MetaNew.GetName(), instanceName)
|
||||
},
|
||||
GenericFunc: func(genericEvent event.GenericEvent) bool {
|
||||
return filterByName(genericEvent.Meta.GetName(), instanceName)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func filterByName(objName, desired string) bool {
|
||||
return objName == desired
|
||||
}
|
||||
127
controllers/secret/tls.go
Normal file
127
controllers/secret/tls.go
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/clastix/capsule/pkg/cert"
|
||||
)
|
||||
|
||||
type TlsReconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
func (r *TlsReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&corev1.Secret{}, forOptionPerInstanceName(tlsSecretName)).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r TlsReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) {
|
||||
var err error
|
||||
|
||||
r.Log = r.Log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
|
||||
r.Log.Info("Reconciling TLS Secret")
|
||||
|
||||
// Fetch the Secret instance
|
||||
instance := &corev1.Secret{}
|
||||
err = r.Get(context.TODO(), request.NamespacedName, instance)
|
||||
if err != nil {
|
||||
// Error reading the object - requeue the request.
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
var ca cert.Ca
|
||||
var rq time.Duration
|
||||
|
||||
ca, err = getCertificateAuthority(r.Client)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
var shouldCreate bool
|
||||
for _, key := range []string{certSecretKey, privateKeySecretKey} {
|
||||
if _, ok := instance.Data[key]; !ok {
|
||||
shouldCreate = true
|
||||
}
|
||||
}
|
||||
|
||||
if shouldCreate {
|
||||
r.Log.Info("Missing Capsule TLS certificate")
|
||||
rq = 6 * 30 * 24 * time.Hour
|
||||
|
||||
opts := cert.NewCertOpts(time.Now().Add(rq), "capsule-webhook-service.capsule-system.svc")
|
||||
crt, key, err := ca.GenerateCertificate(opts)
|
||||
if err != nil {
|
||||
r.Log.Error(err, "Cannot generate new TLS certificate")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
instance.Data = map[string][]byte{
|
||||
certSecretKey: crt.Bytes(),
|
||||
privateKeySecretKey: key.Bytes(),
|
||||
}
|
||||
} else {
|
||||
var c *x509.Certificate
|
||||
var b *pem.Block
|
||||
b, _ = pem.Decode(instance.Data[certSecretKey])
|
||||
c, err = x509.ParseCertificate(b.Bytes)
|
||||
if err != nil {
|
||||
r.Log.Error(err, "cannot parse Capsule TLS")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
rq = time.Duration(c.NotAfter.Unix()-time.Now().Unix()) * time.Second
|
||||
if time.Now().After(c.NotAfter) {
|
||||
r.Log.Info("Capsule TLS is expired, cleaning to obtain a new one")
|
||||
instance.Data = map[string][]byte{}
|
||||
}
|
||||
}
|
||||
|
||||
var res controllerutil.OperationResult
|
||||
t := &corev1.Secret{ObjectMeta: instance.ObjectMeta}
|
||||
res, err = controllerutil.CreateOrUpdate(context.TODO(), r.Client, t, func() error {
|
||||
t.Data = instance.Data
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
r.Log.Error(err, "cannot update Capsule TLS")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
if instance.Name == tlsSecretName && res == controllerutil.OperationResultUpdated {
|
||||
r.Log.Info("Capsule TLS certificates has been updated, we need to restart the Controller")
|
||||
_ = syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
||||
}
|
||||
|
||||
r.Log.Info("Reconciliation completed, processing back in " + rq.String())
|
||||
return reconcile.Result{Requeue: true, RequeueAfter: rq}, nil
|
||||
}
|
||||
81
controllers/suite_test.go
Normal file
81
controllers/suite_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
capsulev1alpha "github.com/clastix/capsule/api/v1alpha1"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
"Controller Suite",
|
||||
[]Reporter{printer.NewlineReporter{}})
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
|
||||
}
|
||||
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
err = capsulev1alpha.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
close(done)
|
||||
}, 60)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -11,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tenant
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -32,119 +35,83 @@ import (
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
capsulev1alpha1 "github.com/clastix/capsule/pkg/apis/capsule/v1alpha1"
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
)
|
||||
|
||||
// Add creates a new Tenant Controller and adds it to the Manager. The Manager will set fields on the Controller
|
||||
// and Start it when the Manager is Started.
|
||||
func Add(mgr manager.Manager) error {
|
||||
return add(mgr, &ReconcileTenant{
|
||||
client: mgr.GetClient(),
|
||||
scheme: mgr.GetScheme(),
|
||||
logger: log.Log.WithName("controller_tenant"),
|
||||
})
|
||||
// TenantReconciler reconciles a Tenant object
|
||||
type TenantReconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// add adds a new Controller to mgr with r as the reconcile.Reconciler
|
||||
func add(mgr manager.Manager, r reconcile.Reconciler) error {
|
||||
// Create a new controller
|
||||
c, err := controller.New("tenant-controller", mgr, controller.Options{Reconciler: r})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Watch for changes to primary resource Tenant
|
||||
err = c.Watch(&source.Kind{Type: &capsulev1alpha1.Tenant{}}, &handler.EnqueueRequestForObject{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Watch for controlled resources
|
||||
for _, r := range []runtime.Object{&networkingv1.NetworkPolicy{}, &corev1.LimitRange{}, &corev1.ResourceQuota{}, &rbacv1.RoleBinding{}} {
|
||||
err = c.Watch(&source.Kind{Type: r}, &handler.EnqueueRequestForOwner{
|
||||
IsController: true,
|
||||
OwnerType: &capsulev1alpha1.Tenant{},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&capsulev1alpha1.Tenant{}).
|
||||
Owns(&networkingv1.NetworkPolicy{}).
|
||||
Owns(&corev1.LimitRange{}).
|
||||
Owns(&corev1.ResourceQuota{}).
|
||||
Owns(&rbacv1.RoleBinding{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// ReconcileTenant reconciles a Tenant object
|
||||
type ReconcileTenant struct {
|
||||
client client.Client
|
||||
scheme *runtime.Scheme
|
||||
logger logr.Logger
|
||||
}
|
||||
|
||||
// Reconcile reads that state of the cluster for a Tenant object and makes changes based on the state read
|
||||
// and what is in the Tenant.Spec
|
||||
// The Controller will requeue the Request to be processed again if the returned error is non-nil or
|
||||
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
|
||||
func (r *ReconcileTenant) Reconcile(request reconcile.Request) (reconcile.Result, error) {
|
||||
r.logger = log.Log.WithName("controller_tenant").WithValues("Request.Name", request.Name)
|
||||
func (r TenantReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) {
|
||||
r.Log = r.Log.WithValues("Request.Name", request.Name)
|
||||
|
||||
// Fetch the Tenant instance
|
||||
instance := &capsulev1alpha1.Tenant{}
|
||||
err := r.client.Get(context.TODO(), request.NamespacedName, instance)
|
||||
err := r.Get(context.TODO(), request.NamespacedName, instance)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
r.logger.Info("Request object not found, could have been deleted after reconcile request")
|
||||
r.Log.Info("Request object not found, could have been deleted after reconcile request")
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
r.logger.Error(err, "Error reading the object")
|
||||
r.Log.Error(err, "Error reading the object")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
r.logger.Info("Starting processing of Network Policies", "items", len(instance.Spec.NetworkPolicies))
|
||||
r.Log.Info("Starting processing of Network Policies", "items", len(instance.Spec.NetworkPolicies))
|
||||
if err := r.syncNetworkPolicies(instance); err != nil {
|
||||
r.logger.Error(err, "Cannot sync NetworkPolicy items")
|
||||
r.Log.Error(err, "Cannot sync NetworkPolicy items")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
r.logger.Info("Starting processing of Node Selector")
|
||||
r.Log.Info("Starting processing of Node Selector")
|
||||
if err := r.ensureNodeSelector(instance); err != nil {
|
||||
r.logger.Error(err, "Cannot sync Namespaces Node Selector items")
|
||||
r.Log.Error(err, "Cannot sync Namespaces Node Selector items")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
r.logger.Info("Starting processing of Limit Ranges", "items", len(instance.Spec.LimitRanges))
|
||||
r.Log.Info("Starting processing of Limit Ranges", "items", len(instance.Spec.LimitRanges))
|
||||
if err := r.syncLimitRanges(instance); err != nil {
|
||||
r.logger.Error(err, "Cannot sync LimitRange items")
|
||||
r.Log.Error(err, "Cannot sync LimitRange items")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
r.logger.Info("Starting processing of Resource Quotas", "items", len(instance.Spec.ResourceQuota))
|
||||
r.Log.Info("Starting processing of Resource Quotas", "items", len(instance.Spec.ResourceQuota))
|
||||
if err := r.syncResourceQuotas(instance); err != nil {
|
||||
r.logger.Error(err, "Cannot sync ResourceQuota items")
|
||||
r.Log.Error(err, "Cannot sync ResourceQuota items")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
r.logger.Info("Ensuring RoleBinding for owner")
|
||||
r.Log.Info("Ensuring RoleBinding for owner")
|
||||
if err := r.ownerRoleBinding(instance); err != nil {
|
||||
r.logger.Error(err, "Cannot sync owner RoleBinding")
|
||||
r.Log.Error(err, "Cannot sync owner RoleBinding")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
r.logger.Info("Tenant reconciling completed")
|
||||
return reconcile.Result{}, nil
|
||||
r.Log.Info("Tenant reconciling completed")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// pruningResources is taking care of removing the no more requested sub-resources as LimitRange, ResourceQuota or
|
||||
// NetworkPolicy using the "notin" LabelSelector to perform an outer-join removal.
|
||||
func (r *ReconcileTenant) pruningResources(ns string, keys []string, obj runtime.Object) error {
|
||||
func (r *TenantReconciler) pruningResources(ns string, keys []string, obj runtime.Object) error {
|
||||
capsuleLabel, err := capsulev1alpha1.GetTypeLabel(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -153,9 +120,9 @@ func (r *ReconcileTenant) pruningResources(ns string, keys []string, obj runtime
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.logger.Info("Pruning objects with label selector " + req.String())
|
||||
r.Log.Info("Pruning objects with label selector " + req.String())
|
||||
err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
return r.client.DeleteAllOf(context.TODO(), obj, &client.DeleteAllOfOptions{
|
||||
return r.DeleteAllOf(context.TODO(), obj, &client.DeleteAllOfOptions{
|
||||
ListOptions: client.ListOptions{
|
||||
LabelSelector: labels.NewSelector().Add(*req),
|
||||
Namespace: ns,
|
||||
@@ -173,7 +140,7 @@ func (r *ReconcileTenant) pruningResources(ns string, keys []string, obj runtime
|
||||
// Serial ResourceQuota processing is expensive: using Go routines we can speed it up.
|
||||
// In case of multiple errors these are logged properly, returning a generic error since we have to repush back the
|
||||
// reconciliation loop.
|
||||
func (r *ReconcileTenant) resourceQuotasUpdate(resourceName corev1.ResourceName, qt resource.Quantity, list ...corev1.ResourceQuota) (err error) {
|
||||
func (r *TenantReconciler) resourceQuotasUpdate(resourceName corev1.ResourceName, qt resource.Quantity, list ...corev1.ResourceQuota) (err error) {
|
||||
ch := make(chan error, len(list))
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
@@ -184,7 +151,7 @@ func (r *ReconcileTenant) resourceQuotasUpdate(resourceName corev1.ResourceName,
|
||||
ch <- retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
// Retrieving from the cache the actual ResourceQuota
|
||||
found := &corev1.ResourceQuota{}
|
||||
_ = r.client.Get(context.TODO(), types.NamespacedName{Namespace: rq.Namespace, Name: rq.Name}, found)
|
||||
_ = r.Get(context.TODO(), types.NamespacedName{Namespace: rq.Namespace, Name: rq.Name}, found)
|
||||
// Ensuring annotation map is there to avoid uninitialized map error and
|
||||
// assigning the overall usage
|
||||
if found.Annotations == nil {
|
||||
@@ -194,7 +161,7 @@ func (r *ReconcileTenant) resourceQuotasUpdate(resourceName corev1.ResourceName,
|
||||
found.Annotations[capsulev1alpha1.UsedQuotaFor(resourceName)] = qt.String()
|
||||
// Updating the Resource according to the qt.Cmp result
|
||||
found.Spec.Hard = rq.Spec.Hard
|
||||
return r.client.Update(context.TODO(), found, &client.UpdateOptions{})
|
||||
return r.Update(context.TODO(), found, &client.UpdateOptions{})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -209,7 +176,7 @@ func (r *ReconcileTenant) resourceQuotasUpdate(resourceName corev1.ResourceName,
|
||||
// We had an error and we mark the whole transaction as failed
|
||||
// to process it another time acording to the Tenant controller back-off factor.
|
||||
err = fmt.Errorf("update of outer ResourceQuota items has failed")
|
||||
r.logger.Error(err, "Cannot update outer ResourceQuotas", "resourceName", resourceName.String())
|
||||
r.Log.Error(err, "Cannot update outer ResourceQuotas", "resourceName", resourceName.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -223,7 +190,7 @@ func (r *ReconcileTenant) resourceQuotasUpdate(resourceName corev1.ResourceName,
|
||||
// .Status.Used value as the .Hard value.
|
||||
// This will trigger a following reconciliation but that's ok: the mutateFn will re-use the same business logic, letting
|
||||
// the mutateFn along with the CreateOrUpdate to don't perform the update since resources are identical.
|
||||
func (r *ReconcileTenant) syncResourceQuotas(tenant *capsulev1alpha1.Tenant) error {
|
||||
func (r *TenantReconciler) syncResourceQuotas(tenant *capsulev1alpha1.Tenant) error {
|
||||
// getting requested ResourceQuota keys
|
||||
keys := make([]string, 0, len(tenant.Spec.ResourceQuota))
|
||||
for i := range tenant.Spec.ResourceQuota {
|
||||
@@ -256,27 +223,27 @@ func (r *ReconcileTenant) syncResourceQuotas(tenant *capsulev1alpha1.Tenant) err
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := controllerutil.CreateOrUpdate(context.TODO(), r.client, target, func() (err error) {
|
||||
res, err := controllerutil.CreateOrUpdate(context.TODO(), r.Client, target, func() (err error) {
|
||||
// Requirement to list ResourceQuota of the current Tenant
|
||||
tr, err := labels.NewRequirement(tenantLabel, selection.Equals, []string{tenant.Name})
|
||||
if err != nil {
|
||||
r.logger.Error(err, "Cannot build ResourceQuota Tenant requirement")
|
||||
r.Log.Error(err, "Cannot build ResourceQuota Tenant requirement")
|
||||
}
|
||||
// Requirement to list ResourceQuota for the current index
|
||||
ir, err := labels.NewRequirement(typeLabel, selection.Equals, []string{strconv.Itoa(i)})
|
||||
if err != nil {
|
||||
r.logger.Error(err, "Cannot build ResourceQuota index requirement")
|
||||
r.Log.Error(err, "Cannot build ResourceQuota index requirement")
|
||||
}
|
||||
|
||||
// Listing all the ResourceQuota according to the said requirements.
|
||||
// These are required since Capsule is going to sum all the used quota to
|
||||
// sum them and get the Tenant one.
|
||||
rql := &corev1.ResourceQuotaList{}
|
||||
err = r.client.List(context.TODO(), rql, &client.ListOptions{
|
||||
err = r.List(context.TODO(), rql, &client.ListOptions{
|
||||
LabelSelector: labels.NewSelector().Add(*tr).Add(*ir),
|
||||
})
|
||||
if err != nil {
|
||||
r.logger.Error(err, "Cannot list ResourceQuota", "tenantFilter", tr.String(), "indexFilter", ir.String())
|
||||
r.Log.Error(err, "Cannot list ResourceQuota", "tenantFilter", tr.String(), "indexFilter", ir.String())
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -286,14 +253,14 @@ func (r *ReconcileTenant) syncResourceQuotas(tenant *capsulev1alpha1.Tenant) err
|
||||
// For this case, we're going to block the Quota setting the Hard as the
|
||||
// used one.
|
||||
for rn, rq := range q.Hard {
|
||||
r.logger.Info("Desired hard " + rn.String() + " quota is " + rq.String())
|
||||
r.Log.Info("Desired hard " + rn.String() + " quota is " + rq.String())
|
||||
|
||||
// Getting the whole usage across all the Tenant Namespaces
|
||||
var qt resource.Quantity
|
||||
for _, rq := range rql.Items {
|
||||
qt.Add(rq.Status.Used[rn])
|
||||
}
|
||||
r.logger.Info("Computed " + rn.String() + " quota for the whole Tenant is " + qt.String())
|
||||
r.Log.Info("Computed " + rn.String() + " quota for the whole Tenant is " + qt.String())
|
||||
|
||||
switch qt.Cmp(q.Hard[rn]) {
|
||||
case 0:
|
||||
@@ -324,13 +291,13 @@ func (r *ReconcileTenant) syncResourceQuotas(tenant *capsulev1alpha1.Tenant) err
|
||||
target.Spec = q
|
||||
}
|
||||
if err := r.resourceQuotasUpdate(rn, qt, rql.Items...); err != nil {
|
||||
r.logger.Error(err, "cannot proceed with outer ResourceQuota")
|
||||
r.Log.Error(err, "cannot proceed with outer ResourceQuota")
|
||||
return err
|
||||
}
|
||||
}
|
||||
return controllerutil.SetControllerReference(tenant, target, r.scheme)
|
||||
return controllerutil.SetControllerReference(tenant, target, r.Scheme)
|
||||
})
|
||||
r.logger.Info("Resource Quota sync result: "+string(res), "name", target.Name, "namespace", target.Namespace)
|
||||
r.Log.Info("Resource Quota sync result: "+string(res), "name", target.Name, "namespace", target.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -341,7 +308,7 @@ func (r *ReconcileTenant) syncResourceQuotas(tenant *capsulev1alpha1.Tenant) err
|
||||
}
|
||||
|
||||
// Ensuring all the LimitRange are applied to each Namespace handled by the Tenant.
|
||||
func (r *ReconcileTenant) syncLimitRanges(tenant *capsulev1alpha1.Tenant) error {
|
||||
func (r *TenantReconciler) syncLimitRanges(tenant *capsulev1alpha1.Tenant) error {
|
||||
// getting requested LimitRange keys
|
||||
keys := make([]string, 0, len(tenant.Spec.LimitRanges))
|
||||
for i := range tenant.Spec.LimitRanges {
|
||||
@@ -369,15 +336,15 @@ func (r *ReconcileTenant) syncLimitRanges(tenant *capsulev1alpha1.Tenant) error
|
||||
Namespace: ns,
|
||||
},
|
||||
}
|
||||
res, err := controllerutil.CreateOrUpdate(context.TODO(), r.client, t, func() (err error) {
|
||||
res, err := controllerutil.CreateOrUpdate(context.TODO(), r.Client, t, func() (err error) {
|
||||
t.ObjectMeta.Labels = map[string]string{
|
||||
tl: tenant.Name,
|
||||
ll: strconv.Itoa(i),
|
||||
}
|
||||
t.Spec = spec
|
||||
return controllerutil.SetControllerReference(tenant, t, r.scheme)
|
||||
return controllerutil.SetControllerReference(tenant, t, r.Scheme)
|
||||
})
|
||||
r.logger.Info("LimitRange sync result: "+string(res), "name", t.Name, "namespace", t.Namespace)
|
||||
r.Log.Info("LimitRange sync result: "+string(res), "name", t.Name, "namespace", t.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -388,7 +355,7 @@ func (r *ReconcileTenant) syncLimitRanges(tenant *capsulev1alpha1.Tenant) error
|
||||
}
|
||||
|
||||
// Ensuring all the NetworkPolicies are applied to each Namespace handled by the Tenant.
|
||||
func (r *ReconcileTenant) syncNetworkPolicies(tenant *capsulev1alpha1.Tenant) error {
|
||||
func (r *TenantReconciler) syncNetworkPolicies(tenant *capsulev1alpha1.Tenant) error {
|
||||
// getting requested NetworkPolicy keys
|
||||
keys := make([]string, 0, len(tenant.Spec.NetworkPolicies))
|
||||
for i := range tenant.Spec.NetworkPolicies {
|
||||
@@ -420,11 +387,11 @@ func (r *ReconcileTenant) syncNetworkPolicies(tenant *capsulev1alpha1.Tenant) er
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := controllerutil.CreateOrUpdate(context.TODO(), r.client, t, func() (err error) {
|
||||
res, err := controllerutil.CreateOrUpdate(context.TODO(), r.Client, t, func() (err error) {
|
||||
t.Spec = spec
|
||||
return controllerutil.SetControllerReference(tenant, t, r.scheme)
|
||||
return controllerutil.SetControllerReference(tenant, t, r.Scheme)
|
||||
})
|
||||
r.logger.Info("Network Policy sync result: "+string(res), "name", t.Name, "namespace", t.Namespace)
|
||||
r.Log.Info("Network Policy sync result: "+string(res), "name", t.Name, "namespace", t.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -438,7 +405,7 @@ func (r *ReconcileTenant) syncNetworkPolicies(tenant *capsulev1alpha1.Tenant) er
|
||||
// Since RBAC is based on deny all first, some specific actions like editing Capsule resources are going to be blocked
|
||||
// via Dynamic Admission Webhooks.
|
||||
// TODO(prometherion): we could create a capsule:admin role rather than hitting webhooks for each action
|
||||
func (r *ReconcileTenant) ownerRoleBinding(tenant *capsulev1alpha1.Tenant) error {
|
||||
func (r *TenantReconciler) ownerRoleBinding(tenant *capsulev1alpha1.Tenant) error {
|
||||
// getting RoleBinding label for the mutateFn
|
||||
tl, err := capsulev1alpha1.GetTypeLabel(&capsulev1alpha1.Tenant{})
|
||||
if err != nil {
|
||||
@@ -463,7 +430,7 @@ func (r *ReconcileTenant) ownerRoleBinding(tenant *capsulev1alpha1.Tenant) error
|
||||
rbl[types.NamespacedName{Namespace: i, Name: "namespace:deleter"}] = rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "ClusterRole",
|
||||
Name: "namespace:deleter",
|
||||
Name: "capsule-namespace:deleter",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,13 +443,13 @@ func (r *ReconcileTenant) ownerRoleBinding(tenant *capsulev1alpha1.Tenant) error
|
||||
}
|
||||
|
||||
var res controllerutil.OperationResult
|
||||
res, err = controllerutil.CreateOrUpdate(context.TODO(), r.client, target, func() (err error) {
|
||||
res, err = controllerutil.CreateOrUpdate(context.TODO(), r.Client, target, func() (err error) {
|
||||
target.ObjectMeta.Labels = l
|
||||
target.Subjects = s
|
||||
target.RoleRef = rr
|
||||
return controllerutil.SetControllerReference(tenant, target, r.scheme)
|
||||
return controllerutil.SetControllerReference(tenant, target, r.Scheme)
|
||||
})
|
||||
r.logger.Info("Role Binding sync result: "+string(res), "name", target.Name, "namespace", target.Namespace)
|
||||
r.Log.Info("Role Binding sync result: "+string(res), "name", target.Name, "namespace", target.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -490,7 +457,7 @@ func (r *ReconcileTenant) ownerRoleBinding(tenant *capsulev1alpha1.Tenant) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ReconcileTenant) ensureNodeSelector(tenant *capsulev1alpha1.Tenant) (err error) {
|
||||
func (r *TenantReconciler) ensureNodeSelector(tenant *capsulev1alpha1.Tenant) (err error) {
|
||||
if tenant.Spec.NodeSelector == nil {
|
||||
return
|
||||
}
|
||||
@@ -508,7 +475,7 @@ func (r *ReconcileTenant) ensureNodeSelector(tenant *capsulev1alpha1.Tenant) (er
|
||||
}
|
||||
|
||||
var res controllerutil.OperationResult
|
||||
res, err = controllerutil.CreateOrUpdate(context.TODO(), r.client, ns, func() error {
|
||||
res, err = controllerutil.CreateOrUpdate(context.TODO(), r.Client, ns, func() error {
|
||||
if ns.Annotations == nil {
|
||||
ns.Annotations = make(map[string]string)
|
||||
}
|
||||
@@ -519,7 +486,7 @@ func (r *ReconcileTenant) ensureNodeSelector(tenant *capsulev1alpha1.Tenant) (er
|
||||
ns.Annotations["scheduler.alpha.kubernetes.io/node-selector"] = strings.Join(selector, ",")
|
||||
return nil
|
||||
})
|
||||
r.logger.Info("Namespace Node sync result: "+string(res), "name", ns.Name)
|
||||
r.Log.Info("Namespace Node sync result: "+string(res), "name", ns.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,710 +0,0 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: tenants.capsule.clastix.io
|
||||
spec:
|
||||
group: capsule.clastix.io
|
||||
names:
|
||||
kind: Tenant
|
||||
listKind: TenantList
|
||||
plural: tenants
|
||||
singular: tenant
|
||||
scope: Cluster
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: The max amount of Namespaces can be created
|
||||
jsonPath: .spec.namespaceQuota
|
||||
name: Namespace quota
|
||||
type: integer
|
||||
- description: The total amount of Namespaces in use
|
||||
jsonPath: .status.size
|
||||
name: Namespace count
|
||||
type: integer
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Tenant is the Schema for the tenants API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: TenantSpec defines the desired state of Tenant
|
||||
properties:
|
||||
ingressClasses:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
limitRanges:
|
||||
items:
|
||||
description: LimitRangeSpec defines a min/max usage limit for resources
|
||||
that match on kind.
|
||||
properties:
|
||||
limits:
|
||||
description: Limits is the list of LimitRangeItem objects that
|
||||
are enforced.
|
||||
items:
|
||||
description: LimitRangeItem defines a min/max usage limit
|
||||
for any resource that matches on kind.
|
||||
properties:
|
||||
default:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: Default resource requirement limit value
|
||||
by resource name if resource limit is omitted.
|
||||
type: object
|
||||
defaultRequest:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: DefaultRequest is the default resource requirement
|
||||
request value by resource name if resource request is
|
||||
omitted.
|
||||
type: object
|
||||
max:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: Max usage constraints on this kind by resource
|
||||
name.
|
||||
type: object
|
||||
maxLimitRequestRatio:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: MaxLimitRequestRatio if specified, the named
|
||||
resource must have a request and limit that are both
|
||||
non-zero where limit divided by request is less than
|
||||
or equal to the enumerated value; this represents the
|
||||
max burst for the named resource.
|
||||
type: object
|
||||
min:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: Min usage constraints on this kind by resource
|
||||
name.
|
||||
type: object
|
||||
type:
|
||||
description: Type of resource that this limit applies
|
||||
to.
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- limits
|
||||
type: object
|
||||
type: array
|
||||
namespaceQuota:
|
||||
minimum: 1
|
||||
type: integer
|
||||
networkPolicies:
|
||||
items:
|
||||
description: NetworkPolicySpec provides the specification of a NetworkPolicy
|
||||
properties:
|
||||
egress:
|
||||
description: List of egress rules to be applied to the selected
|
||||
pods. Outgoing traffic is allowed if there are no NetworkPolicies
|
||||
selecting the pod (and cluster policy otherwise allows the
|
||||
traffic), OR if the traffic matches at least one egress rule
|
||||
across all of the NetworkPolicy objects whose podSelector
|
||||
matches the pod. If this field is empty then this NetworkPolicy
|
||||
limits all outgoing traffic (and serves solely to ensure that
|
||||
the pods it selects are isolated by default). This field is
|
||||
beta-level in 1.8
|
||||
items:
|
||||
description: NetworkPolicyEgressRule describes a particular
|
||||
set of traffic that is allowed out of pods matched by a
|
||||
NetworkPolicySpec's podSelector. The traffic must match
|
||||
both ports and to. This type is beta-level in 1.8
|
||||
properties:
|
||||
ports:
|
||||
description: List of destination ports for outgoing traffic.
|
||||
Each item in this list is combined using a logical OR.
|
||||
If this field is empty or missing, this rule matches
|
||||
all ports (traffic not restricted by port). If this
|
||||
field is present and contains at least one item, then
|
||||
this rule allows traffic only if the traffic matches
|
||||
at least one port in the list.
|
||||
items:
|
||||
description: NetworkPolicyPort describes a port to allow
|
||||
traffic on
|
||||
properties:
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: The port on the given protocol. This
|
||||
can either be a numerical or named port on a pod.
|
||||
If this field is not provided, this matches all
|
||||
port names and numbers.
|
||||
x-kubernetes-int-or-string: true
|
||||
protocol:
|
||||
description: The protocol (TCP, UDP, or SCTP) which
|
||||
traffic must match. If not specified, this field
|
||||
defaults to TCP.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
to:
|
||||
description: List of destinations for outgoing traffic
|
||||
of pods selected for this rule. Items in this list are
|
||||
combined using a logical OR operation. If this field
|
||||
is empty or missing, this rule matches all destinations
|
||||
(traffic not restricted by destination). If this field
|
||||
is present and contains at least one item, this rule
|
||||
allows traffic only if the traffic matches at least
|
||||
one item in the to list.
|
||||
items:
|
||||
description: NetworkPolicyPeer describes a peer to allow
|
||||
traffic from. Only certain combinations of fields
|
||||
are allowed
|
||||
properties:
|
||||
ipBlock:
|
||||
description: IPBlock defines policy on a particular
|
||||
IPBlock. If this field is set then neither of
|
||||
the other fields can be.
|
||||
properties:
|
||||
cidr:
|
||||
description: CIDR is a string representing the
|
||||
IP Block Valid examples are "192.168.1.1/24"
|
||||
or "2001:db9::/64"
|
||||
type: string
|
||||
except:
|
||||
description: Except is a slice of CIDRs that
|
||||
should not be included within an IP Block
|
||||
Valid examples are "192.168.1.1/24" or "2001:db9::/64"
|
||||
Except values will be rejected if they are
|
||||
outside the CIDR range
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- cidr
|
||||
type: object
|
||||
namespaceSelector:
|
||||
description: "Selects Namespaces using cluster-scoped
|
||||
labels. This field follows standard label selector
|
||||
semantics; if present but empty, it selects all
|
||||
namespaces. \n If PodSelector is also set, then
|
||||
the NetworkPolicyPeer as a whole selects the Pods
|
||||
matching PodSelector in the Namespaces selected
|
||||
by NamespaceSelector. Otherwise it selects all
|
||||
Pods in the Namespaces selected by NamespaceSelector."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label
|
||||
selector requirements. The requirements are
|
||||
ANDed.
|
||||
items:
|
||||
description: A label selector requirement
|
||||
is a selector that contains values, a key,
|
||||
and an operator that relates the key and
|
||||
values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that
|
||||
the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's
|
||||
relationship to a set of values. Valid
|
||||
operators are In, NotIn, Exists and
|
||||
DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string
|
||||
values. If the operator is In or NotIn,
|
||||
the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This
|
||||
array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value}
|
||||
pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions,
|
||||
whose key field is "key", the operator is
|
||||
"In", and the values array contains only "value".
|
||||
The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
podSelector:
|
||||
description: "This is a label selector which selects
|
||||
Pods. This field follows standard label selector
|
||||
semantics; if present but empty, it selects all
|
||||
pods. \n If NamespaceSelector is also set, then
|
||||
the NetworkPolicyPeer as a whole selects the Pods
|
||||
matching PodSelector in the Namespaces selected
|
||||
by NamespaceSelector. Otherwise it selects the
|
||||
Pods matching PodSelector in the policy's own
|
||||
Namespace."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label
|
||||
selector requirements. The requirements are
|
||||
ANDed.
|
||||
items:
|
||||
description: A label selector requirement
|
||||
is a selector that contains values, a key,
|
||||
and an operator that relates the key and
|
||||
values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that
|
||||
the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's
|
||||
relationship to a set of values. Valid
|
||||
operators are In, NotIn, Exists and
|
||||
DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string
|
||||
values. If the operator is In or NotIn,
|
||||
the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This
|
||||
array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value}
|
||||
pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions,
|
||||
whose key field is "key", the operator is
|
||||
"In", and the values array contains only "value".
|
||||
The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
ingress:
|
||||
description: List of ingress rules to be applied to the selected
|
||||
pods. Traffic is allowed to a pod if there are no NetworkPolicies
|
||||
selecting the pod (and cluster policy otherwise allows the
|
||||
traffic), OR if the traffic source is the pod's local node,
|
||||
OR if the traffic matches at least one ingress rule across
|
||||
all of the NetworkPolicy objects whose podSelector matches
|
||||
the pod. If this field is empty then this NetworkPolicy does
|
||||
not allow any traffic (and serves solely to ensure that the
|
||||
pods it selects are isolated by default)
|
||||
items:
|
||||
description: NetworkPolicyIngressRule describes a particular
|
||||
set of traffic that is allowed to the pods matched by a
|
||||
NetworkPolicySpec's podSelector. The traffic must match
|
||||
both ports and from.
|
||||
properties:
|
||||
from:
|
||||
description: List of sources which should be able to access
|
||||
the pods selected for this rule. Items in this list
|
||||
are combined using a logical OR operation. If this field
|
||||
is empty or missing, this rule matches all sources (traffic
|
||||
not restricted by source). If this field is present
|
||||
and contains at least one item, this rule allows traffic
|
||||
only if the traffic matches at least one item in the
|
||||
from list.
|
||||
items:
|
||||
description: NetworkPolicyPeer describes a peer to allow
|
||||
traffic from. Only certain combinations of fields
|
||||
are allowed
|
||||
properties:
|
||||
ipBlock:
|
||||
description: IPBlock defines policy on a particular
|
||||
IPBlock. If this field is set then neither of
|
||||
the other fields can be.
|
||||
properties:
|
||||
cidr:
|
||||
description: CIDR is a string representing the
|
||||
IP Block Valid examples are "192.168.1.1/24"
|
||||
or "2001:db9::/64"
|
||||
type: string
|
||||
except:
|
||||
description: Except is a slice of CIDRs that
|
||||
should not be included within an IP Block
|
||||
Valid examples are "192.168.1.1/24" or "2001:db9::/64"
|
||||
Except values will be rejected if they are
|
||||
outside the CIDR range
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- cidr
|
||||
type: object
|
||||
namespaceSelector:
|
||||
description: "Selects Namespaces using cluster-scoped
|
||||
labels. This field follows standard label selector
|
||||
semantics; if present but empty, it selects all
|
||||
namespaces. \n If PodSelector is also set, then
|
||||
the NetworkPolicyPeer as a whole selects the Pods
|
||||
matching PodSelector in the Namespaces selected
|
||||
by NamespaceSelector. Otherwise it selects all
|
||||
Pods in the Namespaces selected by NamespaceSelector."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label
|
||||
selector requirements. The requirements are
|
||||
ANDed.
|
||||
items:
|
||||
description: A label selector requirement
|
||||
is a selector that contains values, a key,
|
||||
and an operator that relates the key and
|
||||
values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that
|
||||
the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's
|
||||
relationship to a set of values. Valid
|
||||
operators are In, NotIn, Exists and
|
||||
DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string
|
||||
values. If the operator is In or NotIn,
|
||||
the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This
|
||||
array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value}
|
||||
pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions,
|
||||
whose key field is "key", the operator is
|
||||
"In", and the values array contains only "value".
|
||||
The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
podSelector:
|
||||
description: "This is a label selector which selects
|
||||
Pods. This field follows standard label selector
|
||||
semantics; if present but empty, it selects all
|
||||
pods. \n If NamespaceSelector is also set, then
|
||||
the NetworkPolicyPeer as a whole selects the Pods
|
||||
matching PodSelector in the Namespaces selected
|
||||
by NamespaceSelector. Otherwise it selects the
|
||||
Pods matching PodSelector in the policy's own
|
||||
Namespace."
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label
|
||||
selector requirements. The requirements are
|
||||
ANDed.
|
||||
items:
|
||||
description: A label selector requirement
|
||||
is a selector that contains values, a key,
|
||||
and an operator that relates the key and
|
||||
values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that
|
||||
the selector applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's
|
||||
relationship to a set of values. Valid
|
||||
operators are In, NotIn, Exists and
|
||||
DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string
|
||||
values. If the operator is In or NotIn,
|
||||
the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This
|
||||
array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value}
|
||||
pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions,
|
||||
whose key field is "key", the operator is
|
||||
"In", and the values array contains only "value".
|
||||
The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
ports:
|
||||
description: List of ports which should be made accessible
|
||||
on the pods selected for this rule. Each item in this
|
||||
list is combined using a logical OR. If this field is
|
||||
empty or missing, this rule matches all ports (traffic
|
||||
not restricted by port). If this field is present and
|
||||
contains at least one item, then this rule allows traffic
|
||||
only if the traffic matches at least one port in the
|
||||
list.
|
||||
items:
|
||||
description: NetworkPolicyPort describes a port to allow
|
||||
traffic on
|
||||
properties:
|
||||
port:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: The port on the given protocol. This
|
||||
can either be a numerical or named port on a pod.
|
||||
If this field is not provided, this matches all
|
||||
port names and numbers.
|
||||
x-kubernetes-int-or-string: true
|
||||
protocol:
|
||||
description: The protocol (TCP, UDP, or SCTP) which
|
||||
traffic must match. If not specified, this field
|
||||
defaults to TCP.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
podSelector:
|
||||
description: Selects the pods to which this NetworkPolicy object
|
||||
applies. The array of ingress rules is applied to any pods
|
||||
selected by this field. Multiple network policies can select
|
||||
the same set of pods. In this case, the ingress rules for
|
||||
each are combined additively. This field is NOT optional and
|
||||
follows standard label selector semantics. An empty podSelector
|
||||
matches all pods in this namespace.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector
|
||||
requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector
|
||||
that contains values, a key, and an operator that relates
|
||||
the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector
|
||||
applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are In, NotIn,
|
||||
Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values.
|
||||
If the operator is In or NotIn, the values array
|
||||
must be non-empty. If the operator is Exists or
|
||||
DoesNotExist, the values array must be empty. This
|
||||
array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs.
|
||||
A single {key,value} in the matchLabels map is equivalent
|
||||
to an element of matchExpressions, whose key field is
|
||||
"key", the operator is "In", and the values array contains
|
||||
only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
policyTypes:
|
||||
description: List of rule types that the NetworkPolicy relates
|
||||
to. Valid options are "Ingress", "Egress", or "Ingress,Egress".
|
||||
If this field is not specified, it will default based on the
|
||||
existence of Ingress or Egress rules; policies that contain
|
||||
an Egress section are assumed to affect Egress, and all policies
|
||||
(whether or not they contain an Ingress section) are assumed
|
||||
to affect Ingress. If you want to write an egress-only policy,
|
||||
you must explicitly specify policyTypes [ "Egress" ]. Likewise,
|
||||
if you want to write a policy that specifies that no egress
|
||||
is allowed, you must specify a policyTypes value that include
|
||||
"Egress" (since such a policy would not include an Egress
|
||||
section and would otherwise default to just [ "Ingress" ]).
|
||||
This field is beta-level in 1.8
|
||||
items:
|
||||
description: Policy Type string describes the NetworkPolicy
|
||||
type This type is beta-level in 1.8
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- podSelector
|
||||
type: object
|
||||
type: array
|
||||
nodeSelector:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
owner:
|
||||
type: string
|
||||
resourceQuotas:
|
||||
items:
|
||||
description: ResourceQuotaSpec defines the desired hard limits to
|
||||
enforce for Quota.
|
||||
properties:
|
||||
hard:
|
||||
additionalProperties:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
|
||||
x-kubernetes-int-or-string: true
|
||||
description: 'hard is the set of desired hard limits for each
|
||||
named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/'
|
||||
type: object
|
||||
scopeSelector:
|
||||
description: scopeSelector is also a collection of filters like
|
||||
scopes that must match each object tracked by a quota but
|
||||
expressed using ScopeSelectorOperator in combination with
|
||||
possible values. For a resource to match, both scopes AND
|
||||
scopeSelector (if specified in spec), must be matched.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: A list of scope selector requirements by scope
|
||||
of the resources.
|
||||
items:
|
||||
description: A scoped-resource selector requirement is
|
||||
a selector that contains values, a scope name, and an
|
||||
operator that relates the scope name and values.
|
||||
properties:
|
||||
operator:
|
||||
description: Represents a scope's relationship to
|
||||
a set of values. Valid operators are In, NotIn,
|
||||
Exists, DoesNotExist.
|
||||
type: string
|
||||
scopeName:
|
||||
description: The name of the scope that the selector
|
||||
applies to.
|
||||
type: string
|
||||
values:
|
||||
description: An array of string values. If the operator
|
||||
is In or NotIn, the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist, the values
|
||||
array must be empty. This array is replaced during
|
||||
a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- operator
|
||||
- scopeName
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
scopes:
|
||||
description: A collection of filters that must match each object
|
||||
tracked by a quota. If not specified, the quota matches all
|
||||
objects.
|
||||
items:
|
||||
description: A ResourceQuotaScope defines a filter that must
|
||||
match each object tracked by a quota
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
storageClasses:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- ingressClasses
|
||||
- limitRanges
|
||||
- namespaceQuota
|
||||
- owner
|
||||
- storageClasses
|
||||
type: object
|
||||
status:
|
||||
description: TenantStatus defines the observed state of Tenant
|
||||
properties:
|
||||
groups:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespaces:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
size:
|
||||
type: integer
|
||||
users:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- size
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -1,96 +0,0 @@
|
||||
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||
kind: MutatingWebhookConfiguration
|
||||
metadata:
|
||||
name: capsule
|
||||
webhooks:
|
||||
- name: owner.namespace.capsule.clastix.io
|
||||
failurePolicy: Fail
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
apiVersions: ["v1"]
|
||||
operations: ["CREATE"]
|
||||
resources: ["namespaces"]
|
||||
clientConfig:
|
||||
# use url if you're developing locally
|
||||
# url: https://<FIXME>.ngrok.io/mutate-v1-namespace-owner-reference
|
||||
caBundle:
|
||||
service:
|
||||
namespace: capsule-system
|
||||
name: capsule
|
||||
path: /mutate-v1-namespace-owner-reference
|
||||
- name: quota.namespace.capsule.clastix.io
|
||||
failurePolicy: Fail
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
apiVersions: ["v1"]
|
||||
operations: ["CREATE"]
|
||||
resources: ["namespaces"]
|
||||
clientConfig:
|
||||
# use url if you're developing locally
|
||||
# url: https://<FIXME>.ngrok.io/validate-v1-namespace-quota
|
||||
caBundle:
|
||||
service:
|
||||
namespace: capsule-system
|
||||
name: capsule
|
||||
path: /validate-v1-namespace-quota
|
||||
- name: validating.network-policy.capsule.clastix.io
|
||||
failurePolicy: Fail
|
||||
rules:
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
apiVersions: ["v1"]
|
||||
operations: ["CREATE", "UPDATE", "DELETE"]
|
||||
resources: ["networkpolicies"]
|
||||
clientConfig:
|
||||
# use url if you're developing locally
|
||||
# url: https://<FIXME>.ngrok.io/validating-v1-network-policy
|
||||
caBundle:
|
||||
service:
|
||||
namespace: capsule-system
|
||||
name: capsule
|
||||
path: /validating-v1-network-policy
|
||||
- name: pvc.capsule.clastix.io
|
||||
failurePolicy: Fail
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
apiVersions: ["v1"]
|
||||
operations: ["CREATE"]
|
||||
resources: ["persistentvolumeclaims"]
|
||||
clientConfig:
|
||||
# use url if you're developing locally
|
||||
# url: https://<FIXME>.ngrok.io/validating-v1-pvc
|
||||
caBundle:
|
||||
service:
|
||||
namespace: capsule-system
|
||||
name: capsule
|
||||
path: /validating-v1-pvc
|
||||
- name: extensions.ingress.capsule.clastix.io
|
||||
failurePolicy: Fail
|
||||
rules:
|
||||
- apiGroups: ["extensions"]
|
||||
apiVersions: ["v1beta1"]
|
||||
operations: ["CREATE", "UPDATE"]
|
||||
resources: ["ingresses"]
|
||||
clientConfig:
|
||||
# use url if you're developing locally
|
||||
# url: https://<FIXME>.ngrok.io/validating-v1-extensions-ingress
|
||||
caBundle:
|
||||
service:
|
||||
namespace: capsule-system
|
||||
name: capsule
|
||||
path: /validating-v1-extensions-ingress
|
||||
|
||||
- name: networking.ingress.capsule.clastix.io
|
||||
failurePolicy: Fail
|
||||
rules:
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
apiVersions: ["v1beta1"]
|
||||
operations: ["CREATE", "UPDATE"]
|
||||
resources: ["ingresses"]
|
||||
clientConfig:
|
||||
# use url if you're developing locally
|
||||
# url: https://<FIXME>.ngrok.io/validating-v1-networking-ingress
|
||||
caBundle:
|
||||
service:
|
||||
namespace: capsule-system
|
||||
name: capsule
|
||||
path: /validating-v1-networking-ingress
|
||||
@@ -1,38 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: capsule
|
||||
namespace: capsule-system
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
name: capsule
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: capsule
|
||||
spec:
|
||||
serviceAccountName: capsule
|
||||
containers:
|
||||
- name: capsule
|
||||
image: quay.io/clastix/capsule:latest
|
||||
command:
|
||||
- capsule
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: WATCH_NAMESPACE
|
||||
value: ""
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: OPERATOR_NAME
|
||||
value: "capsule"
|
||||
volumeMounts:
|
||||
- name: tls
|
||||
mountPath: /tmp/k8s-webhook-server/serving-certs
|
||||
volumes:
|
||||
- name: tls
|
||||
secret:
|
||||
secretName: capsule-tls
|
||||
@@ -1,44 +0,0 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: capsule
|
||||
rules:
|
||||
- apiGroups:
|
||||
- admissionregistration.k8s.io
|
||||
resources:
|
||||
- mutatingwebhookconfigurations
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- update
|
||||
- patch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- limitranges
|
||||
- resourcequotas
|
||||
- namespaces
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- deletecollection
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- capsule.clastix.io
|
||||
resources:
|
||||
- '*'
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
@@ -1,25 +0,0 @@
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: capsule-cluster-admin
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: capsule
|
||||
namespace: capsule-system
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: admin
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: capsule
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: capsule
|
||||
namespace: capsule-system
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: capsule
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
app: capsule
|
||||
name: capsule-ca
|
||||
namespace: capsule-system
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
labels:
|
||||
app: capsule
|
||||
name: capsule-tls
|
||||
namespace: capsule-system
|
||||
@@ -1,16 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: capsule
|
||||
name: capsule
|
||||
namespace: capsule-system
|
||||
spec:
|
||||
ports:
|
||||
- name: https
|
||||
port: 443
|
||||
protocol: TCP
|
||||
targetPort: 443
|
||||
selector:
|
||||
name: capsule
|
||||
type: ClusterIP
|
||||
@@ -1,5 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: capsule
|
||||
namespace: capsule-system
|
||||
13
go.mod
13
go.mod
@@ -4,16 +4,11 @@ go 1.13
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v0.1.0
|
||||
github.com/operator-framework/operator-sdk v0.18.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/onsi/ginkgo v1.11.0
|
||||
github.com/onsi/gomega v1.8.1
|
||||
github.com/stretchr/testify v1.4.0
|
||||
k8s.io/api v0.18.2
|
||||
k8s.io/apimachinery v0.18.2
|
||||
k8s.io/client-go v12.0.0+incompatible
|
||||
k8s.io/client-go v0.18.2
|
||||
sigs.k8s.io/controller-runtime v0.6.0
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/Azure/go-autorest => github.com/Azure/go-autorest v13.3.2+incompatible // Required by OLM
|
||||
k8s.io/client-go => k8s.io/client-go v0.18.2 // Required by prometheus-operator
|
||||
)
|
||||
|
||||
1
hack/.gitignore
vendored
1
hack/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*.kubeconfig
|
||||
@@ -1,20 +1,15 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import "github.com/clastix/capsule/pkg/webhook/owner_reference"
|
||||
|
||||
func init() {
|
||||
AddToWebhookServer = append(AddToWebhookServer, owner_reference.Add)
|
||||
}
|
||||
*/
|
||||
149
main.go
Normal file
149
main.go
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
goRuntime "runtime"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
|
||||
"github.com/clastix/capsule/controllers"
|
||||
"github.com/clastix/capsule/controllers/secret"
|
||||
"github.com/clastix/capsule/pkg/indexer"
|
||||
"github.com/clastix/capsule/pkg/webhook"
|
||||
"github.com/clastix/capsule/pkg/webhook/ingress"
|
||||
"github.com/clastix/capsule/pkg/webhook/namespace_quota"
|
||||
"github.com/clastix/capsule/pkg/webhook/network_policies"
|
||||
"github.com/clastix/capsule/pkg/webhook/owner_reference"
|
||||
"github.com/clastix/capsule/pkg/webhook/pvc"
|
||||
"github.com/clastix/capsule/version"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
var (
|
||||
scheme = runtime.NewScheme()
|
||||
setupLog = ctrl.Log.WithName("setup")
|
||||
)
|
||||
|
||||
func init() {
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
|
||||
utilruntime.Must(capsulev1alpha1.AddToScheme(scheme))
|
||||
utilruntime.Must(corev1.AddToScheme(scheme))
|
||||
// +kubebuilder:scaffold:scheme
|
||||
}
|
||||
|
||||
func printVersion() {
|
||||
setupLog.Info(fmt.Sprintf("Operator Version: %s", version.Version))
|
||||
setupLog.Info(fmt.Sprintf("Go Version: %s", goRuntime.Version()))
|
||||
setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH))
|
||||
}
|
||||
|
||||
func main() {
|
||||
var metricsAddr string
|
||||
var enableLeaderElection bool
|
||||
var v bool
|
||||
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
|
||||
"Enable leader election for controller manager. "+
|
||||
"Enabling this will ensure there is only one active controller manager.")
|
||||
flag.BoolVar(&v, "version", false, "Print the Capsule version and exit")
|
||||
flag.Parse()
|
||||
|
||||
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
|
||||
|
||||
printVersion()
|
||||
if v {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
Port: 9443,
|
||||
LeaderElection: enableLeaderElection,
|
||||
LeaderElectionID: "42c733ea.clastix.capsule.io",
|
||||
})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&controllers.TenantReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("Tenant"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Tenant")
|
||||
os.Exit(1)
|
||||
}
|
||||
// +kubebuilder:scaffold:builder
|
||||
|
||||
//webhooks
|
||||
err = webhook.Register(mgr, &ingress.ExtensionIngress{}, &ingress.NetworkIngress{}, pvc.Webhook{}, &owner_reference.Webhook{}, &namespace_quota.Webhook{}, network_policies.Webhook{})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to setup webhooks")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&controllers.NamespaceReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("Namespace"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Namespace")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&secret.CaReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("CA"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Namespace")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = (&secret.TlsReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("Tls"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Namespace")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := indexer.AddToManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to setup indexers")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apis
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/apis/capsule/v1alpha1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register the types with the Scheme so the components can map objects to GroupVersionKinds and back
|
||||
AddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package capsule contains capsule API versions.
|
||||
//
|
||||
// This file ensures Go source parsers acknowledge the capsule package
|
||||
// and any child packages. It can be removed if any other Go source files are
|
||||
// added to this package.
|
||||
package capsule
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package v1alpha1 contains API Schema definitions for the capsule v1alpha1 API group
|
||||
// +k8s:deepcopy-gen=package,register
|
||||
// +groupName=capsule.clastix.io
|
||||
package v1alpha1
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cert
|
||||
|
||||
import (
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestCapsuleCa_GenerateCertificate(t *testing.T) {
|
||||
}
|
||||
for name, c := range map[string]testCase{
|
||||
"foo.tld": {[]string{"foo.tld"}},
|
||||
"SAN": {[]string{"capsule.capsule-system.svc", "capsule.capsule-system.default.cluster"}},
|
||||
"SAN": {[]string{"capsule-webhook-service.capsule-system.svc", "capsule-webhook-service.capsule-system.default.cluster"}},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var ca *CapsuleCa
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cert
|
||||
|
||||
type CaNotYetValidError struct{}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cert
|
||||
|
||||
import "time"
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import "github.com/clastix/capsule/pkg/controller/namespace"
|
||||
|
||||
func init() {
|
||||
// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.
|
||||
AddToManagerFuncs = append(AddToManagerFuncs, namespace.Add)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/controller/secret"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.
|
||||
AddToManagerFuncs = append(AddToManagerFuncs, secret.AddTls, secret.AddCa)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/controller/tenant"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.
|
||||
AddToManagerFuncs = append(AddToManagerFuncs, tenant.Add)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
)
|
||||
|
||||
// AddToManagerFuncs is a list of functions to add all Controllers to the Manager
|
||||
var AddToManagerFuncs []func(manager.Manager) error
|
||||
|
||||
// AddToManager adds all Controllers to the Manager
|
||||
func AddToManager(m manager.Manager) error {
|
||||
for _, f := range AddToManagerFuncs {
|
||||
if err := f(m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package secret
|
||||
|
||||
type MissingCaError struct {
|
||||
}
|
||||
|
||||
func (MissingCaError) Error() string {
|
||||
return "CA has not been created yet, please generate a new"
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/clastix/capsule/pkg/cert"
|
||||
)
|
||||
|
||||
type secretReconciliationFunc func(reconciler *ReconcileSecret, request reconcile.Request) (reconcile.Result, error)
|
||||
|
||||
// ReconcileSecret reconciles a Secret object
|
||||
type ReconcileSecret struct {
|
||||
client client.Client
|
||||
scheme *runtime.Scheme
|
||||
logger logr.Logger
|
||||
reconcileFunc secretReconciliationFunc
|
||||
}
|
||||
|
||||
func newReconciler(mgr manager.Manager, name string, f secretReconciliationFunc) reconcile.Reconciler {
|
||||
return &ReconcileSecret{
|
||||
client: mgr.GetClient(),
|
||||
scheme: mgr.GetScheme(),
|
||||
logger: log.Log.WithName(name),
|
||||
reconcileFunc: f,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReconcileSecret) Reconcile(request reconcile.Request) (reconcile.Result, error) {
|
||||
return r.reconcileFunc(r, request)
|
||||
}
|
||||
|
||||
func (r *ReconcileSecret) GetCertificateAuthority() (ca cert.Ca, err error) {
|
||||
instance := &corev1.Secret{}
|
||||
|
||||
err = r.client.Get(context.TODO(), types.NamespacedName{
|
||||
Namespace: "capsule-system",
|
||||
Name: CaSecretName,
|
||||
}, instance)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("missing secret %s, cannot reconcile", CaSecretName)
|
||||
}
|
||||
|
||||
if instance.Data == nil {
|
||||
return nil, MissingCaError{}
|
||||
}
|
||||
|
||||
ca, err = cert.NewCertificateAuthorityFromBytes(instance.Data[Cert], instance.Data[PrivateKey])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func filterByName(objName, desired string) bool {
|
||||
return objName == desired
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package secret
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
"github.com/clastix/capsule/pkg/cert"
|
||||
)
|
||||
|
||||
// Add creates a new Secret Controller and adds it to the Manager. The Manager will set fields on the Controller
|
||||
// and Start it when the Manager is Started.
|
||||
func AddCa(mgr manager.Manager) error {
|
||||
r := newReconciler(mgr, "controller_secret", caReconcile)
|
||||
return ca(mgr, r)
|
||||
}
|
||||
|
||||
// add adds a new Controller to mgr with r as the reconcile.Reconciler
|
||||
func ca(mgr manager.Manager, r reconcile.Reconciler) error {
|
||||
// Create a new controller
|
||||
c, err := controller.New("secret-controller", mgr, controller.Options{Reconciler: r})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Watch for changes to CA Secret
|
||||
err = c.Watch(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestForObject{}, predicate.Funcs{
|
||||
CreateFunc: func(event event.CreateEvent) (ok bool) {
|
||||
return filterByName(event.Meta.GetName(), CaSecretName)
|
||||
},
|
||||
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
|
||||
return filterByName(deleteEvent.Meta.GetName(), CaSecretName)
|
||||
},
|
||||
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
|
||||
return filterByName(updateEvent.MetaNew.GetName(), CaSecretName)
|
||||
},
|
||||
GenericFunc: func(genericEvent event.GenericEvent) bool {
|
||||
return filterByName(genericEvent.Meta.GetName(), CaSecretName)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func caReconcile(r *ReconcileSecret, request reconcile.Request) (reconcile.Result, error) {
|
||||
var err error
|
||||
|
||||
r.logger = r.logger.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
|
||||
r.logger.Info("Reconciling CA Secret")
|
||||
|
||||
// Fetch the CA instance
|
||||
instance := &corev1.Secret{}
|
||||
err = r.client.Get(context.TODO(), request.NamespacedName, instance)
|
||||
if err != nil {
|
||||
// Error reading the object - requeue the request.
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
var ca cert.Ca
|
||||
var rq time.Duration
|
||||
ca, err = r.GetCertificateAuthority()
|
||||
if err != nil && errors.Is(err, MissingCaError{}) {
|
||||
ca, err = cert.GenerateCertificateAuthority()
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
r.logger.Info("Handling CA Secret")
|
||||
|
||||
rq, err = ca.ExpiresIn(time.Now())
|
||||
if err != nil {
|
||||
r.logger.Info("CA is expired, cleaning to obtain a new one")
|
||||
instance.Data = map[string][]byte{}
|
||||
} else {
|
||||
r.logger.Info("Updating CA secret with new PEM and RSA")
|
||||
|
||||
var crt *bytes.Buffer
|
||||
var key *bytes.Buffer
|
||||
crt, _ = ca.CaCertificatePem()
|
||||
key, _ = ca.CaPrivateKeyPem()
|
||||
|
||||
instance.Data = map[string][]byte{
|
||||
Cert: crt.Bytes(),
|
||||
PrivateKey: key.Bytes(),
|
||||
}
|
||||
|
||||
wh := &v1.MutatingWebhookConfiguration{}
|
||||
err = r.client.Get(context.TODO(), types.NamespacedName{
|
||||
Name: "capsule",
|
||||
}, wh)
|
||||
if err != nil {
|
||||
r.logger.Error(err, "cannot retrieve MutatingWebhookConfiguration")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
for i, w := range wh.Webhooks {
|
||||
// Updating CABundle only in case of an internal service reference
|
||||
if w.ClientConfig.Service != nil {
|
||||
wh.Webhooks[i].ClientConfig.CABundle = instance.Data[Cert]
|
||||
}
|
||||
}
|
||||
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
return r.client.Update(context.TODO(), wh, &client.UpdateOptions{})
|
||||
})
|
||||
if err != nil {
|
||||
r.logger.Error(err, "cannot update MutatingWebhookConfiguration webhooks CA bundle")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
var res controllerutil.OperationResult
|
||||
t := &corev1.Secret{ObjectMeta: instance.ObjectMeta}
|
||||
res, err = controllerutil.CreateOrUpdate(context.TODO(), r.client, t, func() error {
|
||||
t.Data = instance.Data
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
r.logger.Error(err, "cannot update Capsule TLS")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
if res == controllerutil.OperationResultUpdated {
|
||||
r.logger.Info("Capsule CA has been updated, we need to trigger TLS update too")
|
||||
tls := &corev1.Secret{}
|
||||
err = r.client.Get(context.TODO(), types.NamespacedName{
|
||||
Namespace: "capsule-system",
|
||||
Name: TlsSecretName,
|
||||
}, tls)
|
||||
if err != nil {
|
||||
r.logger.Error(err, "Capsule TLS Secret missing")
|
||||
}
|
||||
err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
_, err = controllerutil.CreateOrUpdate(context.TODO(), r.client, tls, func() error {
|
||||
tls.Data = map[string][]byte{}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
r.logger.Error(err, "Cannot clean Capsule TLS Secret due to CA update")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Info("Reconciliation completed, processing back in " + rq.String())
|
||||
return reconcile.Result{Requeue: true, RequeueAfter: rq}, nil
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
"github.com/clastix/capsule/pkg/cert"
|
||||
)
|
||||
|
||||
// Add creates a new Secret Controller and adds it to the Manager. The Manager will set fields on the Controller
|
||||
// and Start it when the Manager is Started.
|
||||
func AddTls(mgr manager.Manager) error {
|
||||
return tls(mgr, newReconciler(mgr, "controller_secret_tls", tlsReconcile))
|
||||
}
|
||||
|
||||
// add adds a new Controller to mgr with r as the reconcile.Reconciler
|
||||
func tls(mgr manager.Manager, r reconcile.Reconciler) error {
|
||||
// Create a new controller
|
||||
c, err := controller.New("secret-controller", mgr, controller.Options{Reconciler: r})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Watch for changes to TLS Secret
|
||||
err = c.Watch(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestForObject{}, predicate.Funcs{
|
||||
CreateFunc: func(event event.CreateEvent) (ok bool) {
|
||||
return filterByName(event.Meta.GetName(), TlsSecretName)
|
||||
},
|
||||
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
|
||||
return filterByName(deleteEvent.Meta.GetName(), TlsSecretName)
|
||||
},
|
||||
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
|
||||
return filterByName(updateEvent.MetaNew.GetName(), TlsSecretName)
|
||||
},
|
||||
GenericFunc: func(genericEvent event.GenericEvent) bool {
|
||||
return filterByName(genericEvent.Meta.GetName(), TlsSecretName)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tlsReconcile(r *ReconcileSecret, request reconcile.Request) (reconcile.Result, error) {
|
||||
var err error
|
||||
|
||||
r.logger = r.logger.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
|
||||
r.logger.Info("Reconciling TLS/CA Secret")
|
||||
|
||||
// Fetch the Secret instance
|
||||
instance := &corev1.Secret{}
|
||||
err = r.client.Get(context.TODO(), request.NamespacedName, instance)
|
||||
if err != nil {
|
||||
// Error reading the object - requeue the request.
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
var ca cert.Ca
|
||||
var rq time.Duration
|
||||
|
||||
ca, err = r.GetCertificateAuthority()
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
var shouldCreate bool
|
||||
for _, key := range []string{Cert, PrivateKey} {
|
||||
if _, ok := instance.Data[key]; !ok {
|
||||
shouldCreate = true
|
||||
}
|
||||
}
|
||||
|
||||
if shouldCreate {
|
||||
r.logger.Info("Missing Capsule TLS certificate")
|
||||
rq = 6 * 30 * 24 * time.Hour
|
||||
|
||||
opts := cert.NewCertOpts(time.Now().Add(rq), "capsule.capsule-system.svc")
|
||||
crt, key, err := ca.GenerateCertificate(opts)
|
||||
if err != nil {
|
||||
r.logger.Error(err, "Cannot generate new TLS certificate")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
instance.Data = map[string][]byte{
|
||||
Cert: crt.Bytes(),
|
||||
PrivateKey: key.Bytes(),
|
||||
}
|
||||
} else {
|
||||
var c *x509.Certificate
|
||||
var b *pem.Block
|
||||
b, _ = pem.Decode(instance.Data[Cert])
|
||||
c, err = x509.ParseCertificate(b.Bytes)
|
||||
if err != nil {
|
||||
r.logger.Error(err, "cannot parse Capsule TLS")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
rq = time.Duration(c.NotAfter.Unix()-time.Now().Unix()) * time.Second
|
||||
if time.Now().After(c.NotAfter) {
|
||||
r.logger.Info("Capsule TLS is expired, cleaning to obtain a new one")
|
||||
instance.Data = map[string][]byte{}
|
||||
}
|
||||
}
|
||||
|
||||
var res controllerutil.OperationResult
|
||||
t := &corev1.Secret{ObjectMeta: instance.ObjectMeta}
|
||||
res, err = controllerutil.CreateOrUpdate(context.TODO(), r.client, t, func() error {
|
||||
t.Data = instance.Data
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
r.logger.Error(err, "cannot update Capsule TLS")
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
if instance.Name == TlsSecretName && res == controllerutil.OperationResultUpdated {
|
||||
r.logger.Info("Capsule TLS certificates has been updated, we need to restart the Controller")
|
||||
os.Exit(15)
|
||||
}
|
||||
|
||||
r.logger.Info("Reconciliation completed, processing back in " + rq.String())
|
||||
return reconcile.Result{Requeue: true, RequeueAfter: rq}, nil
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -17,7 +20,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/clastix/capsule/pkg/apis/capsule/v1alpha1"
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
)
|
||||
|
||||
type NamespacesReference struct {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -17,7 +20,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/clastix/capsule/pkg/apis/capsule/v1alpha1"
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
)
|
||||
|
||||
type OwnerReference struct {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -17,7 +20,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/clastix/capsule/pkg/apis/capsule/v1alpha1"
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
)
|
||||
|
||||
type UserGroupList []string
|
||||
@@ -35,7 +38,7 @@ func (u UserGroupList) Swap(i, j int) {
|
||||
}
|
||||
|
||||
func (u UserGroupList) IsInCapsuleGroup() (ok bool) {
|
||||
v := v1alpha1.SchemeGroupVersion.Group
|
||||
v := v1alpha1.GroupVersion.Group
|
||||
|
||||
sort.Sort(u)
|
||||
i := sort.SearchStrings(u, v)
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/webhook/namespace_quota"
|
||||
)
|
||||
|
||||
func init() {
|
||||
AddToWebhookServer = append(AddToWebhookServer, namespace_quota.Add)
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -14,9 +17,14 @@ limitations under the License.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"github.com/clastix/capsule/pkg/webhook/ingress_class"
|
||||
"context"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
)
|
||||
|
||||
func init() {
|
||||
AddToWebhookServer = append(AddToWebhookServer, ingress_class.AddExtensions, ingress_class.AddNetworking)
|
||||
type Handler interface {
|
||||
OnCreate(ctx context.Context, req admission.Request, client client.Client, decoder *admission.Decoder) admission.Response
|
||||
OnDelete(ctx context.Context, req admission.Request, client client.Client, decoder *admission.Decoder) admission.Response
|
||||
OnUpdate(ctx context.Context, req admission.Request, client client.Client, decoder *admission.Decoder) admission.Response
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -11,16 +14,20 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apis
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// AddToSchemes may be used to add all resources defined in the project to a Scheme
|
||||
var AddToSchemes runtime.SchemeBuilder
|
||||
|
||||
// AddToScheme adds all Resources to the Scheme
|
||||
func AddToScheme(s *runtime.Scheme) error {
|
||||
return AddToSchemes.AddToScheme(s)
|
||||
type ingressClassForbidden struct {
|
||||
ingressClass string
|
||||
}
|
||||
|
||||
func NewIngressClassForbidden(ingressClass string) error {
|
||||
return &ingressClassForbidden{ingressClass: ingressClass}
|
||||
}
|
||||
|
||||
func (i ingressClassForbidden) Error() string {
|
||||
return fmt.Sprintf("Ingress Class %s is forbidden for the current Tenant", i.ingressClass)
|
||||
}
|
||||
69
pkg/webhook/ingress/extension.go
Normal file
69
pkg/webhook/ingress/extension.go
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"github.com/clastix/capsule/pkg/webhook"
|
||||
)
|
||||
|
||||
// +kubebuilder:webhook:path=/validating-v1-extensions-ingress,mutating=false,failurePolicy=fail,groups=extensions,resources=ingresses,verbs=create;update,versions=v1beta1,name=extensions.ingress.capsule.clastix.io
|
||||
|
||||
type ExtensionIngress struct{}
|
||||
|
||||
func (r *ExtensionIngress) GetHandler() webhook.Handler {
|
||||
return &extensionIngressHandler{}
|
||||
}
|
||||
|
||||
func (r *ExtensionIngress) GetName() string {
|
||||
return "ExtensionIngress"
|
||||
}
|
||||
|
||||
func (r *ExtensionIngress) GetPath() string {
|
||||
return "/validating-v1-extensions-ingress"
|
||||
}
|
||||
|
||||
type extensionIngressHandler struct {
|
||||
}
|
||||
|
||||
func (r *extensionIngressHandler) OnCreate(ctx context.Context, req admission.Request, client client.Client, decoder *admission.Decoder) admission.Response {
|
||||
i := &extensionsv1beta1.Ingress{}
|
||||
if err := decoder.Decode(req, i); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
return handleIngress(ctx, i, i.Spec.IngressClassName, client)
|
||||
}
|
||||
|
||||
func (r *extensionIngressHandler) OnDelete(ctx context.Context, req admission.Request, client client.Client, decoder *admission.Decoder) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
func (r *extensionIngressHandler) OnUpdate(ctx context.Context, req admission.Request, client client.Client, decoder *admission.Decoder) admission.Response {
|
||||
i := &extensionsv1beta1.Ingress{}
|
||||
if err := decoder.Decode(req, i); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
return handleIngress(ctx, i, i.Spec.IngressClassName, client)
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -11,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ingress_class
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -23,7 +26,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"github.com/clastix/capsule/pkg/apis/capsule/v1alpha1"
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
)
|
||||
|
||||
func handleIngress(ctx context.Context, object metav1.Object, ic *string, c client.Client) admission.Response {
|
||||
@@ -43,8 +46,7 @@ func handleIngress(ctx context.Context, object metav1.Object, ic *string, c clie
|
||||
}
|
||||
|
||||
if !tl.Items[0].Spec.IngressClasses.IsStringInList(*ic) {
|
||||
err := fmt.Errorf("Ingress Class %s is forbidden for the current Tenant", *ic)
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
return admission.Errored(http.StatusBadRequest, NewIngressClassForbidden(*ic))
|
||||
}
|
||||
|
||||
return admission.Allowed("")
|
||||
67
pkg/webhook/ingress/networking.go
Normal file
67
pkg/webhook/ingress/networking.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"github.com/clastix/capsule/pkg/webhook"
|
||||
)
|
||||
|
||||
// +kubebuilder:webhook:path=/validating-v1-networking-ingress,mutating=false,failurePolicy=fail,groups=networking.k8s.io,resources=ingresses,verbs=create;update,versions=v1beta1,name=networking.ingress.capsule.clastix.io
|
||||
|
||||
type NetworkIngress struct{}
|
||||
|
||||
func (r *NetworkIngress) GetHandler() webhook.Handler {
|
||||
return &handler{}
|
||||
}
|
||||
|
||||
func (r *NetworkIngress) GetName() string {
|
||||
return "NetworkIngress"
|
||||
}
|
||||
|
||||
func (r *NetworkIngress) GetPath() string {
|
||||
return "/validating-v1-networking-ingress"
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
}
|
||||
|
||||
func (r *handler) OnCreate(ctx context.Context, req admission.Request, client client.Client, decoder *admission.Decoder) admission.Response {
|
||||
i := &networkingv1beta1.Ingress{}
|
||||
if err := decoder.Decode(req, i); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
return handleIngress(ctx, i, i.Spec.IngressClassName, client)
|
||||
}
|
||||
|
||||
func (r *handler) OnDelete(ctx context.Context, req admission.Request, client client.Client, decoder *admission.Decoder) admission.Response {
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
func (r *handler) OnUpdate(ctx context.Context, req admission.Request, client client.Client, decoder *admission.Decoder) admission.Response {
|
||||
i := &networkingv1beta1.Ingress{}
|
||||
if err := decoder.Decode(req, i); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
return handleIngress(ctx, i, i.Spec.IngressClassName, client)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ingress_class
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"github.com/clastix/capsule/pkg/webhook/utils"
|
||||
)
|
||||
|
||||
func AddExtensions(mgr manager.Manager) error {
|
||||
mgr.GetWebhookServer().Register("/validating-v1-extensions-ingress", &webhook.Admission{
|
||||
Handler: &extensionIngress{},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
type extensionIngress struct {
|
||||
client client.Client
|
||||
decoder *admission.Decoder
|
||||
}
|
||||
|
||||
func (r *extensionIngress) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
g := utils.UserGroupList(req.UserInfo.Groups)
|
||||
if !g.IsInCapsuleGroup() {
|
||||
// not a Capsule user, can be skipped
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
i := &extensionsv1beta1.Ingress{}
|
||||
if err := r.decoder.Decode(req, i); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
return handleIngress(ctx, i, i.Spec.IngressClassName, r.client)
|
||||
}
|
||||
|
||||
func (r *extensionIngress) InjectDecoder(d *admission.Decoder) error {
|
||||
r.decoder = d
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *extensionIngress) InjectClient(c client.Client) error {
|
||||
r.client = c
|
||||
return nil
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ingress_class
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
networkingv1beta1 "k8s.io/api/networking/v1beta1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"github.com/clastix/capsule/pkg/webhook/utils"
|
||||
)
|
||||
|
||||
func AddNetworking(mgr manager.Manager) error {
|
||||
mgr.GetWebhookServer().Register("/validating-v1-networking-ingress", &webhook.Admission{
|
||||
Handler: &validatingV1{},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
type validatingV1 struct {
|
||||
client client.Client
|
||||
decoder *admission.Decoder
|
||||
}
|
||||
|
||||
func (r *validatingV1) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
g := utils.UserGroupList(req.UserInfo.Groups)
|
||||
if !g.IsInCapsuleGroup() {
|
||||
// not a Capsule user, can be skipped
|
||||
return admission.Allowed("")
|
||||
}
|
||||
|
||||
i := &networkingv1beta1.Ingress{}
|
||||
if err := r.decoder.Decode(req, i); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
return handleIngress(ctx, i, i.Spec.IngressClassName, r.client)
|
||||
}
|
||||
|
||||
func (r *validatingV1) InjectDecoder(d *admission.Decoder) error {
|
||||
r.decoder = d
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *validatingV1) InjectClient(c client.Client) error {
|
||||
r.client = c
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user