mirror of
https://github.com/clastix/kamaji.git
synced 2026-02-14 10:00:02 +00:00
feat: releasing kamaji
This commit is contained in:
committed by
Dario Tranchitella
parent
7089412a96
commit
432c50b081
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
|
||||
# Ignore build and test binaries.
|
||||
bin/
|
||||
testbin/
|
||||
37
.github/workflows/ci.yaml
vendored
Normal file
37
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*" ]
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2.3.0
|
||||
with:
|
||||
version: v1.45.2
|
||||
only-new-issues: false
|
||||
args: --timeout 5m --config .golangci.yml
|
||||
diff:
|
||||
name: diff
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.17'
|
||||
- run: make yaml-installation-file
|
||||
- name: Checking if YAML installer file is not aligned
|
||||
run: if [[ $(git diff | wc -l) -gt 0 ]]; then echo ">>> Untracked generated files have not been committed" && git --no-pager diff && exit 1; fi
|
||||
- name: Checking if YAML installer generated untracked files
|
||||
run: test -z "$(git ls-files --others --exclude-standard 2> /dev/null)"
|
||||
- name: Checking if source code is not formatted
|
||||
run: test -z "$(git diff 2> /dev/null)"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,5 +27,6 @@ bin
|
||||
**/*.kubeconfig
|
||||
**/*.crt
|
||||
**/*.key
|
||||
**/*.pem
|
||||
**/*.csr
|
||||
.DS_Store
|
||||
|
||||
|
||||
35
.golangci.yml
Normal file
35
.golangci.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
linters-settings:
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/clastix/kamaji)
|
||||
|
||||
linters:
|
||||
disable:
|
||||
- wrapcheck
|
||||
- gomnd
|
||||
- scopelint
|
||||
- golint
|
||||
- interfacer
|
||||
- maligned
|
||||
- varnamelen
|
||||
- testpackage
|
||||
- tagliatelle
|
||||
- paralleltest
|
||||
- ireturn
|
||||
- goerr113
|
||||
- gochecknoglobals
|
||||
- exhaustivestruct
|
||||
- wsl
|
||||
- exhaustive
|
||||
- lll
|
||||
- gosec
|
||||
- gomoddirectives
|
||||
- godox
|
||||
- gochecknoinits
|
||||
- funlen
|
||||
- dupl
|
||||
- maintidx
|
||||
- cyclop
|
||||
enable-all: true
|
||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
# Build the manager binary
|
||||
FROM golang:1.17 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 internal/ internal/
|
||||
|
||||
# Build
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -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 .
|
||||
COPY ./kamaji.yaml .
|
||||
USER 65532:65532
|
||||
|
||||
ENTRYPOINT ["/manager"]
|
||||
215
Makefile
Normal file
215
Makefile
Normal file
@@ -0,0 +1,215 @@
|
||||
# VERSION defines the project version for the bundle.
|
||||
# Update this value when you upgrade the version of your project.
|
||||
# To re-generate a bundle for another specific version without changing the standard setup, you can:
|
||||
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
|
||||
# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
|
||||
VERSION ?= 0.0.1
|
||||
|
||||
# CHANNELS define the bundle channels used in the bundle.
|
||||
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
|
||||
# To re-generate a bundle for other specific channels without changing the standard setup, you can:
|
||||
# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable)
|
||||
# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable")
|
||||
ifneq ($(origin CHANNELS), undefined)
|
||||
BUNDLE_CHANNELS := --channels=$(CHANNELS)
|
||||
endif
|
||||
|
||||
# DEFAULT_CHANNEL defines the default channel used in the bundle.
|
||||
# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable")
|
||||
# To re-generate a bundle for any other default channel without changing the default setup, you can:
|
||||
# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable)
|
||||
# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable")
|
||||
ifneq ($(origin DEFAULT_CHANNEL), undefined)
|
||||
BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL)
|
||||
endif
|
||||
BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
|
||||
|
||||
# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images.
|
||||
# This variable is used to construct full image tags for bundle and catalog images.
|
||||
#
|
||||
# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both
|
||||
# clastix.io/operator-bundle:$VERSION and clastix.io/operator-catalog:$VERSION.
|
||||
IMAGE_TAG_BASE ?= clastix.io/operator
|
||||
|
||||
# BUNDLE_IMG defines the image:tag used for the bundle.
|
||||
# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=<some-registry>/<project-name-bundle>:<tag>)
|
||||
BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION)
|
||||
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= controller:latest
|
||||
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
||||
CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false"
|
||||
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
|
||||
ENVTEST_K8S_VERSION = 1.21
|
||||
|
||||
# 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
|
||||
|
||||
# Setting SHELL to bash allows bash commands to be executed by recipes.
|
||||
# This is a requirement for 'setup-envtest.sh' in the test target.
|
||||
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
|
||||
SHELL = /usr/bin/env bash -o pipefail
|
||||
.SHELLFLAGS = -ec
|
||||
|
||||
all: build
|
||||
|
||||
##@ General
|
||||
|
||||
# The help target prints out all targets with their descriptions organized
|
||||
# beneath their categories. The categories are represented by '##@' and the
|
||||
# target descriptions by '##'. The awk commands is responsible for reading the
|
||||
# entire set of makefiles included in this invocation, looking for lines of the
|
||||
# file as xyz: ## something, and then pretty-format the target and help. Then,
|
||||
# if there's a line with ##@ something, that gets pretty-printed as a category.
|
||||
# More info on the usage of ANSI control characters for terminal formatting:
|
||||
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
|
||||
# More info on the awk command:
|
||||
# http://linuxcommand.org/lc3_adv_awk.php
|
||||
|
||||
help: ## Display this help.
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||
|
||||
##@ Development
|
||||
|
||||
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
|
||||
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
|
||||
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
|
||||
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
|
||||
|
||||
fmt: ## Run go fmt against code.
|
||||
go fmt ./...
|
||||
|
||||
vet: ## Run go vet against code.
|
||||
go vet ./...
|
||||
|
||||
test: manifests generate fmt vet envtest ## Run tests.
|
||||
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out
|
||||
|
||||
##@ Build
|
||||
|
||||
build: generate fmt vet ## Build manager binary.
|
||||
go build -o bin/manager main.go
|
||||
|
||||
run: manifests generate fmt vet ## Run a controller from your host.
|
||||
go run ./main.go
|
||||
|
||||
docker-build: test ## Build docker image with the manager.
|
||||
docker build -t ${IMG} .
|
||||
|
||||
docker-push: ## Push docker image with the manager.
|
||||
docker push ${IMG}
|
||||
|
||||
##@ Deployment
|
||||
|
||||
dev: generate manifests uninstall install rbac ## Full installation for development purposes
|
||||
go fmt ./...
|
||||
|
||||
load: dev docker-build
|
||||
kind load docker-image --name kamaji ${IMG}
|
||||
|
||||
rbac: manifests kustomize ## Install RBAC into the K8s cluster specified in ~/.kube/config.
|
||||
$(KUSTOMIZE) build config/rbac | kubectl apply -f -
|
||||
|
||||
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
|
||||
$(KUSTOMIZE) build config/crd | kubectl apply -f -
|
||||
|
||||
uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config.
|
||||
$(KUSTOMIZE) build config/crd | kubectl delete -f -
|
||||
|
||||
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
|
||||
$(KUSTOMIZE) build config/default | kubectl apply -f -
|
||||
|
||||
undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config.
|
||||
$(KUSTOMIZE) build config/default | kubectl delete -f -
|
||||
|
||||
yaml-installation-file: manifests kustomize ## Create yaml installation file
|
||||
$(KUSTOMIZE) build config/default > config/install.yaml
|
||||
|
||||
|
||||
CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
|
||||
controller-gen: ## Download controller-gen locally if necessary.
|
||||
$(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.1)
|
||||
|
||||
KUSTOMIZE = $(shell pwd)/bin/kustomize
|
||||
kustomize: ## Download kustomize locally if necessary.
|
||||
$(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7)
|
||||
|
||||
ENVTEST = $(shell pwd)/bin/setup-envtest
|
||||
envtest: ## Download envtest-setup locally if necessary.
|
||||
$(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)
|
||||
|
||||
# go-get-tool will 'go get' any package $2 and install it to $1.
|
||||
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
define go-get-tool
|
||||
@[ -f $(1) ] || { \
|
||||
set -e ;\
|
||||
TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
echo "Downloading $(2)" ;\
|
||||
GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\
|
||||
rm -rf $$TMP_DIR ;\
|
||||
}
|
||||
endef
|
||||
|
||||
.PHONY: bundle
|
||||
bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files.
|
||||
operator-sdk generate kustomize manifests -q
|
||||
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
|
||||
$(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
|
||||
operator-sdk bundle validate ./bundle
|
||||
|
||||
.PHONY: bundle-build
|
||||
bundle-build: ## Build the bundle image.
|
||||
docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) .
|
||||
|
||||
.PHONY: bundle-push
|
||||
bundle-push: ## Push the bundle image.
|
||||
$(MAKE) docker-push IMG=$(BUNDLE_IMG)
|
||||
|
||||
.PHONY: opm
|
||||
OPM = ./bin/opm
|
||||
opm: ## Download opm locally if necessary.
|
||||
ifeq (,$(wildcard $(OPM)))
|
||||
ifeq (,$(shell which opm 2>/dev/null))
|
||||
@{ \
|
||||
set -e ;\
|
||||
mkdir -p $(dir $(OPM)) ;\
|
||||
OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \
|
||||
curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.15.1/$${OS}-$${ARCH}-opm ;\
|
||||
chmod +x $(OPM) ;\
|
||||
}
|
||||
else
|
||||
OPM = $(shell which opm)
|
||||
endif
|
||||
endif
|
||||
|
||||
# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0).
|
||||
# These images MUST exist in a registry and be pull-able.
|
||||
BUNDLE_IMGS ?= $(BUNDLE_IMG)
|
||||
|
||||
# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0).
|
||||
CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION)
|
||||
|
||||
# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image.
|
||||
ifneq ($(origin CATALOG_BASE_IMG), undefined)
|
||||
FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG)
|
||||
endif
|
||||
|
||||
# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'.
|
||||
# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see:
|
||||
# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator
|
||||
.PHONY: catalog-build
|
||||
catalog-build: opm ## Build a catalog image.
|
||||
$(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT)
|
||||
|
||||
# Push the catalog image.
|
||||
.PHONY: catalog-push
|
||||
catalog-push: ## Push a catalog image.
|
||||
$(MAKE) docker-push IMG=$(CATALOG_IMG)
|
||||
19
PROJECT
Normal file
19
PROJECT
Normal file
@@ -0,0 +1,19 @@
|
||||
domain: clastix.io
|
||||
layout:
|
||||
- go.kubebuilder.io/v3
|
||||
plugins:
|
||||
manifests.sdk.operatorframework.io/v2: {}
|
||||
scorecard.sdk.operatorframework.io/v2: {}
|
||||
projectName: operator
|
||||
repo: github.com/clastix/kamaji
|
||||
resources:
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: clastix.io
|
||||
group: kamaji
|
||||
kind: TenantControlPlane
|
||||
path: github.com/clastix/kamaji/api/v1alpha1
|
||||
version: v1alpha1
|
||||
version: "3"
|
||||
@@ -119,4 +119,4 @@ A. Lighter Multi-Tenancy solutions, like Capsule shares the Kubernetes control p
|
||||
|
||||
Q. So I need a costly cloud infrastructure to try Kamaji?
|
||||
|
||||
A. No, it is possible to try Kamaji on your laptop with [KinD](./deploy/kind/README.md). We're porting it also on Canonical MicroK8s as an add-on, _tbd_.
|
||||
A. No, it is possible to try Kamaji on your laptop with [KinD](./deploy/kind/README.md). We're porting it also on Canonical MicroK8s as an add-on, _tbd_.
|
||||
|
||||
23
api/v1alpha1/groupversion_info.go
Normal file
23
api/v1alpha1/groupversion_info.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package v1alpha1 contains API Schema definitions for the kamaji v1alpha1 API group
|
||||
//+kubebuilder:object:generate=true
|
||||
//+groupName=kamaji.clastix.io
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is group version used to register these objects.
|
||||
GroupVersion = schema.GroupVersion{Group: "kamaji.clastix.io", Version: "v1alpha1"}
|
||||
|
||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
|
||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||
|
||||
// AddToScheme adds the types in this group-version to the given scheme.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
43
api/v1alpha1/tenantcontrolplane_funcs.go
Normal file
43
api/v1alpha1/tenantcontrolplane_funcs.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func (in *TenantControlPlane) GetAddress(ctx context.Context, client client.Client) (string, error) {
|
||||
var loadBalancerStatus corev1.LoadBalancerStatus
|
||||
|
||||
svc := &corev1.Service{}
|
||||
err := client.Get(ctx, types.NamespacedName{Namespace: in.GetNamespace(), Name: in.GetName()}, svc)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "cannot retrieve Service for the TenantControlPlane")
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(in.Spec.NetworkProfile.Address) > 0:
|
||||
// Returning the hard-coded value in the specification in case of non LoadBalanced resources
|
||||
return in.Spec.NetworkProfile.Address, nil
|
||||
case svc.Spec.Type == corev1.ServiceTypeLoadBalancer:
|
||||
loadBalancerStatus = svc.Status.LoadBalancer
|
||||
if len(loadBalancerStatus.Ingress) == 0 {
|
||||
return "", fmt.Errorf("cannot retrieve the TenantControlPlane address, Service resource is not yet exposed as LoadBalancer")
|
||||
}
|
||||
|
||||
for _, lb := range loadBalancerStatus.Ingress {
|
||||
if ip := lb.IP; len(ip) > 0 {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("the actual resource doesn't have yet a valid IP address")
|
||||
}
|
||||
296
api/v1alpha1/tenantcontrolplane_types.go
Normal file
296
api/v1alpha1/tenantcontrolplane_types.go
Normal file
@@ -0,0 +1,296 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
appv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/clastix/kamaji/internal/etcd"
|
||||
)
|
||||
|
||||
// NetworkProfileSpec defines the desired state of NetworkProfile.
|
||||
type NetworkProfileSpec struct {
|
||||
// Address where API server of will be exposed.
|
||||
// In case of LoadBalancer Service, this can be empty in order to use the exposed IP provided by the cloud controller manager.
|
||||
Address string `json:"address,omitempty"`
|
||||
// AllowAddressAsExternalIP will include tenantControlPlane.Spec.NetworkProfile.Address in the section of
|
||||
// ExternalIPs of the Kubernetes Service (only ClusterIP or NodePort)
|
||||
AllowAddressAsExternalIP bool `json:"allowAddressAsExternalIP,omitempty"`
|
||||
// Port where API server of will be exposed
|
||||
// +kubebuilder:default=6443
|
||||
Port int32 `json:"port"`
|
||||
|
||||
// Domain of the tenant control plane
|
||||
Domain string `json:"domain"`
|
||||
// Kubernetes Service
|
||||
ServiceCIDR string `json:"serviceCidr"`
|
||||
// CIDR for Kubernetes Pods
|
||||
PodCIDR string `json:"podCidr"`
|
||||
DNSServiceIPs []string `json:"dnsServiceIPs"`
|
||||
}
|
||||
|
||||
type KubeletSpec struct {
|
||||
// CGroupFS defines the cgroup driver for Kubelet
|
||||
// https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/
|
||||
CGroupFS CGroupDriver `json:"cgroupfs,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesSpec defines the desired state of Kubernetes.
|
||||
type KubernetesSpec struct {
|
||||
// Kubernetes Version for the tenant control plane
|
||||
Version string `json:"version"`
|
||||
Kubelet KubeletSpec `json:"kubelet"`
|
||||
|
||||
// List of enabled Admission Controllers for the Tenant cluster.
|
||||
// Full reference available here: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers
|
||||
// +kubebuilder:default=CertificateApproval;CertificateSigning;CertificateSubjectRestriction;DefaultIngressClass;DefaultStorageClass;DefaultTolerationSeconds;LimitRanger;MutatingAdmissionWebhook;NamespaceLifecycle;PersistentVolumeClaimResize;Priority;ResourceQuota;RuntimeClass;ServiceAccount;StorageObjectInUseProtection;TaintNodesByCondition;ValidatingAdmissionWebhook
|
||||
AdmissionControllers AdmissionControllers `json:"admissionControllers,omitempty"`
|
||||
}
|
||||
|
||||
// AdditionalMetadata defines which additional metadata, such as labels and annotations, must be attached to the created resource.
|
||||
type AdditionalMetadata struct {
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// ControlPlane defines how the Tenant Control Plane Kubernetes resources must be created in the Admin Cluster,
|
||||
// such as the number of Pod replicas, the Service resource, or the Ingress.
|
||||
type ControlPlane struct {
|
||||
// Defining the options for the deployed Tenant Control Plane as Deployment resource.
|
||||
Deployment DeploymentSpec `json:"deployment,omitempty"`
|
||||
// Defining the options for the Tenant Control Plane Service resource.
|
||||
Service ServiceSpec `json:"service"`
|
||||
// Defining the options for an Optional Ingress which will expose API Server of the Tenant Control Plane
|
||||
Ingress IngressSpec `json:"ingress,omitempty"`
|
||||
}
|
||||
|
||||
// IngressSpec defines the options for the ingress which will expose API Server of the Tenant Control Plane.
|
||||
type IngressSpec struct {
|
||||
AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
IngressClassName string `json:"ingressClassName,omitempty"`
|
||||
// Hostname is an optional field which will be used as Ingress's Host. If it is not defined,
|
||||
// Ingress's host will be "<tenant>.<namespace>.<domain>", where domain is specified under NetworkProfileSpec
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
}
|
||||
|
||||
type DeploymentSpec struct {
|
||||
// +kubebuilder:default=2
|
||||
Replicas int32 `json:"replicas,omitempty"`
|
||||
AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"`
|
||||
}
|
||||
|
||||
type ServiceSpec struct {
|
||||
AdditionalMetadata AdditionalMetadata `json:"additionalMetadata,omitempty"`
|
||||
// ServiceType allows specifying how to expose the Tenant Control Plane.
|
||||
ServiceType ServiceType `json:"serviceType"`
|
||||
}
|
||||
|
||||
// TenantControlPlaneSpec defines the desired state of TenantControlPlane.
|
||||
type TenantControlPlaneSpec struct {
|
||||
ControlPlane ControlPlane `json:"controlPlane"`
|
||||
|
||||
// Kubernetes specification for tenant control plane
|
||||
Kubernetes KubernetesSpec `json:"kubernetes"`
|
||||
|
||||
// NetworkProfile specifies how the network is
|
||||
NetworkProfile NetworkProfileSpec `json:"networkProfile,omitempty"`
|
||||
}
|
||||
|
||||
// ETCDAPIServerCertificate defines the observed state of ETCD Certificate for API server.
|
||||
type APIServerCertificatesStatus struct {
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
|
||||
}
|
||||
|
||||
// ETCDAPIServerCertificate defines the observed state of ETCD Certificate for API server.
|
||||
type ETCDCertificateStatus struct {
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
|
||||
}
|
||||
|
||||
// ETCDAPIServerCertificate defines the observed state of ETCD Certificate for API server.
|
||||
type ETCDCertificatesStatus struct {
|
||||
APIServer ETCDCertificateStatus `json:"apiServer,omitempty"`
|
||||
CA ETCDCertificateStatus `json:"ca,omitempty"`
|
||||
}
|
||||
|
||||
// CertificatePrivateKeyPair defines the status.
|
||||
type CertificatePrivateKeyPairStatus struct {
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
|
||||
}
|
||||
|
||||
// CertificatePrivateKeyPair defines the status.
|
||||
type PublicKeyPrivateKeyPairStatus struct {
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
|
||||
}
|
||||
|
||||
// ETCDCertificates defines the observed state of ETCD Certificates.
|
||||
type CertificatesStatus struct {
|
||||
CA CertificatePrivateKeyPairStatus `json:"ca,omitempty"`
|
||||
APIServer CertificatePrivateKeyPairStatus `json:"apiServer,omitempty"`
|
||||
APIServerKubeletClient CertificatePrivateKeyPairStatus `json:"apiServerKubeletClient,omitempty"`
|
||||
FrontProxyCA CertificatePrivateKeyPairStatus `json:"frontProxyCA,omitempty"`
|
||||
FrontProxyClient CertificatePrivateKeyPairStatus `json:"frontProxyClient,omitempty"`
|
||||
SA PublicKeyPrivateKeyPairStatus `json:"sa,omitempty"`
|
||||
ETCD *ETCDCertificatesStatus `json:"etcd,omitempty"`
|
||||
}
|
||||
|
||||
// ETCDStatus defines the observed state of ETCDStatus.
|
||||
type ETCDStatus struct {
|
||||
Role etcd.Role `json:"role,omitempty"`
|
||||
User etcd.User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// StorageStatus defines the observed state of StorageStatus.
|
||||
type StorageStatus struct {
|
||||
ETCD *ETCDStatus `json:"etcd,omitempty"`
|
||||
}
|
||||
|
||||
// TenantControlPlaneKubeconfigsStatus contains information about a the generated kubeconfig.
|
||||
type KubeconfigStatus struct {
|
||||
SecretName string `json:"secretName,omitempty"`
|
||||
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
|
||||
}
|
||||
|
||||
// TenantControlPlaneKubeconfigsStatus stores information about all the generated kubeconfigs.
|
||||
type KubeconfigsStatus struct {
|
||||
Admin KubeconfigStatus `json:"admin,omitempty"`
|
||||
ControllerManager KubeconfigStatus `json:"controlerManager,omitempty"`
|
||||
Scheduler KubeconfigStatus `json:"scheduler,omitempty"`
|
||||
}
|
||||
|
||||
// KubeadmConfigStatus contains the status of the configuration required by kubeadm.
|
||||
type KubeadmConfigStatus struct {
|
||||
ConfigmapName string `json:"configmapName,omitempty"`
|
||||
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
|
||||
ResourceVersion string `json:"resourceVersion"`
|
||||
}
|
||||
|
||||
// KubeadmPhasesStatus contains the status of of a kubeadm phase action.
|
||||
type KubeadmPhaseStatus struct {
|
||||
KubeadmConfigResourceVersion string `json:"kubeadmConfigResourceVersion,omitempty"`
|
||||
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
|
||||
}
|
||||
|
||||
// KubeadmPhasesStatus contains the status of the different kubeadm phases action.
|
||||
type KubeadmPhasesStatus struct {
|
||||
UploadConfigKubeadm KubeadmPhaseStatus `json:"uploadConfigKubeadm"`
|
||||
UploadConfigKubelet KubeadmPhaseStatus `json:"uploadConfigKubelet"`
|
||||
AddonCoreDNS KubeadmPhaseStatus `json:"addonCoreDNS"`
|
||||
AddonKubeProxy KubeadmPhaseStatus `json:"addonKubeProxy"`
|
||||
BootstrapToken KubeadmPhaseStatus `json:"bootstrapToken"`
|
||||
}
|
||||
|
||||
// TenantControlPlaneStatus defines the observed state of TenantControlPlane.
|
||||
type TenantControlPlaneStatus struct {
|
||||
// Storage Status contains information about Kubernetes storage system
|
||||
Storage StorageStatus `json:"storage,omitempty"`
|
||||
// Certificates contains information about the different certificates
|
||||
// that are necessary to run a kubernetes control plane
|
||||
Certificates CertificatesStatus `json:"certificates,omitempty"`
|
||||
// KubeConfig contains information about the kubenconfigs that control plane pieces need
|
||||
KubeConfig KubeconfigsStatus `json:"kubeconfig,omitempty"`
|
||||
// Kubernetes contains information about the reconciliation of the required Kubernetes resources deployed in the admin cluster
|
||||
Kubernetes KubernetesStatus `json:"kubernetesResources,omitempty"`
|
||||
// KubeadmConfig contains the status of the configuration required by kubeadm
|
||||
KubeadmConfig KubeadmConfigStatus `json:"kubeadmconfig,omitempty"`
|
||||
// KubeadmPhase contains the status of the kubeadm phases action
|
||||
KubeadmPhase KubeadmPhasesStatus `json:"kubeadmPhase,omitempty"`
|
||||
// ControlPlaneEndpoint contains the status of the kubernetes control plane
|
||||
ControlPlaneEndpoint string `json:"controlPlaneEndpoint,omitempty"`
|
||||
}
|
||||
|
||||
// KubernetesStatus defines the status of the resources deployed in the management cluster,
|
||||
// such as Deployment and Service.
|
||||
type KubernetesStatus struct {
|
||||
// KubernetesVersion contains the information regarding the running Kubernetes version, and its upgrade status.
|
||||
Version KubernetesVersion `json:"version,omitempty"`
|
||||
Deployment KubernetesDeploymentStatus `json:"deployment,omitempty"`
|
||||
Service KubernetesServiceStatus `json:"service,omitempty"`
|
||||
Ingress KubernetesIngressStatus `json:"ingress,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=Provisioning;Upgrading;Ready;NotReady
|
||||
type KubernetesVersionStatus string
|
||||
|
||||
var (
|
||||
VersionProvisioning KubernetesVersionStatus = "Provisioning"
|
||||
VersionUpgrading KubernetesVersionStatus = "Upgrading"
|
||||
VersionReady KubernetesVersionStatus = "Ready"
|
||||
VersionNotReady KubernetesVersionStatus = "NotReady"
|
||||
)
|
||||
|
||||
type KubernetesVersion struct {
|
||||
// Version is the running Kubernetes version of the Tenant Control Plane.
|
||||
Version string `json:"version,omitempty"`
|
||||
// +kubebuilder:default=Provisioning
|
||||
// Status returns the current status of the Kubernetes version, such as its provisioning state, or completed upgrade.
|
||||
Status *KubernetesVersionStatus `json:"status"`
|
||||
}
|
||||
|
||||
// KubernetesDeploymentStatus defines the status for the Tenant Control Plane Deployment in the management cluster.
|
||||
type KubernetesDeploymentStatus struct {
|
||||
appv1.DeploymentStatus `json:",inline"`
|
||||
// The name of the Deployment for the given cluster.
|
||||
Name string `json:"name"`
|
||||
// The namespace which the Deployment for the given cluster is deployed.
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// KubernetesServiceStatus defines the status for the Tenant Control Plane Service in the management cluster.
|
||||
type KubernetesServiceStatus struct {
|
||||
corev1.ServiceStatus `json:",inline"`
|
||||
// The name of the Service for the given cluster.
|
||||
Name string `json:"name"`
|
||||
// The namespace which the Service for the given cluster is deployed.
|
||||
Namespace string `json:"namespace"`
|
||||
// The port where the service is running
|
||||
Port int32 `json:"port"`
|
||||
}
|
||||
|
||||
// KubernetesIngressStatus defines the status for the Tenant Control Plane Ingress in the management cluster.
|
||||
type KubernetesIngressStatus struct {
|
||||
networkingv1.IngressStatus `json:",inline"`
|
||||
// The name of the Ingress for the given cluster.
|
||||
Name string `json:"name"`
|
||||
// The namespace which the Ingress for the given cluster is deployed.
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:shortName=tcp
|
||||
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.kubernetes.version",description="Kubernetes version"
|
||||
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.kubernetesResources.version.status",description="Kubernetes version"
|
||||
// +kubebuilder:printcolumn:name="Control-Plane-Endpoint",type="string",JSONPath=".status.controlPlaneEndpoint",description="Tenant Control Plane Endpoint (API server)"
|
||||
// +kubebuilder:printcolumn:name="Kubeconfig",type="string",JSONPath=".status.kubeconfig.admin.secretName",description="Secret which contains admin kubeconfig"
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
|
||||
|
||||
// TenantControlPlane is the Schema for the tenantcontrolplanes API.
|
||||
type TenantControlPlane struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec TenantControlPlaneSpec `json:"spec,omitempty"`
|
||||
Status TenantControlPlaneStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
|
||||
// TenantControlPlaneList contains a list of TenantControlPlane.
|
||||
type TenantControlPlaneList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []TenantControlPlane `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&TenantControlPlane{}, &TenantControlPlaneList{})
|
||||
}
|
||||
37
api/v1alpha1/types.go
Normal file
37
api/v1alpha1/types.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import corev1 "k8s.io/api/core/v1"
|
||||
|
||||
// +kubebuilder:validation:Enum=AlwaysAdmit;AlwaysDeny;AlwaysPullImages;CertificateApproval;CertificateSigning;CertificateSubjectRestriction;DefaultIngressClass;DefaultStorageClass;DefaultTolerationSeconds;DenyEscalatingExec;DenyExecOnPrivileged;DenyServiceExternalIPs;EventRateLimit;ExtendedResourceToleration;ImagePolicyWebhook;LimitPodHardAntiAffinityTopology;LimitRanger;MutatingAdmissionWebhook;NamespaceAutoProvision;NamespaceExists;NamespaceLifecycle;NodeRestriction;OwnerReferencesPermissionEnforcement;PersistentVolumeClaimResize;PersistentVolumeLabel;PodNodeSelector;PodSecurity;PodSecurityPolicy;PodTolerationRestriction;Priority;ResourceQuota;RuntimeClass;SecurityContextDeny;ServiceAccount;StorageObjectInUseProtection;TaintNodesByCondition;ValidatingAdmissionWebhook
|
||||
type AdmissionController string
|
||||
|
||||
type AdmissionControllers []AdmissionController
|
||||
|
||||
func (a AdmissionControllers) ToSlice() []string {
|
||||
out := make([]string, len(a))
|
||||
|
||||
for i, v := range a {
|
||||
out[i] = string(v)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=systemd;cgroupfs
|
||||
type CGroupDriver string
|
||||
|
||||
func (c CGroupDriver) String() string {
|
||||
return (string)(c)
|
||||
}
|
||||
|
||||
const (
|
||||
ServiceTypeLoadBalancer = (ServiceType)(corev1.ServiceTypeLoadBalancer)
|
||||
ServiceTypeClusterIP = (ServiceType)(corev1.ServiceTypeClusterIP)
|
||||
ServiceTypeNodePort = (ServiceType)(corev1.ServiceTypeNodePort)
|
||||
)
|
||||
|
||||
// +kubebuilder:validation:Enum=ClusterIP;NodePort;LoadBalancer
|
||||
type ServiceType corev1.ServiceType
|
||||
598
api/v1alpha1/zz_generated.deepcopy.go
Normal file
598
api/v1alpha1/zz_generated.deepcopy.go
Normal file
@@ -0,0 +1,598 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *APIServerCertificatesStatus) DeepCopyInto(out *APIServerCertificatesStatus) {
|
||||
*out = *in
|
||||
in.LastUpdate.DeepCopyInto(&out.LastUpdate)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServerCertificatesStatus.
|
||||
func (in *APIServerCertificatesStatus) DeepCopy() *APIServerCertificatesStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(APIServerCertificatesStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalMetadata) DeepCopyInto(out *AdditionalMetadata) {
|
||||
*out = *in
|
||||
if in.Labels != nil {
|
||||
in, out := &in.Labels, &out.Labels
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Annotations != nil {
|
||||
in, out := &in.Annotations, &out.Annotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdditionalMetadata.
|
||||
func (in *AdditionalMetadata) DeepCopy() *AdditionalMetadata {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdditionalMetadata)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in AdmissionControllers) DeepCopyInto(out *AdmissionControllers) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(AdmissionControllers, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionControllers.
|
||||
func (in AdmissionControllers) DeepCopy() AdmissionControllers {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdmissionControllers)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CertificatePrivateKeyPairStatus) DeepCopyInto(out *CertificatePrivateKeyPairStatus) {
|
||||
*out = *in
|
||||
in.LastUpdate.DeepCopyInto(&out.LastUpdate)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificatePrivateKeyPairStatus.
|
||||
func (in *CertificatePrivateKeyPairStatus) DeepCopy() *CertificatePrivateKeyPairStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CertificatePrivateKeyPairStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CertificatesStatus) DeepCopyInto(out *CertificatesStatus) {
|
||||
*out = *in
|
||||
in.CA.DeepCopyInto(&out.CA)
|
||||
in.APIServer.DeepCopyInto(&out.APIServer)
|
||||
in.APIServerKubeletClient.DeepCopyInto(&out.APIServerKubeletClient)
|
||||
in.FrontProxyCA.DeepCopyInto(&out.FrontProxyCA)
|
||||
in.FrontProxyClient.DeepCopyInto(&out.FrontProxyClient)
|
||||
in.SA.DeepCopyInto(&out.SA)
|
||||
if in.ETCD != nil {
|
||||
in, out := &in.ETCD, &out.ETCD
|
||||
*out = new(ETCDCertificatesStatus)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificatesStatus.
|
||||
func (in *CertificatesStatus) DeepCopy() *CertificatesStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CertificatesStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ControlPlane) DeepCopyInto(out *ControlPlane) {
|
||||
*out = *in
|
||||
in.Deployment.DeepCopyInto(&out.Deployment)
|
||||
in.Service.DeepCopyInto(&out.Service)
|
||||
in.Ingress.DeepCopyInto(&out.Ingress)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControlPlane.
|
||||
func (in *ControlPlane) DeepCopy() *ControlPlane {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ControlPlane)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DeploymentSpec) DeepCopyInto(out *DeploymentSpec) {
|
||||
*out = *in
|
||||
in.AdditionalMetadata.DeepCopyInto(&out.AdditionalMetadata)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentSpec.
|
||||
func (in *DeploymentSpec) DeepCopy() *DeploymentSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DeploymentSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ETCDCertificateStatus) DeepCopyInto(out *ETCDCertificateStatus) {
|
||||
*out = *in
|
||||
in.LastUpdate.DeepCopyInto(&out.LastUpdate)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ETCDCertificateStatus.
|
||||
func (in *ETCDCertificateStatus) DeepCopy() *ETCDCertificateStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ETCDCertificateStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ETCDCertificatesStatus) DeepCopyInto(out *ETCDCertificatesStatus) {
|
||||
*out = *in
|
||||
in.APIServer.DeepCopyInto(&out.APIServer)
|
||||
in.CA.DeepCopyInto(&out.CA)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ETCDCertificatesStatus.
|
||||
func (in *ETCDCertificatesStatus) DeepCopy() *ETCDCertificatesStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ETCDCertificatesStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ETCDStatus) DeepCopyInto(out *ETCDStatus) {
|
||||
*out = *in
|
||||
in.Role.DeepCopyInto(&out.Role)
|
||||
in.User.DeepCopyInto(&out.User)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ETCDStatus.
|
||||
func (in *ETCDStatus) DeepCopy() *ETCDStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ETCDStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *IngressSpec) DeepCopyInto(out *IngressSpec) {
|
||||
*out = *in
|
||||
in.AdditionalMetadata.DeepCopyInto(&out.AdditionalMetadata)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressSpec.
|
||||
func (in *IngressSpec) DeepCopy() *IngressSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(IngressSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeadmConfigStatus) DeepCopyInto(out *KubeadmConfigStatus) {
|
||||
*out = *in
|
||||
in.LastUpdate.DeepCopyInto(&out.LastUpdate)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeadmConfigStatus.
|
||||
func (in *KubeadmConfigStatus) DeepCopy() *KubeadmConfigStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubeadmConfigStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeadmPhaseStatus) DeepCopyInto(out *KubeadmPhaseStatus) {
|
||||
*out = *in
|
||||
in.LastUpdate.DeepCopyInto(&out.LastUpdate)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeadmPhaseStatus.
|
||||
func (in *KubeadmPhaseStatus) DeepCopy() *KubeadmPhaseStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubeadmPhaseStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeadmPhasesStatus) DeepCopyInto(out *KubeadmPhasesStatus) {
|
||||
*out = *in
|
||||
in.UploadConfigKubeadm.DeepCopyInto(&out.UploadConfigKubeadm)
|
||||
in.UploadConfigKubelet.DeepCopyInto(&out.UploadConfigKubelet)
|
||||
in.AddonCoreDNS.DeepCopyInto(&out.AddonCoreDNS)
|
||||
in.AddonKubeProxy.DeepCopyInto(&out.AddonKubeProxy)
|
||||
in.BootstrapToken.DeepCopyInto(&out.BootstrapToken)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeadmPhasesStatus.
|
||||
func (in *KubeadmPhasesStatus) DeepCopy() *KubeadmPhasesStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubeadmPhasesStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeconfigStatus) DeepCopyInto(out *KubeconfigStatus) {
|
||||
*out = *in
|
||||
in.LastUpdate.DeepCopyInto(&out.LastUpdate)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigStatus.
|
||||
func (in *KubeconfigStatus) DeepCopy() *KubeconfigStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubeconfigStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeconfigsStatus) DeepCopyInto(out *KubeconfigsStatus) {
|
||||
*out = *in
|
||||
in.Admin.DeepCopyInto(&out.Admin)
|
||||
in.ControllerManager.DeepCopyInto(&out.ControllerManager)
|
||||
in.Scheduler.DeepCopyInto(&out.Scheduler)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeconfigsStatus.
|
||||
func (in *KubeconfigsStatus) DeepCopy() *KubeconfigsStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubeconfigsStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubeletSpec) DeepCopyInto(out *KubeletSpec) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubeletSpec.
|
||||
func (in *KubeletSpec) DeepCopy() *KubeletSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubeletSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesDeploymentStatus) DeepCopyInto(out *KubernetesDeploymentStatus) {
|
||||
*out = *in
|
||||
in.DeploymentStatus.DeepCopyInto(&out.DeploymentStatus)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesDeploymentStatus.
|
||||
func (in *KubernetesDeploymentStatus) DeepCopy() *KubernetesDeploymentStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubernetesDeploymentStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesIngressStatus) DeepCopyInto(out *KubernetesIngressStatus) {
|
||||
*out = *in
|
||||
in.IngressStatus.DeepCopyInto(&out.IngressStatus)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesIngressStatus.
|
||||
func (in *KubernetesIngressStatus) DeepCopy() *KubernetesIngressStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubernetesIngressStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesServiceStatus) DeepCopyInto(out *KubernetesServiceStatus) {
|
||||
*out = *in
|
||||
in.ServiceStatus.DeepCopyInto(&out.ServiceStatus)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesServiceStatus.
|
||||
func (in *KubernetesServiceStatus) DeepCopy() *KubernetesServiceStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubernetesServiceStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesSpec) DeepCopyInto(out *KubernetesSpec) {
|
||||
*out = *in
|
||||
out.Kubelet = in.Kubelet
|
||||
if in.AdmissionControllers != nil {
|
||||
in, out := &in.AdmissionControllers, &out.AdmissionControllers
|
||||
*out = make(AdmissionControllers, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesSpec.
|
||||
func (in *KubernetesSpec) DeepCopy() *KubernetesSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubernetesSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesStatus) DeepCopyInto(out *KubernetesStatus) {
|
||||
*out = *in
|
||||
in.Version.DeepCopyInto(&out.Version)
|
||||
in.Deployment.DeepCopyInto(&out.Deployment)
|
||||
in.Service.DeepCopyInto(&out.Service)
|
||||
in.Ingress.DeepCopyInto(&out.Ingress)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesStatus.
|
||||
func (in *KubernetesStatus) DeepCopy() *KubernetesStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubernetesStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KubernetesVersion) DeepCopyInto(out *KubernetesVersion) {
|
||||
*out = *in
|
||||
if in.Status != nil {
|
||||
in, out := &in.Status, &out.Status
|
||||
*out = new(KubernetesVersionStatus)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesVersion.
|
||||
func (in *KubernetesVersion) DeepCopy() *KubernetesVersion {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(KubernetesVersion)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NetworkProfileSpec) DeepCopyInto(out *NetworkProfileSpec) {
|
||||
*out = *in
|
||||
if in.DNSServiceIPs != nil {
|
||||
in, out := &in.DNSServiceIPs, &out.DNSServiceIPs
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkProfileSpec.
|
||||
func (in *NetworkProfileSpec) DeepCopy() *NetworkProfileSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NetworkProfileSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PublicKeyPrivateKeyPairStatus) DeepCopyInto(out *PublicKeyPrivateKeyPairStatus) {
|
||||
*out = *in
|
||||
in.LastUpdate.DeepCopyInto(&out.LastUpdate)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublicKeyPrivateKeyPairStatus.
|
||||
func (in *PublicKeyPrivateKeyPairStatus) DeepCopy() *PublicKeyPrivateKeyPairStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PublicKeyPrivateKeyPairStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
|
||||
*out = *in
|
||||
in.AdditionalMetadata.DeepCopyInto(&out.AdditionalMetadata)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec.
|
||||
func (in *ServiceSpec) DeepCopy() *ServiceSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ServiceSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *StorageStatus) DeepCopyInto(out *StorageStatus) {
|
||||
*out = *in
|
||||
if in.ETCD != nil {
|
||||
in, out := &in.ETCD, &out.ETCD
|
||||
*out = new(ETCDStatus)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageStatus.
|
||||
func (in *StorageStatus) DeepCopy() *StorageStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(StorageStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantControlPlane) DeepCopyInto(out *TenantControlPlane) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantControlPlane.
|
||||
func (in *TenantControlPlane) DeepCopy() *TenantControlPlane {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantControlPlane)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TenantControlPlane) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantControlPlaneList) DeepCopyInto(out *TenantControlPlaneList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]TenantControlPlane, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantControlPlaneList.
|
||||
func (in *TenantControlPlaneList) DeepCopy() *TenantControlPlaneList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantControlPlaneList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TenantControlPlaneList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantControlPlaneSpec) DeepCopyInto(out *TenantControlPlaneSpec) {
|
||||
*out = *in
|
||||
in.ControlPlane.DeepCopyInto(&out.ControlPlane)
|
||||
in.Kubernetes.DeepCopyInto(&out.Kubernetes)
|
||||
in.NetworkProfile.DeepCopyInto(&out.NetworkProfile)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantControlPlaneSpec.
|
||||
func (in *TenantControlPlaneSpec) DeepCopy() *TenantControlPlaneSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantControlPlaneSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantControlPlaneStatus) DeepCopyInto(out *TenantControlPlaneStatus) {
|
||||
*out = *in
|
||||
in.Storage.DeepCopyInto(&out.Storage)
|
||||
in.Certificates.DeepCopyInto(&out.Certificates)
|
||||
in.KubeConfig.DeepCopyInto(&out.KubeConfig)
|
||||
in.Kubernetes.DeepCopyInto(&out.Kubernetes)
|
||||
in.KubeadmConfig.DeepCopyInto(&out.KubeadmConfig)
|
||||
in.KubeadmPhase.DeepCopyInto(&out.KubeadmPhase)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantControlPlaneStatus.
|
||||
func (in *TenantControlPlaneStatus) DeepCopy() *TenantControlPlaneStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantControlPlaneStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
889
config/crd/bases/kamaji.clastix.io_tenantcontrolplanes.yaml
Normal file
889
config/crd/bases/kamaji.clastix.io_tenantcontrolplanes.yaml
Normal file
@@ -0,0 +1,889 @@
|
||||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.6.1
|
||||
creationTimestamp: null
|
||||
name: tenantcontrolplanes.kamaji.clastix.io
|
||||
spec:
|
||||
group: kamaji.clastix.io
|
||||
names:
|
||||
kind: TenantControlPlane
|
||||
listKind: TenantControlPlaneList
|
||||
plural: tenantcontrolplanes
|
||||
shortNames:
|
||||
- tcp
|
||||
singular: tenantcontrolplane
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: Kubernetes version
|
||||
jsonPath: .spec.kubernetes.version
|
||||
name: Version
|
||||
type: string
|
||||
- description: Kubernetes version
|
||||
jsonPath: .status.kubernetesResources.version.status
|
||||
name: Status
|
||||
type: string
|
||||
- description: Tenant Control Plane Endpoint (API server)
|
||||
jsonPath: .status.controlPlaneEndpoint
|
||||
name: Control-Plane-Endpoint
|
||||
type: string
|
||||
- description: Secret which contains admin kubeconfig
|
||||
jsonPath: .status.kubeconfig.admin.secretName
|
||||
name: Kubeconfig
|
||||
type: string
|
||||
- description: Age
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: TenantControlPlane is the Schema for the tenantcontrolplanes
|
||||
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: TenantControlPlaneSpec defines the desired state of TenantControlPlane.
|
||||
properties:
|
||||
controlPlane:
|
||||
description: ControlPlane defines how the Tenant Control Plane Kubernetes
|
||||
resources must be created in the Admin Cluster, such as the number
|
||||
of Pod replicas, the Service resource, or the Ingress.
|
||||
properties:
|
||||
deployment:
|
||||
description: Defining the options for the deployed Tenant Control
|
||||
Plane as Deployment resource.
|
||||
properties:
|
||||
additionalMetadata:
|
||||
description: AdditionalMetadata defines which additional metadata,
|
||||
such as labels and annotations, must be attached to the
|
||||
created resource.
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
replicas:
|
||||
default: 2
|
||||
format: int32
|
||||
type: integer
|
||||
type: object
|
||||
ingress:
|
||||
description: Defining the options for an Optional Ingress which
|
||||
will expose API Server of the Tenant Control Plane
|
||||
properties:
|
||||
additionalMetadata:
|
||||
description: AdditionalMetadata defines which additional metadata,
|
||||
such as labels and annotations, must be attached to the
|
||||
created resource.
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
enabled:
|
||||
type: boolean
|
||||
hostname:
|
||||
description: Hostname is an optional field which will be used
|
||||
as Ingress's Host. If it is not defined, Ingress's host
|
||||
will be "<tenant>.<namespace>.<domain>", where domain is
|
||||
specified under NetworkProfileSpec
|
||||
type: string
|
||||
ingressClassName:
|
||||
type: string
|
||||
required:
|
||||
- enabled
|
||||
type: object
|
||||
service:
|
||||
description: Defining the options for the Tenant Control Plane
|
||||
Service resource.
|
||||
properties:
|
||||
additionalMetadata:
|
||||
description: AdditionalMetadata defines which additional metadata,
|
||||
such as labels and annotations, must be attached to the
|
||||
created resource.
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
serviceType:
|
||||
description: ServiceType allows specifying how to expose the
|
||||
Tenant Control Plane.
|
||||
enum:
|
||||
- ClusterIP
|
||||
- NodePort
|
||||
- LoadBalancer
|
||||
type: string
|
||||
required:
|
||||
- serviceType
|
||||
type: object
|
||||
required:
|
||||
- service
|
||||
type: object
|
||||
kubernetes:
|
||||
description: Kubernetes specification for tenant control plane
|
||||
properties:
|
||||
admissionControllers:
|
||||
default:
|
||||
- CertificateApproval
|
||||
- CertificateSigning
|
||||
- CertificateSubjectRestriction
|
||||
- DefaultIngressClass
|
||||
- DefaultStorageClass
|
||||
- DefaultTolerationSeconds
|
||||
- LimitRanger
|
||||
- MutatingAdmissionWebhook
|
||||
- NamespaceLifecycle
|
||||
- PersistentVolumeClaimResize
|
||||
- Priority
|
||||
- ResourceQuota
|
||||
- RuntimeClass
|
||||
- ServiceAccount
|
||||
- StorageObjectInUseProtection
|
||||
- TaintNodesByCondition
|
||||
- ValidatingAdmissionWebhook
|
||||
description: 'List of enabled Admission Controllers for the Tenant
|
||||
cluster. Full reference available here: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers'
|
||||
items:
|
||||
enum:
|
||||
- AlwaysAdmit
|
||||
- AlwaysDeny
|
||||
- AlwaysPullImages
|
||||
- CertificateApproval
|
||||
- CertificateSigning
|
||||
- CertificateSubjectRestriction
|
||||
- DefaultIngressClass
|
||||
- DefaultStorageClass
|
||||
- DefaultTolerationSeconds
|
||||
- DenyEscalatingExec
|
||||
- DenyExecOnPrivileged
|
||||
- DenyServiceExternalIPs
|
||||
- EventRateLimit
|
||||
- ExtendedResourceToleration
|
||||
- ImagePolicyWebhook
|
||||
- LimitPodHardAntiAffinityTopology
|
||||
- LimitRanger
|
||||
- MutatingAdmissionWebhook
|
||||
- NamespaceAutoProvision
|
||||
- NamespaceExists
|
||||
- NamespaceLifecycle
|
||||
- NodeRestriction
|
||||
- OwnerReferencesPermissionEnforcement
|
||||
- PersistentVolumeClaimResize
|
||||
- PersistentVolumeLabel
|
||||
- PodNodeSelector
|
||||
- PodSecurity
|
||||
- PodSecurityPolicy
|
||||
- PodTolerationRestriction
|
||||
- Priority
|
||||
- ResourceQuota
|
||||
- RuntimeClass
|
||||
- SecurityContextDeny
|
||||
- ServiceAccount
|
||||
- StorageObjectInUseProtection
|
||||
- TaintNodesByCondition
|
||||
- ValidatingAdmissionWebhook
|
||||
type: string
|
||||
type: array
|
||||
kubelet:
|
||||
properties:
|
||||
cgroupfs:
|
||||
description: CGroupFS defines the cgroup driver for Kubelet
|
||||
https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/
|
||||
enum:
|
||||
- systemd
|
||||
- cgroupfs
|
||||
type: string
|
||||
type: object
|
||||
version:
|
||||
description: Kubernetes Version for the tenant control plane
|
||||
type: string
|
||||
required:
|
||||
- kubelet
|
||||
- version
|
||||
type: object
|
||||
networkProfile:
|
||||
description: NetworkProfile specifies how the network is
|
||||
properties:
|
||||
address:
|
||||
description: Address where API server of will be exposed. In case
|
||||
of LoadBalancer Service, this can be empty in order to use the
|
||||
exposed IP provided by the cloud controller manager.
|
||||
type: string
|
||||
allowAddressAsExternalIP:
|
||||
description: AllowAddressAsExternalIP will include tenantControlPlane.Spec.NetworkProfile.Address
|
||||
in the section of ExternalIPs of the Kubernetes Service (only
|
||||
ClusterIP or NodePort)
|
||||
type: boolean
|
||||
dnsServiceIPs:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
domain:
|
||||
description: Domain of the tenant control plane
|
||||
type: string
|
||||
podCidr:
|
||||
description: CIDR for Kubernetes Pods
|
||||
type: string
|
||||
port:
|
||||
default: 6443
|
||||
description: Port where API server of will be exposed
|
||||
format: int32
|
||||
type: integer
|
||||
serviceCidr:
|
||||
description: Kubernetes Service
|
||||
type: string
|
||||
required:
|
||||
- dnsServiceIPs
|
||||
- domain
|
||||
- podCidr
|
||||
- port
|
||||
- serviceCidr
|
||||
type: object
|
||||
required:
|
||||
- controlPlane
|
||||
- kubernetes
|
||||
type: object
|
||||
status:
|
||||
description: TenantControlPlaneStatus defines the observed state of TenantControlPlane.
|
||||
properties:
|
||||
certificates:
|
||||
description: Certificates contains information about the different
|
||||
certificates that are necessary to run a kubernetes control plane
|
||||
properties:
|
||||
apiServer:
|
||||
description: CertificatePrivateKeyPair defines the status.
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
apiServerKubeletClient:
|
||||
description: CertificatePrivateKeyPair defines the status.
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
ca:
|
||||
description: CertificatePrivateKeyPair defines the status.
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
etcd:
|
||||
description: ETCDAPIServerCertificate defines the observed state
|
||||
of ETCD Certificate for API server.
|
||||
properties:
|
||||
apiServer:
|
||||
description: ETCDAPIServerCertificate defines the observed
|
||||
state of ETCD Certificate for API server.
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
ca:
|
||||
description: ETCDAPIServerCertificate defines the observed
|
||||
state of ETCD Certificate for API server.
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
frontProxyCA:
|
||||
description: CertificatePrivateKeyPair defines the status.
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
frontProxyClient:
|
||||
description: CertificatePrivateKeyPair defines the status.
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
sa:
|
||||
description: CertificatePrivateKeyPair defines the status.
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
controlPlaneEndpoint:
|
||||
description: ControlPlaneEndpoint contains the status of the kubernetes
|
||||
control plane
|
||||
type: string
|
||||
kubeadmPhase:
|
||||
description: KubeadmPhase contains the status of the kubeadm phases
|
||||
action
|
||||
properties:
|
||||
addonCoreDNS:
|
||||
description: KubeadmPhasesStatus contains the status of of a kubeadm
|
||||
phase action.
|
||||
properties:
|
||||
kubeadmConfigResourceVersion:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
addonKubeProxy:
|
||||
description: KubeadmPhasesStatus contains the status of of a kubeadm
|
||||
phase action.
|
||||
properties:
|
||||
kubeadmConfigResourceVersion:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
bootstrapToken:
|
||||
description: KubeadmPhasesStatus contains the status of of a kubeadm
|
||||
phase action.
|
||||
properties:
|
||||
kubeadmConfigResourceVersion:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
uploadConfigKubeadm:
|
||||
description: KubeadmPhasesStatus contains the status of of a kubeadm
|
||||
phase action.
|
||||
properties:
|
||||
kubeadmConfigResourceVersion:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
uploadConfigKubelet:
|
||||
description: KubeadmPhasesStatus contains the status of of a kubeadm
|
||||
phase action.
|
||||
properties:
|
||||
kubeadmConfigResourceVersion:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- addonCoreDNS
|
||||
- addonKubeProxy
|
||||
- bootstrapToken
|
||||
- uploadConfigKubeadm
|
||||
- uploadConfigKubelet
|
||||
type: object
|
||||
kubeadmconfig:
|
||||
description: KubeadmConfig contains the status of the configuration
|
||||
required by kubeadm
|
||||
properties:
|
||||
configmapName:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
resourceVersion:
|
||||
type: string
|
||||
required:
|
||||
- resourceVersion
|
||||
type: object
|
||||
kubeconfig:
|
||||
description: KubeConfig contains information about the kubenconfigs
|
||||
that control plane pieces need
|
||||
properties:
|
||||
admin:
|
||||
description: TenantControlPlaneKubeconfigsStatus contains information
|
||||
about a the generated kubeconfig.
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
controlerManager:
|
||||
description: TenantControlPlaneKubeconfigsStatus contains information
|
||||
about a the generated kubeconfig.
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
scheduler:
|
||||
description: TenantControlPlaneKubeconfigsStatus contains information
|
||||
about a the generated kubeconfig.
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
kubernetesResources:
|
||||
description: Kubernetes contains information about the reconciliation
|
||||
of the required Kubernetes resources deployed in the admin cluster
|
||||
properties:
|
||||
deployment:
|
||||
description: KubernetesDeploymentStatus defines the status for
|
||||
the Tenant Control Plane Deployment in the management cluster.
|
||||
properties:
|
||||
availableReplicas:
|
||||
description: Total number of available pods (ready for at
|
||||
least minReadySeconds) targeted by this deployment.
|
||||
format: int32
|
||||
type: integer
|
||||
collisionCount:
|
||||
description: Count of hash collisions for the Deployment.
|
||||
The Deployment controller uses this field as a collision
|
||||
avoidance mechanism when it needs to create the name for
|
||||
the newest ReplicaSet.
|
||||
format: int32
|
||||
type: integer
|
||||
conditions:
|
||||
description: Represents the latest available observations
|
||||
of a deployment's current state.
|
||||
items:
|
||||
description: DeploymentCondition describes the state of
|
||||
a deployment at a certain point.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: Last time the condition transitioned from
|
||||
one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
lastUpdateTime:
|
||||
description: The last time this condition was updated.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating details
|
||||
about the transition.
|
||||
type: string
|
||||
reason:
|
||||
description: The reason for the condition's last transition.
|
||||
type: string
|
||||
status:
|
||||
description: Status of the condition, one of True, False,
|
||||
Unknown.
|
||||
type: string
|
||||
type:
|
||||
description: Type of deployment condition.
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: The name of the Deployment for the given cluster.
|
||||
type: string
|
||||
namespace:
|
||||
description: The namespace which the Deployment for the given
|
||||
cluster is deployed.
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: The generation observed by the deployment controller.
|
||||
format: int64
|
||||
type: integer
|
||||
readyReplicas:
|
||||
description: readyReplicas is the number of pods targeted
|
||||
by this Deployment with a Ready Condition.
|
||||
format: int32
|
||||
type: integer
|
||||
replicas:
|
||||
description: Total number of non-terminated pods targeted
|
||||
by this deployment (their labels match the selector).
|
||||
format: int32
|
||||
type: integer
|
||||
unavailableReplicas:
|
||||
description: Total number of unavailable pods targeted by
|
||||
this deployment. This is the total number of pods that are
|
||||
still required for the deployment to have 100% available
|
||||
capacity. They may either be pods that are running but not
|
||||
yet available or pods that still have not been created.
|
||||
format: int32
|
||||
type: integer
|
||||
updatedReplicas:
|
||||
description: Total number of non-terminated pods targeted
|
||||
by this deployment that have the desired template spec.
|
||||
format: int32
|
||||
type: integer
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
ingress:
|
||||
description: KubernetesIngressStatus defines the status for the
|
||||
Tenant Control Plane Ingress in the management cluster.
|
||||
properties:
|
||||
loadBalancer:
|
||||
description: LoadBalancer contains the current status of the
|
||||
load-balancer.
|
||||
properties:
|
||||
ingress:
|
||||
description: Ingress is a list containing ingress points
|
||||
for the load-balancer. Traffic intended for the service
|
||||
should be sent to these ingress points.
|
||||
items:
|
||||
description: 'LoadBalancerIngress represents the status
|
||||
of a load-balancer ingress point: traffic intended
|
||||
for the service should be sent to an ingress point.'
|
||||
properties:
|
||||
hostname:
|
||||
description: Hostname is set for load-balancer ingress
|
||||
points that are DNS based (typically AWS load-balancers)
|
||||
type: string
|
||||
ip:
|
||||
description: IP is set for load-balancer ingress
|
||||
points that are IP based (typically GCE or OpenStack
|
||||
load-balancers)
|
||||
type: string
|
||||
ports:
|
||||
description: Ports is a list of records of service
|
||||
ports If used, every port defined in the service
|
||||
should have an entry in it
|
||||
items:
|
||||
properties:
|
||||
error:
|
||||
description: 'Error is to record the problem
|
||||
with the service port The format of the
|
||||
error shall comply with the following rules:
|
||||
- built-in error values shall be specified
|
||||
in this file and those shall use CamelCase
|
||||
names - cloud provider specific error values
|
||||
must have names that comply with the format
|
||||
foo.example.com/CamelCase. --- The regex
|
||||
it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
port:
|
||||
description: Port is the port number of the
|
||||
service port of which status is recorded
|
||||
here
|
||||
format: int32
|
||||
type: integer
|
||||
protocol:
|
||||
default: TCP
|
||||
description: 'Protocol is the protocol of
|
||||
the service port of which status is recorded
|
||||
here The supported values are: "TCP", "UDP",
|
||||
"SCTP"'
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
- protocol
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
name:
|
||||
description: The name of the Ingress for the given cluster.
|
||||
type: string
|
||||
namespace:
|
||||
description: The namespace which the Ingress for the given
|
||||
cluster is deployed.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
service:
|
||||
description: KubernetesServiceStatus defines the status for the
|
||||
Tenant Control Plane Service in the management cluster.
|
||||
properties:
|
||||
conditions:
|
||||
description: Current service state
|
||||
items:
|
||||
description: "Condition contains details for one aspect
|
||||
of the current state of this API Resource. --- This struct
|
||||
is intended for direct use as an array at the field path
|
||||
.status.conditions. For example, type FooStatus struct{
|
||||
\ // Represents the observations of a foo's current
|
||||
state. // Known .status.conditions.type are: \"Available\",
|
||||
\"Progressing\", and \"Degraded\" // +patchMergeKey=type
|
||||
\ // +patchStrategy=merge // +listType=map //
|
||||
+listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\"
|
||||
patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`
|
||||
\n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the
|
||||
condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If
|
||||
that is not known, then using the time when the API
|
||||
field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty
|
||||
string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance,
|
||||
if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to
|
||||
the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier
|
||||
indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected
|
||||
values and meanings for this field, and whether the
|
||||
values are considered a guaranteed API. The value
|
||||
should be a CamelCase string. This field may not be
|
||||
empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False,
|
||||
Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
--- Many .condition.type values are consistent across
|
||||
resources like Available, but because arbitrary conditions
|
||||
can be useful (see .node.status.conditions), the ability
|
||||
to deconflict is important. The regex it matches is
|
||||
(dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
loadBalancer:
|
||||
description: LoadBalancer contains the current status of the
|
||||
load-balancer, if one is present.
|
||||
properties:
|
||||
ingress:
|
||||
description: Ingress is a list containing ingress points
|
||||
for the load-balancer. Traffic intended for the service
|
||||
should be sent to these ingress points.
|
||||
items:
|
||||
description: 'LoadBalancerIngress represents the status
|
||||
of a load-balancer ingress point: traffic intended
|
||||
for the service should be sent to an ingress point.'
|
||||
properties:
|
||||
hostname:
|
||||
description: Hostname is set for load-balancer ingress
|
||||
points that are DNS based (typically AWS load-balancers)
|
||||
type: string
|
||||
ip:
|
||||
description: IP is set for load-balancer ingress
|
||||
points that are IP based (typically GCE or OpenStack
|
||||
load-balancers)
|
||||
type: string
|
||||
ports:
|
||||
description: Ports is a list of records of service
|
||||
ports If used, every port defined in the service
|
||||
should have an entry in it
|
||||
items:
|
||||
properties:
|
||||
error:
|
||||
description: 'Error is to record the problem
|
||||
with the service port The format of the
|
||||
error shall comply with the following rules:
|
||||
- built-in error values shall be specified
|
||||
in this file and those shall use CamelCase
|
||||
names - cloud provider specific error values
|
||||
must have names that comply with the format
|
||||
foo.example.com/CamelCase. --- The regex
|
||||
it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
port:
|
||||
description: Port is the port number of the
|
||||
service port of which status is recorded
|
||||
here
|
||||
format: int32
|
||||
type: integer
|
||||
protocol:
|
||||
default: TCP
|
||||
description: 'Protocol is the protocol of
|
||||
the service port of which status is recorded
|
||||
here The supported values are: "TCP", "UDP",
|
||||
"SCTP"'
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
- protocol
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
name:
|
||||
description: The name of the Service for the given cluster.
|
||||
type: string
|
||||
namespace:
|
||||
description: The namespace which the Service for the given
|
||||
cluster is deployed.
|
||||
type: string
|
||||
port:
|
||||
description: The port where the service is running
|
||||
format: int32
|
||||
type: integer
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
- port
|
||||
type: object
|
||||
version:
|
||||
description: KubernetesVersion contains the information regarding
|
||||
the running Kubernetes version, and its upgrade status.
|
||||
properties:
|
||||
status:
|
||||
default: Provisioning
|
||||
description: Status returns the current status of the Kubernetes
|
||||
version, such as its provisioning state, or completed upgrade.
|
||||
enum:
|
||||
- Provisioning
|
||||
- Upgrading
|
||||
- Ready
|
||||
- NotReady
|
||||
type: string
|
||||
version:
|
||||
description: Version is the running Kubernetes version of
|
||||
the Tenant Control Plane.
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
type: object
|
||||
storage:
|
||||
description: Storage Status contains information about Kubernetes
|
||||
storage system
|
||||
properties:
|
||||
etcd:
|
||||
description: ETCDStatus defines the observed state of ETCDStatus.
|
||||
properties:
|
||||
role:
|
||||
properties:
|
||||
exists:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
permissions:
|
||||
items:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
rangeEnd:
|
||||
type: string
|
||||
type:
|
||||
type: integer
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- exists
|
||||
- name
|
||||
type: object
|
||||
user:
|
||||
properties:
|
||||
exists:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
roles:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- exists
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
21
config/crd/kustomization.yaml
Normal file
21
config/crd/kustomization.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
# 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/kamaji.clastix.io_tenantcontrolplanes.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
patchesStrategicMerge:
|
||||
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
|
||||
# patches here are for enabling the conversion webhook for each CRD
|
||||
#- patches/webhook_in_clusters.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizewebhookpatch
|
||||
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
|
||||
# patches here are for enabling the CA injection for each CRD
|
||||
#- patches/cainjection_in_clusters.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizecainjectionpatch
|
||||
|
||||
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
||||
configurations:
|
||||
- kustomizeconfig.yaml
|
||||
19
config/crd/kustomizeconfig.yaml
Normal file
19
config/crd/kustomizeconfig.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
# This file is for teaching kustomize how to substitute name and namespace reference in CRD
|
||||
nameReference:
|
||||
- kind: Service
|
||||
version: v1
|
||||
fieldSpecs:
|
||||
- kind: CustomResourceDefinition
|
||||
version: v1
|
||||
group: apiextensions.k8s.io
|
||||
path: spec/conversion/webhook/clientConfig/service/name
|
||||
|
||||
namespace:
|
||||
- kind: CustomResourceDefinition
|
||||
version: v1
|
||||
group: apiextensions.k8s.io
|
||||
path: spec/conversion/webhook/clientConfig/service/namespace
|
||||
create: false
|
||||
|
||||
varReference:
|
||||
- path: metadata/annotations
|
||||
7
config/crd/patches/cainjection_in_clusters.yaml
Normal file
7
config/crd/patches/cainjection_in_clusters.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# The following patch adds a directive for certmanager to inject CA into the CRD
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
|
||||
name: tenantcontrolplanes.kamaji.clastix.io
|
||||
16
config/crd/patches/webhook_in_clusters.yaml
Normal file
16
config/crd/patches/webhook_in_clusters.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
# The following patch enables a conversion webhook for the CRD
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: tenantcontrolplanes.kamaji.clastix.io
|
||||
spec:
|
||||
conversion:
|
||||
strategy: Webhook
|
||||
webhook:
|
||||
clientConfig:
|
||||
service:
|
||||
namespace: system
|
||||
name: webhook-service
|
||||
path: /convert
|
||||
conversionReviewVersions:
|
||||
- v1
|
||||
74
config/default/kustomization.yaml
Normal file
74
config/default/kustomization.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
# Adds namespace to all resources.
|
||||
namespace: kamaji-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: kamaji-
|
||||
|
||||
# Labels to add to all resources and selectors.
|
||||
#commonLabels:
|
||||
# someName: someValue
|
||||
|
||||
bases:
|
||||
- ../crd
|
||||
- ../rbac
|
||||
- ../manager
|
||||
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
|
||||
# crd/kustomization.yaml
|
||||
#- ../webhook
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
|
||||
#- ../certmanager
|
||||
# [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
|
||||
|
||||
# Mount the controller config file for loading manager configurations
|
||||
# through a ComponentConfig type
|
||||
#- manager_config_patch.yaml
|
||||
|
||||
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
|
||||
# crd/kustomization.yaml
|
||||
#- manager_webhook_patch.yaml
|
||||
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
|
||||
# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
|
||||
# 'CERTMANAGER' needs to be enabled to use ca injection
|
||||
#- webhookcainjection_patch.yaml
|
||||
|
||||
# the following config is for teaching kustomize how to do var substitution
|
||||
vars:
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
|
||||
#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR
|
||||
# objref:
|
||||
# kind: Certificate
|
||||
# group: cert-manager.io
|
||||
# version: v1
|
||||
# name: serving-cert # this name should match the one in certificate.yaml
|
||||
# fieldref:
|
||||
# fieldpath: metadata.namespace
|
||||
#- name: CERTIFICATE_NAME
|
||||
# objref:
|
||||
# kind: Certificate
|
||||
# group: cert-manager.io
|
||||
# version: v1
|
||||
# name: serving-cert # this name should match the one in certificate.yaml
|
||||
#- name: SERVICE_NAMESPACE # namespace of the service
|
||||
# objref:
|
||||
# kind: Service
|
||||
# version: v1
|
||||
# name: webhook-service
|
||||
# fieldref:
|
||||
# fieldpath: metadata.namespace
|
||||
#- name: SERVICE_NAME
|
||||
# objref:
|
||||
# kind: Service
|
||||
# version: v1
|
||||
# name: webhook-service
|
||||
27
config/default/manager_auth_proxy_patch.yaml
Normal file
27
config/default/manager_auth_proxy_patch.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# 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.8.0
|
||||
args:
|
||||
- "--secure-listen-address=0.0.0.0:8443"
|
||||
- "--upstream=http://127.0.0.1:8080/"
|
||||
- "--logtostderr=true"
|
||||
- "--v=10"
|
||||
ports:
|
||||
- containerPort: 8443
|
||||
protocol: TCP
|
||||
name: https
|
||||
- name: manager
|
||||
args:
|
||||
- "--health-probe-bind-address=:8081"
|
||||
- "--metrics-bind-address=127.0.0.1:8080"
|
||||
- "--leader-elect"
|
||||
20
config/default/manager_config_patch.yaml
Normal file
20
config/default/manager_config_patch.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: manager
|
||||
args:
|
||||
- "--config=controller_manager_config.yaml"
|
||||
volumeMounts:
|
||||
- name: manager-config
|
||||
mountPath: /controller_manager_config.yaml
|
||||
subPath: controller_manager_config.yaml
|
||||
volumes:
|
||||
- name: manager-config
|
||||
configMap:
|
||||
name: manager-config
|
||||
1051
config/install.yaml
Normal file
1051
config/install.yaml
Normal file
File diff suppressed because it is too large
Load Diff
11
config/manager/controller_manager_config.yaml
Normal file
11
config/manager/controller_manager_config.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: controller-runtime.sigs.k8s.io/v1alpha1
|
||||
kind: ControllerManagerConfig
|
||||
health:
|
||||
healthProbeBindAddress: :8081
|
||||
metrics:
|
||||
bindAddress: 127.0.0.1:8080
|
||||
webhook:
|
||||
port: 9443
|
||||
leaderElection:
|
||||
leaderElect: true
|
||||
resourceName: 799b98bc.clastix.io
|
||||
16
config/manager/kustomization.yaml
Normal file
16
config/manager/kustomization.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
resources:
|
||||
- manager.yaml
|
||||
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: true
|
||||
|
||||
configMapGenerator:
|
||||
- files:
|
||||
- controller_manager_config.yaml
|
||||
name: manager-config
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
images:
|
||||
- name: controller
|
||||
newName: quay.io/clastix/kamaji
|
||||
newTag: latest
|
||||
57
config/manager/manager.yaml
Normal file
57
config/manager/manager.yaml
Normal file
@@ -0,0 +1,57 @@
|
||||
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:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
containers:
|
||||
- command:
|
||||
- /manager
|
||||
args:
|
||||
- --leader-elect
|
||||
image: controller:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: manager
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8081
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: 8081
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 100Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 20Mi
|
||||
serviceAccountName: controller-manager
|
||||
terminationGracePeriodSeconds: 10
|
||||
27
config/manifests/kustomization.yaml
Normal file
27
config/manifests/kustomization.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# These resources constitute the fully configured set of manifests
|
||||
# used to generate the 'manifests/' directory in a bundle.
|
||||
resources:
|
||||
- bases/operator.clusterserviceversion.yaml
|
||||
- ../default
|
||||
- ../samples
|
||||
- ../scorecard
|
||||
|
||||
# [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix.
|
||||
# Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager.
|
||||
# These patches remove the unnecessary "cert" volume and its manager container volumeMount.
|
||||
#patchesJson6902:
|
||||
#- target:
|
||||
# group: apps
|
||||
# version: v1
|
||||
# kind: Deployment
|
||||
# name: controller-manager
|
||||
# namespace: system
|
||||
# patch: |-
|
||||
# # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs.
|
||||
# # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment.
|
||||
# - op: remove
|
||||
# path: /spec/template/spec/containers/1/volumeMounts/0
|
||||
# # Remove the "cert" volume, since OLM will create and mount a set of certs.
|
||||
# # Update the indices in this path if adding or removing volumes in the manager's Deployment.
|
||||
# - op: remove
|
||||
# path: /spec/template/spec/volumes/0
|
||||
2
config/prometheus/kustomization.yaml
Normal file
2
config/prometheus/kustomization.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
resources:
|
||||
- monitor.yaml
|
||||
20
config/prometheus/monitor.yaml
Normal file
20
config/prometheus/monitor.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
# 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
|
||||
scheme: https
|
||||
bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
tlsConfig:
|
||||
insecureSkipVerify: true
|
||||
selector:
|
||||
matchLabels:
|
||||
control-plane: controller-manager
|
||||
9
config/rbac/auth_proxy_client_clusterrole.yaml
Normal file
9
config/rbac/auth_proxy_client_clusterrole.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: metrics-reader
|
||||
rules:
|
||||
- nonResourceURLs:
|
||||
- "/metrics"
|
||||
verbs:
|
||||
- get
|
||||
17
config/rbac/auth_proxy_role.yaml
Normal file
17
config/rbac/auth_proxy_role.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
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: controller-manager
|
||||
namespace: system
|
||||
15
config/rbac/auth_proxy_service.yaml
Normal file
15
config/rbac/auth_proxy_service.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
control-plane: controller-manager
|
||||
name: controller-manager-metrics-service
|
||||
namespace: system
|
||||
spec:
|
||||
ports:
|
||||
- name: https
|
||||
port: 8443
|
||||
protocol: TCP
|
||||
targetPort: https
|
||||
selector:
|
||||
control-plane: controller-manager
|
||||
18
config/rbac/kustomization.yaml
Normal file
18
config/rbac/kustomization.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
resources:
|
||||
# All RBAC will be applied under this service account in
|
||||
# the deployment namespace. You may comment out this resource
|
||||
# if your manager will use a service account that exists at
|
||||
# runtime. Be sure to update RoleBinding and ClusterRoleBinding
|
||||
# subjects if changing service account names.
|
||||
- service_account.yaml
|
||||
- role.yaml
|
||||
- role_binding.yaml
|
||||
- leader_election_role.yaml
|
||||
- leader_election_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
|
||||
37
config/rbac/leader_election_role.yaml
Normal file
37
config/rbac/leader_election_role.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
# permissions to do leader election.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: leader-election-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- create
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- coordination.k8s.io
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- create
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
12
config/rbac/leader_election_role_binding.yaml
Normal file
12
config/rbac/leader_election_role_binding.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: leader-election-rolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: leader-election-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
94
config/rbac/role.yaml
Normal file
94
config/rbac/role.yaml
Normal file
@@ -0,0 +1,94 @@
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: manager-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- deployments
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- tenantcontrolplanes
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- tenantcontrolplanes/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- tenantcontrolplanes/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
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:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: manager-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
5
config/rbac/service_account.yaml
Normal file
5
config/rbac/service_account.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: controller-manager
|
||||
namespace: system
|
||||
24
config/rbac/tenantcluster_editor_role.yaml
Normal file
24
config/rbac/tenantcluster_editor_role.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# permissions for end users to edit clusters.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: tenantcontrolplane-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- tenantcontrolplanes
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- tenantcontrolplanes/status
|
||||
verbs:
|
||||
- get
|
||||
20
config/rbac/tenantcluster_viewer_role.yaml
Normal file
20
config/rbac/tenantcluster_viewer_role.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
# permissions for end users to view clusters.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: tenantcontrolplane-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- tenantcontrolplanes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- tenantcontrolplanes/status
|
||||
verbs:
|
||||
- get
|
||||
48
config/samples/kamaji_v1alpha1_tenantcontrolplane.yaml
Normal file
48
config/samples/kamaji_v1alpha1_tenantcontrolplane.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
apiVersion: kamaji.clastix.io/v1alpha1
|
||||
kind: TenantControlPlane
|
||||
metadata:
|
||||
name: test
|
||||
spec:
|
||||
controlPlane:
|
||||
deployment:
|
||||
replicas: 2
|
||||
additionalMetadata:
|
||||
annotations:
|
||||
environment.clastix.io: test
|
||||
tier.clastix.io: "0"
|
||||
labels:
|
||||
tenant.clastix.io: test
|
||||
kind.clastix.io: deployment
|
||||
service:
|
||||
additionalMetadata:
|
||||
annotations:
|
||||
environment.clastix.io: test
|
||||
tier.clastix.io: "0"
|
||||
labels:
|
||||
tenant.clastix.io: test
|
||||
kind.clastix.io: service
|
||||
serviceType: ClusterIP
|
||||
ingress:
|
||||
enabled: true
|
||||
hostname: kamaji.local
|
||||
ingressClassName: nginx
|
||||
additionalMetadata:
|
||||
annotations:
|
||||
kubernetes.io/ingress.allow-http: "false"
|
||||
nginx.ingress.kubernetes.io/secure-backends: "true"
|
||||
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
|
||||
kubernetes:
|
||||
version: "v1.23.1"
|
||||
kubelet:
|
||||
cgroupfs: systemd
|
||||
admissionControllers:
|
||||
- ResourceQuota
|
||||
- LimitRanger
|
||||
networkProfile:
|
||||
address: "127.0.0.1"
|
||||
port: 6443
|
||||
domain: "clastix.labs"
|
||||
serviceCidr: "10.96.0.0/16"
|
||||
podCidr: "10.244.0.0/16"
|
||||
dnsServiceIPs:
|
||||
- "10.96.0.10"
|
||||
4
config/samples/kustomization.yaml
Normal file
4
config/samples/kustomization.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
## Append samples you want in your CSV to this file as resources ##
|
||||
resources:
|
||||
- kamaji_v1alpha1_tenantcontrolplane.yaml
|
||||
#+kubebuilder:scaffold:manifestskustomizesamples
|
||||
7
config/scorecard/bases/config.yaml
Normal file
7
config/scorecard/bases/config.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
apiVersion: scorecard.operatorframework.io/v1alpha3
|
||||
kind: Configuration
|
||||
metadata:
|
||||
name: config
|
||||
stages:
|
||||
- parallel: true
|
||||
tests: []
|
||||
16
config/scorecard/kustomization.yaml
Normal file
16
config/scorecard/kustomization.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
resources:
|
||||
- bases/config.yaml
|
||||
patchesJson6902:
|
||||
- path: patches/basic.config.yaml
|
||||
target:
|
||||
group: scorecard.operatorframework.io
|
||||
version: v1alpha3
|
||||
kind: Configuration
|
||||
name: config
|
||||
- path: patches/olm.config.yaml
|
||||
target:
|
||||
group: scorecard.operatorframework.io
|
||||
version: v1alpha3
|
||||
kind: Configuration
|
||||
name: config
|
||||
#+kubebuilder:scaffold:patchesJson6902
|
||||
10
config/scorecard/patches/basic.config.yaml
Normal file
10
config/scorecard/patches/basic.config.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
- op: add
|
||||
path: /stages/0/tests/-
|
||||
value:
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- basic-check-spec
|
||||
image: quay.io/operator-framework/scorecard-test:v1.13.1
|
||||
labels:
|
||||
suite: basic
|
||||
test: basic-check-spec-test
|
||||
50
config/scorecard/patches/olm.config.yaml
Normal file
50
config/scorecard/patches/olm.config.yaml
Normal file
@@ -0,0 +1,50 @@
|
||||
- op: add
|
||||
path: /stages/0/tests/-
|
||||
value:
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- olm-bundle-validation
|
||||
image: quay.io/operator-framework/scorecard-test:v1.13.1
|
||||
labels:
|
||||
suite: olm
|
||||
test: olm-bundle-validation-test
|
||||
- op: add
|
||||
path: /stages/0/tests/-
|
||||
value:
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- olm-crds-have-validation
|
||||
image: quay.io/operator-framework/scorecard-test:v1.13.1
|
||||
labels:
|
||||
suite: olm
|
||||
test: olm-crds-have-validation-test
|
||||
- op: add
|
||||
path: /stages/0/tests/-
|
||||
value:
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- olm-crds-have-resources
|
||||
image: quay.io/operator-framework/scorecard-test:v1.13.1
|
||||
labels:
|
||||
suite: olm
|
||||
test: olm-crds-have-resources-test
|
||||
- op: add
|
||||
path: /stages/0/tests/-
|
||||
value:
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- olm-spec-descriptors
|
||||
image: quay.io/operator-framework/scorecard-test:v1.13.1
|
||||
labels:
|
||||
suite: olm
|
||||
test: olm-spec-descriptors-test
|
||||
- op: add
|
||||
path: /stages/0/tests/-
|
||||
value:
|
||||
entrypoint:
|
||||
- scorecard-test
|
||||
- olm-status-descriptors
|
||||
image: quay.io/operator-framework/scorecard-test:v1.13.1
|
||||
labels:
|
||||
suite: olm
|
||||
test: olm-status-descriptors-test
|
||||
65
controllers/suite_test.go
Normal file
65
controllers/suite_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
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"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
)
|
||||
|
||||
// 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 // nolint
|
||||
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() {
|
||||
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
|
||||
ErrorIfCRDPathMissing: true,
|
||||
}
|
||||
|
||||
cfg, err := testEnv.Start()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cfg).NotTo(BeNil())
|
||||
|
||||
err = kamajiv1alpha1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
//+kubebuilder:scaffold:scheme
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(k8sClient).NotTo(BeNil())
|
||||
}, 60)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
350
controllers/tenantcontrolplane_controller.go
Normal file
350
controllers/tenantcontrolplane_controller.go
Normal file
@@ -0,0 +1,350 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
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"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/resources"
|
||||
)
|
||||
|
||||
const (
|
||||
separator = ","
|
||||
finalizer = "finalizer.kamaji.clastix.io"
|
||||
)
|
||||
|
||||
// TenantControlPlaneReconciler reconciles a TenantControlPlane object.
|
||||
type TenantControlPlaneReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
Config TenantControlPlaneReconcilerConfig
|
||||
}
|
||||
|
||||
// TenantControlPlaneReconcilerConfig gives the necessary configuration for TenantControlPlaneReconciler.
|
||||
type TenantControlPlaneReconcilerConfig struct {
|
||||
ETCDCASecretName string
|
||||
ETCDCASecretNamespace string
|
||||
ETCDClientSecretName string
|
||||
ETCDClientSecretNamespace string
|
||||
ETCDEndpoints string
|
||||
ETCDCompactionInterval string
|
||||
TmpBaseDirectory string
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=tenantcontrolplanes,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=tenantcontrolplanes/status,verbs=get;update;patch
|
||||
//+kubebuilder:rbac:groups=kamaji.clastix.io,resources=tenantcontrolplanes/finalizers,verbs=update
|
||||
// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
|
||||
|
||||
func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := log.FromContext(ctx)
|
||||
|
||||
tenantControlPlane := &kamajiv1alpha1.TenantControlPlane{}
|
||||
isTenantControlPlane, err := r.getTenantControlPlane(ctx, req.NamespacedName, tenantControlPlane)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if !isTenantControlPlane {
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
markedToBeDeleted := tenantControlPlane.GetDeletionTimestamp() != nil
|
||||
hasFinalizer := hasFinalizer(*tenantControlPlane)
|
||||
|
||||
if markedToBeDeleted && !hasFinalizer {
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
if markedToBeDeleted {
|
||||
registeredDeleteableResources := []resources.DeleteableResource{
|
||||
&resources.ETCDSetupResource{
|
||||
Name: "etcd-setup",
|
||||
Client: r.Client,
|
||||
Scheme: r.Scheme,
|
||||
Log: log,
|
||||
ETCDClientCertsSecret: getNamespacedName(r.Config.ETCDClientSecretNamespace, r.Config.ETCDClientSecretName),
|
||||
ETCDCACertsSecret: getNamespacedName(r.Config.ETCDCASecretNamespace, r.Config.ETCDCASecretName),
|
||||
Endpoints: getArrayFromString(r.Config.ETCDEndpoints),
|
||||
},
|
||||
}
|
||||
|
||||
for _, resource := range registeredDeleteableResources {
|
||||
if err := resource.Delete(ctx, tenantControlPlane); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if hasFinalizer {
|
||||
controllerutil.RemoveFinalizer(tenantControlPlane, finalizer)
|
||||
if err := r.Update(ctx, tenantControlPlane); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
if !hasFinalizer {
|
||||
controllerutil.AddFinalizer(tenantControlPlane, finalizer)
|
||||
if err := r.Update(ctx, tenantControlPlane); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
registeredResources := []resources.Resource{
|
||||
&resources.KubernetesServiceResource{
|
||||
Client: r.Client,
|
||||
},
|
||||
&resources.KubeadmConfigResource{
|
||||
Name: "kubeadmconfig",
|
||||
Port: tenantControlPlane.Spec.NetworkProfile.Port,
|
||||
KubernetesVersion: tenantControlPlane.Spec.Kubernetes.Version,
|
||||
PodCIDR: tenantControlPlane.Spec.NetworkProfile.PodCIDR,
|
||||
ServiceCIDR: tenantControlPlane.Spec.NetworkProfile.ServiceCIDR,
|
||||
Domain: tenantControlPlane.Spec.NetworkProfile.Domain,
|
||||
ETCDs: getArrayFromString(r.Config.ETCDEndpoints),
|
||||
ETCDCompactionInterval: r.Config.ETCDCompactionInterval,
|
||||
Client: r.Client,
|
||||
Scheme: r.Scheme,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(r.Config.TmpBaseDirectory, *tenantControlPlane),
|
||||
},
|
||||
&resources.CACertificate{
|
||||
Name: "ca",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(r.Config.TmpBaseDirectory, *tenantControlPlane),
|
||||
},
|
||||
&resources.FrontProxyCACertificate{
|
||||
Name: "front-proxy-ca-certificate",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(r.Config.TmpBaseDirectory, *tenantControlPlane),
|
||||
},
|
||||
&resources.SACertificate{
|
||||
Name: "sa-certificate",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(r.Config.TmpBaseDirectory, *tenantControlPlane),
|
||||
},
|
||||
&resources.APIServerCertificate{
|
||||
Name: "api-server-certificate",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(r.Config.TmpBaseDirectory, *tenantControlPlane),
|
||||
},
|
||||
&resources.APIServerKubeletClientCertificate{
|
||||
Name: "api-server-kubelet-client-certificate",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(r.Config.TmpBaseDirectory, *tenantControlPlane),
|
||||
},
|
||||
&resources.FrontProxyClientCertificate{
|
||||
Name: "front-proxy-client-certificate",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
TmpDirectory: getTmpDirectory(r.Config.TmpBaseDirectory, *tenantControlPlane),
|
||||
},
|
||||
&resources.KubeconfigResource{
|
||||
Name: "admin-kubeconfig",
|
||||
Client: r.Client,
|
||||
Scheme: r.Scheme,
|
||||
Log: log,
|
||||
KubeConfigFileName: resources.AdminKubeConfigFileName,
|
||||
TmpDirectory: getTmpDirectory(r.Config.TmpBaseDirectory, *tenantControlPlane),
|
||||
},
|
||||
&resources.KubeconfigResource{
|
||||
Name: "controller-manager-kubeconfig",
|
||||
Client: r.Client,
|
||||
Scheme: r.Scheme,
|
||||
Log: log,
|
||||
KubeConfigFileName: resources.ControllerManagerKubeConfigFileName,
|
||||
TmpDirectory: getTmpDirectory(r.Config.TmpBaseDirectory, *tenantControlPlane),
|
||||
},
|
||||
&resources.KubeconfigResource{
|
||||
Name: "scheduler-kubeconfig",
|
||||
Client: r.Client,
|
||||
Scheme: r.Scheme,
|
||||
Log: log,
|
||||
KubeConfigFileName: resources.SchedulerKubeConfigFileName,
|
||||
TmpDirectory: getTmpDirectory(r.Config.TmpBaseDirectory, *tenantControlPlane),
|
||||
},
|
||||
&resources.ETCDCACertificatesResource{
|
||||
Name: "etcd-ca-certificates",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
ETCDCASecretName: r.Config.ETCDCASecretName,
|
||||
ETCDCASecretNamespace: r.Config.ETCDCASecretNamespace,
|
||||
},
|
||||
&resources.ETCDCertificatesResource{
|
||||
Name: "etcd-certificates",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
},
|
||||
&resources.ETCDSetupResource{
|
||||
Name: "etcd-setup",
|
||||
Client: r.Client,
|
||||
Scheme: r.Scheme,
|
||||
Log: log,
|
||||
ETCDClientCertsSecret: getNamespacedName(r.Config.ETCDClientSecretNamespace, r.Config.ETCDClientSecretName),
|
||||
ETCDCACertsSecret: getNamespacedName(r.Config.ETCDCASecretNamespace, r.Config.ETCDCASecretName),
|
||||
Endpoints: getArrayFromString(r.Config.ETCDEndpoints),
|
||||
},
|
||||
&resources.KubernetesUpgrade{
|
||||
Name: "upgrade",
|
||||
Client: r.Client,
|
||||
},
|
||||
&resources.KubernetesDeploymentResource{
|
||||
Client: r.Client,
|
||||
ETCDEndpoints: getArrayFromString(r.Config.ETCDEndpoints),
|
||||
ETCDCompactionInterval: r.Config.ETCDCompactionInterval,
|
||||
},
|
||||
&resources.KubernetesIngressResource{
|
||||
Client: r.Client,
|
||||
},
|
||||
&resources.KubeadmPhaseResource{
|
||||
Name: "upload-config-kubeadm",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
KubeadmPhase: resources.PhaseUploadConfigKubeadm,
|
||||
},
|
||||
&resources.KubeadmPhaseResource{
|
||||
Name: "upload-config-kubelet",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
KubeadmPhase: resources.PhaseUploadConfigKubelet,
|
||||
},
|
||||
&resources.KubeadmPhaseResource{
|
||||
Name: "addon-coredns",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
KubeadmPhase: resources.PhaseAddonCoreDNS,
|
||||
},
|
||||
&resources.KubeadmPhaseResource{
|
||||
Name: "addon-kubeproxy",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
KubeadmPhase: resources.PhaseAddonKubeProxy,
|
||||
},
|
||||
&resources.KubeadmPhaseResource{
|
||||
Name: "bootstrap-token",
|
||||
Client: r.Client,
|
||||
Log: log,
|
||||
KubeadmPhase: resources.PhaseBootstrapToken,
|
||||
},
|
||||
}
|
||||
|
||||
for _, resource := range registeredResources {
|
||||
result, err := resources.Handle(ctx, resource, tenantControlPlane)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if result == controllerutil.OperationResultNone {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := r.updateStatus(ctx, req.NamespacedName, resource); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
log.Info(fmt.Sprintf("%s has been configured", resource.GetName()))
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *TenantControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&kamajiv1alpha1.TenantControlPlane{}).
|
||||
Owns(&corev1.Secret{}).
|
||||
Owns(&corev1.ConfigMap{}).
|
||||
Owns(&appsv1.Deployment{}).
|
||||
Owns(&corev1.Service{}).
|
||||
Owns(&networkingv1.Ingress{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *TenantControlPlaneReconciler) getTenantControlPlane(ctx context.Context, namespacedName k8stypes.NamespacedName, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (bool, error) {
|
||||
if err := r.Client.Get(ctx, namespacedName, tenantControlPlane); err != nil {
|
||||
if !k8serrors.IsNotFound(err) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *TenantControlPlaneReconciler) updateStatus(ctx context.Context, namespacedName k8stypes.NamespacedName, resource resources.Resource) error {
|
||||
tenantControlPlane := &kamajiv1alpha1.TenantControlPlane{}
|
||||
isTenantControlPlane, err := r.getTenantControlPlane(ctx, namespacedName, tenantControlPlane)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isTenantControlPlane {
|
||||
return fmt.Errorf("error updating tenantControlPlane %s: not found", namespacedName.Name)
|
||||
}
|
||||
|
||||
if err := resource.UpdateTenantControlPlaneStatus(ctx, tenantControlPlane); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.Status().Update(ctx, tenantControlPlane); err != nil {
|
||||
return fmt.Errorf("error updating tenantControlPlane status: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getArrayFromString(s string) []string {
|
||||
var a []string
|
||||
a = append(a, strings.Split(s, separator)...)
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func getNamespacedName(namespace string, name string) k8stypes.NamespacedName {
|
||||
return k8stypes.NamespacedName{Namespace: namespace, Name: name}
|
||||
}
|
||||
|
||||
func getTmpDirectory(base string, tenantControlPlane kamajiv1alpha1.TenantControlPlane) string {
|
||||
return fmt.Sprintf("%s/%s/%s", base, tenantControlPlane.GetName(), uuid.New())
|
||||
}
|
||||
|
||||
func hasFinalizer(tenantControlPlane kamajiv1alpha1.TenantControlPlane) bool {
|
||||
for _, f := range tenantControlPlane.GetFinalizers() {
|
||||
if f == finalizer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
142
go.mod
Normal file
142
go.mod
Normal file
@@ -0,0 +1,142 @@
|
||||
module github.com/clastix/kamaji
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v1.2.0
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.17.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.10.1
|
||||
go.etcd.io/etcd/api/v3 v3.5.1
|
||||
go.etcd.io/etcd/client/v3 v3.5.1
|
||||
google.golang.org/grpc v1.43.0
|
||||
k8s.io/api v0.23.5
|
||||
k8s.io/apimachinery v0.23.5
|
||||
k8s.io/client-go v0.23.5
|
||||
k8s.io/cluster-bootstrap v0.0.0
|
||||
k8s.io/component-base v0.23.5
|
||||
k8s.io/kube-proxy v0.0.0
|
||||
k8s.io/kubelet v0.0.0
|
||||
k8s.io/kubernetes v1.23.5
|
||||
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
|
||||
sigs.k8s.io/controller-runtime v0.11.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.99.0 // indirect
|
||||
cloud.google.com/go/storage v1.18.2 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.17 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20211216145620-d92e9ce0af51 // indirect
|
||||
github.com/coredns/caddy v1.1.0 // indirect
|
||||
github.com/coredns/corefile-migration v1.0.14 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.10.1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/go-logr/zapr v1.2.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/spf13/afero v1.7.0 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/cobra v1.4.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.19.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/api v0.63.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
k8s.io/apiextensions-apiserver v0.23.5 // indirect
|
||||
k8s.io/apiserver v0.23.5 // indirect
|
||||
k8s.io/klog/v2 v2.60.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||
k8s.io/system-validators v1.6.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.23.5
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.5
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.23.5
|
||||
k8s.io/apiserver => k8s.io/apiserver v0.23.5
|
||||
k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.5
|
||||
k8s.io/client-go => k8s.io/client-go v0.23.5
|
||||
k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.5
|
||||
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.5
|
||||
k8s.io/code-generator => k8s.io/code-generator v0.23.5
|
||||
k8s.io/component-base => k8s.io/component-base v0.23.5
|
||||
k8s.io/component-helpers => k8s.io/component-helpers v0.23.5
|
||||
k8s.io/controller-manager => k8s.io/controller-manager v0.23.5
|
||||
k8s.io/cri-api => k8s.io/cri-api v0.23.5
|
||||
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.5
|
||||
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.5
|
||||
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.5
|
||||
k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.5
|
||||
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.5
|
||||
k8s.io/kubectl => k8s.io/kubectl v0.23.5
|
||||
k8s.io/kubelet => k8s.io/kubelet v0.23.5
|
||||
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.5
|
||||
k8s.io/metrics => k8s.io/metrics v0.23.5
|
||||
k8s.io/mount-utils => k8s.io/mount-utils v0.23.5
|
||||
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.23.5
|
||||
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.5
|
||||
)
|
||||
2
hack/boilerplate.go.txt
Normal file
2
hack/boilerplate.go.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
15
helm/README.md
Normal file
15
helm/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Deploy with Helm
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
1. Deploy a [multi-tenant Etcd cluster](https://github.com/clastix/kamaji-internal/blob/master/deploy/getting-started-with-kamaji.md#setup-internal-multi-tenant-etcd)
|
||||
|
||||
```
|
||||
make -C ../deploy/etcd
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
helm upgrade --install --namespace kamaji-system --create-namespace kamaji ./kamaji
|
||||
```
|
||||
23
helm/kamaji/.helmignore
Normal file
23
helm/kamaji/.helmignore
Normal file
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
35
helm/kamaji/Chart.yaml
Normal file
35
helm/kamaji/Chart.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
apiVersion: v2
|
||||
name: kamaji
|
||||
description: A Kubernetes distribution aimed to build and operate a Managed Kubernetes service with a fraction of operational burde.
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: 0.1.0
|
||||
|
||||
home: https://github.com/clastix/kamaji-internal/tree/master/helm/kamaji
|
||||
sources: ["https://github.com/clastix/kamaji-internal"]
|
||||
kubeVersion: ">=1.18"
|
||||
maintainers:
|
||||
- email: iam@mendrugory.com
|
||||
name: Gonzalo Gabriel Jiménez Fuentes
|
||||
- email: dario@tranchitella.eu
|
||||
name: Dario Tranchitella
|
||||
- email: me@maxgio.it
|
||||
name: Massimiliano Giovagnoli
|
||||
9
helm/kamaji/Makefile
Normal file
9
helm/kamaji/Makefile
Normal file
@@ -0,0 +1,9 @@
|
||||
docs: HELMDOCS_VERSION := v1.8.1
|
||||
docs: docker
|
||||
@docker run --rm -v "$$(pwd):/helm-docs" -u $$(id -u) jnorwood/helm-docs:$(HELMDOCS_VERSION)
|
||||
|
||||
docker:
|
||||
@hash docker 2>/dev/null || {\
|
||||
echo "You need docker" &&\
|
||||
exit 1;\
|
||||
}
|
||||
100
helm/kamaji/README.md
Normal file
100
helm/kamaji/README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# kamaji
|
||||
|
||||
  
|
||||
|
||||
A Kubernetes distribution aimed to build and operate a Managed Kubernetes service with a fraction of operational burde.
|
||||
|
||||
**Homepage:** <https://github.com/clastix/kamaji-internal/tree/master/helm/kamaji>
|
||||
|
||||
## Installing the Chart
|
||||
|
||||
To install the chart with the release name `kamaji`:
|
||||
|
||||
### Pre-requisites
|
||||
|
||||
1. Deploy a [multi-tenant Etcd cluster](https://github.com/clastix/kamaji-internal/blob/master/deploy/getting-started-with-kamaji.md#setup-internal-multi-tenant-etcd)
|
||||
2. Create the `Secret` containing the Etcd CA cert keypair:
|
||||
|
||||
```
|
||||
kubectl -n kamaji-system create secret generic etcd-certs \
|
||||
--from-file=/path/to/etcd/ca.crt \
|
||||
--from-file=/path/to/etcd/ca.key
|
||||
```
|
||||
|
||||
3. Create a `Secret` containing the Etcd root user client cert keypair:
|
||||
|
||||
```
|
||||
kubectl -n kamaji-system create secret tls root-client-certs \
|
||||
--cert=/path/to/etcd/root.pem \
|
||||
--key=/path/to/etcd/root-key.pem
|
||||
```
|
||||
|
||||
### Install Kamaji
|
||||
|
||||
```console
|
||||
helm upgrade --install --namespace kamaji-system --create-namespace kamaji .
|
||||
```
|
||||
|
||||
## Maintainers
|
||||
|
||||
| Name | Email | Url |
|
||||
| ---- | ------ | --- |
|
||||
| Gonzalo Gabriel Jiménez Fuentes | <iam@mendrugory.com> | |
|
||||
| Dario Tranchitella | <dario@tranchitella.eu> | |
|
||||
| Massimiliano Giovagnoli | <me@maxgio.it> | |
|
||||
|
||||
## Source Code
|
||||
|
||||
* <https://github.com/clastix/kamaji-internal>
|
||||
|
||||
## Requirements
|
||||
|
||||
Kubernetes: `>=1.18`
|
||||
|
||||
## Values
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| affinity | object | `{}` | Kubernetes affinity rules to apply to Kamaji controller pods |
|
||||
| configPath | string | `"./kamaji.yaml"` | Configuration file path alternative. (default "./kamaji.yaml") |
|
||||
| etcd.caSecret.name | string | `"etcd-certs"` | Name of the secret which contains CA's certificate and private key. (default: "etcd-certs") |
|
||||
| etcd.caSecret.namespace | string | `"kamaji-system"` | Namespace of the secret which contains CA's certificate and private key. (default: "kamaji") |
|
||||
| etcd.clientSecret.name | string | `"root-client-certs"` | Name of the secret which contains ETCD client certificates. (default: "root-client-certs") |
|
||||
| etcd.clientSecret.namespace | string | `"kamaji-system"` | Name of the namespace where the secret which contains ETCD client certificates is. (default: "kamaji") |
|
||||
| etcd.compactionInterval | int | `0` | ETCD Compaction interval (e.g. "5m0s"). (default: "0" (disabled)) |
|
||||
| etcd.endpoints | string | `"etcd-0.etcd.kamaji-system.svc.cluster.local:2379,etcd-1.etcd.kamaji-system.svc.cluster.local:2379,etcd-2.etcd.kamaji-system.svc.cluster.local:2379"` | (string) Comma-separated list of the endpoints of the etcd cluster's members. |
|
||||
| extraArgs | list | `[]` | A list of extra arguments to add to the kamaji controller default ones |
|
||||
| fullnameOverride | string | `""` | |
|
||||
| healthProbeBindAddress | string | `":8081"` | The address the probe endpoint binds to. (default ":8081") |
|
||||
| image.pullPolicy | string | `"IfNotPresent"` | |
|
||||
| image.repository | string | `"quay.io/clastix/kamaji"` | The container image of the Kamaji controller. |
|
||||
| image.tag | string | `"latest"` | |
|
||||
| imagePullSecrets | list | `[]` | |
|
||||
| ingress.annotations | object | `{}` | |
|
||||
| ingress.className | string | `""` | Name of the ingress class to route through this controller. |
|
||||
| ingress.enabled | bool | `false` | Whether to expose the Kamaji controller through an Ingress. |
|
||||
| ingress.hosts[0].host | string | `"chart-example.local"` | |
|
||||
| ingress.hosts[0].paths[0].path | string | `"/"` | |
|
||||
| ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | |
|
||||
| ingress.tls | list | `[]` | |
|
||||
| livenessProbe | object | `{"httpGet":{"path":"/healthz","port":"healthcheck"},"initialDelaySeconds":15,"periodSeconds":20}` | The livenessProbe for the controller container |
|
||||
| loggingDevel.enable | bool | `false` | (string) Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) (default false) |
|
||||
| metricsBindAddress | string | `":8080"` | (string) The address the metric endpoint binds to. (default ":8080") |
|
||||
| nameOverride | string | `""` | |
|
||||
| nodeSelector | object | `{}` | Kubernetes node selector rules to schedule Kamaji controller |
|
||||
| podAnnotations | object | `{}` | The annotations to apply to the Kamaji controller pods. |
|
||||
| podSecurityContext | object | `{"runAsNonRoot":true}` | The securityContext to apply to the Kamaji controller pods. |
|
||||
| readinessProbe | object | `{"httpGet":{"path":"/readyz","port":"healthcheck"},"initialDelaySeconds":5,"periodSeconds":10}` | The readinessProbe for the controller container |
|
||||
| replicaCount | int | `1` | The number of the pod replicas for the Kamaji controller. |
|
||||
| resources.limits.cpu | string | `"200m"` | |
|
||||
| resources.limits.memory | string | `"100Mi"` | |
|
||||
| resources.requests.cpu | string | `"100m"` | |
|
||||
| resources.requests.memory | string | `"20Mi"` | |
|
||||
| securityContext | object | `{"allowPrivilegeEscalation":false}` | The securityContext to apply to the Kamaji controller container only. It does not apply to the Kamaji RBAC proxy container. |
|
||||
| service.port | int | `8443` | |
|
||||
| service.type | string | `"ClusterIP"` | |
|
||||
| serviceAccount.annotations | object | `{}` | |
|
||||
| serviceAccount.create | bool | `true` | |
|
||||
| serviceAccount.name | string | `"kamaji-controller-manager"` | |
|
||||
| temporaryDirectoryPath | string | `"/tmp/kamaji"` | Directory which will be used to work with temporary files. (default "/tmp/kamaji") |
|
||||
| tolerations | list | `[]` | Kubernetes node taints that the Kamaji controller pods would tolerate |
|
||||
45
helm/kamaji/README.md.gotmpl
Normal file
45
helm/kamaji/README.md.gotmpl
Normal file
@@ -0,0 +1,45 @@
|
||||
{{ template "chart.header" . }}
|
||||
{{ template "chart.deprecationWarning" . }}
|
||||
|
||||
{{ template "chart.badgesSection" . }}
|
||||
|
||||
{{ template "chart.description" . }}
|
||||
|
||||
{{ template "chart.homepageLine" . }}
|
||||
|
||||
## Installing the Chart
|
||||
|
||||
To install the chart with the release name `kamaji`:
|
||||
|
||||
### Pre-requisites
|
||||
|
||||
1. Deploy a [multi-tenant Etcd cluster](https://github.com/clastix/kamaji-internal/blob/master/deploy/getting-started-with-kamaji.md#setup-internal-multi-tenant-etcd)
|
||||
2. Create the `Secret` containing the Etcd CA cert keypair:
|
||||
|
||||
```
|
||||
kubectl -n kamaji-system create secret generic etcd-certs \
|
||||
--from-file=/path/to/etcd/ca.crt \
|
||||
--from-file=/path/to/etcd/ca.key
|
||||
```
|
||||
|
||||
3. Create a `Secret` containing the Etcd root user client cert keypair:
|
||||
|
||||
```
|
||||
kubectl -n kamaji-system create secret tls root-client-certs \
|
||||
--cert=/path/to/etcd/root.pem \
|
||||
--key=/path/to/etcd/root-key.pem
|
||||
```
|
||||
|
||||
### Install Kamaji
|
||||
|
||||
```console
|
||||
helm upgrade --install --namespace kamaji-system --create-namespace kamaji .
|
||||
```
|
||||
|
||||
{{ template "chart.maintainersSection" . }}
|
||||
|
||||
{{ template "chart.sourcesSection" . }}
|
||||
|
||||
{{ template "chart.requirementsSection" . }}
|
||||
|
||||
{{ template "chart.valuesSection" . }}
|
||||
716
helm/kamaji/crds/tenantcontrolplane.yaml
Normal file
716
helm/kamaji/crds/tenantcontrolplane.yaml
Normal file
@@ -0,0 +1,716 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.6.1
|
||||
creationTimestamp: null
|
||||
name: tenantcontrolplanes.kamaji.clastix.io
|
||||
spec:
|
||||
group: kamaji.clastix.io
|
||||
names:
|
||||
kind: TenantControlPlane
|
||||
listKind: TenantControlPlaneList
|
||||
plural: tenantcontrolplanes
|
||||
shortNames:
|
||||
- tcp
|
||||
singular: tenantcontrolplane
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: Kubernetes version
|
||||
jsonPath: .spec.kubernetes.version
|
||||
name: Version
|
||||
type: string
|
||||
- description: Tenant Control Plane Endpoint (API server)
|
||||
jsonPath: .status.controlPlaneEndpoint
|
||||
name: Control-Plane-Endpoint
|
||||
type: string
|
||||
- description: Secret which contains admin kubeconfig
|
||||
jsonPath: .status.kubeconfig.admin.secretName
|
||||
name: Kubeconfig
|
||||
type: string
|
||||
- description: Age
|
||||
jsonPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: TenantControlPlane is the Schema for the tenantcontrolplanes 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: TenantControlPlaneSpec defines the desired state of TenantControlPlane
|
||||
properties:
|
||||
controlPlane:
|
||||
description: ControlPlane defines how the Tenant Control Plane Kubernetes resources must be created in the Admin Cluster, such as the number of Pod replicas, the Service resource, or the Ingress.
|
||||
properties:
|
||||
deployment:
|
||||
description: Defining the options for the deployed Tenant Control Plane as Deployment resource.
|
||||
properties:
|
||||
additionalMetadata:
|
||||
description: AdditionalMetadata defines which additional metadata, such as labels and annotations, must be attached to the created resource.
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
replicas:
|
||||
default: 2
|
||||
format: int32
|
||||
type: integer
|
||||
type: object
|
||||
ingress:
|
||||
description: Defining the options for an Optional Ingress which will expose API Server of the Tenant Control Plane
|
||||
properties:
|
||||
additionalMetadata:
|
||||
description: AdditionalMetadata defines which additional metadata, such as labels and annotations, must be attached to the created resource.
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
enabled:
|
||||
type: boolean
|
||||
hostname:
|
||||
description: Hostname is an optional field which will be used as Ingress's Host. If it is not defined, Ingress's host will be "<tenant>.<namespace>.<domain>", where domain is specified under NetworkProfileSpec
|
||||
type: string
|
||||
ingressClassName:
|
||||
type: string
|
||||
required:
|
||||
- enabled
|
||||
type: object
|
||||
service:
|
||||
description: Defining the options for the Tenant Control Plane Service resource.
|
||||
properties:
|
||||
additionalMetadata:
|
||||
description: AdditionalMetadata defines which additional metadata, such as labels and annotations, must be attached to the created resource.
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
serviceType:
|
||||
description: ServiceType allows specifying how to expose the Tenant Control Plane.
|
||||
enum:
|
||||
- ClusterIP
|
||||
- NodePort
|
||||
- LoadBalancer
|
||||
type: string
|
||||
required:
|
||||
- serviceType
|
||||
type: object
|
||||
required:
|
||||
- service
|
||||
type: object
|
||||
kubernetes:
|
||||
description: Kubernetes specification for tenant control plane
|
||||
properties:
|
||||
admissionControllers:
|
||||
default:
|
||||
- CertificateApproval
|
||||
- CertificateSigning
|
||||
- CertificateSubjectRestriction
|
||||
- DefaultIngressClass
|
||||
- DefaultStorageClass
|
||||
- DefaultTolerationSeconds
|
||||
- LimitRanger
|
||||
- MutatingAdmissionWebhook
|
||||
- NamespaceLifecycle
|
||||
- PersistentVolumeClaimResize
|
||||
- Priority
|
||||
- ResourceQuota
|
||||
- RuntimeClass
|
||||
- ServiceAccount
|
||||
- StorageObjectInUseProtection
|
||||
- TaintNodesByCondition
|
||||
- ValidatingAdmissionWebhook
|
||||
description: 'List of enabled Admission Controllers for the Tenant cluster. Full reference available here: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers'
|
||||
items:
|
||||
enum:
|
||||
- AlwaysAdmit
|
||||
- AlwaysDeny
|
||||
- AlwaysPullImages
|
||||
- CertificateApproval
|
||||
- CertificateSigning
|
||||
- CertificateSubjectRestriction
|
||||
- DefaultIngressClass
|
||||
- DefaultStorageClass
|
||||
- DefaultTolerationSeconds
|
||||
- DenyEscalatingExec
|
||||
- DenyExecOnPrivileged
|
||||
- DenyServiceExternalIPs
|
||||
- EventRateLimit
|
||||
- ExtendedResourceToleration
|
||||
- ImagePolicyWebhook
|
||||
- LimitPodHardAntiAffinityTopology
|
||||
- LimitRanger
|
||||
- MutatingAdmissionWebhook
|
||||
- NamespaceAutoProvision
|
||||
- NamespaceExists
|
||||
- NamespaceLifecycle
|
||||
- NodeRestriction
|
||||
- OwnerReferencesPermissionEnforcement
|
||||
- PersistentVolumeClaimResize
|
||||
- PersistentVolumeLabel
|
||||
- PodNodeSelector
|
||||
- PodSecurity
|
||||
- PodSecurityPolicy
|
||||
- PodTolerationRestriction
|
||||
- Priority
|
||||
- ResourceQuota
|
||||
- RuntimeClass
|
||||
- SecurityContextDeny
|
||||
- ServiceAccount
|
||||
- StorageObjectInUseProtection
|
||||
- TaintNodesByCondition
|
||||
- ValidatingAdmissionWebhook
|
||||
type: string
|
||||
type: array
|
||||
kubelet:
|
||||
properties:
|
||||
cgroupfs:
|
||||
description: CGroupFS defines the cgroup driver for Kubelet https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/
|
||||
enum:
|
||||
- systemd
|
||||
- cgroupfs
|
||||
type: string
|
||||
type: object
|
||||
version:
|
||||
description: Kubernetes Version for the tenant control plane
|
||||
type: string
|
||||
required:
|
||||
- kubelet
|
||||
- version
|
||||
type: object
|
||||
networkProfile:
|
||||
description: NetworkProfile specifies how the network is
|
||||
properties:
|
||||
address:
|
||||
description: Address where API server of will be exposed
|
||||
type: string
|
||||
dnsServiceIPs:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
domain:
|
||||
description: Domain of the tenant control plane
|
||||
type: string
|
||||
podCidr:
|
||||
description: CIDR for Kubernetes Pods
|
||||
type: string
|
||||
port:
|
||||
default: 6443
|
||||
description: Port where API server of will be exposed
|
||||
format: int32
|
||||
type: integer
|
||||
serviceCidr:
|
||||
description: Kubernetes Service
|
||||
type: string
|
||||
required:
|
||||
- address
|
||||
- dnsServiceIPs
|
||||
- domain
|
||||
- podCidr
|
||||
- port
|
||||
- serviceCidr
|
||||
type: object
|
||||
required:
|
||||
- controlPlane
|
||||
- kubernetes
|
||||
type: object
|
||||
status:
|
||||
description: TenantControlPlaneStatus defines the observed state of TenantControlPlane
|
||||
properties:
|
||||
certificates:
|
||||
description: Certificates contains information about the different certificates that are necessary to run a kubernetes control plane
|
||||
properties:
|
||||
apiServer:
|
||||
description: CertificatePrivateKeyPair defines the status
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
apiServerKubeletClient:
|
||||
description: CertificatePrivateKeyPair defines the status
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
ca:
|
||||
description: CertificatePrivateKeyPair defines the status
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
etcd:
|
||||
description: ETCDAPIServerCertificate defines the observed state of ETCD Certificate for API server
|
||||
properties:
|
||||
apiServer:
|
||||
description: ETCDAPIServerCertificate defines the observed state of ETCD Certificate for API server
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
ca:
|
||||
description: ETCDAPIServerCertificate defines the observed state of ETCD Certificate for API server
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
frontProxyCA:
|
||||
description: CertificatePrivateKeyPair defines the status
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
frontProxyClient:
|
||||
description: CertificatePrivateKeyPair defines the status
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
sa:
|
||||
description: CertificatePrivateKeyPair defines the status
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
controlPlaneEndpoint:
|
||||
description: ControlPlaneEndpoint contains the status of the kubernetes control plane
|
||||
type: string
|
||||
kubeadmPhase:
|
||||
description: KubeadmPhase contains the status of the kubeadm phases action
|
||||
properties:
|
||||
addonCoreDNS:
|
||||
description: KubeadmPhasesStatus contains the status of of a kubeadm phase action
|
||||
properties:
|
||||
kubeadmConfigResourceVersion:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
addonKubeProxy:
|
||||
description: KubeadmPhasesStatus contains the status of of a kubeadm phase action
|
||||
properties:
|
||||
kubeadmConfigResourceVersion:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
bootstrapToken:
|
||||
description: KubeadmPhasesStatus contains the status of of a kubeadm phase action
|
||||
properties:
|
||||
kubeadmConfigResourceVersion:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
uploadConfigKubeadm:
|
||||
description: KubeadmPhasesStatus contains the status of of a kubeadm phase action
|
||||
properties:
|
||||
kubeadmConfigResourceVersion:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
uploadConfigKubelet:
|
||||
description: KubeadmPhasesStatus contains the status of of a kubeadm phase action
|
||||
properties:
|
||||
kubeadmConfigResourceVersion:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- addonCoreDNS
|
||||
- addonKubeProxy
|
||||
- bootstrapToken
|
||||
- uploadConfigKubeadm
|
||||
- uploadConfigKubelet
|
||||
type: object
|
||||
kubeadmconfig:
|
||||
description: KubeadmConfig contains the status of the configuration required by kubeadm
|
||||
properties:
|
||||
configmapName:
|
||||
type: string
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
resourceVersion:
|
||||
type: string
|
||||
required:
|
||||
- resourceVersion
|
||||
type: object
|
||||
kubeconfig:
|
||||
description: KubeConfig contains information about the kubenconfigs that control plane pieces need
|
||||
properties:
|
||||
admin:
|
||||
description: TenantControlPlaneKubeconfigsStatus contains information about a the generated kubeconfig
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
controlerManager:
|
||||
description: TenantControlPlaneKubeconfigsStatus contains information about a the generated kubeconfig
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
scheduler:
|
||||
description: TenantControlPlaneKubeconfigsStatus contains information about a the generated kubeconfig
|
||||
properties:
|
||||
lastUpdate:
|
||||
format: date-time
|
||||
type: string
|
||||
secretName:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
kubernetesResources:
|
||||
description: KubernetesResources contains information about the reconciliation of the required Kubernetes resources deployed in the admin cluster
|
||||
properties:
|
||||
deployment:
|
||||
description: KubernetesDeploymentStatus defines the status for the Tenant Control Plane Deployment in the management cluster.
|
||||
properties:
|
||||
availableReplicas:
|
||||
description: Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.
|
||||
format: int32
|
||||
type: integer
|
||||
collisionCount:
|
||||
description: Count of hash collisions for the Deployment. The Deployment controller uses this field as a collision avoidance mechanism when it needs to create the name for the newest ReplicaSet.
|
||||
format: int32
|
||||
type: integer
|
||||
conditions:
|
||||
description: Represents the latest available observations of a deployment's current state.
|
||||
items:
|
||||
description: DeploymentCondition describes the state of a deployment at a certain point.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: Last time the condition transitioned from one status to another.
|
||||
format: date-time
|
||||
type: string
|
||||
lastUpdateTime:
|
||||
description: The last time this condition was updated.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating details about the transition.
|
||||
type: string
|
||||
reason:
|
||||
description: The reason for the condition's last transition.
|
||||
type: string
|
||||
status:
|
||||
description: Status of the condition, one of True, False, Unknown.
|
||||
type: string
|
||||
type:
|
||||
description: Type of deployment condition.
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: The name of the Deployment for the given cluster.
|
||||
type: string
|
||||
namespace:
|
||||
description: The namespace which the Deployment for the given cluster is deployed.
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: The generation observed by the deployment controller.
|
||||
format: int64
|
||||
type: integer
|
||||
readyReplicas:
|
||||
description: readyReplicas is the number of pods targeted by this Deployment with a Ready Condition.
|
||||
format: int32
|
||||
type: integer
|
||||
replicas:
|
||||
description: Total number of non-terminated pods targeted by this deployment (their labels match the selector).
|
||||
format: int32
|
||||
type: integer
|
||||
unavailableReplicas:
|
||||
description: Total number of unavailable pods targeted by this deployment. This is the total number of pods that are still required for the deployment to have 100% available capacity. They may either be pods that are running but not yet available or pods that still have not been created.
|
||||
format: int32
|
||||
type: integer
|
||||
updatedReplicas:
|
||||
description: Total number of non-terminated pods targeted by this deployment that have the desired template spec.
|
||||
format: int32
|
||||
type: integer
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
ingress:
|
||||
description: KubernetesIngressStatus defines the status for the Tenant Control Plane Ingress in the management cluster.
|
||||
properties:
|
||||
loadBalancer:
|
||||
description: LoadBalancer contains the current status of the load-balancer.
|
||||
properties:
|
||||
ingress:
|
||||
description: Ingress is a list containing ingress points for the load-balancer. Traffic intended for the service should be sent to these ingress points.
|
||||
items:
|
||||
description: 'LoadBalancerIngress represents the status of a load-balancer ingress point: traffic intended for the service should be sent to an ingress point.'
|
||||
properties:
|
||||
hostname:
|
||||
description: Hostname is set for load-balancer ingress points that are DNS based (typically AWS load-balancers)
|
||||
type: string
|
||||
ip:
|
||||
description: IP is set for load-balancer ingress points that are IP based (typically GCE or OpenStack load-balancers)
|
||||
type: string
|
||||
ports:
|
||||
description: Ports is a list of records of service ports If used, every port defined in the service should have an entry in it
|
||||
items:
|
||||
properties:
|
||||
error:
|
||||
description: 'Error is to record the problem with the service port The format of the error shall comply with the following rules: - built-in error values shall be specified in this file and those shall use CamelCase names - cloud provider specific error values must have names that comply with the format foo.example.com/CamelCase. --- The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
port:
|
||||
description: Port is the port number of the service port of which status is recorded here
|
||||
format: int32
|
||||
type: integer
|
||||
protocol:
|
||||
default: TCP
|
||||
description: 'Protocol is the protocol of the service port of which status is recorded here The supported values are: "TCP", "UDP", "SCTP"'
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
- protocol
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
name:
|
||||
description: The name of the Ingress for the given cluster.
|
||||
type: string
|
||||
namespace:
|
||||
description: The namespace which the Ingress for the given cluster is deployed.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
service:
|
||||
description: KubernetesServiceStatus defines the status for the Tenant Control Plane Service in the management cluster.
|
||||
properties:
|
||||
conditions:
|
||||
description: Current service state
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating details about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
loadBalancer:
|
||||
description: LoadBalancer contains the current status of the load-balancer, if one is present.
|
||||
properties:
|
||||
ingress:
|
||||
description: Ingress is a list containing ingress points for the load-balancer. Traffic intended for the service should be sent to these ingress points.
|
||||
items:
|
||||
description: 'LoadBalancerIngress represents the status of a load-balancer ingress point: traffic intended for the service should be sent to an ingress point.'
|
||||
properties:
|
||||
hostname:
|
||||
description: Hostname is set for load-balancer ingress points that are DNS based (typically AWS load-balancers)
|
||||
type: string
|
||||
ip:
|
||||
description: IP is set for load-balancer ingress points that are IP based (typically GCE or OpenStack load-balancers)
|
||||
type: string
|
||||
ports:
|
||||
description: Ports is a list of records of service ports If used, every port defined in the service should have an entry in it
|
||||
items:
|
||||
properties:
|
||||
error:
|
||||
description: 'Error is to record the problem with the service port The format of the error shall comply with the following rules: - built-in error values shall be specified in this file and those shall use CamelCase names - cloud provider specific error values must have names that comply with the format foo.example.com/CamelCase. --- The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)'
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
port:
|
||||
description: Port is the port number of the service port of which status is recorded here
|
||||
format: int32
|
||||
type: integer
|
||||
protocol:
|
||||
default: TCP
|
||||
description: 'Protocol is the protocol of the service port of which status is recorded here The supported values are: "TCP", "UDP", "SCTP"'
|
||||
type: string
|
||||
required:
|
||||
- port
|
||||
- protocol
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
name:
|
||||
description: The name of the Service for the given cluster.
|
||||
type: string
|
||||
namespace:
|
||||
description: The namespace which the Service for the given cluster is deployed.
|
||||
type: string
|
||||
port:
|
||||
description: The port where the service is running
|
||||
format: int32
|
||||
type: integer
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
- port
|
||||
type: object
|
||||
type: object
|
||||
storage:
|
||||
description: Storage Status contains information about Kubernetes storage system
|
||||
properties:
|
||||
etcd:
|
||||
description: ETCDStatus defines the observed state of ETCDStatus
|
||||
properties:
|
||||
role:
|
||||
properties:
|
||||
exists:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
permissions:
|
||||
items:
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
rangeEnd:
|
||||
type: string
|
||||
type:
|
||||
type: integer
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- exists
|
||||
- name
|
||||
type: object
|
||||
user:
|
||||
properties:
|
||||
exists:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
roles:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- exists
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
22
helm/kamaji/templates/NOTES.txt
Normal file
22
helm/kamaji/templates/NOTES.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "kamaji.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "kamaji.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "kamaji.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "kamaji.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||
{{- end }}
|
||||
63
helm/kamaji/templates/_helpers.tpl
Normal file
63
helm/kamaji/templates/_helpers.tpl
Normal file
@@ -0,0 +1,63 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "kamaji.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "kamaji.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "kamaji.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "kamaji.labels" -}}
|
||||
helm.sh/chart: {{ include "kamaji.chart" . }}
|
||||
{{ include "kamaji.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "kamaji.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "kamaji.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/component: controller-manager
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "kamaji.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "kamaji.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
92
helm/kamaji/templates/controller.yaml
Normal file
92
helm/kamaji/templates/controller.yaml
Normal file
@@ -0,0 +1,92 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "kamaji.fullname" . }}
|
||||
labels:
|
||||
{{- include "kamaji.labels" . | nindent 4 }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "kamaji.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "kamaji.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
serviceAccountName: {{ include "kamaji.serviceAccountName" . }}
|
||||
containers:
|
||||
- args:
|
||||
- --secure-listen-address=0.0.0.0:8443
|
||||
- --upstream=http://127.0.0.1:8080/
|
||||
- --logtostderr=true
|
||||
- --v=10
|
||||
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0
|
||||
name: kube-rbac-proxy
|
||||
ports:
|
||||
- containerPort: 8443
|
||||
name: https
|
||||
protocol: TCP
|
||||
- args:
|
||||
- --config-file={{ .Values.configPath }}
|
||||
- --etcd-ca-secret-name={{ .Values.etcd.caSecret.name }}
|
||||
- --etcd-ca-secret-namespace={{ .Values.etcd.caSecret.namespace }}
|
||||
- --etcd-client-secret-name={{ .Values.etcd.clientSecret.name }}
|
||||
- --etcd-client-secret-namespace={{ .Values.etcd.clientSecret.namespace }}
|
||||
- --etcd-compaction-interval={{ .Values.etcd.compactionInterval }}
|
||||
- --etcd-endpoints={{ .Values.etcd.endpoints }}
|
||||
- --health-probe-bind-address={{ .Values.healthProbeBindAddress }}
|
||||
- --leader-elect
|
||||
- --metrics-bind-address={{ .Values.metricsBindAddress }}
|
||||
- --tmp-directory={{ .Values.temporaryDirectoryPath }}
|
||||
{{- if .Values.loggingDevel.enable }}
|
||||
- --zap-devel
|
||||
{{- end }}
|
||||
{{- with .Values.extraArgs }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
command:
|
||||
- /manager
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- with .Values.livenessProbe }}
|
||||
livenessProbe:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
name: manager
|
||||
ports:
|
||||
- containerPort: 8081
|
||||
name: healthcheck
|
||||
protocol: TCP
|
||||
{{- with .Values.readinessProbe }}
|
||||
readinessProbe:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
terminationGracePeriodSeconds: 10
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
61
helm/kamaji/templates/ingress.yaml
Normal file
61
helm/kamaji/templates/ingress.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "kamaji.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "kamaji.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||
pathType: {{ .pathType }}
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- else }}
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
210
helm/kamaji/templates/rbac.yaml
Normal file
210
helm/kamaji/templates/rbac.yaml
Normal file
@@ -0,0 +1,210 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "kamaji.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "kamaji.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: kamaji-leader-election-role
|
||||
namespace: {{ .Release.Namespace }}
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- create
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- coordination.k8s.io
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- create
|
||||
- update
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: kamaji-manager-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- deployments
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- tenantcontrolplanes
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- tenantcontrolplanes/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- kamaji.clastix.io
|
||||
resources:
|
||||
- tenantcontrolplanes/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: kamaji-metrics-reader
|
||||
rules:
|
||||
- nonResourceURLs:
|
||||
- /metrics
|
||||
verbs:
|
||||
- get
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: kamaji-proxy-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- authentication.k8s.io
|
||||
resources:
|
||||
- tokenreviews
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- authorization.k8s.io
|
||||
resources:
|
||||
- subjectaccessreviews
|
||||
verbs:
|
||||
- create
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: kamaji-leader-election-rolebinding
|
||||
namespace: {{ .Release.Namespace }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: kamaji-leader-election-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "kamaji.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: kamaji-manager-rolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: kamaji-manager-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "kamaji.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: kamaji-proxy-rolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: kamaji-proxy-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "kamaji.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
16
helm/kamaji/templates/service.yaml
Normal file
16
helm/kamaji/templates/service.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "kamaji.fullname" . }}
|
||||
labels:
|
||||
{{- include "kamaji.labels" . | nindent 4 }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- name: https
|
||||
port: {{ .Values.service.port }}
|
||||
protocol: TCP
|
||||
targetPort: https
|
||||
selector:
|
||||
{{- include "kamaji.selectorLabels" . | nindent 4 }}
|
||||
2
helm/kamaji/values.sample.yaml
Normal file
2
helm/kamaji/values.sample.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
etcd:
|
||||
endpoints: "etcd-0.etcd.kamaji-system.svc.cluster.local:2379,etcd-1.etcd.kamaji-system.svc.cluster.local:2379,etcd-2.etcd.kamaji-system.svc.cluster.local:2379"
|
||||
136
helm/kamaji/values.yaml
Normal file
136
helm/kamaji/values.yaml
Normal file
@@ -0,0 +1,136 @@
|
||||
# Default values for kamaji.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
# -- The number of the pod replicas for the Kamaji controller.
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
# -- The container image of the Kamaji controller.
|
||||
repository: quay.io/clastix/kamaji
|
||||
pullPolicy: IfNotPresent
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: latest
|
||||
|
||||
# -- A list of extra arguments to add to the kamaji controller default ones
|
||||
extraArgs: []
|
||||
|
||||
# -- Configuration file path alternative. (default "./kamaji.yaml")
|
||||
configPath: "./kamaji.yaml"
|
||||
|
||||
etcd:
|
||||
caSecret:
|
||||
# -- Name of the secret which contains CA's certificate and private key. (default: "etcd-certs")
|
||||
name: etcd-certs
|
||||
# -- Namespace of the secret which contains CA's certificate and private key. (default: "kamaji")
|
||||
namespace: kamaji-system
|
||||
|
||||
clientSecret:
|
||||
# -- Name of the secret which contains ETCD client certificates. (default: "root-client-certs")
|
||||
name: root-client-certs
|
||||
# -- Name of the namespace where the secret which contains ETCD client certificates is. (default: "kamaji")
|
||||
namespace: kamaji-system
|
||||
|
||||
# -- ETCD Compaction interval (e.g. "5m0s"). (default: "0" (disabled))
|
||||
compactionInterval: 0
|
||||
|
||||
# -- (string) Comma-separated list of the endpoints of the etcd cluster's members.
|
||||
endpoints: "etcd-0.etcd.kamaji-system.svc.cluster.local:2379,etcd-1.etcd.kamaji-system.svc.cluster.local:2379,etcd-2.etcd.kamaji-system.svc.cluster.local:2379"
|
||||
|
||||
# -- The address the probe endpoint binds to. (default ":8081")
|
||||
healthProbeBindAddress: ":8081"
|
||||
|
||||
# -- The livenessProbe for the controller container
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: healthcheck
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
|
||||
# -- The readinessProbe for the controller container
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: healthcheck
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
|
||||
# -- (string) The address the metric endpoint binds to. (default ":8080")
|
||||
metricsBindAddress: ":8080"
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: kamaji-controller-manager
|
||||
|
||||
# -- The annotations to apply to the Kamaji controller pods.
|
||||
podAnnotations: {}
|
||||
|
||||
# -- The securityContext to apply to the Kamaji controller pods.
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
|
||||
# -- The securityContext to apply to the Kamaji controller container only. It does not apply to the Kamaji RBAC proxy container.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8443
|
||||
|
||||
ingress:
|
||||
# -- Whether to expose the Kamaji controller through an Ingress.
|
||||
enabled: false
|
||||
# -- Name of the ingress class to route through this controller.
|
||||
className: ""
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 100Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 20Mi
|
||||
|
||||
# -- Kubernetes node selector rules to schedule Kamaji controller
|
||||
nodeSelector: {}
|
||||
|
||||
# -- Kubernetes node taints that the Kamaji controller pods would tolerate
|
||||
tolerations: []
|
||||
|
||||
# -- Kubernetes affinity rules to apply to Kamaji controller pods
|
||||
affinity: {}
|
||||
|
||||
# -- Directory which will be used to work with temporary files. (default "/tmp/kamaji")
|
||||
temporaryDirectoryPath: "/tmp/kamaji"
|
||||
|
||||
loggingDevel:
|
||||
# -- (string) Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) (default false)
|
||||
enable: false
|
||||
123
internal/config/config.go
Normal file
123
internal/config/config.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
config *viper.Viper
|
||||
cfgFile string
|
||||
)
|
||||
|
||||
const (
|
||||
envPrefix = "KAMAJI"
|
||||
defaultETCDCASecretName = "etcd-certs"
|
||||
defaultETCDCASecretNamespace = "kamaji-system"
|
||||
defaultETCDEndpoints = "etcd-server:2379"
|
||||
defaultETCDCompactionInterval = "0"
|
||||
defaultETCDClientSecretName = "root-client-certs"
|
||||
defaultETCDClientSecretNamespace = "kamaji-system"
|
||||
defaultTmpDirectory = "/tmp/kamaji"
|
||||
)
|
||||
|
||||
func InitConfig() (*viper.Viper, error) {
|
||||
config = viper.New()
|
||||
|
||||
// Setup flags with standard "flag" module
|
||||
flag.StringVar(&cfgFile, "config-file", "kamaji.yaml", "Configuration file alternative.")
|
||||
flag.String("metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
|
||||
flag.String("health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
|
||||
flag.Bool("leader-elect", false, "Enable leader election for controller manager. "+
|
||||
"Enabling this will ensure there is only one active controller manager.")
|
||||
flag.String("etcd-ca-secret-name", defaultETCDCASecretName, "Name of the secret which contains CA's certificate and private key.")
|
||||
flag.String("etcd-ca-secret-namespace", defaultETCDCASecretNamespace, "Namespace of the secret which contains CA's certificate and private key.")
|
||||
flag.String("etcd-client-secret-name", defaultETCDClientSecretName, "Name of the secret which contains ETCD client certificates")
|
||||
flag.String("etcd-client-secret-namespace", defaultETCDClientSecretNamespace, "Name of the namespace where the secret which contains ETCD client certificates is")
|
||||
flag.String("etcd-endpoints", defaultETCDEndpoints, "Comma-separated list with ETCD endpoints (i.e. etcd-0.etcd.kamaji-system.svc.cluster.local,etcd-1.etcd.kamaji-system.svc.cluster.local,etcd-2.etcd.kamaji-system.svc.cluster.local)")
|
||||
flag.String("etcd-compaction-interval", defaultETCDCompactionInterval, "ETCD Compaction interval (i.e. \"5m0s\"). (default: \"0\" (disabled))")
|
||||
flag.String("tmp-directory", defaultTmpDirectory, "Directory which will be used to work with temporary files.")
|
||||
|
||||
// Setup zap configuration
|
||||
opts := zap.Options{
|
||||
Development: true,
|
||||
}
|
||||
opts.BindFlags(flag.CommandLine)
|
||||
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
|
||||
|
||||
// Add flag set to pflag in order to parse with viper
|
||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||
pflag.Parse()
|
||||
if err := config.BindPFlags(pflag.CommandLine); err != nil {
|
||||
log.Printf("error binding flags: %s", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Setup environment variables
|
||||
if err := config.BindEnv("metrics-bind-address", fmt.Sprintf("%s_METRICS_BIND_ADDRESS", envPrefix)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.BindEnv("health-probe-bind-address", fmt.Sprintf("%s_HEALTH_PROBE_BIND_ADDRESS", envPrefix)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.BindEnv("leader-elect", fmt.Sprintf("%s_LEADER_ELECTION", envPrefix)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.BindEnv("etcd-ca-secret-name", fmt.Sprintf("%s_ETCD_CA_SECRET_NAME", envPrefix)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.BindEnv("etcd-ca-secret-namespace", fmt.Sprintf("%s_ETCD_CA_SECRET_NAMESPACE", envPrefix)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.BindEnv("etcd-client-secret-name", fmt.Sprintf("%s_ETCD_CLIENT_SECRET_NAME", envPrefix)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.BindEnv("etcd-client-secret-namespace", fmt.Sprintf("%s_ETCD_CLIENT_SECRET_NAMESPACE", envPrefix)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.BindEnv("etcd-endpoints", fmt.Sprintf("%s_ETCD_ENDPOINTS", envPrefix)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.BindEnv("etcd-compaction-interval", fmt.Sprintf("%s_ETCD_COMPACTION_INTERVAL", envPrefix)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := config.BindEnv("tmp-directory", fmt.Sprintf("%s_TMP_DIRECTORY", envPrefix)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Setup config file
|
||||
if cfgFile != "" {
|
||||
// Using flag-passed config file
|
||||
config.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Using default config file
|
||||
config.AddConfigPath(".")
|
||||
config.SetConfigName("kamaji")
|
||||
}
|
||||
config.SetConfigType("yaml")
|
||||
if err := config.ReadInConfig(); err != nil {
|
||||
if errors.Is(err, &viper.ConfigParseError{}) {
|
||||
log.Printf("error parsing config file: %v", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
log.Println("No config file used")
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Using config file: %v", viper.ConfigFileUsed())
|
||||
|
||||
return config, nil
|
||||
}
|
||||
9
internal/constants/labels.go
Normal file
9
internal/constants/labels.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package constants
|
||||
|
||||
const (
|
||||
ProjectNameLabelKey = "kamaji.clastix.io/project"
|
||||
ProjectNameLabelValue = "kamaji"
|
||||
)
|
||||
125
internal/crypto/crypto.go
Normal file
125
internal/crypto/crypto.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetCertificate(cert []byte) (*x509.Certificate, error) {
|
||||
pemContent, _ := pem.Decode(cert)
|
||||
if pemContent == nil {
|
||||
return nil, fmt.Errorf("no right PEM block")
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(pemContent.Bytes)
|
||||
}
|
||||
|
||||
func GetPrivateKey(privKey []byte) (*rsa.PrivateKey, error) {
|
||||
pemContent, _ := pem.Decode(privKey)
|
||||
if pemContent == nil {
|
||||
return nil, fmt.Errorf("no right PEM block")
|
||||
}
|
||||
|
||||
return x509.ParsePKCS1PrivateKey(pemContent.Bytes)
|
||||
}
|
||||
|
||||
func GetPublickKey(pubKey []byte) (*rsa.PublicKey, error) {
|
||||
pemContent, _ := pem.Decode(pubKey)
|
||||
if pemContent == nil {
|
||||
return nil, fmt.Errorf("no right PEM block")
|
||||
}
|
||||
|
||||
pub, err := x509.ParsePKIXPublicKey(pemContent.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pub.(*rsa.PublicKey), nil // nolint:forcetypeassert
|
||||
}
|
||||
|
||||
func GenerateCertificateKeyPairBytes(template *x509.Certificate, bitSize int, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*bytes.Buffer, *bytes.Buffer, error) {
|
||||
certPrivKey, err := rsa.GenerateKey(rand.Reader, bitSize)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, template, caCert, &certPrivKey.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
certPEM := &bytes.Buffer{}
|
||||
if err := pem.Encode(certPEM, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certBytes,
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
certPrivKeyPEM := &bytes.Buffer{}
|
||||
if err := pem.Encode(certPrivKeyPEM, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return certPEM, certPrivKeyPEM, nil
|
||||
}
|
||||
|
||||
func IsValidKeyPairBytes(pubKeyBytes []byte, privKeyBytes []byte) (bool, error) {
|
||||
privKey, err := GetPrivateKey(privKeyBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
pubKey, err := GetPublickKey(pubKeyBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return checkPublicKeys(privKey.PublicKey, *pubKey), nil
|
||||
}
|
||||
|
||||
func IsValidCertificateKeyPairBytes(certBytes []byte, privKeyBytes []byte) (bool, error) {
|
||||
cert, err := GetCertificate(certBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
privKey, err := GetPrivateKey(privKeyBytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return isValidCertificateKeyPairBytes(*cert, *privKey), nil
|
||||
}
|
||||
|
||||
func isValidCertificateKeyPairBytes(cert x509.Certificate, privKey rsa.PrivateKey) bool {
|
||||
return checkCertificateValidity(cert) && checkCertificateKeyPair(cert, privKey)
|
||||
}
|
||||
|
||||
func checkCertificateValidity(cert x509.Certificate) bool {
|
||||
now := time.Now()
|
||||
|
||||
return now.Before(cert.NotAfter) && now.After(cert.NotBefore)
|
||||
}
|
||||
|
||||
func checkCertificateKeyPair(cert x509.Certificate, privKey rsa.PrivateKey) bool {
|
||||
return checkPublicKeys(*cert.PublicKey.(*rsa.PublicKey), privKey.PublicKey) // nolint:forcetypeassert
|
||||
}
|
||||
|
||||
func checkPublicKeys(a rsa.PublicKey, b rsa.PublicKey) bool {
|
||||
isN := a.N.Cmp(b.N) == 0
|
||||
isE := a.E == b.E
|
||||
|
||||
return isN && isE
|
||||
}
|
||||
177
internal/etcd/api.go
Normal file
177
internal/etcd/api.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/api/v3/authpb"
|
||||
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
|
||||
etcdclient "go.etcd.io/etcd/client/v3"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
const (
|
||||
etcdTimeout = 10 // seconds
|
||||
|
||||
// rangeEnd is the key following the last key of the range.
|
||||
// If rangeEnd is ‘\0’, the range is all keys greater than or equal to the key argument
|
||||
// source: https://etcd.io/docs/v3.5/learning/api/
|
||||
rangeEnd = "\\0"
|
||||
)
|
||||
|
||||
func NewClient(config Config) (*etcdclient.Client, error) {
|
||||
cert, err := tls.X509KeyPair(config.ETCDCertificate, config.ETCDPrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
pool.AppendCertsFromPEM(config.ETCDCA)
|
||||
|
||||
cfg := etcdclient.Config{
|
||||
Endpoints: config.Endpoints,
|
||||
TLS: &tls.Config{ // nolint:gosec
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: pool,
|
||||
},
|
||||
}
|
||||
|
||||
return etcdclient.New(cfg)
|
||||
}
|
||||
|
||||
func GetUser(ctx context.Context, client *etcdclient.Client, user *User) error {
|
||||
ctxWithTimeout, cancel := context.WithTimeout(ctx, etcdTimeout*time.Second)
|
||||
defer cancel()
|
||||
|
||||
response, err := client.UserGet(ctxWithTimeout, user.Name)
|
||||
if err != nil {
|
||||
var etcdError rpctypes.EtcdError
|
||||
if errors.As(err, &etcdError) && etcdError.Code() == codes.FailedPrecondition {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
user.Roles = response.Roles
|
||||
user.Exists = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddUser(ctx context.Context, client *etcdclient.Client, username string) error {
|
||||
ctxWithTimeout, cancel := getContextWithTimeout(ctx)
|
||||
defer cancel()
|
||||
opts := etcdclient.UserAddOptions{
|
||||
NoPassword: true,
|
||||
}
|
||||
_, err := client.Auth.UserAddWithOptions(ctxWithTimeout, username, "", &opts)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func RemoveUser(ctx context.Context, client *etcdclient.Client, username string) error {
|
||||
ctxWithTimeout, cancel := getContextWithTimeout(ctx)
|
||||
defer cancel()
|
||||
|
||||
_, err := client.Auth.UserDelete(ctxWithTimeout, username)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetRole(ctx context.Context, client *etcdclient.Client, role *Role) error {
|
||||
ctxWithTimeout, cancel := getContextWithTimeout(ctx)
|
||||
defer cancel()
|
||||
|
||||
response, err := client.RoleGet(ctxWithTimeout, role.Name)
|
||||
if err != nil {
|
||||
var etcdError rpctypes.EtcdError
|
||||
if errors.As(err, &etcdError) && etcdError.Code() == codes.FailedPrecondition {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
role.Exists = true
|
||||
for _, perm := range response.Perm {
|
||||
permission := Permission{
|
||||
Type: int(perm.PermType),
|
||||
Key: string(perm.Key),
|
||||
RangeEnd: string(perm.RangeEnd),
|
||||
}
|
||||
role.Permissions = append(role.Permissions, permission)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddRole(ctx context.Context, client *etcdclient.Client, roleName string) error {
|
||||
ctxWithTimeout, cancel := getContextWithTimeout(ctx)
|
||||
defer cancel()
|
||||
|
||||
_, err := client.Auth.RoleAdd(ctxWithTimeout, roleName)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func RemoveRole(ctx context.Context, client *etcdclient.Client, roleName string) error {
|
||||
ctxWithTimeout, cancel := getContextWithTimeout(ctx)
|
||||
defer cancel()
|
||||
|
||||
_, err := client.Auth.RoleDelete(ctxWithTimeout, roleName)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GrantUserRole(ctx context.Context, client *etcdclient.Client, user User, role Role) error {
|
||||
ctxWithTimeout, cancel := getContextWithTimeout(ctx)
|
||||
defer cancel()
|
||||
|
||||
_, err := client.UserGrantRole(ctxWithTimeout, user.Name, role.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GrantRolePermission(ctx context.Context, client *etcdclient.Client, role Role) error {
|
||||
ctxWithTimeout, cancel := getContextWithTimeout(ctx)
|
||||
defer cancel()
|
||||
|
||||
permission := etcdclient.PermissionType(authpb.READWRITE)
|
||||
key := BuildKey(role.Name)
|
||||
_, err := client.RoleGrantPermission(ctxWithTimeout, role.Name, key, rangeEnd, permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CleanUpPrefix(ctx context.Context, client *etcdclient.Client, name string) error {
|
||||
ctxWithTimeout, cancel := getContextWithTimeout(ctx)
|
||||
defer cancel()
|
||||
|
||||
withRange := etcdclient.WithRange(rangeEnd)
|
||||
prefix := BuildKey(name)
|
||||
_, err := client.Delete(ctxWithTimeout, prefix, withRange)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func BuildKey(roleName string) string {
|
||||
return fmt.Sprintf("/%s/", roleName)
|
||||
}
|
||||
|
||||
func getContextWithTimeout(ctx context.Context) (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(ctx, etcdTimeout*time.Second)
|
||||
}
|
||||
57
internal/etcd/certs.go
Normal file
57
internal/etcd/certs.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/clastix/kamaji/internal/crypto"
|
||||
)
|
||||
|
||||
func GetETCDCACertificateAndKeyPair(tenant string, caCert []byte, caPrivKey []byte) (*bytes.Buffer, *bytes.Buffer, error) {
|
||||
template := getCertTemplate(tenant)
|
||||
|
||||
caCertBytes, err := crypto.GetCertificate(caCert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
caPrivKeyBytes, err := crypto.GetPrivateKey(caPrivKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return crypto.GenerateCertificateKeyPairBytes(template, certBitSize, caCertBytes, caPrivKeyBytes)
|
||||
}
|
||||
|
||||
func IsETCDCertificateAndKeyPairValid(cert []byte, privKey []byte) (bool, error) {
|
||||
return crypto.IsValidCertificateKeyPairBytes(cert, privKey)
|
||||
}
|
||||
|
||||
func getCertTemplate(tenant string) *x509.Certificate {
|
||||
serialNumber := big.NewInt(rand.Int63())
|
||||
|
||||
return &x509.Certificate{
|
||||
PublicKeyAlgorithm: x509.RSA,
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: tenant,
|
||||
Organization: []string{certOrganization},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(certExpirationDelayYears, 0, 0),
|
||||
SubjectKeyId: []byte{1, 2, 3, 4, 6},
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
x509.ExtKeyUsageCodeSigning,
|
||||
},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
}
|
||||
}
|
||||
10
internal/etcd/constant.go
Normal file
10
internal/etcd/constant.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package etcd
|
||||
|
||||
const (
|
||||
certExpirationDelayYears = 10
|
||||
certOrganization = "system:masters"
|
||||
certBitSize = 2048
|
||||
)
|
||||
71
internal/etcd/types.go
Normal file
71
internal/etcd/types.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package etcd
|
||||
|
||||
type Config struct {
|
||||
ETCDCertificate []byte
|
||||
ETCDPrivateKey []byte
|
||||
ETCDCA []byte
|
||||
Endpoints []string
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
Type int `json:"type,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
RangeEnd string `json:"rangeEnd,omitempty"`
|
||||
}
|
||||
|
||||
func (in *Permission) DeepCopyInto(out *Permission) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
func (in *Permission) DeepCopy() *Permission {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Permission)
|
||||
in.DeepCopyInto(out)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
Name string `json:"name"`
|
||||
Permissions []Permission `json:"permissions,omitempty"`
|
||||
Exists bool `json:"exists"`
|
||||
}
|
||||
|
||||
func (in *Role) DeepCopyInto(out *Role) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
func (in *Role) DeepCopy() *Role {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Role)
|
||||
in.DeepCopyInto(out)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
Exists bool `json:"exists"`
|
||||
}
|
||||
|
||||
func (in *User) DeepCopyInto(out *User) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
func (in *User) DeepCopy() *User {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(User)
|
||||
in.DeepCopyInto(out)
|
||||
|
||||
return out
|
||||
}
|
||||
287
internal/kubeadm/addon.go
Normal file
287
internal/kubeadm/addon.go
Normal file
@@ -0,0 +1,287 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
"k8s.io/component-base/config/v1alpha1"
|
||||
kubeproxyconfig "k8s.io/kube-proxy/config/v1alpha1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
func CoreDNSAddon(client kubernetes.Interface, config *Configuration) error {
|
||||
return dns.EnsureDNSAddon(&config.InitConfiguration.ClusterConfiguration, client)
|
||||
}
|
||||
|
||||
func KubeProxyAddon(client kubernetes.Interface, config *Configuration) error {
|
||||
if err := proxy.CreateServiceAccount(client); err != nil {
|
||||
return errors.Wrap(err, "error when creating kube-proxy service account")
|
||||
}
|
||||
|
||||
if err := createKubeProxyConfigMap(client, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createKubeProxyAddon(client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := proxy.CreateRBACRules(client); err != nil {
|
||||
return errors.Wrap(err, "error when creating kube-proxy RBAC rules")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createKubeProxyConfigMap(client kubernetes.Interface, config *Configuration) error {
|
||||
configConf, err := getKubeproxyConfigmapContent(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kubeconfigConf, err := getKubeproxyKubeconfigContent(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configMap := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: kubeadmconstants.KubeProxyConfigMap,
|
||||
Namespace: "kube-system",
|
||||
Labels: map[string]string{
|
||||
"app": "kube-proxy",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
kubeadmconstants.KubeProxyConfigMapKey: string(configConf),
|
||||
"kubeconfig.conf": string(kubeconfigConf),
|
||||
},
|
||||
}
|
||||
|
||||
return apiclient.CreateOrUpdateConfigMap(client, configMap)
|
||||
}
|
||||
|
||||
func createKubeProxyAddon(client kubernetes.Interface) error {
|
||||
daemonSet := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "kube-proxy",
|
||||
Namespace: "kube-system",
|
||||
Labels: map[string]string{
|
||||
"k8s-app": "kube-proxy",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
RevisionHistoryLimit: pointer.Int32(10),
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"k8s-app": "kube-proxy",
|
||||
},
|
||||
},
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"k8s-app": "kube-proxy",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Command: []string{
|
||||
"/usr/local/bin/kube-proxy",
|
||||
"--config=/var/lib/kube-proxy/config.conf",
|
||||
"--hostname-override=$(NODE_NAME)",
|
||||
},
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "NODE_NAME",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
FieldRef: &corev1.ObjectFieldSelector{
|
||||
APIVersion: "v1",
|
||||
FieldPath: "spec.nodeName",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Image: "k8s.gcr.io/kube-proxy:v1.21.2",
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
Name: "kube-proxy",
|
||||
SecurityContext: &corev1.SecurityContext{
|
||||
Privileged: pointer.Bool(true),
|
||||
},
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
TerminationMessagePolicy: "File",
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
MountPath: "/var/lib/kube-proxy",
|
||||
Name: "kube-proxy",
|
||||
},
|
||||
{
|
||||
MountPath: "/run/xtables.lock",
|
||||
Name: "xtables-lock",
|
||||
},
|
||||
{
|
||||
MountPath: "/lib/modules",
|
||||
Name: "lib-modules",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DNSPolicy: corev1.DNSClusterFirst,
|
||||
HostNetwork: true,
|
||||
NodeSelector: map[string]string{
|
||||
"kubernetes.io/os": "linux",
|
||||
},
|
||||
PriorityClassName: "system-node-critical",
|
||||
RestartPolicy: corev1.RestartPolicyAlways,
|
||||
SchedulerName: "default-scheduler",
|
||||
ServiceAccountName: "kube-proxy",
|
||||
TerminationGracePeriodSeconds: pointer.Int64(30),
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "kube-proxy",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
DefaultMode: pointer.Int32(420),
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "kube-proxy",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "xtables-lock",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
HostPath: &corev1.HostPathVolumeSource{
|
||||
Path: "/run/xtables.lock",
|
||||
Type: (*corev1.HostPathType)(pointer.String(string(corev1.HostPathFileOrCreate))),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "lib-modules",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
HostPath: &corev1.HostPathVolumeSource{
|
||||
Path: "/lib/modules",
|
||||
Type: (*corev1.HostPathType)(pointer.String(string(corev1.HostPathUnset))),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return apiclient.CreateOrUpdateDaemonSet(client, daemonSet)
|
||||
}
|
||||
|
||||
func getKubeproxyConfigmapContent(config *Configuration) ([]byte, error) {
|
||||
zeroDuration := metav1.Duration{Duration: 0}
|
||||
oneSecondDuration := metav1.Duration{Duration: time.Second}
|
||||
kubeProxyConfiguration := kubeproxyconfig.KubeProxyConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "KubeProxyConfiguration",
|
||||
APIVersion: "kubeproxy.config.k8s.io/v1alpha1",
|
||||
},
|
||||
BindAddress: "0.0.0.0",
|
||||
BindAddressHardFail: false,
|
||||
ClientConnection: v1alpha1.ClientConnectionConfiguration{
|
||||
AcceptContentTypes: "",
|
||||
Burst: 0,
|
||||
ContentType: "",
|
||||
Kubeconfig: "/var/lib/kube-proxy/kubeconfig.conf",
|
||||
QPS: 0,
|
||||
},
|
||||
ClusterCIDR: config.Parameters.TenantControlPlanePodCIDR,
|
||||
ConfigSyncPeriod: zeroDuration,
|
||||
Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
|
||||
MaxPerCore: pointer.Int32(0),
|
||||
Min: nil,
|
||||
TCPCloseWaitTimeout: nil,
|
||||
TCPEstablishedTimeout: nil,
|
||||
},
|
||||
DetectLocalMode: "",
|
||||
EnableProfiling: false,
|
||||
HealthzBindAddress: "",
|
||||
HostnameOverride: "",
|
||||
IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
|
||||
MasqueradeAll: false,
|
||||
MasqueradeBit: nil,
|
||||
MinSyncPeriod: oneSecondDuration,
|
||||
SyncPeriod: zeroDuration,
|
||||
},
|
||||
IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{
|
||||
ExcludeCIDRs: nil,
|
||||
MinSyncPeriod: zeroDuration,
|
||||
Scheduler: "",
|
||||
StrictARP: false,
|
||||
SyncPeriod: zeroDuration,
|
||||
TCPTimeout: zeroDuration,
|
||||
TCPFinTimeout: zeroDuration,
|
||||
UDPTimeout: zeroDuration,
|
||||
},
|
||||
MetricsBindAddress: "",
|
||||
Mode: "iptables",
|
||||
NodePortAddresses: nil,
|
||||
OOMScoreAdj: nil,
|
||||
PortRange: "",
|
||||
ShowHiddenMetricsForVersion: "",
|
||||
UDPIdleTimeout: zeroDuration,
|
||||
Winkernel: kubeproxyconfig.KubeProxyWinkernelConfiguration{
|
||||
EnableDSR: false,
|
||||
NetworkName: "",
|
||||
SourceVip: "",
|
||||
},
|
||||
}
|
||||
|
||||
return EncondeToYaml(&kubeProxyConfiguration)
|
||||
}
|
||||
|
||||
func getKubeproxyKubeconfigContent(config *Configuration) ([]byte, error) {
|
||||
kubeconfig := clientcmdapi.Config{
|
||||
APIVersion: "v1",
|
||||
Kind: "Config",
|
||||
Clusters: []clientcmdapi.NamedCluster{
|
||||
{
|
||||
Name: "default",
|
||||
Cluster: clientcmdapi.Cluster{
|
||||
CertificateAuthority: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
|
||||
Server: fmt.Sprintf("https://%s:%d", config.Parameters.TenantControlPlaneAddress, config.Parameters.TenantControlPlanePort),
|
||||
},
|
||||
},
|
||||
},
|
||||
Contexts: []clientcmdapi.NamedContext{
|
||||
{
|
||||
Context: clientcmdapi.Context{
|
||||
Cluster: "default",
|
||||
Namespace: "default",
|
||||
AuthInfo: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
AuthInfos: []clientcmdapi.NamedAuthInfo{
|
||||
{
|
||||
Name: "default",
|
||||
AuthInfo: clientcmdapi.AuthInfo{
|
||||
TokenFile: "/var/run/secrets/kubernetes.io/serviceaccount/token",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return EncondeToYaml(&kubeconfig)
|
||||
}
|
||||
73
internal/kubeadm/bootstraptoken.go
Normal file
73
internal/kubeadm/bootstraptoken.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
)
|
||||
|
||||
func BootstrapToken(client kubernetes.Interface, config *Configuration) error {
|
||||
initConfiguration := config.InitConfiguration
|
||||
|
||||
if err := node.UpdateOrCreateTokens(client, false, initConfiguration.BootstrapTokens); err != nil {
|
||||
return errors.Wrap(err, "error updating or creating token")
|
||||
}
|
||||
|
||||
if err := node.AllowBoostrapTokensToGetNodes(client); err != nil {
|
||||
return errors.Wrap(err, "error allowing bootstrap tokens to get Nodes")
|
||||
}
|
||||
|
||||
if err := node.AllowBootstrapTokensToPostCSRs(client); err != nil {
|
||||
return errors.Wrap(err, "error allowing bootstrap tokens to post CSRs")
|
||||
}
|
||||
|
||||
if err := node.AutoApproveNodeBootstrapTokens(client); err != nil {
|
||||
return errors.Wrap(err, "error auto-approving node bootstrap tokens")
|
||||
}
|
||||
|
||||
if err := node.AutoApproveNodeCertificateRotation(client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bootstrapConfig := &clientcmdapi.Config{
|
||||
Clusters: map[string]*clientcmdapi.Cluster{
|
||||
"": {
|
||||
Server: config.Kubeconfig.Clusters[0].Cluster.Server,
|
||||
CertificateAuthorityData: config.Kubeconfig.Clusters[0].Cluster.CertificateAuthorityData,
|
||||
},
|
||||
},
|
||||
}
|
||||
bootstrapBytes, err := clientcmd.Write(*bootstrapConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = apiclient.CreateOrUpdateConfigMap(client, &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: bootstrapapi.ConfigMapClusterInfo,
|
||||
Namespace: metav1.NamespacePublic,
|
||||
},
|
||||
Data: map[string]string{
|
||||
bootstrapapi.KubeConfigKey: string(bootstrapBytes),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := clusterinfo.CreateClusterInfoRBACRules(client); err != nil {
|
||||
return errors.Wrap(err, "error creating clusterinfo RBAC rules")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
165
internal/kubeadm/certificates.go
Normal file
165
internal/kubeadm/certificates.go
Normal file
@@ -0,0 +1,165 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
||||
|
||||
cryptoKamaji "github.com/clastix/kamaji/internal/crypto"
|
||||
)
|
||||
|
||||
func GenerateCACertificatePrivateKeyPair(baseName string, config *Configuration) (*CertificatePrivateKeyPair, error) {
|
||||
defer deleteCertificateDirectory(config.InitConfiguration.CertificatesDir)
|
||||
|
||||
kubeadmCert, err := getKubeadmCert(baseName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, _, err = initPhaseAsCA(kubeadmCert, config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contents, err := readCertificateFiles(baseName, config.InitConfiguration.CertificatesDir, "crt", "key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certificatePrivateKeyPair := &CertificatePrivateKeyPair{
|
||||
Certificate: contents[0],
|
||||
PrivateKey: contents[1],
|
||||
}
|
||||
|
||||
return certificatePrivateKeyPair, err
|
||||
}
|
||||
|
||||
func GenerateCertificatePrivateKeyPair(baseName string, config *Configuration, ca CertificatePrivateKeyPair) (*CertificatePrivateKeyPair, error) {
|
||||
defer deleteCertificateDirectory(config.InitConfiguration.CertificatesDir)
|
||||
|
||||
certificate, _ := cryptoKamaji.GetCertificate(ca.Certificate)
|
||||
signer, _ := cryptoKamaji.GetPrivateKey(ca.PrivateKey)
|
||||
|
||||
kubeadmCert, err := getKubeadmCert(baseName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = initPhaseFromCA(kubeadmCert, config, certificate, signer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contents, err := readCertificateFiles(baseName, config.InitConfiguration.CertificatesDir, "crt", "key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certificatePrivateKeyPair := &CertificatePrivateKeyPair{
|
||||
Certificate: contents[0],
|
||||
PrivateKey: contents[1],
|
||||
}
|
||||
|
||||
return certificatePrivateKeyPair, err
|
||||
}
|
||||
|
||||
func getKubeadmCert(baseName string) (*certs.KubeadmCert, error) {
|
||||
switch baseName {
|
||||
case kubeadmconstants.CACertAndKeyBaseName:
|
||||
return certs.KubeadmCertRootCA(), nil
|
||||
case kubeadmconstants.APIServerCertAndKeyBaseName:
|
||||
return certs.KubeadmCertAPIServer(), nil
|
||||
case kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName:
|
||||
return certs.KubeadmCertKubeletClient(), nil
|
||||
case kubeadmconstants.FrontProxyCACertAndKeyBaseName:
|
||||
return certs.KubeadmCertFrontProxyCA(), nil
|
||||
case kubeadmconstants.FrontProxyClientCertAndKeyBaseName:
|
||||
return certs.KubeadmCertFrontProxyClient(), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("wrong ca file name %s", baseName)
|
||||
}
|
||||
}
|
||||
|
||||
func GeneratePublicKeyPrivateKeyPair(baseName string, config *Configuration) (*PublicKeyPrivateKeyPair, error) {
|
||||
defer deleteCertificateDirectory(config.InitConfiguration.CertificatesDir)
|
||||
|
||||
if err := initPhaseCertsSA(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
contents, err := readCertificateFiles(baseName, config.InitConfiguration.CertificatesDir, "pub", "key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKeyPrivateKeyPair := &PublicKeyPrivateKeyPair{
|
||||
PublicKey: contents[0],
|
||||
PrivateKey: contents[1],
|
||||
}
|
||||
|
||||
return publicKeyPrivateKeyPair, err
|
||||
}
|
||||
|
||||
func IsCertificatePrivateKeyPairValid(certificate []byte, privKey []byte) (bool, error) {
|
||||
if len(certificate) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
if len(privKey) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return cryptoKamaji.IsValidCertificateKeyPairBytes(certificate, privKey)
|
||||
}
|
||||
|
||||
func IsPublicKeyPrivateKeyPairValid(pubKey []byte, privKey []byte) (bool, error) {
|
||||
if len(pubKey) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
if len(privKey) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return cryptoKamaji.IsValidKeyPairBytes(pubKey, privKey)
|
||||
}
|
||||
|
||||
func initPhaseCertsSA(config *Configuration) error {
|
||||
return certs.CreateServiceAccountKeyAndPublicKeyFiles(config.InitConfiguration.CertificatesDir, config.InitConfiguration.PublicKeyAlgorithm())
|
||||
}
|
||||
|
||||
func initPhaseFromCA(kubeadmCert *certs.KubeadmCert, config *Configuration, certificate *x509.Certificate, signer crypto.Signer) error {
|
||||
return kubeadmCert.CreateFromCA(&config.InitConfiguration, certificate, signer)
|
||||
}
|
||||
|
||||
func initPhaseAsCA(kubeadmCert *certs.KubeadmCert, config *Configuration) (*x509.Certificate, crypto.Signer, error) {
|
||||
return kubeadmCert.CreateAsCA(&config.InitConfiguration)
|
||||
}
|
||||
|
||||
func readCertificateFiles(name string, directory string, extensions ...string) ([][]byte, error) {
|
||||
result := make([][]byte, 0, len(extensions))
|
||||
|
||||
for _, extension := range extensions {
|
||||
fileName := fmt.Sprintf("%s.%s", name, extension)
|
||||
path := filepath.Join(directory, fileName)
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, content)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func deleteCertificateDirectory(certificateDirectory string) {
|
||||
if err := os.RemoveAll(certificateDirectory); err != nil {
|
||||
// TODO(prometherion): we should log rather than printing to stdout
|
||||
fmt.Printf("Error removing %s: %s", certificateDirectory, err.Error()) // nolint:forbidigo
|
||||
}
|
||||
}
|
||||
142
internal/kubeadm/configuration.go
Normal file
142
internal/kubeadm/configuration.go
Normal file
@@ -0,0 +1,142 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
func CreateKubeadmInitConfiguration(params Parameters) Configuration {
|
||||
config := kubeadmapi.InitConfiguration{
|
||||
ClusterConfiguration: getKubeadmClusterConfiguration(params),
|
||||
BootstrapTokens: []bootstraptokenv1.BootstrapToken{
|
||||
{
|
||||
Groups: []string{"system:bootstrappers:kubeadm:default-node-token"},
|
||||
TTL: &metav1.Duration{Duration: 48 * time.Hour},
|
||||
Usages: []string{
|
||||
"signing",
|
||||
"authentication",
|
||||
},
|
||||
},
|
||||
},
|
||||
LocalAPIEndpoint: kubeadmapi.APIEndpoint{
|
||||
AdvertiseAddress: params.TenantControlPlaneAddress,
|
||||
BindPort: params.TenantControlPlanePort,
|
||||
},
|
||||
NodeRegistration: kubeadmapi.NodeRegistrationOptions{
|
||||
CRISocket: "unix:///run/containerd/containerd.sock",
|
||||
Name: params.TenantControlPlaneName,
|
||||
},
|
||||
}
|
||||
|
||||
return Configuration{InitConfiguration: config}
|
||||
}
|
||||
|
||||
func getKubeadmClusterConfiguration(params Parameters) kubeadmapi.ClusterConfiguration {
|
||||
return kubeadmapi.ClusterConfiguration{
|
||||
KubernetesVersion: params.TenantControlPlaneVersion,
|
||||
ClusterName: params.TenantControlPlaneName,
|
||||
CertificatesDir: "/etc/kubernetes/pki",
|
||||
ImageRepository: "k8s.gcr.io",
|
||||
Networking: kubeadmapi.Networking{
|
||||
DNSDomain: "cluster.local",
|
||||
PodSubnet: params.TenantControlPlanePodCIDR,
|
||||
ServiceSubnet: params.TenantControlPlaneServiceCIDR,
|
||||
},
|
||||
DNS: kubeadmapi.DNS{
|
||||
Type: "CoreDNS",
|
||||
},
|
||||
ControlPlaneEndpoint: params.TenantControlPlaneEndpoint,
|
||||
Etcd: kubeadmapi.Etcd{
|
||||
External: &kubeadmapi.ExternalEtcd{
|
||||
Endpoints: formatETCDEndpoints(params.ETCDs),
|
||||
CAFile: "/etc/kubernetes/pki/etcd/ca.crt",
|
||||
CertFile: "/etc/kubernetes/pki/apiserver-etcd-client.crt",
|
||||
KeyFile: "/etc/kubernetes/pki/apiserver-etcd-client.key",
|
||||
},
|
||||
},
|
||||
APIServer: kubeadmapi.APIServer{
|
||||
CertSANs: []string{
|
||||
"127.0.0.1",
|
||||
"localhost",
|
||||
fmt.Sprintf("%s.%s", params.TenantControlPlaneName, params.TenantControlPlaneDomain),
|
||||
params.TenantControlPlaneName,
|
||||
fmt.Sprintf("%s.%s.svc", params.TenantControlPlaneName, params.TenantControlPlaneNamespace),
|
||||
fmt.Sprintf("%s.%s.svc.cluster.local", params.TenantControlPlaneName, params.TenantControlPlaneNamespace),
|
||||
params.TenantControlPlaneAddress,
|
||||
},
|
||||
ControlPlaneComponent: kubeadmapi.ControlPlaneComponent{
|
||||
ExtraArgs: map[string]string{
|
||||
"etcd-compaction-interval": params.ETCDCompactionInterval,
|
||||
"etcd-prefix": fmt.Sprintf("/%s", params.TenantControlPlaneName),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func GetKubeadmInitConfigurationMap(config Configuration) (map[string]string, error) {
|
||||
initConfigurationString, err := getJSONStringFromStruct(config.InitConfiguration)
|
||||
if err != nil {
|
||||
return map[string]string{}, err
|
||||
}
|
||||
|
||||
clusterConfigurationString, err := getJSONStringFromStruct(config.InitConfiguration.ClusterConfiguration)
|
||||
if err != nil {
|
||||
return map[string]string{}, err
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
kubeadmconstants.InitConfigurationKind: initConfigurationString,
|
||||
kubeadmconstants.ClusterConfigurationKind: clusterConfigurationString,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetKubeadmInitConfigurationFromMap(config map[string]string) (*Configuration, error) {
|
||||
initConfigurationString, ok := config[kubeadmconstants.InitConfigurationKind]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not in the map", kubeadmconstants.InitConfigurationKind)
|
||||
}
|
||||
|
||||
clusterConfigurationString, ok := config[kubeadmconstants.ClusterConfigurationKind]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not in the map", kubeadmconstants.ClusterConfigurationKind)
|
||||
}
|
||||
|
||||
initConfiguration := kubeadmapi.InitConfiguration{}
|
||||
if err := json.Unmarshal([]byte(initConfigurationString), &initConfiguration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(clusterConfigurationString), &initConfiguration.ClusterConfiguration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Configuration{InitConfiguration: initConfiguration}, nil
|
||||
}
|
||||
|
||||
func getJSONStringFromStruct(i interface{}) (string, error) {
|
||||
b, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func formatETCDEndpoints(etcds []string) []string {
|
||||
formatedETCDs := make([]string, 0, len(etcds))
|
||||
for _, etcd := range etcds {
|
||||
formatedETCDs = append(formatedETCDs, fmt.Sprintf("https://%s/", etcd))
|
||||
}
|
||||
|
||||
return formatedETCDs
|
||||
}
|
||||
52
internal/kubeadm/kubeconfig.go
Normal file
52
internal/kubeadm/kubeconfig.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
|
||||
)
|
||||
|
||||
func buildCertificateDirectoryWithCA(ca CertificatePrivateKeyPair, directory string) error {
|
||||
if err := os.MkdirAll(directory, os.FileMode(0o755)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certPath := path.Join(directory, kubeadmconstants.CACertName)
|
||||
if err := ioutil.WriteFile(certPath, ca.Certificate, os.FileMode(0o600)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyPath := path.Join(directory, kubeadmconstants.CAKeyName)
|
||||
if err := ioutil.WriteFile(keyPath, ca.PrivateKey, os.FileMode(0o600)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateKubeconfig(kubeconfigName string, ca CertificatePrivateKeyPair, config *Configuration) ([]byte, error) {
|
||||
if err := buildCertificateDirectoryWithCA(ca, config.InitConfiguration.CertificatesDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer deleteCertificateDirectory(config.InitConfiguration.CertificatesDir)
|
||||
|
||||
if err := kubeconfig.CreateKubeConfigFile(kubeconfigName, config.InitConfiguration.CertificatesDir, &config.InitConfiguration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path := filepath.Join(config.InitConfiguration.CertificatesDir, kubeconfigName)
|
||||
|
||||
return ioutil.ReadFile(path)
|
||||
}
|
||||
|
||||
func IsKubeconfigValid(kubeconfigBytes []byte) bool {
|
||||
return len(kubeconfigBytes) > 0
|
||||
}
|
||||
52
internal/kubeadm/types.go
Normal file
52
internal/kubeadm/types.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
|
||||
kubeconfigutil "github.com/clastix/kamaji/internal/kubeconfig"
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
InitConfiguration kubeadmapi.InitConfiguration
|
||||
Kubeconfig kubeconfigutil.Kubeconfig
|
||||
Parameters Parameters
|
||||
}
|
||||
|
||||
type Parameters struct {
|
||||
TenantControlPlaneName string
|
||||
TenantControlPlaneNamespace string
|
||||
TenantControlPlaneEndpoint string
|
||||
TenantControlPlaneAddress string
|
||||
TenantControlPlanePort int32
|
||||
TenantControlPlaneDomain string
|
||||
TenantControlPlanePodCIDR string
|
||||
TenantControlPlaneServiceCIDR string
|
||||
TenantDNSServiceIPs []string
|
||||
TenantControlPlaneVersion string
|
||||
TenantControlPlaneCGroupDriver string
|
||||
ETCDs []string
|
||||
ETCDCompactionInterval string
|
||||
CertificatesDir string
|
||||
KubeconfigDir string
|
||||
}
|
||||
|
||||
type KubeletConfiguration struct {
|
||||
TenantControlPlaneDomain string
|
||||
TenantControlPlaneDNSServiceIPs []string
|
||||
TenantControlPlaneCgroupDriver string
|
||||
}
|
||||
|
||||
type CertificatePrivateKeyPair struct {
|
||||
Name string
|
||||
Certificate []byte
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
type PublicKeyPrivateKeyPair struct {
|
||||
Name string
|
||||
PublicKey []byte
|
||||
PrivateKey []byte
|
||||
}
|
||||
188
internal/kubeadm/uploadconfig.go
Normal file
188
internal/kubeadm/uploadconfig.go
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8sversion "k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
kubelettypes "k8s.io/kubelet/config/v1beta1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
"k8s.io/kubernetes/pkg/apis/rbac"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
func UploadKubeadmConfig(client kubernetes.Interface, config *Configuration) error {
|
||||
return uploadconfig.UploadConfiguration(&config.InitConfiguration, client)
|
||||
}
|
||||
|
||||
func UploadKubeletConfig(client kubernetes.Interface, config *Configuration) error {
|
||||
kubeletConfiguration := KubeletConfiguration{
|
||||
TenantControlPlaneDomain: config.InitConfiguration.Networking.DNSDomain,
|
||||
TenantControlPlaneDNSServiceIPs: config.Parameters.TenantDNSServiceIPs,
|
||||
TenantControlPlaneCgroupDriver: config.Parameters.TenantControlPlaneCGroupDriver,
|
||||
}
|
||||
content, err := getKubeletConfigmapContent(kubeletConfiguration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configMapName, err := configMapName(config.Parameters.TenantControlPlaneVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configMap := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: configMapName,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Data: map[string]string{
|
||||
kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(content),
|
||||
},
|
||||
}
|
||||
|
||||
if err := apiclient.CreateOrUpdateConfigMap(client, configMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createConfigMapRBACRules(client, config.Parameters.TenantControlPlaneVersion); err != nil {
|
||||
return errors.Wrap(err, "error creating kubelet configuration configmap RBAC rules")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKubeletConfigmapContent(kubeletConfiguration KubeletConfiguration) ([]byte, error) {
|
||||
zeroDuration := metav1.Duration{Duration: 0}
|
||||
|
||||
kc := kubelettypes.KubeletConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "KubeletConfiguration",
|
||||
APIVersion: "kubelet.config.k8s.io/v1beta1",
|
||||
},
|
||||
Authentication: kubelettypes.KubeletAuthentication{
|
||||
Anonymous: kubelettypes.KubeletAnonymousAuthentication{
|
||||
Enabled: pointer.Bool(false),
|
||||
},
|
||||
Webhook: kubelettypes.KubeletWebhookAuthentication{
|
||||
Enabled: pointer.Bool(true),
|
||||
CacheTTL: zeroDuration,
|
||||
},
|
||||
X509: kubelettypes.KubeletX509Authentication{
|
||||
ClientCAFile: "/etc/kubernetes/pki/ca.crt",
|
||||
},
|
||||
},
|
||||
Authorization: kubelettypes.KubeletAuthorization{
|
||||
Mode: kubelettypes.KubeletAuthorizationModeWebhook,
|
||||
Webhook: kubelettypes.KubeletWebhookAuthorization{
|
||||
CacheAuthorizedTTL: zeroDuration,
|
||||
CacheUnauthorizedTTL: zeroDuration,
|
||||
},
|
||||
},
|
||||
CgroupDriver: kubeletConfiguration.TenantControlPlaneCgroupDriver,
|
||||
ClusterDNS: kubeletConfiguration.TenantControlPlaneDNSServiceIPs,
|
||||
ClusterDomain: kubeletConfiguration.TenantControlPlaneDomain,
|
||||
CPUManagerReconcilePeriod: zeroDuration,
|
||||
EvictionHard: map[string]string{
|
||||
"imagefs.available": "0%",
|
||||
"nodefs.available": "0%",
|
||||
"nodefs.inodesFree": "0%",
|
||||
},
|
||||
EvictionPressureTransitionPeriod: zeroDuration,
|
||||
FileCheckFrequency: zeroDuration,
|
||||
HealthzBindAddress: "127.0.0.1",
|
||||
HealthzPort: pointer.Int32(10248),
|
||||
HTTPCheckFrequency: zeroDuration,
|
||||
ImageGCHighThresholdPercent: pointer.Int32(100),
|
||||
NodeStatusUpdateFrequency: zeroDuration,
|
||||
NodeStatusReportFrequency: zeroDuration,
|
||||
RotateCertificates: true,
|
||||
RuntimeRequestTimeout: zeroDuration,
|
||||
ShutdownGracePeriod: zeroDuration,
|
||||
ShutdownGracePeriodCriticalPods: zeroDuration,
|
||||
StaticPodPath: "/etc/kubernetes/manifests",
|
||||
StreamingConnectionIdleTimeout: zeroDuration,
|
||||
SyncFrequency: zeroDuration,
|
||||
VolumeStatsAggPeriod: zeroDuration,
|
||||
}
|
||||
|
||||
return EncondeToYaml(&kc)
|
||||
}
|
||||
|
||||
func createConfigMapRBACRules(client kubernetes.Interface, kubernetesVersion string) error {
|
||||
configMapName, err := configMapName(kubernetesVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configMapRBACName, err := configMapRBACName(kubernetesVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := apiclient.CreateOrUpdateRole(client, &rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: configMapRBACName,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"configmaps"},
|
||||
ResourceNames: []string{configMapName},
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return apiclient.CreateOrUpdateRoleBinding(client, &rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: configMapRBACName,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: rbac.GroupName,
|
||||
Kind: "Role",
|
||||
Name: configMapRBACName,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: rbac.GroupKind,
|
||||
Name: kubeadmconstants.NodesGroup,
|
||||
},
|
||||
{
|
||||
Kind: rbac.GroupKind,
|
||||
Name: kubeadmconstants.NodeBootstrapTokenAuthGroup,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func configMapName(kubernetesVersion string) (string, error) {
|
||||
version, err := k8sversion.ParseSemantic(kubernetesVersion)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return kubeadmconstants.GetKubeletConfigMapName(version, true), nil
|
||||
}
|
||||
|
||||
func configMapRBACName(kubernetesVersion string) (string, error) {
|
||||
version, err := k8sversion.ParseSemantic(kubernetesVersion)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%d.%d", kubeadmconstants.KubeletBaseConfigMapRolePrefix, version.Major(), version.Minor()), nil
|
||||
}
|
||||
20
internal/kubeadm/utils.go
Normal file
20
internal/kubeadm/utils.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubeadm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
)
|
||||
|
||||
func EncondeToYaml(o runtime.Object) ([]byte, error) {
|
||||
scheme := runtime.NewScheme()
|
||||
encoder := json.NewYAMLSerializer(json.SimpleMetaFactory{}, scheme, scheme)
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err := encoder.Encode(o, buf)
|
||||
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
28
internal/kubeconfig/kubeconfig.go
Normal file
28
internal/kubeconfig/kubeconfig.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kubeconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
v1 "k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
)
|
||||
|
||||
type Kubeconfig v1.Config
|
||||
|
||||
func GetKubeconfigFromBytesBuffer(buffer *bytes.Buffer) (*Kubeconfig, error) {
|
||||
kubeconfig := &Kubeconfig{}
|
||||
if err := yaml.NewYAMLOrJSONDecoder(buffer, buffer.Len()).Decode(kubeconfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kubeconfig, nil
|
||||
}
|
||||
|
||||
func GetKubeconfigFromBytes(b []byte) (*Kubeconfig, error) {
|
||||
buffer := bytes.NewBuffer(b)
|
||||
|
||||
return GetKubeconfigFromBytesBuffer(buffer)
|
||||
}
|
||||
136
internal/resources/api_server_certificate.go
Normal file
136
internal/resources/api_server_certificate.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type APIServerCertificate struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
Name string
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Certificates.APIServer.SecretName != r.resource.GetName()
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: r.getPrefixedName(tenantControlPlane),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
|
||||
return utilities.AddTenantPrefix(r.Name, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) GetClient() client.Client {
|
||||
return r.Client
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) GetTmpDirectory() string {
|
||||
return r.TmpDirectory
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Certificates.APIServer.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.Certificates.APIServer.SecretName = r.resource.GetName()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIServerCertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
latestConfigRV := getLatestConfigRV(*tenantControlPlane)
|
||||
actualConfigRV := r.resource.GetLabels()["latest-config-rv"]
|
||||
if latestConfigRV == actualConfigRV {
|
||||
isValid, err := kubeadm.IsCertificatePrivateKeyPairValid(
|
||||
r.resource.Data[kubeadmconstants.APIServerCertName],
|
||||
r.resource.Data[kubeadmconstants.APIServerKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.APIServerCertAndKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
config, _, err := getKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.CA.SecretName}
|
||||
secretCA := &corev1.Secret{}
|
||||
if err = r.Client.Get(ctx, namespacedName, secretCA); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ca := kubeadm.CertificatePrivateKeyPair{
|
||||
Name: kubeadmconstants.CACertAndKeyBaseName,
|
||||
Certificate: secretCA.Data[kubeadmconstants.CACertName],
|
||||
PrivateKey: secretCA.Data[kubeadmconstants.CAKeyName],
|
||||
}
|
||||
certificateKeyPair, err := kubeadm.GenerateCertificatePrivateKeyPair(kubeadmconstants.APIServerCertAndKeyBaseName, config, ca)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
kubeadmconstants.APIServerCertName: certificateKeyPair.Certificate,
|
||||
kubeadmconstants.APIServerKeyName: certificateKeyPair.PrivateKey,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(),
|
||||
map[string]string{
|
||||
"latest-config-rv": latestConfigRV,
|
||||
"kamaji.clastix.io/name": tenantControlPlane.GetName(),
|
||||
"kamaji.clastix.io/component": r.GetName(),
|
||||
},
|
||||
))
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
}
|
||||
}
|
||||
136
internal/resources/api_server_kubelet_client_certificate.go
Normal file
136
internal/resources/api_server_kubelet_client_certificate.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type APIServerKubeletClientCertificate struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
Name string
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Certificates.APIServerKubeletClient.SecretName != r.resource.GetName()
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: r.getPrefixedName(tenantControlPlane),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
|
||||
return utilities.AddTenantPrefix(r.Name, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) GetClient() client.Client {
|
||||
return r.Client
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) GetTmpDirectory() string {
|
||||
return r.TmpDirectory
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Certificates.APIServerKubeletClient.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.Certificates.APIServerKubeletClient.SecretName = r.resource.GetName()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
latestConfigRV := getLatestConfigRV(*tenantControlPlane)
|
||||
actualConfigRV := r.resource.GetLabels()["latest-config-rv"]
|
||||
if latestConfigRV == actualConfigRV {
|
||||
isValid, err := kubeadm.IsCertificatePrivateKeyPairValid(
|
||||
r.resource.Data[kubeadmconstants.APIServerKubeletClientCertName],
|
||||
r.resource.Data[kubeadmconstants.APIServerKubeletClientKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
config, _, err := getKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.CA.SecretName}
|
||||
secretCA := &corev1.Secret{}
|
||||
if err = r.Client.Get(ctx, namespacedName, secretCA); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ca := kubeadm.CertificatePrivateKeyPair{
|
||||
Name: kubeadmconstants.CACertAndKeyBaseName,
|
||||
Certificate: secretCA.Data[kubeadmconstants.CACertName],
|
||||
PrivateKey: secretCA.Data[kubeadmconstants.CAKeyName],
|
||||
}
|
||||
certificateKeyPair, err := kubeadm.GenerateCertificatePrivateKeyPair(kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName, config, ca)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
kubeadmconstants.APIServerKubeletClientCertName: certificateKeyPair.Certificate,
|
||||
kubeadmconstants.APIServerKubeletClientKeyName: certificateKeyPair.PrivateKey,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(),
|
||||
map[string]string{
|
||||
"latest-config-rv": latestConfigRV,
|
||||
"kamaji.clastix.io/name": tenantControlPlane.GetName(),
|
||||
"kamaji.clastix.io/component": r.GetName(),
|
||||
},
|
||||
))
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
}
|
||||
}
|
||||
119
internal/resources/ca_certificate.go
Normal file
119
internal/resources/ca_certificate.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type CACertificate struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
Name string
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *CACertificate) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Certificates.CA.SecretName != r.resource.GetName()
|
||||
}
|
||||
|
||||
func (r *CACertificate) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *CACertificate) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *CACertificate) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: r.getPrefixedName(tenantControlPlane),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *CACertificate) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
|
||||
return utilities.AddTenantPrefix(r.Name, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *CACertificate) GetClient() client.Client {
|
||||
return r.Client
|
||||
}
|
||||
|
||||
func (r *CACertificate) GetTmpDirectory() string {
|
||||
return r.TmpDirectory
|
||||
}
|
||||
|
||||
func (r *CACertificate) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
|
||||
}
|
||||
|
||||
func (r *CACertificate) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *CACertificate) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Certificates.CA.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.Certificates.CA.SecretName = r.resource.GetName()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *CACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
isValid, err := kubeadm.IsCertificatePrivateKeyPairValid(
|
||||
r.resource.Data[kubeadmconstants.CACertName],
|
||||
r.resource.Data[kubeadmconstants.CAKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.CACertAndKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
return nil
|
||||
}
|
||||
|
||||
config, _, err := getKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ca, err := kubeadm.GenerateCACertificatePrivateKeyPair(kubeadmconstants.CACertAndKeyBaseName, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
kubeadmconstants.CACertName: ca.Certificate,
|
||||
kubeadmconstants.CAKeyName: ca.PrivateKey,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(),
|
||||
map[string]string{
|
||||
"kamaji.clastix.io/name": tenantControlPlane.GetName(),
|
||||
"kamaji.clastix.io/component": r.GetName(),
|
||||
},
|
||||
))
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
}
|
||||
}
|
||||
8
internal/resources/constants.go
Normal file
8
internal/resources/constants.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
const (
|
||||
defaultIngressPort = 443
|
||||
)
|
||||
119
internal/resources/etcd_ca_certificates.go
Normal file
119
internal/resources/etcd_ca_certificates.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/etcd"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type ETCDCACertificatesResource struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
Name string
|
||||
ETCDCASecretName string
|
||||
ETCDCASecretNamespace string
|
||||
}
|
||||
|
||||
func (r *ETCDCACertificatesResource) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
if tenantControlPlane.Status.Certificates.ETCD == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return tenantControlPlane.Status.Certificates.ETCD.CA.SecretName != r.resource.GetName()
|
||||
}
|
||||
|
||||
func (r *ETCDCACertificatesResource) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *ETCDCACertificatesResource) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *ETCDCACertificatesResource) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: r.getPrefixedName(tenantControlPlane),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ETCDCACertificatesResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
|
||||
}
|
||||
|
||||
func (r *ETCDCACertificatesResource) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *ETCDCACertificatesResource) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
if tenantControlPlane.Status.Certificates.ETCD == nil {
|
||||
tenantControlPlane.Status.Certificates.ETCD = &kamajiv1alpha1.ETCDCertificatesStatus{}
|
||||
}
|
||||
|
||||
tenantControlPlane.Status.Certificates.ETCD.CA.SecretName = r.resource.GetName()
|
||||
tenantControlPlane.Status.Certificates.ETCD.CA.LastUpdate = metav1.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ETCDCACertificatesResource) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
|
||||
return utilities.AddTenantPrefix(r.Name, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *ETCDCACertificatesResource) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
r.resource.SetLabels(utilities.KamajiLabels())
|
||||
|
||||
etcdCASecretNamespacedName := k8stypes.NamespacedName{Namespace: r.ETCDCASecretNamespace, Name: r.ETCDCASecretName}
|
||||
etcdCASecret := &corev1.Secret{}
|
||||
if err := r.Client.Get(ctx, etcdCASecretNamespacedName, etcdCASecret); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isValid, err := etcd.IsETCDCertificateAndKeyPairValid(r.resource.Data[kubeadmconstants.CACertName], r.resource.Data[kubeadmconstants.CAKeyName])
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("etcd certificates are not valid: %s", err.Error()))
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(etcdCASecret.Data[kubeadmconstants.CACertName], r.resource.Data[kubeadmconstants.CACertName]) &&
|
||||
reflect.DeepEqual(etcdCASecret.Data[kubeadmconstants.CAKeyName], r.resource.Data[kubeadmconstants.CAKeyName]) {
|
||||
if isValid {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("CA certificates provided into secrets %s/%s are not valid", r.ETCDCASecretNamespace, r.ETCDCASecretName)
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
kubeadmconstants.CACertName: etcdCASecret.Data[kubeadmconstants.CACertName],
|
||||
kubeadmconstants.CAKeyName: etcdCASecret.Data[kubeadmconstants.CAKeyName],
|
||||
}
|
||||
|
||||
if err = ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
124
internal/resources/etcd_certificates.go
Normal file
124
internal/resources/etcd_certificates.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/etcd"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type ETCDCertificatesResource struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
Name string
|
||||
}
|
||||
|
||||
func (r *ETCDCertificatesResource) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
if tenantControlPlane.Status.Certificates.ETCD == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return tenantControlPlane.Status.Certificates.ETCD.APIServer.SecretName != r.resource.GetName()
|
||||
}
|
||||
|
||||
func (r *ETCDCertificatesResource) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *ETCDCertificatesResource) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *ETCDCertificatesResource) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: r.getPrefixedName(tenantControlPlane),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ETCDCertificatesResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
|
||||
}
|
||||
|
||||
func (r *ETCDCertificatesResource) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *ETCDCertificatesResource) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
if tenantControlPlane.Status.Certificates.ETCD == nil {
|
||||
tenantControlPlane.Status.Certificates.ETCD = &kamajiv1alpha1.ETCDCertificatesStatus{}
|
||||
}
|
||||
|
||||
tenantControlPlane.Status.Certificates.ETCD.APIServer.SecretName = r.resource.GetName()
|
||||
tenantControlPlane.Status.Certificates.ETCD.APIServer.LastUpdate = metav1.Now()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ETCDCertificatesResource) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
|
||||
return utilities.AddTenantPrefix(r.Name, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *ETCDCertificatesResource) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
r.resource.SetLabels(utilities.KamajiLabels())
|
||||
|
||||
if tenantControlPlane.Status.Certificates.ETCD == nil {
|
||||
return fmt.Errorf("etcd is still synchronizing latest changes")
|
||||
}
|
||||
|
||||
etcdCASecretNamespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.ETCD.CA.SecretName}
|
||||
etcdCASecret := &corev1.Secret{}
|
||||
if err := r.Client.Get(ctx, etcdCASecretNamespacedName, etcdCASecret); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isValid, err := etcd.IsETCDCertificateAndKeyPairValid(r.resource.Data[kubeadmconstants.APIServerEtcdClientCertName], r.resource.Data[kubeadmconstants.APIServerEtcdClientKeyName])
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("etcd certificates are not valid: %s", err.Error()))
|
||||
}
|
||||
|
||||
if isValid {
|
||||
return nil
|
||||
}
|
||||
|
||||
cert, privKey, err := etcd.GetETCDCACertificateAndKeyPair(
|
||||
tenantControlPlane.GetName(),
|
||||
etcdCASecret.Data[kubeadmconstants.CACertName],
|
||||
etcdCASecret.Data[kubeadmconstants.CAKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
kubeadmconstants.APIServerEtcdClientCertName: cert.Bytes(),
|
||||
kubeadmconstants.APIServerEtcdClientKeyName: privKey.Bytes(),
|
||||
}
|
||||
|
||||
if err = ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
279
internal/resources/etcd_setup.go
Normal file
279
internal/resources/etcd_setup.go
Normal file
@@ -0,0 +1,279 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
etcdclient "go.etcd.io/etcd/client/v3"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/etcd"
|
||||
)
|
||||
|
||||
const (
|
||||
caKeyName = kubeadmconstants.CACertName
|
||||
)
|
||||
|
||||
type resource struct {
|
||||
role etcd.Role
|
||||
user etcd.User
|
||||
}
|
||||
|
||||
type ETCDSetupResource struct {
|
||||
resource *resource
|
||||
Client client.Client
|
||||
Scheme *runtime.Scheme
|
||||
Log logr.Logger
|
||||
Name string
|
||||
Endpoints []string
|
||||
ETCDClientCertsSecret k8stypes.NamespacedName
|
||||
ETCDCACertsSecret k8stypes.NamespacedName
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
if tenantControlPlane.Status.Storage.ETCD == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return tenantControlPlane.Status.Storage.ETCD.Role.Name != r.resource.role.Name ||
|
||||
tenantControlPlane.Status.Storage.ETCD.User.Name != r.resource.user.Name
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &resource{
|
||||
role: etcd.Role{Name: tenantControlPlane.Name, Exists: false},
|
||||
user: etcd.User{Name: tenantControlPlane.Name, Exists: false},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return r.reconcile(ctx)
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) Delete(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
if err := r.Define(ctx, tenantControlPlane); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := r.getETCDClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.deleteData(ctx, client, tenantControlPlane); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.deleteUser(ctx, client, tenantControlPlane); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.deleteRole(ctx, client, tenantControlPlane); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
if tenantControlPlane.Status.Storage.ETCD == nil {
|
||||
tenantControlPlane.Status.Storage.ETCD = &kamajiv1alpha1.ETCDStatus{}
|
||||
}
|
||||
tenantControlPlane.Status.Storage.ETCD.Role = r.resource.role
|
||||
tenantControlPlane.Status.Storage.ETCD.User = r.resource.user
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) reconcile(ctx context.Context) (controllerutil.OperationResult, error) {
|
||||
reconcilationResult := controllerutil.OperationResultNone
|
||||
var operationResult controllerutil.OperationResult
|
||||
|
||||
client, err := r.getETCDClient(ctx)
|
||||
if err != nil {
|
||||
return reconcilationResult, err
|
||||
}
|
||||
|
||||
operationResult, err = r.reconcileUser(ctx, client)
|
||||
if err != nil {
|
||||
return reconcilationResult, err
|
||||
}
|
||||
reconcilationResult = updateOperationResult(reconcilationResult, operationResult)
|
||||
|
||||
operationResult, err = r.reconcileRole(ctx, client)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
reconcilationResult = updateOperationResult(reconcilationResult, operationResult)
|
||||
|
||||
operationResult, err = r.grantUserRole(ctx, client)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
reconcilationResult = updateOperationResult(reconcilationResult, operationResult)
|
||||
|
||||
operationResult, err = r.grantRolePermissions(ctx, client)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
reconcilationResult = updateOperationResult(reconcilationResult, operationResult)
|
||||
|
||||
return reconcilationResult, nil
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) getETCDClient(ctx context.Context) (*etcdclient.Client, error) {
|
||||
var certsClientSecret corev1.Secret
|
||||
if err := r.Client.Get(ctx, r.ETCDClientCertsSecret, &certsClientSecret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var certsCASecret corev1.Secret
|
||||
if err := r.Client.Get(ctx, r.ETCDCACertsSecret, &certsCASecret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := etcd.Config{
|
||||
ETCDCertificate: certsClientSecret.Data[corev1.TLSCertKey],
|
||||
ETCDPrivateKey: certsClientSecret.Data[corev1.TLSPrivateKeyKey],
|
||||
ETCDCA: certsCASecret.Data[caKeyName],
|
||||
Endpoints: r.Endpoints,
|
||||
}
|
||||
|
||||
return etcd.NewClient(config)
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) reconcileUser(ctx context.Context, client *etcdclient.Client) (controllerutil.OperationResult, error) {
|
||||
if err := etcd.GetUser(ctx, client, &r.resource.user); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
if !r.resource.user.Exists {
|
||||
if err := etcd.AddUser(ctx, client, r.resource.user.Name); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultCreated, nil
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) reconcileRole(ctx context.Context, client *etcdclient.Client) (controllerutil.OperationResult, error) {
|
||||
if err := etcd.GetRole(ctx, client, &r.resource.role); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
if !r.resource.role.Exists {
|
||||
if err := etcd.AddRole(ctx, client, r.resource.role.Name); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultCreated, nil
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) grantUserRole(ctx context.Context, client *etcdclient.Client) (controllerutil.OperationResult, error) {
|
||||
if err := etcd.GetUser(ctx, client, &r.resource.user); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
if len(r.resource.user.Roles) > 0 && isRole(r.resource.user.Roles, r.resource.role.Name) {
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
|
||||
if err := etcd.GrantUserRole(ctx, client, r.resource.user, r.resource.role); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultUpdated, nil
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) grantRolePermissions(ctx context.Context, client *etcdclient.Client) (controllerutil.OperationResult, error) {
|
||||
if err := etcd.GetRole(ctx, client, &r.resource.role); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
if len(r.resource.role.Permissions) > 0 && isPermission(r.resource.role.Permissions, r.resource.role.Name) {
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
|
||||
if err := etcd.GrantRolePermission(ctx, client, r.resource.role); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultUpdated, nil
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) deleteData(ctx context.Context, client *etcdclient.Client, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
return etcd.CleanUpPrefix(ctx, client, tenantControlPlane.GetName())
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) deleteUser(ctx context.Context, client *etcdclient.Client, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
if err := etcd.GetUser(ctx, client, &r.resource.user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !r.resource.user.Exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
return etcd.RemoveUser(ctx, client, tenantControlPlane.GetName())
|
||||
}
|
||||
|
||||
func (r *ETCDSetupResource) deleteRole(ctx context.Context, client *etcdclient.Client, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
if err := etcd.GetRole(ctx, client, &r.resource.role); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !r.resource.role.Exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
return etcd.RemoveRole(ctx, client, tenantControlPlane.GetName())
|
||||
}
|
||||
|
||||
func isRole(s []string, x string) bool {
|
||||
for _, o := range s {
|
||||
if o == x {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isPermission(s []etcd.Permission, role string) bool {
|
||||
key := etcd.BuildKey(role)
|
||||
for _, o := range s {
|
||||
if o.Key == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
136
internal/resources/front-proxy-client-certificate.go
Normal file
136
internal/resources/front-proxy-client-certificate.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type FrontProxyClientCertificate struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
Name string
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Certificates.FrontProxyClient.SecretName != r.resource.GetName()
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: r.getPrefixedName(tenantControlPlane),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
|
||||
return utilities.AddTenantPrefix(r.Name, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) GetClient() client.Client {
|
||||
return r.Client
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) GetTmpDirectory() string {
|
||||
return r.TmpDirectory
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Certificates.FrontProxyClient.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.Certificates.FrontProxyClient.SecretName = r.resource.GetName()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *FrontProxyClientCertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
latestConfigRV := getLatestConfigRV(*tenantControlPlane)
|
||||
actualConfigRV := r.resource.GetLabels()["latest-config-rv"]
|
||||
if latestConfigRV == actualConfigRV {
|
||||
isValid, err := kubeadm.IsCertificatePrivateKeyPairValid(
|
||||
r.resource.Data[kubeadmconstants.FrontProxyClientCertName],
|
||||
r.resource.Data[kubeadmconstants.FrontProxyClientKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.FrontProxyClientCertAndKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
config, _, err := getKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.FrontProxyCA.SecretName}
|
||||
secretCA := &corev1.Secret{}
|
||||
if err = r.Client.Get(ctx, namespacedName, secretCA); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ca := kubeadm.CertificatePrivateKeyPair{
|
||||
Name: kubeadmconstants.FrontProxyCACertAndKeyBaseName,
|
||||
Certificate: secretCA.Data[kubeadmconstants.FrontProxyCACertName],
|
||||
PrivateKey: secretCA.Data[kubeadmconstants.FrontProxyCAKeyName],
|
||||
}
|
||||
certificateKeyPair, err := kubeadm.GenerateCertificatePrivateKeyPair(kubeadmconstants.FrontProxyClientCertAndKeyBaseName, config, ca)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
kubeadmconstants.FrontProxyClientCertName: certificateKeyPair.Certificate,
|
||||
kubeadmconstants.FrontProxyClientKeyName: certificateKeyPair.PrivateKey,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(),
|
||||
map[string]string{
|
||||
"latest-config-rv": latestConfigRV,
|
||||
"kamaji.clastix.io/name": tenantControlPlane.GetName(),
|
||||
"kamaji.clastix.io/component": r.GetName(),
|
||||
},
|
||||
))
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
}
|
||||
}
|
||||
121
internal/resources/front_proxy_ca_certificate.go
Normal file
121
internal/resources/front_proxy_ca_certificate.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type FrontProxyCACertificate struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
Name string
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Certificates.FrontProxyCA.SecretName != r.resource.GetName()
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: r.getPrefixedName(tenantControlPlane),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
},
|
||||
}
|
||||
|
||||
r.Name = "front-proxy-ca-certificate"
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
|
||||
return utilities.AddTenantPrefix(r.Name, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) GetClient() client.Client {
|
||||
return r.Client
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) GetTmpDirectory() string {
|
||||
return r.TmpDirectory
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Certificates.FrontProxyCA.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.Certificates.FrontProxyCA.SecretName = r.resource.GetName()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *FrontProxyCACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
isValid, err := kubeadm.IsCertificatePrivateKeyPairValid(
|
||||
r.resource.Data[kubeadmconstants.FrontProxyCACertName],
|
||||
r.resource.Data[kubeadmconstants.FrontProxyCAKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("%s certificate-private_key pair is not valid: %s", kubeadmconstants.FrontProxyCACertAndKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
return nil
|
||||
}
|
||||
|
||||
config, _, err := getKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ca, err := kubeadm.GenerateCACertificatePrivateKeyPair(kubeadmconstants.FrontProxyCACertAndKeyBaseName, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
kubeadmconstants.FrontProxyCACertName: ca.Certificate,
|
||||
kubeadmconstants.FrontProxyCAKeyName: ca.PrivateKey,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(),
|
||||
map[string]string{
|
||||
"kamaji.clastix.io/name": tenantControlPlane.GetName(),
|
||||
"kamaji.clastix.io/component": r.GetName(),
|
||||
},
|
||||
))
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
}
|
||||
}
|
||||
605
internal/resources/k8s_deployment_resource.go
Normal file
605
internal/resources/k8s_deployment_resource.go
Normal file
@@ -0,0 +1,605 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
quantity "k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type KubernetesDeploymentResource struct {
|
||||
resource *appsv1.Deployment
|
||||
Client client.Client
|
||||
ETCDEndpoints []string
|
||||
ETCDCompactionInterval string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) isStatusEqual(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return r.resource.Status.String() == tenantControlPlane.Status.Kubernetes.Deployment.DeploymentStatus.String()
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return !r.isStatusEqual(tenantControlPlane) || tenantControlPlane.Spec.Kubernetes.Version != tenantControlPlane.Status.Kubernetes.Version.Version
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: tenantControlPlane.GetName(),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
Labels: utilities.CommonLabels(tenantControlPlane.GetName()),
|
||||
},
|
||||
}
|
||||
|
||||
r.Name = "deployment"
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// secretHashValue function returns the md5 value for the given secret:
|
||||
// this will trigger a new rollout in case of value change.
|
||||
func (r *KubernetesDeploymentResource) secretHashValue(ctx context.Context, namespace, name string) (string, error) {
|
||||
secret := &corev1.Secret{}
|
||||
|
||||
if err := r.Client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret); err != nil {
|
||||
return "", errors.Wrap(err, "cannot retrieve *corev1.Secret for resource version retrieval")
|
||||
}
|
||||
// Go access map values in random way, it means we have to sort them
|
||||
keys := make([]string, 0, len(secret.Data))
|
||||
|
||||
for k := range secret.Data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
// Generating MD5 of Secret values, sorted by key
|
||||
h := md5.New()
|
||||
|
||||
for _, key := range keys {
|
||||
h.Write(secret.Data[key])
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
maxSurge := intstr.FromString("100%")
|
||||
|
||||
maxUnavailable := intstr.FromInt(0)
|
||||
|
||||
etcdEndpoints := make([]string, len(r.ETCDEndpoints))
|
||||
for i, v := range r.ETCDEndpoints {
|
||||
etcdEndpoints[i] = fmt.Sprintf("https://%s", v)
|
||||
}
|
||||
|
||||
address, err := tenantControlPlane.GetAddress(ctx, r.Client)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "cannot create TenantControlPlane Deployment")
|
||||
}
|
||||
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, func() error {
|
||||
labels := utilities.MergeMaps(r.resource.GetLabels(), tenantControlPlane.Spec.ControlPlane.Deployment.AdditionalMetadata.Labels)
|
||||
r.resource.SetLabels(labels)
|
||||
|
||||
annotations := utilities.MergeMaps(r.resource.GetAnnotations(), tenantControlPlane.Spec.ControlPlane.Deployment.AdditionalMetadata.Annotations)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
r.resource.Spec.Replicas = pointer.Int32(tenantControlPlane.Spec.ControlPlane.Deployment.Replicas)
|
||||
r.resource.Spec.Selector = &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"kamaji.clastix.io/soot": tenantControlPlane.GetName(),
|
||||
},
|
||||
}
|
||||
r.resource.Spec.Template.ObjectMeta = metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"kamaji.clastix.io/soot": tenantControlPlane.GetName(),
|
||||
"component.kamaji.clastix.io/api-server-certificate": func() (hash string) {
|
||||
hash, _ = r.secretHashValue(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.APIServer.SecretName)
|
||||
|
||||
return
|
||||
}(),
|
||||
"component.kamaji.clastix.io/api-server-kubelet-client-certificate": func() (hash string) {
|
||||
hash, _ = r.secretHashValue(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.APIServerKubeletClient.SecretName)
|
||||
|
||||
return
|
||||
}(),
|
||||
"component.kamaji.clastix.io/ca": func() (hash string) {
|
||||
hash, _ = r.secretHashValue(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.CA.SecretName)
|
||||
|
||||
return
|
||||
}(),
|
||||
"component.kamaji.clastix.io/controller-manager-kubeconfig": func() (hash string) {
|
||||
hash, _ = r.secretHashValue(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.KubeConfig.ControllerManager.SecretName)
|
||||
|
||||
return
|
||||
}(),
|
||||
"component.kamaji.clastix.io/etcd-ca-certificates": func() (hash string) {
|
||||
hash, _ = r.secretHashValue(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.ETCD.CA.SecretName)
|
||||
|
||||
return
|
||||
}(),
|
||||
"component.kamaji.clastix.io/etcd-certificates": func() (hash string) {
|
||||
hash, _ = r.secretHashValue(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.ETCD.APIServer.SecretName)
|
||||
|
||||
return
|
||||
}(),
|
||||
"component.kamaji.clastix.io/front-proxy-ca-certificate": func() (hash string) {
|
||||
hash, _ = r.secretHashValue(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.FrontProxyCA.SecretName)
|
||||
|
||||
return
|
||||
}(),
|
||||
"component.kamaji.clastix.io/front-proxy-client-certificate": func() (hash string) {
|
||||
hash, _ = r.secretHashValue(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.FrontProxyClient.SecretName)
|
||||
|
||||
return
|
||||
}(),
|
||||
"component.kamaji.clastix.io/service-account": func() (hash string) {
|
||||
hash, _ = r.secretHashValue(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.SA.SecretName)
|
||||
|
||||
return
|
||||
}(),
|
||||
"component.kamaji.clastix.io/scheduler-kubeconfig": func() (hash string) {
|
||||
hash, _ = r.secretHashValue(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.KubeConfig.Scheduler.SecretName)
|
||||
|
||||
return
|
||||
}(),
|
||||
},
|
||||
}
|
||||
r.resource.Spec.Template.Spec.Volumes = []corev1.Volume{
|
||||
{
|
||||
Name: "etc-kubernetes-pki",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{
|
||||
Sources: []corev1.VolumeProjection{
|
||||
{
|
||||
Secret: secretProjection(tenantControlPlane.Status.Certificates.APIServer.SecretName, constants.APIServerCertName, constants.APIServerKeyName),
|
||||
},
|
||||
{
|
||||
Secret: secretProjection(tenantControlPlane.Status.Certificates.CA.SecretName, constants.CACertName, constants.CAKeyName),
|
||||
},
|
||||
{
|
||||
Secret: secretProjection(tenantControlPlane.Status.Certificates.APIServerKubeletClient.SecretName, constants.APIServerKubeletClientCertName, constants.APIServerKubeletClientKeyName),
|
||||
},
|
||||
{
|
||||
Secret: secretProjection(tenantControlPlane.Status.Certificates.FrontProxyCA.SecretName, constants.FrontProxyCACertName, constants.FrontProxyCAKeyName),
|
||||
},
|
||||
{
|
||||
Secret: secretProjection(tenantControlPlane.Status.Certificates.FrontProxyClient.SecretName, constants.FrontProxyClientCertName, constants.FrontProxyClientKeyName),
|
||||
},
|
||||
{
|
||||
Secret: secretProjection(tenantControlPlane.Status.Certificates.SA.SecretName, constants.ServiceAccountPublicKeyName, constants.ServiceAccountPrivateKeyName),
|
||||
},
|
||||
{
|
||||
Secret: secretProjection(tenantControlPlane.Status.Certificates.ETCD.APIServer.SecretName, constants.APIServerEtcdClientCertName, constants.APIServerEtcdClientKeyName),
|
||||
},
|
||||
{
|
||||
Secret: &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: tenantControlPlane.Status.Certificates.ETCD.CA.SecretName,
|
||||
},
|
||||
Items: []corev1.KeyToPath{
|
||||
{
|
||||
Key: constants.CACertName,
|
||||
Path: constants.EtcdCACertName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "etc-ca-certificates",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tenantControlPlane.Status.Certificates.CA.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "etc-ssl-certs",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tenantControlPlane.Status.Certificates.CA.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "usr-share-ca-certificates",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tenantControlPlane.Status.Certificates.CA.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "usr-local-share-ca-certificates",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tenantControlPlane.Status.Certificates.CA.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "scheduler-kubeconfig",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tenantControlPlane.Status.KubeConfig.Scheduler.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "controller-manager-kubeconfig",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: tenantControlPlane.Status.KubeConfig.ControllerManager.SecretName,
|
||||
DefaultMode: pointer.Int32Ptr(420),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r.resource.Spec.Template.Spec.Containers = []corev1.Container{
|
||||
{
|
||||
Name: "kube-apiserver",
|
||||
Image: fmt.Sprintf("k8s.gcr.io/kube-apiserver:%s", tenantControlPlane.Spec.Kubernetes.Version),
|
||||
Command: []string{
|
||||
"kube-apiserver",
|
||||
"--allow-privileged=true",
|
||||
"--authorization-mode=Node,RBAC",
|
||||
fmt.Sprintf("--advertise-address=%s", address),
|
||||
fmt.Sprintf("--client-ca-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName)),
|
||||
fmt.Sprintf("--enable-admission-plugins=%s", strings.Join(tenantControlPlane.Spec.Kubernetes.AdmissionControllers.ToSlice(), ",")),
|
||||
"--enable-bootstrap-token-auth=true",
|
||||
fmt.Sprintf("--etcd-compaction-interval=%s", r.ETCDCompactionInterval),
|
||||
fmt.Sprintf("--etcd-cafile=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.EtcdCACertName)),
|
||||
fmt.Sprintf("--etcd-certfile=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerEtcdClientCertName)),
|
||||
fmt.Sprintf("--etcd-keyfile=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerEtcdClientKeyName)),
|
||||
fmt.Sprintf("--etcd-servers=%s", strings.Join(etcdEndpoints, ",")),
|
||||
fmt.Sprintf("--etcd-prefix=/%s", tenantControlPlane.GetName()),
|
||||
fmt.Sprintf("--service-cluster-ip-range=%s", tenantControlPlane.Spec.NetworkProfile.ServiceCIDR),
|
||||
fmt.Sprintf("--kubelet-client-certificate=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerKubeletClientCertName)),
|
||||
fmt.Sprintf("--kubelet-client-key=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerKubeletClientKeyName)),
|
||||
"--kubelet-preferred-address-types=Hostname,InternalIP,ExternalIP",
|
||||
fmt.Sprintf("--proxy-client-cert-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyClientCertName)),
|
||||
fmt.Sprintf("--proxy-client-key-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyClientKeyName)),
|
||||
"--requestheader-allowed-names=front-proxy-client",
|
||||
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
|
||||
"--requestheader-group-headers=X-Remote-Group",
|
||||
"--requestheader-username-headers=X-Remote-User",
|
||||
fmt.Sprintf("--secure-port=%d", tenantControlPlane.Spec.NetworkProfile.Port),
|
||||
fmt.Sprintf("--service-account-issuer=https://localhost:%d", tenantControlPlane.Spec.NetworkProfile.Port),
|
||||
fmt.Sprintf("--service-account-key-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPublicKeyName)),
|
||||
fmt.Sprintf("--service-account-signing-key-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPrivateKeyName)),
|
||||
fmt.Sprintf("--tls-cert-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerCertName)),
|
||||
fmt.Sprintf("--tls-private-key-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerKeyName)),
|
||||
},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Limits: nil,
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: quantity.MustParse("250m"),
|
||||
},
|
||||
},
|
||||
LivenessProbe: &corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/livez",
|
||||
Port: intstr.FromInt(int(tenantControlPlane.Spec.NetworkProfile.Port)),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 0,
|
||||
TimeoutSeconds: 1,
|
||||
PeriodSeconds: 10,
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 3,
|
||||
},
|
||||
ReadinessProbe: &corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/readyz",
|
||||
Port: intstr.FromInt(int(tenantControlPlane.Spec.NetworkProfile.Port)),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 0,
|
||||
TimeoutSeconds: 1,
|
||||
PeriodSeconds: 10,
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 3,
|
||||
},
|
||||
StartupProbe: &corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/livez",
|
||||
Port: intstr.FromInt(int(tenantControlPlane.Spec.NetworkProfile.Port)),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 0,
|
||||
TimeoutSeconds: 1,
|
||||
PeriodSeconds: 10,
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 3,
|
||||
},
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
TerminationMessagePolicy: "File",
|
||||
ImagePullPolicy: corev1.PullAlways,
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
Name: "etc-kubernetes-pki",
|
||||
ReadOnly: true,
|
||||
MountPath: v1beta3.DefaultCertificatesDir,
|
||||
},
|
||||
{
|
||||
Name: "etc-ca-certificates",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/ca-certificates",
|
||||
},
|
||||
{
|
||||
Name: "etc-ssl-certs",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/ssl/certs",
|
||||
},
|
||||
{
|
||||
Name: "usr-share-ca-certificates",
|
||||
ReadOnly: true,
|
||||
MountPath: "/usr/share/ca-certificates",
|
||||
},
|
||||
{
|
||||
Name: "usr-local-share-ca-certificates",
|
||||
ReadOnly: true,
|
||||
MountPath: "/usr/local/share/ca-certificates",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "kube-scheduler",
|
||||
Image: fmt.Sprintf("k8s.gcr.io/kube-scheduler:%s", tenantControlPlane.Spec.Kubernetes.Version),
|
||||
Command: []string{
|
||||
"kube-scheduler",
|
||||
"--authentication-kubeconfig=/etc/kubernetes/scheduler.conf",
|
||||
"--authorization-kubeconfig=/etc/kubernetes/scheduler.conf",
|
||||
"--bind-address=0.0.0.0",
|
||||
"--kubeconfig=/etc/kubernetes/scheduler.conf",
|
||||
"--leader-elect=true",
|
||||
},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Limits: nil,
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: quantity.MustParse("100m"),
|
||||
},
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
Name: "scheduler-kubeconfig",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/kubernetes",
|
||||
},
|
||||
},
|
||||
LivenessProbe: &corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/healthz",
|
||||
Port: intstr.FromInt(10259),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 0,
|
||||
TimeoutSeconds: 1,
|
||||
PeriodSeconds: 10,
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 3,
|
||||
},
|
||||
StartupProbe: &corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/healthz",
|
||||
Port: intstr.FromInt(10259),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 0,
|
||||
TimeoutSeconds: 1,
|
||||
PeriodSeconds: 10,
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 3,
|
||||
},
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
TerminationMessagePolicy: "File",
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
},
|
||||
{
|
||||
Name: "kube-controller-manager",
|
||||
Image: fmt.Sprintf("k8s.gcr.io/kube-controller-manager:%s", tenantControlPlane.Spec.Kubernetes.Version),
|
||||
Command: []string{
|
||||
"kube-controller-manager",
|
||||
"--allocate-node-cidrs=true",
|
||||
"--authentication-kubeconfig=/etc/kubernetes/controller-manager.conf",
|
||||
"--authorization-kubeconfig=/etc/kubernetes/controller-manager.conf",
|
||||
"--bind-address=0.0.0.0",
|
||||
fmt.Sprintf("--client-ca-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName)),
|
||||
fmt.Sprintf("--cluster-name=%s", tenantControlPlane.GetName()),
|
||||
fmt.Sprintf("--cluster-signing-cert-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName)),
|
||||
fmt.Sprintf("--cluster-signing-key-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.CAKeyName)),
|
||||
"--controllers=*,bootstrapsigner,tokencleaner",
|
||||
"--kubeconfig=/etc/kubernetes/controller-manager.conf",
|
||||
"--leader-elect=true",
|
||||
fmt.Sprintf("--service-cluster-ip-range=%s", tenantControlPlane.Spec.NetworkProfile.ServiceCIDR),
|
||||
fmt.Sprintf("--cluster-cidr=%s", tenantControlPlane.Spec.NetworkProfile.PodCIDR),
|
||||
fmt.Sprintf("--requestheader-client-ca-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyCACertName)),
|
||||
fmt.Sprintf("--root-ca-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName)),
|
||||
fmt.Sprintf("--service-account-private-key-file=%s", path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPrivateKeyName)),
|
||||
"--use-service-account-credentials=true",
|
||||
},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Limits: nil,
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceCPU: quantity.MustParse("200m"),
|
||||
},
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
Name: "controller-manager-kubeconfig",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/kubernetes",
|
||||
},
|
||||
{
|
||||
Name: "etc-kubernetes-pki",
|
||||
ReadOnly: true,
|
||||
MountPath: v1beta3.DefaultCertificatesDir,
|
||||
},
|
||||
{
|
||||
Name: "etc-ca-certificates",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/ca-certificates",
|
||||
},
|
||||
{
|
||||
Name: "etc-ssl-certs",
|
||||
ReadOnly: true,
|
||||
MountPath: "/etc/ssl/certs",
|
||||
},
|
||||
{
|
||||
Name: "usr-share-ca-certificates",
|
||||
ReadOnly: true,
|
||||
MountPath: "/usr/share/ca-certificates",
|
||||
},
|
||||
{
|
||||
Name: "usr-local-share-ca-certificates",
|
||||
ReadOnly: true,
|
||||
MountPath: "/usr/local/share/ca-certificates",
|
||||
},
|
||||
},
|
||||
LivenessProbe: &corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/healthz",
|
||||
Port: intstr.FromInt(10257),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 0,
|
||||
TimeoutSeconds: 1,
|
||||
PeriodSeconds: 10,
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 3,
|
||||
},
|
||||
StartupProbe: &corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{
|
||||
HTTPGet: &corev1.HTTPGetAction{
|
||||
Path: "/healthz",
|
||||
Port: intstr.FromInt(10257),
|
||||
Scheme: corev1.URISchemeHTTPS,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 0,
|
||||
TimeoutSeconds: 1,
|
||||
PeriodSeconds: 10,
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 3,
|
||||
},
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
TerminationMessagePolicy: "File",
|
||||
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||
},
|
||||
}
|
||||
r.resource.Spec.Strategy = appsv1.DeploymentStrategy{
|
||||
Type: appsv1.RollingUpdateDeploymentStrategyType,
|
||||
RollingUpdate: &appsv1.RollingUpdateDeployment{
|
||||
MaxUnavailable: &maxUnavailable,
|
||||
MaxSurge: &maxSurge,
|
||||
},
|
||||
}
|
||||
|
||||
return controllerutil.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
})
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
switch {
|
||||
case !r.isProgressingUpgrade():
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionReady
|
||||
tenantControlPlane.Status.Kubernetes.Version.Version = tenantControlPlane.Spec.Kubernetes.Version
|
||||
case r.isUpgrading(tenantControlPlane):
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionUpgrading
|
||||
case r.isProvisioning(tenantControlPlane):
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionProvisioning
|
||||
case r.isNotReady():
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionNotReady
|
||||
}
|
||||
|
||||
tenantControlPlane.Status.Kubernetes.Deployment = kamajiv1alpha1.KubernetesDeploymentStatus{
|
||||
DeploymentStatus: r.resource.Status,
|
||||
Name: r.resource.GetName(),
|
||||
Namespace: r.resource.GetNamespace(),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) isProgressingUpgrade() bool {
|
||||
if r.resource.ObjectMeta.GetGeneration() != r.resource.Status.ObservedGeneration {
|
||||
return true
|
||||
}
|
||||
|
||||
if r.resource.Status.UnavailableReplicas > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) isUpgrading(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return len(tenantControlPlane.Status.Kubernetes.Version.Version) > 0 &&
|
||||
tenantControlPlane.Spec.Kubernetes.Version != tenantControlPlane.Status.Kubernetes.Version.Version &&
|
||||
r.isProgressingUpgrade()
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) isProvisioning(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return len(tenantControlPlane.Status.Kubernetes.Version.Version) == 0
|
||||
}
|
||||
|
||||
func (r *KubernetesDeploymentResource) isNotReady() bool {
|
||||
return r.resource.Status.ReadyReplicas == 0
|
||||
}
|
||||
137
internal/resources/k8s_ingress_resource.go
Normal file
137
internal/resources/k8s_ingress_resource.go
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type KubernetesIngressResource struct {
|
||||
resource *networkingv1.Ingress
|
||||
Client client.Client
|
||||
Name string
|
||||
}
|
||||
|
||||
func (r *KubernetesIngressResource) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return !(tenantControlPlane.Status.Kubernetes.Ingress.Name == r.resource.GetName() &&
|
||||
tenantControlPlane.Status.Kubernetes.Ingress.Namespace == r.resource.GetNamespace())
|
||||
}
|
||||
|
||||
func (r *KubernetesIngressResource) ShouldCleanup(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return !tenantControlPlane.Spec.ControlPlane.Ingress.Enabled
|
||||
}
|
||||
|
||||
func (r *KubernetesIngressResource) CleanUp(ctx context.Context) (bool, error) {
|
||||
if err := r.Client.Delete(ctx, r.resource); err != nil {
|
||||
if !k8serrors.IsNotFound(err) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *KubernetesIngressResource) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
if tenantControlPlane.Spec.ControlPlane.Ingress.Enabled {
|
||||
tenantControlPlane.Status.Kubernetes.Ingress.IngressStatus = r.resource.Status
|
||||
tenantControlPlane.Status.Kubernetes.Ingress.Name = r.resource.GetName()
|
||||
tenantControlPlane.Status.Kubernetes.Ingress.Namespace = r.resource.GetNamespace()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
tenantControlPlane.Status.Kubernetes.Ingress = kamajiv1alpha1.KubernetesIngressStatus{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubernetesIngressResource) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: tenantControlPlane.GetName(),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
Labels: utilities.CommonLabels(tenantControlPlane.GetName()),
|
||||
},
|
||||
}
|
||||
|
||||
r.Name = "ingress"
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubernetesIngressResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, func() error {
|
||||
labels := utilities.MergeMaps(r.resource.GetLabels(), tenantControlPlane.Spec.ControlPlane.Ingress.AdditionalMetadata.Labels)
|
||||
r.resource.SetLabels(labels)
|
||||
|
||||
annotations := utilities.MergeMaps(r.resource.GetAnnotations(), tenantControlPlane.Spec.ControlPlane.Ingress.AdditionalMetadata.Annotations)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
if tenantControlPlane.Spec.ControlPlane.Ingress.IngressClassName != "" {
|
||||
r.resource.Spec.IngressClassName = &tenantControlPlane.Spec.ControlPlane.Ingress.IngressClassName
|
||||
}
|
||||
|
||||
var rule networkingv1.IngressRule
|
||||
if len(r.resource.Spec.Rules) > 0 {
|
||||
rule = r.resource.Spec.Rules[0]
|
||||
}
|
||||
|
||||
var path networkingv1.HTTPIngressPath
|
||||
if rule.HTTP != nil && len(rule.HTTP.Paths) > 0 {
|
||||
path = rule.HTTP.Paths[0]
|
||||
}
|
||||
|
||||
path.Path = "/"
|
||||
path.PathType = (*networkingv1.PathType)(pointer.StringPtr(string(networkingv1.PathTypePrefix)))
|
||||
|
||||
if path.Backend.Service == nil {
|
||||
path.Backend.Service = &networkingv1.IngressServiceBackend{}
|
||||
}
|
||||
|
||||
if tenantControlPlane.Status.Kubernetes.Service.Name == "" ||
|
||||
tenantControlPlane.Status.Kubernetes.Service.Port == 0 {
|
||||
return fmt.Errorf("ingress cannot be configured yet")
|
||||
}
|
||||
|
||||
path.Backend.Service.Name = tenantControlPlane.Status.Kubernetes.Service.Name
|
||||
path.Backend.Service.Port.Number = tenantControlPlane.Status.Kubernetes.Service.Port
|
||||
|
||||
if rule.HTTP == nil {
|
||||
rule.HTTP = &networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{
|
||||
{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
rule.HTTP.Paths[0] = path
|
||||
rule.Host = tenantControlPlane.Spec.ControlPlane.Ingress.Hostname
|
||||
if rule.Host == "" {
|
||||
rule.Host = fmt.Sprintf("%s.%s.%s", tenantControlPlane.GetName(), tenantControlPlane.GetNamespace(), tenantControlPlane.Spec.NetworkProfile.Domain)
|
||||
}
|
||||
|
||||
r.resource.Spec.Rules = []networkingv1.IngressRule{
|
||||
rule,
|
||||
}
|
||||
|
||||
return controllerutil.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
})
|
||||
}
|
||||
|
||||
func (r *KubernetesIngressResource) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
117
internal/resources/k8s_service_resource.go
Normal file
117
internal/resources/k8s_service_resource.go
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
// KubernetesServiceResource must be the first Resource processed by the TenantControlPlane:
|
||||
// when a TenantControlPlan is expecting a dynamic IP address, the Service will get it from the controller-manager.
|
||||
type KubernetesServiceResource struct {
|
||||
resource *corev1.Service
|
||||
Client client.Client
|
||||
Name string
|
||||
}
|
||||
|
||||
func (r *KubernetesServiceResource) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Kubernetes.Service.Name != r.resource.GetName() ||
|
||||
tenantControlPlane.Status.Kubernetes.Service.Namespace != r.resource.GetNamespace() ||
|
||||
tenantControlPlane.Status.Kubernetes.Service.Port != r.resource.Spec.Ports[0].Port
|
||||
}
|
||||
|
||||
func (r *KubernetesServiceResource) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *KubernetesServiceResource) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *KubernetesServiceResource) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Kubernetes.Service.ServiceStatus = r.resource.Status
|
||||
tenantControlPlane.Status.Kubernetes.Service.Name = r.resource.GetName()
|
||||
tenantControlPlane.Status.Kubernetes.Service.Namespace = r.resource.GetNamespace()
|
||||
tenantControlPlane.Status.Kubernetes.Service.Port = r.resource.Spec.Ports[0].Port
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubernetesServiceResource) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: tenantControlPlane.GetName(),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
Labels: utilities.CommonLabels(tenantControlPlane.GetName()),
|
||||
},
|
||||
}
|
||||
|
||||
r.Name = "service"
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubernetesServiceResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
// We don't need to check error here: in case of dynamic external IP, the Service must be created in advance.
|
||||
// After that, the specific cloud controller-manager will provide an IP that will be then used.
|
||||
address, _ := tenantControlPlane.GetAddress(ctx, r.Client)
|
||||
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, func() error {
|
||||
var servicePort corev1.ServicePort
|
||||
if len(r.resource.Spec.Ports) > 0 {
|
||||
servicePort = r.resource.Spec.Ports[0]
|
||||
}
|
||||
servicePort.Protocol = corev1.ProtocolTCP
|
||||
servicePort.Port = tenantControlPlane.Spec.NetworkProfile.Port
|
||||
servicePort.TargetPort = intstr.FromInt(int(tenantControlPlane.Spec.NetworkProfile.Port))
|
||||
|
||||
r.resource.Spec.Ports = []corev1.ServicePort{servicePort}
|
||||
r.resource.Spec.Selector = map[string]string{
|
||||
"kamaji.clastix.io/soot": tenantControlPlane.GetName(),
|
||||
}
|
||||
|
||||
labels := utilities.MergeMaps(r.resource.GetLabels(), tenantControlPlane.Spec.ControlPlane.Service.AdditionalMetadata.Labels)
|
||||
r.resource.SetLabels(labels)
|
||||
|
||||
annotations := utilities.MergeMaps(r.resource.GetAnnotations(), tenantControlPlane.Spec.ControlPlane.Service.AdditionalMetadata.Annotations)
|
||||
r.resource.SetAnnotations(annotations)
|
||||
|
||||
switch tenantControlPlane.Spec.ControlPlane.Service.ServiceType {
|
||||
case kamajiv1alpha1.ServiceTypeLoadBalancer:
|
||||
r.resource.Spec.Type = corev1.ServiceTypeLoadBalancer
|
||||
|
||||
if len(address) > 0 {
|
||||
r.resource.Spec.LoadBalancerIP = address
|
||||
}
|
||||
case kamajiv1alpha1.ServiceTypeNodePort:
|
||||
r.resource.Spec.Type = corev1.ServiceTypeNodePort
|
||||
r.resource.Spec.Ports[0].NodePort = tenantControlPlane.Spec.NetworkProfile.Port
|
||||
|
||||
if tenantControlPlane.Spec.NetworkProfile.AllowAddressAsExternalIP && len(address) > 0 {
|
||||
r.resource.Spec.ExternalIPs = []string{address}
|
||||
}
|
||||
default:
|
||||
r.resource.Spec.Type = corev1.ServiceTypeClusterIP
|
||||
|
||||
if tenantControlPlane.Spec.NetworkProfile.AllowAddressAsExternalIP && len(address) > 0 {
|
||||
r.resource.Spec.ExternalIPs = []string{address}
|
||||
}
|
||||
}
|
||||
|
||||
return controllerutil.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
})
|
||||
}
|
||||
|
||||
func (r *KubernetesServiceResource) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
142
internal/resources/kubeadm_config.go
Normal file
142
internal/resources/kubeadm_config.go
Normal file
@@ -0,0 +1,142 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/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"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type KubeadmConfigResource struct {
|
||||
resource *corev1.ConfigMap
|
||||
Client client.Client
|
||||
Scheme *runtime.Scheme
|
||||
Log logr.Logger
|
||||
Name string
|
||||
Port int32
|
||||
Domain string
|
||||
PodCIDR string
|
||||
ServiceCIDR string
|
||||
KubernetesVersion string
|
||||
ETCDs []string
|
||||
ETCDCompactionInterval string
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *KubeadmConfigResource) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
address, err := tenantControlPlane.GetAddress(ctx, r.Client)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return !(tenantControlPlane.Status.KubeadmConfig.ResourceVersion == r.resource.ObjectMeta.ResourceVersion &&
|
||||
tenantControlPlane.Status.KubeadmConfig.ConfigmapName == r.resource.GetName() &&
|
||||
tenantControlPlane.Status.ControlPlaneEndpoint == r.getControlPlaneEndpoint(tenantControlPlane, address))
|
||||
}
|
||||
|
||||
func (r *KubeadmConfigResource) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *KubeadmConfigResource) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *KubeadmConfigResource) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: r.getPrefixedName(tenantControlPlane),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubeadmConfigResource) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
|
||||
return utilities.AddTenantPrefix(r.Name, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *KubeadmConfigResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
address, err := tenantControlPlane.GetAddress(ctx, r.Client)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, r.mutate(tenantControlPlane, address))
|
||||
}
|
||||
|
||||
func (r *KubeadmConfigResource) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *KubeadmConfigResource) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
address, _ := tenantControlPlane.GetAddress(ctx, r.Client)
|
||||
|
||||
tenantControlPlane.Status.KubeadmConfig.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.KubeadmConfig.ResourceVersion = r.resource.ObjectMeta.ResourceVersion
|
||||
tenantControlPlane.Status.KubeadmConfig.ConfigmapName = r.resource.GetName()
|
||||
tenantControlPlane.Status.ControlPlaneEndpoint = r.getControlPlaneEndpoint(tenantControlPlane, address)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubeadmConfigResource) getControlPlaneEndpoint(tenantControlPlane *kamajiv1alpha1.TenantControlPlane, address string) string {
|
||||
if !tenantControlPlane.Spec.ControlPlane.Ingress.Enabled {
|
||||
return fmt.Sprintf("%s:%d", address, tenantControlPlane.Spec.NetworkProfile.Port)
|
||||
}
|
||||
|
||||
if tenantControlPlane.Spec.ControlPlane.Ingress.Hostname != "" {
|
||||
return tenantControlPlane.Spec.ControlPlane.Ingress.Hostname
|
||||
}
|
||||
|
||||
return getTenantControllerExternalFQDN(*tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *KubeadmConfigResource) mutate(tenantControlPlane *kamajiv1alpha1.TenantControlPlane, address string) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
r.resource.SetLabels(utilities.KamajiLabels())
|
||||
|
||||
params := kubeadm.Parameters{
|
||||
TenantControlPlaneName: tenantControlPlane.GetName(),
|
||||
TenantControlPlaneNamespace: tenantControlPlane.GetNamespace(),
|
||||
TenantControlPlaneEndpoint: r.getControlPlaneEndpoint(tenantControlPlane, address),
|
||||
TenantControlPlaneAddress: address,
|
||||
TenantControlPlanePort: r.Port,
|
||||
TenantControlPlaneDomain: r.Domain,
|
||||
TenantControlPlanePodCIDR: r.PodCIDR,
|
||||
TenantControlPlaneServiceCIDR: r.ServiceCIDR,
|
||||
TenantControlPlaneVersion: r.KubernetesVersion,
|
||||
ETCDs: r.ETCDs,
|
||||
ETCDCompactionInterval: r.ETCDCompactionInterval,
|
||||
CertificatesDir: r.TmpDirectory,
|
||||
}
|
||||
|
||||
config := kubeadm.CreateKubeadmInitConfiguration(params)
|
||||
data, err := kubeadm.GetKubeadmInitConfigurationMap(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.resource.Data = data
|
||||
|
||||
if err := ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
271
internal/resources/kubeadm_phases.go
Normal file
271
internal/resources/kubeadm_phases.go
Normal file
@@ -0,0 +1,271 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
kubeconfigutil "github.com/clastix/kamaji/internal/kubeconfig"
|
||||
)
|
||||
|
||||
const kubeadmPhaseTimeout = 10 // seconds
|
||||
|
||||
type KubeadmPhase int
|
||||
|
||||
const (
|
||||
PhaseUploadConfigKubeadm KubeadmPhase = iota
|
||||
PhaseUploadConfigKubelet
|
||||
PhaseAddonCoreDNS
|
||||
PhaseAddonKubeProxy
|
||||
PhaseBootstrapToken
|
||||
)
|
||||
|
||||
func (d KubeadmPhase) String() string {
|
||||
return [...]string{"PhaseUploadConfigKubeadm", "PhaseUploadConfigKubelet", "PhaseAddonCoreDNS", "PhaseAddonKubeProxy", "PhaseBootstrapToken"}[d]
|
||||
}
|
||||
|
||||
const (
|
||||
kubeconfigAdminKeyName = "admin.conf"
|
||||
)
|
||||
|
||||
type KubeadmPhaseResource struct {
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
Name string
|
||||
KubeadmPhase KubeadmPhase
|
||||
kubeadmConfigResourceVersion string
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) isStatusEqual(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
status, err := r.getStatus(tenantControlPlane)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return status.KubeadmConfigResourceVersion == r.kubeadmConfigResourceVersion
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return !r.isStatusEqual(tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) getKubeadmPhaseFunction() (func(clientset.Interface, *kubeadm.Configuration) error, error) {
|
||||
switch r.KubeadmPhase {
|
||||
case PhaseUploadConfigKubeadm:
|
||||
return kubeadm.UploadKubeadmConfig, nil
|
||||
case PhaseUploadConfigKubelet:
|
||||
return kubeadm.UploadKubeletConfig, nil
|
||||
case PhaseAddonCoreDNS:
|
||||
return kubeadm.CoreDNSAddon, nil
|
||||
case PhaseAddonKubeProxy:
|
||||
return kubeadm.KubeProxyAddon, nil
|
||||
case PhaseBootstrapToken:
|
||||
return func(client clientset.Interface, config *kubeadm.Configuration) error {
|
||||
bootstrapTokensEnrichment(config.InitConfiguration.BootstrapTokens)
|
||||
|
||||
return kubeadm.BootstrapToken(client, config)
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("no available functionality for phase %s", r.KubeadmPhase)
|
||||
}
|
||||
}
|
||||
|
||||
func bootstrapTokensEnrichment(bootstrapTokens []bootstraptokenv1.BootstrapToken) {
|
||||
var bootstrapToken bootstraptokenv1.BootstrapToken
|
||||
if len(bootstrapTokens) > 0 {
|
||||
bootstrapToken = bootstrapTokens[0]
|
||||
}
|
||||
|
||||
enrichBootstrapToken(&bootstrapToken)
|
||||
bootstrapTokens[0] = bootstrapToken
|
||||
}
|
||||
|
||||
func enrichBootstrapToken(bootstrapToken *bootstraptokenv1.BootstrapToken) {
|
||||
if bootstrapToken.Token == nil {
|
||||
bootstrapToken.Token = &bootstraptokenv1.BootstrapTokenString{}
|
||||
}
|
||||
|
||||
if bootstrapToken.Token.ID == "" {
|
||||
bootstrapToken.Token.ID = fmt.Sprintf("%s.%s", randomString(6), randomString(16))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) GetClient() client.Client {
|
||||
return r.Client
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) GetTmpDirectory() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
status, err := r.getStatus(tenantControlPlane)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
status.LastUpdate = metav1.Now()
|
||||
status.KubeadmConfigResourceVersion = r.kubeadmConfigResourceVersion
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) getStatus(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*kamajiv1alpha1.KubeadmPhaseStatus, error) {
|
||||
switch r.KubeadmPhase {
|
||||
case PhaseUploadConfigKubeadm:
|
||||
return &tenantControlPlane.Status.KubeadmPhase.UploadConfigKubeadm, nil
|
||||
case PhaseUploadConfigKubelet:
|
||||
return &tenantControlPlane.Status.KubeadmPhase.UploadConfigKubelet, nil
|
||||
case PhaseAddonCoreDNS:
|
||||
return &tenantControlPlane.Status.KubeadmPhase.AddonCoreDNS, nil
|
||||
case PhaseAddonKubeProxy:
|
||||
return &tenantControlPlane.Status.KubeadmPhase.AddonKubeProxy, nil
|
||||
case PhaseBootstrapToken:
|
||||
return &tenantControlPlane.Status.KubeadmPhase.BootstrapToken, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%s is not a right kubeadm phase", r.KubeadmPhase)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return r.reconcile(ctx, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) reconcile(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
config, resourceVersion, err := getKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
kubeconfig, err := r.getKubeconfig(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
config.Kubeconfig = *kubeconfig
|
||||
config.Parameters = kubeadm.Parameters{
|
||||
TenantControlPlaneName: tenantControlPlane.GetName(),
|
||||
TenantDNSServiceIPs: tenantControlPlane.Spec.NetworkProfile.DNSServiceIPs,
|
||||
TenantControlPlaneVersion: tenantControlPlane.Spec.Kubernetes.Version,
|
||||
TenantControlPlanePodCIDR: tenantControlPlane.Spec.NetworkProfile.PodCIDR,
|
||||
TenantControlPlaneAddress: tenantControlPlane.Spec.NetworkProfile.Address,
|
||||
TenantControlPlanePort: tenantControlPlane.Spec.NetworkProfile.Port,
|
||||
TenantControlPlaneCGroupDriver: tenantControlPlane.Spec.Kubernetes.Kubelet.CGroupFS.String(),
|
||||
}
|
||||
|
||||
status, err := r.getStatus(tenantControlPlane)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
if resourceVersion == status.KubeadmConfigResourceVersion {
|
||||
r.kubeadmConfigResourceVersion = resourceVersion
|
||||
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
|
||||
client, err := r.getRESTClient(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
fun, err := r.getKubeadmPhaseFunction()
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
if err = fun(client, config); err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
r.kubeadmConfigResourceVersion = resourceVersion
|
||||
|
||||
if status.LastUpdate.IsZero() {
|
||||
return controllerutil.OperationResultCreated, nil
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultUpdated, nil
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) getKubeconfigSecret(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*corev1.Secret, error) {
|
||||
kubeconfigSecretName := tenantControlPlane.Status.KubeConfig.Admin.SecretName
|
||||
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: kubeconfigSecretName}
|
||||
secret := &corev1.Secret{}
|
||||
if err := r.Client.Get(ctx, namespacedName, secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) getKubeconfig(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*kubeconfigutil.Kubeconfig, error) {
|
||||
secretKubeconfig, err := r.getKubeconfigSecret(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bytes, ok := secretKubeconfig.Data[kubeconfigAdminKeyName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not into kubeconfig secret", kubeconfigAdminKeyName)
|
||||
}
|
||||
|
||||
return kubeconfigutil.GetKubeconfigFromBytes(bytes)
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) getRESTClient(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*clientset.Clientset, error) {
|
||||
config, err := r.getRESTClientConfig(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return clientset.NewForConfig(config)
|
||||
}
|
||||
|
||||
func (r *KubeadmPhaseResource) getRESTClientConfig(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*restclient.Config, error) {
|
||||
kubeconfig, err := r.getKubeconfig(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &restclient.Config{
|
||||
Host: fmt.Sprintf("https://%s:%d", getTenantControllerInternalFQDN(*tenantControlPlane), tenantControlPlane.Spec.NetworkProfile.Port),
|
||||
TLSClientConfig: restclient.TLSClientConfig{
|
||||
CAData: kubeconfig.Clusters[0].Cluster.CertificateAuthorityData,
|
||||
CertData: kubeconfig.AuthInfos[0].AuthInfo.ClientCertificateData,
|
||||
KeyData: kubeconfig.AuthInfos[0].AuthInfo.ClientKeyData,
|
||||
},
|
||||
Timeout: time.Second * kubeadmPhaseTimeout,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
190
internal/resources/kubeadm_upgrade.go
Normal file
190
internal/resources/kubeadm_upgrade.go
Normal file
@@ -0,0 +1,190 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
kubeconfigutil "github.com/clastix/kamaji/internal/kubeconfig"
|
||||
kamajiupgrade "github.com/clastix/kamaji/internal/upgrade"
|
||||
)
|
||||
|
||||
type KubernetesUpgrade struct {
|
||||
Name string
|
||||
Client client.Client
|
||||
upgrade upgrade.Upgrade
|
||||
|
||||
inProgress bool
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) Define(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
k.upgrade = upgrade.Upgrade{
|
||||
Before: upgrade.ClusterState{
|
||||
KubeVersion: tenantControlPlane.Status.Kubernetes.Version.Version,
|
||||
},
|
||||
After: upgrade.ClusterState{
|
||||
KubeVersion: tenantControlPlane.Spec.Kubernetes.Version,
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) CleanUp(context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) CreateOrUpdate(ctx context.Context, plane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
// A new installation, no need to upgrade
|
||||
if len(plane.Status.Kubernetes.Version.Version) == 0 {
|
||||
k.inProgress = false
|
||||
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
// No version change, no need to upgrade
|
||||
if plane.Status.Kubernetes.Version.Version == plane.Spec.Kubernetes.Version {
|
||||
k.inProgress = false
|
||||
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
// An upgrade is in progress, let it go
|
||||
if status := plane.Status.Kubernetes.Version.Status; status != nil && *status == kamajiv1alpha1.VersionUpgrading {
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
// Checking if the upgrade is allowed, or not
|
||||
restClient, err := k.getRESTClient(ctx, plane)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "cannot create REST client required for Kubernetes upgrade plan")
|
||||
}
|
||||
|
||||
versionGetter := kamajiupgrade.NewKamajiKubeVersionGetter(restClient)
|
||||
|
||||
if _, err = upgrade.GetAvailableUpgrades(versionGetter, false, false, true, restClient, ""); err != nil {
|
||||
return controllerutil.OperationResultNone, errors.Wrap(err, "cannot retrieve available Upgrades for Kubernetes upgrade plan")
|
||||
}
|
||||
|
||||
if err = k.isUpgradable(); err != nil {
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("the required upgrade plan is not available")
|
||||
}
|
||||
|
||||
k.inProgress = true
|
||||
|
||||
return controllerutil.OperationResultNone, nil
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) GetName() string {
|
||||
return k.Name
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) ShouldStatusBeUpdated(context.Context, *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return k.inProgress
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
if k.inProgress {
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionUpgrading
|
||||
}
|
||||
|
||||
if tenantControlPlane.Spec.Kubernetes.Version == tenantControlPlane.Status.Kubernetes.Version.Version {
|
||||
tenantControlPlane.Status.Kubernetes.Version.Status = &kamajiv1alpha1.VersionReady
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) getKubeconfigSecret(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*corev1.Secret, error) {
|
||||
kubeconfigSecretName := tenantControlPlane.Status.KubeConfig.Admin.SecretName
|
||||
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: kubeconfigSecretName}
|
||||
secret := &corev1.Secret{}
|
||||
if err := k.Client.Get(ctx, namespacedName, secret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) getKubeconfig(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*kubeconfigutil.Kubeconfig, error) {
|
||||
secretKubeconfig, err := k.getKubeconfigSecret(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bytes, ok := secretKubeconfig.Data[kubeconfigAdminKeyName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not into kubeconfig secret", kubeconfigAdminKeyName)
|
||||
}
|
||||
|
||||
return kubeconfigutil.GetKubeconfigFromBytes(bytes)
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) getRESTClient(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*clientset.Clientset, error) {
|
||||
config, err := k.getRESTClientConfig(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return clientset.NewForConfig(config)
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) getRESTClientConfig(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*restclient.Config, error) {
|
||||
kubeconfig, err := k.getKubeconfig(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &restclient.Config{
|
||||
Host: fmt.Sprintf("https://%s:%d", getTenantControllerInternalFQDN(*tenantControlPlane), tenantControlPlane.Spec.NetworkProfile.Port),
|
||||
TLSClientConfig: restclient.TLSClientConfig{
|
||||
CAData: kubeconfig.Clusters[0].Cluster.CertificateAuthorityData,
|
||||
CertData: kubeconfig.AuthInfos[0].AuthInfo.ClientCertificateData,
|
||||
KeyData: kubeconfig.AuthInfos[0].AuthInfo.ClientKeyData,
|
||||
},
|
||||
Timeout: time.Second * kubeadmPhaseTimeout,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (k *KubernetesUpgrade) isUpgradable() error {
|
||||
newK8sVersion, err := version.ParseSemantic(k.upgrade.After.KubeVersion)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("unable to parse normalized version %q as a semantic version", k.upgrade.After.KubeVersion))
|
||||
}
|
||||
|
||||
oldK8sVersion, err := version.ParseSemantic(k.upgrade.Before.KubeVersion)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("unable to parse normalized version %q as a semantic version", k.upgrade.After.KubeVersion))
|
||||
}
|
||||
|
||||
if newK8sVersion.Minor() < oldK8sVersion.Minor() {
|
||||
return fmt.Errorf("cannot downgrade to a previous minor version of Kubernetes")
|
||||
}
|
||||
// Patch upgrades are allowed
|
||||
if newK8sVersion.Minor() == oldK8sVersion.Minor() {
|
||||
return nil
|
||||
}
|
||||
// Following minor release upgrades are allowed
|
||||
if newK8sVersion.Minor() > oldK8sVersion.WithMinor(oldK8sVersion.Minor()+1).Minor() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("an upgrade to a non consecutive Kubernetes minor release is forbidden")
|
||||
}
|
||||
186
internal/resources/kubeconfig.go
Normal file
186
internal/resources/kubeconfig.go
Normal file
@@ -0,0 +1,186 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
const (
|
||||
AdminKubeConfigFileName = kubeadmconstants.AdminKubeConfigFileName
|
||||
ControllerManagerKubeConfigFileName = kubeadmconstants.ControllerManagerKubeConfigFileName
|
||||
SchedulerKubeConfigFileName = kubeadmconstants.SchedulerKubeConfigFileName
|
||||
localhost = "127.0.0.1"
|
||||
)
|
||||
|
||||
type KubeconfigResource struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Scheme *runtime.Scheme
|
||||
Log logr.Logger
|
||||
Name string
|
||||
KubeConfigFileName string
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: r.getPrefixedName(tenantControlPlane),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
|
||||
return utilities.AddTenantPrefix(r.Name, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) GetClient() client.Client {
|
||||
return r.Client
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) GetTmpDirectory() string {
|
||||
return r.TmpDirectory
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
status, err := r.getKubeconfigStatus(tenantControlPlane)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
status.LastUpdate = metav1.Now()
|
||||
status.SecretName = r.resource.GetName()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) getKubeconfigStatus(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*kamajiv1alpha1.KubeconfigStatus, error) {
|
||||
switch r.KubeConfigFileName {
|
||||
case kubeadmconstants.AdminKubeConfigFileName:
|
||||
return &tenantControlPlane.Status.KubeConfig.Admin, nil
|
||||
case kubeadmconstants.ControllerManagerKubeConfigFileName:
|
||||
return &tenantControlPlane.Status.KubeConfig.ControllerManager, nil
|
||||
case kubeadmconstants.SchedulerKubeConfigFileName:
|
||||
return &tenantControlPlane.Status.KubeConfig.Scheduler, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("kubeconfigfilename %s is not a right name", r.KubeConfigFileName)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
latestConfigRV := getLatestConfigRV(*tenantControlPlane)
|
||||
actualConfigRV := r.resource.GetLabels()["latest-config-rv"]
|
||||
if latestConfigRV == actualConfigRV {
|
||||
if kubeadm.IsKubeconfigValid(r.resource.Data[r.KubeConfigFileName]) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
config, _, err := getKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.customizeConfig(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiServerCertificatesSecretNamespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.Certificates.CA.SecretName}
|
||||
apiServerCertificatesSecret := &corev1.Secret{}
|
||||
if err := r.Client.Get(ctx, apiServerCertificatesSecretNamespacedName, apiServerCertificatesSecret); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kubeconfig, err := kubeadm.CreateKubeconfig(
|
||||
r.KubeConfigFileName,
|
||||
kubeadm.CertificatePrivateKeyPair{
|
||||
Certificate: apiServerCertificatesSecret.Data[kubeadmconstants.CACertName],
|
||||
PrivateKey: apiServerCertificatesSecret.Data[kubeadmconstants.CAKeyName],
|
||||
},
|
||||
config,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.resource.Data = map[string][]byte{
|
||||
r.KubeConfigFileName: kubeconfig,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(),
|
||||
map[string]string{
|
||||
"latest-config-rv": latestConfigRV,
|
||||
"kamaji.clastix.io/name": tenantControlPlane.GetName(),
|
||||
"kamaji.clastix.io/component": r.GetName(),
|
||||
},
|
||||
))
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
}
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) customizeConfig(config *kubeadm.Configuration) error {
|
||||
switch r.KubeConfigFileName {
|
||||
case kubeadmconstants.AdminKubeConfigFileName:
|
||||
return r.ingressAsAdvertiseAddress(config)
|
||||
case kubeadmconstants.ControllerManagerKubeConfigFileName:
|
||||
return r.localhostAsAdvertiseAddress(config)
|
||||
case kubeadmconstants.SchedulerKubeConfigFileName:
|
||||
return r.localhostAsAdvertiseAddress(config)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) ingressAsAdvertiseAddress(config *kubeadm.Configuration) error {
|
||||
config.InitConfiguration.LocalAPIEndpoint.BindPort = defaultIngressPort
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *KubeconfigResource) localhostAsAdvertiseAddress(config *kubeadm.Configuration) error {
|
||||
config.InitConfiguration.LocalAPIEndpoint.AdvertiseAddress = localhost
|
||||
|
||||
return nil
|
||||
}
|
||||
96
internal/resources/resource.go
Normal file
96
internal/resources/resource.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
)
|
||||
|
||||
type Resource interface {
|
||||
Define(context.Context, *kamajiv1alpha1.TenantControlPlane) error
|
||||
ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool
|
||||
CleanUp(context.Context) (bool, error)
|
||||
CreateOrUpdate(context.Context, *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error)
|
||||
GetName() string
|
||||
ShouldStatusBeUpdated(context.Context, *kamajiv1alpha1.TenantControlPlane) bool
|
||||
UpdateTenantControlPlaneStatus(context.Context, *kamajiv1alpha1.TenantControlPlane) error
|
||||
}
|
||||
|
||||
type DeleteableResource interface {
|
||||
Delete(context.Context, *kamajiv1alpha1.TenantControlPlane) error
|
||||
}
|
||||
|
||||
type KubeadmResource interface {
|
||||
Resource
|
||||
GetClient() client.Client
|
||||
GetTmpDirectory() string
|
||||
}
|
||||
|
||||
type HandlerConfig struct {
|
||||
Resource Resource
|
||||
TenantControlPlane *kamajiv1alpha1.TenantControlPlane
|
||||
}
|
||||
|
||||
// Handle handles the given resource and returns a boolean to say if the tenantControlPlane has been modified.
|
||||
func Handle(ctx context.Context, resource Resource, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
if err := resource.Define(ctx, tenantControlPlane); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !resource.ShouldCleanup(tenantControlPlane) {
|
||||
return createOrUpdate(ctx, resource, tenantControlPlane)
|
||||
}
|
||||
|
||||
cleanUp, err := resource.CleanUp(ctx)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
if cleanUp {
|
||||
return controllerutil.OperationResultUpdated, nil
|
||||
}
|
||||
|
||||
return controllerutil.OperationResultNone, err
|
||||
}
|
||||
|
||||
func createOrUpdate(ctx context.Context, resource Resource, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
result, err := resource.CreateOrUpdate(ctx, tenantControlPlane)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if result == controllerutil.OperationResultNone && resource.ShouldStatusBeUpdated(ctx, tenantControlPlane) {
|
||||
return controllerutil.OperationResultUpdatedStatusOnly, nil
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getKubeadmConfiguration(ctx context.Context, r KubeadmResource, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*kubeadm.Configuration, string, error) {
|
||||
var configmap corev1.ConfigMap
|
||||
namespacedName := k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.KubeadmConfig.ConfigmapName}
|
||||
if err := r.GetClient().Get(ctx, namespacedName, &configmap); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
config, err := kubeadm.GetKubeadmInitConfigurationFromMap(configmap.Data)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
tmpDirectory := r.GetTmpDirectory()
|
||||
if tmpDirectory != "" {
|
||||
config.InitConfiguration.ClusterConfiguration.CertificatesDir = tmpDirectory
|
||||
}
|
||||
|
||||
return config, configmap.ObjectMeta.ResourceVersion, nil
|
||||
}
|
||||
124
internal/resources/sa_certificate.go
Normal file
124
internal/resources/sa_certificate.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2022 Clastix Labs
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
|
||||
"github.com/clastix/kamaji/internal/kubeadm"
|
||||
"github.com/clastix/kamaji/internal/utilities"
|
||||
)
|
||||
|
||||
type SACertificate struct {
|
||||
resource *corev1.Secret
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
Name string
|
||||
TmpDirectory string
|
||||
}
|
||||
|
||||
func (r *SACertificate) ShouldStatusBeUpdated(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return tenantControlPlane.Status.Certificates.SA.SecretName != r.resource.GetName()
|
||||
}
|
||||
|
||||
func (r *SACertificate) ShouldCleanup(plane *kamajiv1alpha1.TenantControlPlane) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *SACertificate) CleanUp(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *SACertificate) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
r.resource = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: r.getPrefixedName(tenantControlPlane),
|
||||
Namespace: tenantControlPlane.GetNamespace(),
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SACertificate) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
|
||||
return utilities.AddTenantPrefix(r.Name, tenantControlPlane)
|
||||
}
|
||||
|
||||
func (r *SACertificate) GetClient() client.Client {
|
||||
return r.Client
|
||||
}
|
||||
|
||||
func (r *SACertificate) GetTmpDirectory() string {
|
||||
return r.TmpDirectory
|
||||
}
|
||||
|
||||
func (r *SACertificate) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
|
||||
return controllerutil.CreateOrUpdate(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
|
||||
}
|
||||
|
||||
func (r *SACertificate) GetName() string {
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func (r *SACertificate) UpdateTenantControlPlaneStatus(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
|
||||
tenantControlPlane.Status.Certificates.SA.LastUpdate = metav1.Now()
|
||||
tenantControlPlane.Status.Certificates.SA.SecretName = r.resource.GetName()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *SACertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
|
||||
return func() error {
|
||||
latestConfigRV := getLatestConfigRV(*tenantControlPlane)
|
||||
actualConfigRV := r.resource.GetLabels()["latest-config-rv"]
|
||||
if latestConfigRV == actualConfigRV {
|
||||
isValid, err := kubeadm.IsPublicKeyPrivateKeyPairValid(
|
||||
r.resource.Data[kubeadmconstants.ServiceAccountPublicKeyName],
|
||||
r.resource.Data[kubeadmconstants.ServiceAccountPrivateKeyName],
|
||||
)
|
||||
if err != nil {
|
||||
r.Log.Info(fmt.Sprintf("%s public_key-private_key pair is not valid: %s", kubeadmconstants.ServiceAccountKeyBaseName, err.Error()))
|
||||
}
|
||||
if isValid {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
config, _, err := getKubeadmConfiguration(ctx, r, tenantControlPlane)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sa, err := kubeadm.GeneratePublicKeyPrivateKeyPair(kubeadmconstants.ServiceAccountKeyBaseName, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.resource.Data = map[string][]byte{
|
||||
kubeadmconstants.ServiceAccountPublicKeyName: sa.PublicKey,
|
||||
kubeadmconstants.ServiceAccountPrivateKeyName: sa.PrivateKey,
|
||||
}
|
||||
|
||||
r.resource.SetLabels(utilities.MergeMaps(
|
||||
utilities.KamajiLabels(),
|
||||
map[string]string{
|
||||
"latest-config-rv": latestConfigRV,
|
||||
"kamaji.clastix.io/name": tenantControlPlane.GetName(),
|
||||
"kamaji.clastix.io/component": r.GetName(),
|
||||
},
|
||||
))
|
||||
|
||||
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user