zitadel/pkg/kubernetes/artifacts.go
Livio Amstutz bacfc3b099
fix: operator picks (#1463)
* feat(crd): add crd mode for operators (#1329)

* feat(operator): add base for zitadel operator

* fix(operator): changed pipeline to release operator

* fix(operator): fmt with only one parameter

* fix(operator): corrected workflow job name

* fix(zitadelctl): added restore and backuplist command

* fix(zitadelctl): scale for restore

* chore(container): use scratch for deploy container

* fix(zitadelctl): limit image to scratch

* fix(migration): added migration scripts for newer version

* fix(operator): changed handling of kubeconfig in operator logic

* fix(operator): changed handling of secrets in operator logic

* fix(operator): use new version of zitadel

* fix(operator): added path for migrations

* fix(operator): delete doublets of migration scripts

* fix(operator): delete subpaths and integrate logic into init container

* fix(operator): corrected path in dockerfile for local migrations

* fix(operator): added migrations for cockroachdb-secure

* fix(operator): delete logic for ambassador module

* fix(operator): added read and write secret commands

* fix(operator): correct and align operator pipeline with zitadel pipeline

* fix(operator): correct yaml error in operator pipeline

* fix(operator): correct action name in operator pipeline

* fix(operator): correct case-sensitive filename in operator pipeline

* fix(operator): upload artifacts from buildx output

* fix(operator): corrected attribute spelling error

* fix(operator): combined jobs for operator binary and image

* fix(operator): added missing comma in operator pipeline

* fix(operator): added codecov for operator image

* fix(operator): added codecov for operator image

* fix(testing): code changes for testing and several unit-tests (#1009)

* fix(operator): usage of interface of kubernetes client for testing and several unit-tests

* fix(operator): several unit-tests

* fix(operator): several unit-tests

* fix(operator): changed order for the operator logic

* fix(operator): added version of zitadelctl from semantic release

* fix(operator): corrected function call with version of zitadelctl

* fix(operator): corrected function call with version of zitadelctl

* fix(operator): add check output to operator release pipeline

* fix(operator): set --short length everywhere to 12

* fix(operator): zitadel setup in job instead of exec with several unit tests

* fix(operator): fixes to combine newest zitadel and testing branch

* fix(operator): corrected path in Dockerfile

* fix(operator): fixed unit-test that was ignored during changes

* fix(operator): fixed unit-test that was ignored during changes

* fix(operator): corrected Dockerfile to correctly use env variable

* fix(operator): quickfix takeoff deployment

* fix(operator): corrected the clusterrolename in the applied artifacts

* fix: update secure migrations

* fix(operator): migrations (#1057)

* fix(operator): copied migrations from orbos repository

* fix(operator): newest migrations

* chore: use cockroach-secure

* fix: rename migration

* fix: remove insecure cockroach migrations

Co-authored-by: Stefan Benz <stefan@caos.ch>

* fix: finalize labels

* fix(operator): cli logging concurrent and fixe deployment of operator during restore

* fix: finalize labels and cli commands

* fix: restore

* chore: cockroachdb is always secure

* chore: use orbos consistent-labels latest commit

* test: make tests compatible with new labels

* fix: default to sa token for start command

* fix: use cockroachdb v12.02

* fix: don't delete flyway user

* test: fix migration test

* fix: use correct table qualifiers

* fix: don't alter sequence ownership

* fix: upgrade flyway

* fix: change ownership of all dbs and tables to admin user

* fix: change defaultdb user

* fix: treat clientid status codes >= 400 as errors

* fix: reconcile specified ZITADEL version, not binary version

* fix: add ca-certs

* fix: use latest orbos code

* fix: use orbos with fixed race condition

* fix: use latest ORBOS code

* fix: use latest ORBOS code

* fix: make migration and scaling around restoring work

* fix(operator): move zitadel operator

* chore(migrations): include owner change migration

* feat(db): add code base for database operator

* fix(db): change used image registry for database operator

* fix(db): generated mock

* fix(db): add accidentally ignored file

* fix(db): add cockroachdb backup image to pipeline

* fix(db): correct pipeline and image versions

* fix(db): correct version of used orbos

* fix(db): correct database import

* fix(db): go mod tidy

* fix(db): use new version for orbos

* fix(migrations): include migrations into zitadelctl binary (#1211)

* fix(db): use statik to integrate migrations into binary

* fix(migrations): corrections unit tests and pipeline for integrated migrations into zitadelctl binary

* fix(migrations): correction in dockerfile for pipeline build

* fix(migrations): correction in dockerfile for pipeline build

* fix(migrations):  dockerfile changes for cache optimization

* fix(database): correct used part-of label in database operator

* fix(database): correct used selectable label in zitadel operator

* fix(operator): correct lables for user secrets in zitadel operator

* fix(operator): correct lables for service test in zitadel operator

* fix: don't enable database features for user operations (#1227)

* fix: don't enable database features for user operations

* fix: omit database feature for connection info adapter

* fix: use latest orbos version

* fix(crd): corrected logic to get database connection and other info

* fix(crd): corrected yaml tags and start for zitadel operator

* fix(crd): move some dependencies and use consistent structure

* fix(crd): corrected unit-tests

* fix(crd): corrected main files for debug starts

* chore(pipeline): use correct version for zitadelctl build

* fix(crd): correct calculating of current db state for zitadel operator

* fix(crd): use binary version for deployment of crd mode operators

* fix(crd): add gitops attribute for reconciling

* fix(crd): corrected crd with newest version

* fix(migration): collect cleanup functions and only use them if all jobs are successful

* fix(zitadelctl): import gcp auth to connect to gke cluster

* feat: Add read and writesecret options for crd mode (#1435)

* fix: don't require orbconfig for crd mode

* test: pass

* fix(zitadelctl): import gcp auth to connect to gke cluster

* feat: add read and writesecret option for crd mode

* test: fix

* fix: make all crd secrets writable

* fix: use in-cluster configs for in-cluster operators

* chore: remove unnecessary debug files

Co-authored-by: Stefan Benz <stefan@caos.ch>

* fix: Crdoperatormerge review (#1385)

* fix: don't require orbconfig for crd mode

* test: pass

* fix(zitadelctl): import gcp auth to connect to gke cluster

* fix: ensure caos-system namespace

* fix: apply orbconfig at takeoff

* docs: improve help for creating an orbconfig

* docs: describe orbconfig properties

* docs: add --gitops to help message example

* fix(pipeline): correct upload of artifacts in dev releases

* test: pass

Co-authored-by: Stefan Benz <stefan@caos.ch>

* fix(test): corrected falsely merged tests

* chore: update orbos library

* fix: only handle exactly named and namespaced crd resource

* fix: print errors, check correct crd namespace

* fix: validate bucket secret

* chore: compile

* fix(operator): corrected secret handling when unused secrets are not defined

* fix(operator): corrected handling of jobs

* fix: dont print logs when readsecret path is provided

* fix(operator): corrected handling of jobs and sort for mounted volumes

* fix(operator): sort for volumes

* fix(operator): change orboos import to newest release

Co-authored-by: Florian Forster <florian@caos.ch>
Co-authored-by: Elio Bischof <eliobischof@gmail.com>

(cherry picked from commit fa9bd5a8e7)

* fix(operator): Standard timeout handling (#1458)

* fix: always use standard time.Duration

* fix: give backup and restore more time

* fix: give backup and restore jobs more time

(cherry picked from commit 7468b7d1e8)

* fix go mod

Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
Co-authored-by: Elio Bischof <eliobischof@gmail.com>
2021-03-24 10:31:19 +01:00

611 lines
19 KiB
Go

package kubernetes
import (
"fmt"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/caos/orbos/mntr"
"github.com/caos/orbos/pkg/kubernetes"
"github.com/caos/orbos/pkg/labels"
apps "k8s.io/api/apps/v1"
core "k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/resource"
mach "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func EnsureZitadelOperatorArtifacts(
monitor mntr.Monitor,
apiLabels *labels.API,
client kubernetes.ClientInt,
version string,
nodeselector map[string]string,
tolerations []core.Toleration,
imageRegistry string,
gitops bool,
) error {
monitor.WithFields(map[string]interface{}{
"zitadel": version,
}).Debug("Ensuring zitadel artifacts")
nameLabels := labels.MustForName(labels.MustForComponent(apiLabels, "operator"), "zitadel-operator")
k8sNameLabels := labels.MustK8sMap(nameLabels)
k8sPodSelector := labels.MustK8sMap(labels.DeriveNameSelector(nameLabels, false))
if version == "" {
return nil
}
if err := client.ApplyServiceAccount(&core.ServiceAccount{
ObjectMeta: mach.ObjectMeta{
Name: nameLabels.Name(),
Namespace: "caos-system",
},
}); err != nil {
return err
}
if err := client.ApplyClusterRole(&rbac.ClusterRole{
ObjectMeta: mach.ObjectMeta{
Name: nameLabels.Name(),
Labels: k8sNameLabels,
},
Rules: []rbac.PolicyRule{{
APIGroups: []string{"*"},
Resources: []string{"*"},
Verbs: []string{"*"},
}},
}); err != nil {
return err
}
if err := client.ApplyClusterRoleBinding(&rbac.ClusterRoleBinding{
ObjectMeta: mach.ObjectMeta{
Name: nameLabels.Name(),
Labels: k8sNameLabels,
},
RoleRef: rbac.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: nameLabels.Name(),
},
Subjects: []rbac.Subject{{
Kind: "ServiceAccount",
Name: nameLabels.Name(),
Namespace: "caos-system",
}},
}); err != nil {
return err
}
if !gitops {
crd := `apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.2
creationTimestamp: null
name: zitadels.caos.ch
spec:
group: caos.ch
names:
kind: Zitadel
listKind: ZitadelList
plural: zitadels
singular: zitadel
scope: ""
validation:
openAPIV3Schema:
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:
properties:
iam:
type: object
kind:
type: string
spec:
properties:
customImageRegistry:
description: 'Use this registry to pull the zitadel operator image
from @default: ghcr.io'
type: string
databaseCrd:
properties:
name:
type: string
namespace:
type: string
type: object
gitops:
type: boolean
nodeSelector:
additionalProperties:
type: string
type: object
selfReconciling:
type: boolean
tolerations:
items:
description: The pod this Toleration is attached to tolerates
any taint that matches the triple <key,value,effect> using the
matching operator <operator>.
properties:
effect:
description: Effect indicates the taint effect to match. Empty
means match all taint effects. When specified, allowed values
are NoSchedule, PreferNoSchedule and NoExecute.
type: string
key:
description: Key is the taint key that the toleration applies
to. Empty means match all taint keys. If the key is empty,
operator must be Exists; this combination means to match
all values and all keys.
type: string
operator:
description: Operator represents a key's relationship to the
value. Valid operators are Exists and Equal. Defaults to
Equal. Exists is equivalent to wildcard for value, so that
a pod can tolerate all taints of a particular category.
type: string
tolerationSeconds:
description: TolerationSeconds represents the period of time
the toleration (which must be of effect NoExecute, otherwise
this field is ignored) tolerates the taint. By default,
it is not set, which means tolerate the taint forever (do
not evict). Zero and negative values will be treated as
0 (evict immediately) by the system.
format: int64
type: integer
value:
description: Value is the taint value the toleration matches
to. If the operator is Exists, the value should be empty,
otherwise just a regular string.
type: string
type: object
type: array
verbose:
type: boolean
version:
type: string
required:
- selfReconciling
- verbose
type: object
version:
type: string
required:
- iam
- kind
- spec
- version
type: object
status:
type: object
type: object
version: v1
versions:
- name: v1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []`
crdDefinition := &unstructured.Unstructured{}
if err := yaml.Unmarshal([]byte(crd), &crdDefinition.Object); err != nil {
return err
}
if err := client.ApplyCRDResource(
crdDefinition,
); err != nil {
return err
}
monitor.WithFields(map[string]interface{}{
"version": version,
}).Debug("Database Operator crd ensured")
}
var (
cmd = []string{"/zitadelctl", "operator"}
volumes []core.Volume
volumeMounts []core.VolumeMount
)
if gitops {
cmd = append(cmd, "--gitops", "-f", "/secrets/orbconfig")
volumes = []core.Volume{{
Name: "orbconfig",
VolumeSource: core.VolumeSource{
Secret: &core.SecretVolumeSource{
SecretName: "caos",
},
},
}}
volumeMounts = []core.VolumeMount{{
Name: "orbconfig",
ReadOnly: true,
MountPath: "/secrets",
}}
} else {
cmd = append(cmd, "--kubeconfig", "")
}
deployment := &apps.Deployment{
ObjectMeta: mach.ObjectMeta{
Name: nameLabels.Name(),
Namespace: "caos-system",
Labels: k8sNameLabels,
},
Spec: apps.DeploymentSpec{
Replicas: int32Ptr(1),
Selector: &mach.LabelSelector{
MatchLabels: k8sPodSelector,
},
Template: core.PodTemplateSpec{
ObjectMeta: mach.ObjectMeta{
Labels: labels.MustK8sMap(labels.AsSelectable(nameLabels)),
},
Spec: core.PodSpec{
ServiceAccountName: nameLabels.Name(),
Containers: []core.Container{{
Name: "zitadel",
ImagePullPolicy: core.PullIfNotPresent,
Image: fmt.Sprintf("%s/caos/zitadel-operator:%s", imageRegistry, version),
Command: cmd,
Args: []string{},
Ports: []core.ContainerPort{{
Name: "metrics",
ContainerPort: 2112,
Protocol: "TCP",
}},
VolumeMounts: volumeMounts,
Resources: core.ResourceRequirements{
Limits: core.ResourceList{
"cpu": resource.MustParse("500m"),
"memory": resource.MustParse("500Mi"),
},
Requests: core.ResourceList{
"cpu": resource.MustParse("250m"),
"memory": resource.MustParse("250Mi"),
},
},
}},
NodeSelector: nodeselector,
Tolerations: tolerations,
Volumes: volumes,
TerminationGracePeriodSeconds: int64Ptr(10),
},
},
},
}
if err := client.ApplyDeployment(deployment, true); err != nil {
return err
}
monitor.WithFields(map[string]interface{}{
"version": version,
}).Debug("Zitadel Operator deployment ensured")
return nil
}
func ScaleZitadelOperator(
monitor mntr.Monitor,
client *kubernetes.Client,
replicaCount int,
) error {
monitor.Debug("Scaling zitadel-operator")
return client.ScaleDeployment("caos-system", "zitadel-operator", replicaCount)
}
func int32Ptr(i int32) *int32 { return &i }
func int64Ptr(i int64) *int64 { return &i }
func toNameLabels(apiLabels *labels.API, operatorName string) *labels.Name {
return labels.MustForName(labels.MustForComponent(apiLabels, "operator"), operatorName)
}
func EnsureDatabaseArtifacts(
monitor mntr.Monitor,
apiLabels *labels.API,
client kubernetes.ClientInt,
version string,
nodeselector map[string]string,
tolerations []core.Toleration,
imageRegistry string,
gitops bool) error {
monitor.WithFields(map[string]interface{}{
"database": version,
}).Debug("Ensuring database artifacts")
if version == "" {
return nil
}
nameLabels := toNameLabels(apiLabels, "database-operator")
k8sNameLabels := labels.MustK8sMap(nameLabels)
if err := client.ApplyServiceAccount(&core.ServiceAccount{
ObjectMeta: mach.ObjectMeta{
Name: "database-operator",
Namespace: "caos-system",
Labels: k8sNameLabels,
},
}); err != nil {
return err
}
if err := client.ApplyClusterRole(&rbac.ClusterRole{
ObjectMeta: mach.ObjectMeta{
Name: "database-operator-clusterrole",
Labels: k8sNameLabels,
},
Rules: []rbac.PolicyRule{{
APIGroups: []string{"*"},
Resources: []string{"*"},
Verbs: []string{"*"},
}},
}); err != nil {
return err
}
if err := client.ApplyClusterRoleBinding(&rbac.ClusterRoleBinding{
ObjectMeta: mach.ObjectMeta{
Name: "database-operator-clusterrolebinding",
Labels: k8sNameLabels,
},
RoleRef: rbac.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "database-operator-clusterrole",
},
Subjects: []rbac.Subject{{
Kind: "ServiceAccount",
Name: "database-operator",
Namespace: "caos-system",
}},
}); err != nil {
return err
}
if !gitops {
crd := `apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.2
creationTimestamp: null
name: databases.caos.ch
spec:
group: caos.ch
names:
kind: Database
listKind: DatabaseList
plural: databases
singular: database
scope: ""
validation:
openAPIV3Schema:
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:
properties:
database:
type: object
kind:
type: string
spec:
properties:
customImageRegistry:
description: 'Use this registry to pull the Database operator image
from @default: ghcr.io'
type: string
gitOps:
type: boolean
nodeSelector:
additionalProperties:
type: string
type: object
selfReconciling:
type: boolean
tolerations:
items:
description: The pod this Toleration is attached to tolerates
any taint that matches the triple <key,value,effect> using the
matching operator <operator>.
properties:
effect:
description: Effect indicates the taint effect to match. Empty
means match all taint effects. When specified, allowed values
are NoSchedule, PreferNoSchedule and NoExecute.
type: string
key:
description: Key is the taint key that the toleration applies
to. Empty means match all taint keys. If the key is empty,
operator must be Exists; this combination means to match
all values and all keys.
type: string
operator:
description: Operator represents a key's relationship to the
value. Valid operators are Exists and Equal. Defaults to
Equal. Exists is equivalent to wildcard for value, so that
a pod can tolerate all taints of a particular category.
type: string
tolerationSeconds:
description: TolerationSeconds represents the period of time
the toleration (which must be of effect NoExecute, otherwise
this field is ignored) tolerates the taint. By default,
it is not set, which means tolerate the taint forever (do
not evict). Zero and negative values will be treated as
0 (evict immediately) by the system.
format: int64
type: integer
value:
description: Value is the taint value the toleration matches
to. If the operator is Exists, the value should be empty,
otherwise just a regular string.
type: string
type: object
type: array
verbose:
type: boolean
version:
type: string
required:
- selfReconciling
- verbose
type: object
version:
type: string
required:
- database
- kind
- spec
- version
type: object
status:
type: object
type: object
version: v1
versions:
- name: v1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []`
crdDefinition := &unstructured.Unstructured{}
if err := yaml.Unmarshal([]byte(crd), &crdDefinition.Object); err != nil {
return err
}
if err := client.ApplyCRDResource(
crdDefinition,
); err != nil {
return err
}
monitor.WithFields(map[string]interface{}{
"version": version,
}).Debug("Database Operator crd ensured")
}
var (
cmd = []string{"/zitadelctl", "database"}
volumes []core.Volume
volumeMounts []core.VolumeMount
)
if gitops {
cmd = append(cmd, "--gitops", "-f", "/secrets/orbconfig")
volumes = []core.Volume{{
Name: "orbconfig",
VolumeSource: core.VolumeSource{
Secret: &core.SecretVolumeSource{
SecretName: "caos",
},
},
}}
volumeMounts = []core.VolumeMount{{
Name: "orbconfig",
ReadOnly: true,
MountPath: "/secrets",
}}
} else {
cmd = append(cmd, "--kubeconfig", "")
}
deployment := &apps.Deployment{
ObjectMeta: mach.ObjectMeta{
Name: "database-operator",
Namespace: "caos-system",
Labels: k8sNameLabels,
},
Spec: apps.DeploymentSpec{
Replicas: int32Ptr(1),
Selector: &mach.LabelSelector{
MatchLabels: labels.MustK8sMap(labels.DeriveNameSelector(nameLabels, false)),
},
Template: core.PodTemplateSpec{
ObjectMeta: mach.ObjectMeta{
Labels: labels.MustK8sMap(labels.AsSelectable(nameLabels)),
},
Spec: core.PodSpec{
ServiceAccountName: "database-operator",
Containers: []core.Container{{
Name: "database",
ImagePullPolicy: core.PullIfNotPresent,
Image: fmt.Sprintf("%s/caos/zitadel-operator:%s", imageRegistry, version),
Command: cmd,
Args: []string{},
Ports: []core.ContainerPort{{
Name: "metrics",
ContainerPort: 2112,
Protocol: "TCP",
}},
VolumeMounts: volumeMounts,
Resources: core.ResourceRequirements{
Limits: core.ResourceList{
"cpu": resource.MustParse("500m"),
"memory": resource.MustParse("500Mi"),
},
Requests: core.ResourceList{
"cpu": resource.MustParse("250m"),
"memory": resource.MustParse("250Mi"),
},
},
}},
NodeSelector: nodeselector,
Tolerations: tolerations,
Volumes: volumes,
TerminationGracePeriodSeconds: int64Ptr(10),
},
},
},
}
if err := client.ApplyDeployment(deployment, true); err != nil {
return err
}
monitor.WithFields(map[string]interface{}{
"version": version,
}).Debug("Database Operator deployment ensured")
return nil
}