Compare commits

...

4 Commits

Author SHA1 Message Date
Dario Tranchitella
8273d7c7b4 chore(golangci-lint): updating to v1.49.0 2022-08-27 15:16:31 +02:00
Dario Tranchitella
a9ea894e32 chore(kustomize)!: storage homogeneity 2022-08-27 15:16:31 +02:00
Dario Tranchitella
ff780aaba6 feat(helm)!: storage homogeneity 2022-08-27 15:16:31 +02:00
Dario Tranchitella
1ddaeccc94 feat: storage homogeneity 2022-08-27 15:16:31 +02:00
58 changed files with 1141 additions and 2082 deletions

View File

@@ -18,7 +18,7 @@ jobs:
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v2.3.0
with:
version: v1.45.2
version: v1.49.0
only-new-issues: false
args: --timeout 5m --config .golangci.yml
diff:

View File

@@ -27,6 +27,10 @@ linters:
- exhaustivestruct
- wsl
- exhaustive
- nosprintfhostport
- nonamedreturns
- interfacebloat
- exhaustruct
- lll
- gosec
- gomoddirectives

View File

@@ -2,8 +2,9 @@
// 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
// +kubebuilder:object:generate=true
// +groupName=kamaji.clastix.io
//nolint
package v1alpha1
import (

View File

@@ -8,8 +8,6 @@ import (
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"
)
// APIServerCertificatesStatus defines the observed state of ETCD Certificate for API server.
@@ -57,41 +55,30 @@ type CertificatesStatus struct {
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"`
}
type SQLCertificateStatus struct {
type DataStoreCertificateStatus struct {
SecretName string `json:"secretName,omitempty"`
Checksum string `json:"checksum,omitempty"`
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
}
type SQLConfigStatus struct {
type DataStoreConfigStatus struct {
SecretName string `json:"secretName,omitempty"`
Checksum string `json:"checksum,omitempty"`
}
type SQLSetupStatus struct {
type DataStoreSetupStatus struct {
Schema string `json:"schema,omitempty"`
User string `json:"user,omitempty"`
LastUpdate metav1.Time `json:"lastUpdate,omitempty"`
Checksum string `json:"checksum,omitempty"`
}
type KineStatus struct {
Driver string `json:"driver,omitempty"`
Config SQLConfigStatus `json:"config,omitempty"`
Setup SQLSetupStatus `json:"setup,omitempty"`
Certificate SQLCertificateStatus `json:"certificate,omitempty"`
}
// StorageStatus defines the observed state of StorageStatus.
type StorageStatus struct {
ETCD *ETCDStatus `json:"etcd,omitempty"`
Kine *KineStatus `json:"kine,omitempty"`
Driver string `json:"driver,omitempty"`
Config DataStoreConfigStatus `json:"config,omitempty"`
Setup DataStoreSetupStatus `json:"setup,omitempty"`
Certificate DataStoreCertificateStatus `json:"certificate,omitempty"`
}
// KubeconfigStatus contains information about the generated kubeconfig.
@@ -163,9 +150,8 @@ type AddonStatus struct {
// AddonsStatus defines the observed state of the different Addons.
type AddonsStatus struct {
CoreDNS AddonStatus `json:"coreDNS,omitempty"`
KubeProxy AddonStatus `json:"kubeProxy,omitempty"`
CoreDNS AddonStatus `json:"coreDNS,omitempty"`
KubeProxy AddonStatus `json:"kubeProxy,omitempty"`
Konnectivity KonnectivityStatus `json:"konnectivity,omitempty"`
}

View File

@@ -392,6 +392,37 @@ func (in *DataStore) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DataStoreCertificateStatus) DeepCopyInto(out *DataStoreCertificateStatus) {
*out = *in
in.LastUpdate.DeepCopyInto(&out.LastUpdate)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataStoreCertificateStatus.
func (in *DataStoreCertificateStatus) DeepCopy() *DataStoreCertificateStatus {
if in == nil {
return nil
}
out := new(DataStoreCertificateStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DataStoreConfigStatus) DeepCopyInto(out *DataStoreConfigStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataStoreConfigStatus.
func (in *DataStoreConfigStatus) DeepCopy() *DataStoreConfigStatus {
if in == nil {
return nil
}
out := new(DataStoreConfigStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DataStoreList) DeepCopyInto(out *DataStoreList) {
*out = *in
@@ -424,6 +455,22 @@ func (in *DataStoreList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DataStoreSetupStatus) DeepCopyInto(out *DataStoreSetupStatus) {
*out = *in
in.LastUpdate.DeepCopyInto(&out.LastUpdate)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataStoreSetupStatus.
func (in *DataStoreSetupStatus) DeepCopy() *DataStoreSetupStatus {
if in == nil {
return nil
}
out := new(DataStoreSetupStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DataStoreSpec) DeepCopyInto(out *DataStoreSpec) {
*out = *in
@@ -529,23 +576,6 @@ func (in *ETCDCertificatesStatus) DeepCopy() *ETCDCertificatesStatus {
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 *ExternalKubernetesObjectStatus) DeepCopyInto(out *ExternalKubernetesObjectStatus) {
*out = *in
@@ -578,24 +608,6 @@ func (in *IngressSpec) DeepCopy() *IngressSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KineStatus) DeepCopyInto(out *KineStatus) {
*out = *in
out.Config = in.Config
in.Setup.DeepCopyInto(&out.Setup)
in.Certificate.DeepCopyInto(&out.Certificate)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KineStatus.
func (in *KineStatus) DeepCopy() *KineStatus {
if in == nil {
return nil
}
out := new(KineStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KonnectivityConfigMap) DeepCopyInto(out *KonnectivityConfigMap) {
*out = *in
@@ -906,53 +918,6 @@ func (in *PublicKeyPrivateKeyPairStatus) DeepCopy() *PublicKeyPrivateKeyPairStat
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SQLCertificateStatus) DeepCopyInto(out *SQLCertificateStatus) {
*out = *in
in.LastUpdate.DeepCopyInto(&out.LastUpdate)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQLCertificateStatus.
func (in *SQLCertificateStatus) DeepCopy() *SQLCertificateStatus {
if in == nil {
return nil
}
out := new(SQLCertificateStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SQLConfigStatus) DeepCopyInto(out *SQLConfigStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQLConfigStatus.
func (in *SQLConfigStatus) DeepCopy() *SQLConfigStatus {
if in == nil {
return nil
}
out := new(SQLConfigStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SQLSetupStatus) DeepCopyInto(out *SQLSetupStatus) {
*out = *in
in.LastUpdate.DeepCopyInto(&out.LastUpdate)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQLSetupStatus.
func (in *SQLSetupStatus) DeepCopy() *SQLSetupStatus {
if in == nil {
return nil
}
out := new(SQLSetupStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretReference) DeepCopyInto(out *SecretReference) {
*out = *in
@@ -988,16 +953,9 @@ func (in *ServiceSpec) DeepCopy() *ServiceSpec {
// 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)
}
if in.Kine != nil {
in, out := &in.Kine, &out.Kine
*out = new(KineStatus)
(*in).DeepCopyInto(*out)
}
out.Config = in.Config
in.Setup.DeepCopyInto(&out.Setup)
in.Certificate.DeepCopyInto(&out.Certificate)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageStatus.

View File

@@ -15,7 +15,7 @@ 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.2.0
version: 0.3.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

View File

@@ -1287,78 +1287,36 @@ spec:
description: Storage Status contains information about Kubernetes
storage system
properties:
etcd:
description: ETCDStatus defines the observed state of ETCDStatus.
certificate:
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
kine:
properties:
certificate:
properties:
checksum:
type: string
lastUpdate:
format: date-time
type: string
secretName:
type: string
type: object
config:
properties:
checksum:
type: string
secretName:
type: string
type: object
driver:
checksum:
type: string
lastUpdate:
format: date-time
type: string
secretName:
type: string
type: object
config:
properties:
checksum:
type: string
secretName:
type: string
type: object
driver:
type: string
setup:
properties:
checksum:
type: string
lastUpdate:
format: date-time
type: string
schema:
type: string
user:
type: string
setup:
properties:
checksum:
type: string
lastUpdate:
format: date-time
type: string
schema:
type: string
user:
type: string
type: object
type: object
type: object
type: object

View File

@@ -99,7 +99,7 @@ Comma separated list of etcd endpoints, using the overrides in case of unmanaged
{{- $list := list -}}
{{- if .Values.etcd.deploy }}
{{- range $count := until 3 -}}
{{- $list = append $list (printf "https://%s-%d.%s.%s.svc.cluster.local:%d" "etcd" $count ( include "etcd.serviceName" . ) $.Release.Namespace (int $.Values.etcd.port) ) -}}
{{- $list = append $list (printf "%s-%d.%s.%s.svc.cluster.local:%d" "etcd" $count ( include "etcd.serviceName" . ) $.Release.Namespace (int $.Values.etcd.port) ) -}}
{{- end }}
{{- else if .Values.etcd.overrides.endpoints }}
{{- range $v := .Values.etcd.overrides.endpoints -}}

View File

@@ -1287,78 +1287,36 @@ spec:
description: Storage Status contains information about Kubernetes
storage system
properties:
etcd:
description: ETCDStatus defines the observed state of ETCDStatus.
certificate:
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
kine:
properties:
certificate:
properties:
checksum:
type: string
lastUpdate:
format: date-time
type: string
secretName:
type: string
type: object
config:
properties:
checksum:
type: string
secretName:
type: string
type: object
driver:
checksum:
type: string
lastUpdate:
format: date-time
type: string
secretName:
type: string
type: object
config:
properties:
checksum:
type: string
secretName:
type: string
type: object
driver:
type: string
setup:
properties:
checksum:
type: string
lastUpdate:
format: date-time
type: string
schema:
type: string
user:
type: string
setup:
properties:
checksum:
type: string
lastUpdate:
format: date-time
type: string
schema:
type: string
user:
type: string
type: object
type: object
type: object
type: object

View File

@@ -1048,78 +1048,36 @@ spec:
storage:
description: Storage Status contains information about Kubernetes storage system
properties:
etcd:
description: ETCDStatus defines the observed state of ETCDStatus.
certificate:
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
kine:
properties:
certificate:
properties:
checksum:
type: string
lastUpdate:
format: date-time
type: string
secretName:
type: string
type: object
config:
properties:
checksum:
type: string
secretName:
type: string
type: object
driver:
checksum:
type: string
lastUpdate:
format: date-time
type: string
secretName:
type: string
type: object
config:
properties:
checksum:
type: string
secretName:
type: string
type: object
driver:
type: string
setup:
properties:
checksum:
type: string
lastUpdate:
format: date-time
type: string
schema:
type: string
user:
type: string
setup:
properties:
checksum:
type: string
lastUpdate:
format: date-time
type: string
schema:
type: string
user:
type: string
type: object
type: object
type: object
type: object

View File

@@ -12,9 +12,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/datastore"
"github.com/clastix/kamaji/internal/resources"
ds "github.com/clastix/kamaji/internal/resources/datastore"
"github.com/clastix/kamaji/internal/resources/konnectivity"
"github.com/clastix/kamaji/internal/sql"
)
type GroupResourceBuilderConfiguration struct {
@@ -22,7 +23,7 @@ type GroupResourceBuilderConfiguration struct {
log logr.Logger
tcpReconcilerConfig TenantControlPlaneReconcilerConfig
tenantControlPlane kamajiv1alpha1.TenantControlPlane
DBConnection sql.DBConnection
Connection datastore.Connection
DataStore kamajiv1alpha1.DataStore
}
@@ -31,62 +32,49 @@ type GroupDeleteableResourceBuilderConfiguration struct {
log logr.Logger
tcpReconcilerConfig TenantControlPlaneReconcilerConfig
tenantControlPlane kamajiv1alpha1.TenantControlPlane
DBConnection sql.DBConnection
connection datastore.Connection
}
// GetResources returns a list of resources that will be used to provide tenant control planes
// Currently there is only a default approach
// TODO: the idea of this function is to become a factory to return the group of resources according to the given configuration.
func GetResources(config GroupResourceBuilderConfiguration, dataStore kamajiv1alpha1.DataStore) []resources.Resource {
return getDefaultResources(config, dataStore)
func GetResources(config GroupResourceBuilderConfiguration) []resources.Resource {
return getDefaultResources(config)
}
// GetDeletableResources returns a list of resources that have to be deleted when tenant control planes are deleted
// Currently there is only a default approach
// TODO: the idea of this function is to become a factory to return the group of deleteable resources according to the given configuration.
func GetDeletableResources(config GroupDeleteableResourceBuilderConfiguration, dataStore kamajiv1alpha1.DataStore) []resources.DeleteableResource {
return getDefaultDeleteableResources(config, dataStore)
func GetDeletableResources(config GroupDeleteableResourceBuilderConfiguration) []resources.DeleteableResource {
return getDefaultDeleteableResources(config)
}
func getDefaultResources(config GroupResourceBuilderConfiguration, dataStore kamajiv1alpha1.DataStore) []resources.Resource {
resources := append(getUpgradeResources(config.client, config.tenantControlPlane), getKubernetesServiceResources(config.client, config.tenantControlPlane)...)
resources = append(resources, getKubeadmConfigResources(config.client, getTmpDirectory(config.tcpReconcilerConfig.TmpBaseDirectory, config.tenantControlPlane), dataStore)...)
func getDefaultResources(config GroupResourceBuilderConfiguration) []resources.Resource {
resources := append(getUpgradeResources(config.client), getKubernetesServiceResources(config.client)...)
resources = append(resources, getKubeadmConfigResources(config.client, getTmpDirectory(config.tcpReconcilerConfig.TmpBaseDirectory, config.tenantControlPlane), config.DataStore)...)
resources = append(resources, getKubernetesCertificatesResources(config.client, config.log, config.tcpReconcilerConfig, config.tenantControlPlane)...)
resources = append(resources, getKubeconfigResources(config.client, config.log, config.tcpReconcilerConfig, config.tenantControlPlane)...)
resources = append(resources, getKubernetesStorageResources(config.client, config.log, config.tcpReconcilerConfig, config.DBConnection, config.tenantControlPlane, dataStore)...)
resources = append(resources, getInternalKonnectivityResources(config.client, config.log, config.tcpReconcilerConfig, config.tenantControlPlane)...)
resources = append(resources, getKubernetesDeploymentResources(config.client, config.tcpReconcilerConfig, dataStore)...)
resources = append(resources, getKubernetesIngressResources(config.client, config.tenantControlPlane)...)
resources = append(resources, getKubeadmPhaseResources(config.client, config.log, config.tenantControlPlane)...)
resources = append(resources, getKubeadmAddonResources(config.client, config.log, config.tenantControlPlane)...)
resources = append(resources, getExternalKonnectivityResources(config.client, config.log, config.tcpReconcilerConfig, config.tenantControlPlane)...)
resources = append(resources, getKubernetesStorageResources(config.client, config.Connection, config.DataStore)...)
resources = append(resources, getInternalKonnectivityResources(config.client, config.log)...)
resources = append(resources, getKubernetesDeploymentResources(config.client, config.tcpReconcilerConfig, config.DataStore)...)
resources = append(resources, getKubernetesIngressResources(config.client)...)
resources = append(resources, getKubeadmPhaseResources(config.client, config.log)...)
resources = append(resources, getKubeadmAddonResources(config.client, config.log)...)
resources = append(resources, getExternalKonnectivityResources(config.client)...)
return resources
}
func getDefaultDeleteableResources(config GroupDeleteableResourceBuilderConfiguration, dataStore kamajiv1alpha1.DataStore) []resources.DeleteableResource {
switch dataStore.Spec.Driver {
case kamajiv1alpha1.EtcdDriver:
return []resources.DeleteableResource{
&resources.ETCDSetupResource{
Client: config.client,
Log: config.log,
DataStore: dataStore,
},
}
case kamajiv1alpha1.KineMySQLDriver, kamajiv1alpha1.KinePostgreSQLDriver:
return []resources.DeleteableResource{
&resources.SQLSetup{
Client: config.client,
DBConnection: config.DBConnection,
},
}
default:
return []resources.DeleteableResource{}
func getDefaultDeleteableResources(config GroupDeleteableResourceBuilderConfiguration) []resources.DeleteableResource {
return []resources.DeleteableResource{
&ds.Setup{
Client: config.client,
Connection: config.connection,
},
}
}
func getUpgradeResources(c client.Client, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
func getUpgradeResources(c client.Client) []resources.Resource {
return []resources.Resource{
&resources.KubernetesUpgrade{
Client: c,
@@ -94,7 +82,7 @@ func getUpgradeResources(c client.Client, tenantControlPlane kamajiv1alpha1.Tena
}
}
func getKubernetesServiceResources(c client.Client, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
func getKubernetesServiceResources(c client.Client) []resources.Resource {
return []resources.Resource{
&resources.KubernetesServiceResource{
Client: c,
@@ -182,72 +170,36 @@ func getKubeconfigResources(c client.Client, log logr.Logger, tcpReconcilerConfi
}
}
func getKubernetesStorageResources(c client.Client, log logr.Logger, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, dbConnection sql.DBConnection, tenantControlPlane kamajiv1alpha1.TenantControlPlane, ds kamajiv1alpha1.DataStore) []resources.Resource {
switch ds.Spec.Driver {
case kamajiv1alpha1.EtcdDriver:
return []resources.Resource{
&resources.ETCDCACertificatesResource{
Name: "etcd-ca-certificates",
Client: c,
Log: log,
DataStore: ds,
},
&resources.ETCDCertificatesResource{
Name: "etcd-certificates",
Client: c,
Log: log,
},
&resources.ETCDSetupResource{
Client: c,
Log: log,
DataStore: ds,
},
}
case kamajiv1alpha1.KineMySQLDriver, kamajiv1alpha1.KinePostgreSQLDriver:
return []resources.Resource{
&resources.SQLStorageConfig{
Client: c,
Name: "sql-config",
Host: dbConnection.GetHost(),
Port: dbConnection.GetPort(),
Driver: dbConnection.Driver(),
},
&resources.SQLSetup{
Client: c,
DBConnection: dbConnection,
Driver: dbConnection.Driver(),
},
&resources.SQLCertificate{
Client: c,
DataStore: ds,
},
}
default:
return []resources.Resource{}
func getKubernetesStorageResources(c client.Client, dbConnection datastore.Connection, datastore kamajiv1alpha1.DataStore) []resources.Resource {
return []resources.Resource{
&ds.Config{
Client: c,
ConnString: dbConnection.GetConnectionString(),
Driver: dbConnection.Driver(),
},
&ds.Setup{
Client: c,
Connection: dbConnection,
DataStore: datastore,
},
&ds.Certificate{
Client: c,
DataStore: datastore,
},
}
}
func getKubernetesDeploymentResources(c client.Client, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, dataStore kamajiv1alpha1.DataStore) []resources.Resource {
var endpoints []string
switch dataStore.Spec.Driver {
case kamajiv1alpha1.EtcdDriver:
endpoints = dataStore.Spec.Endpoints
default:
endpoints = []string{"127.0.0.1:2379"}
}
return []resources.Resource{
&resources.KubernetesDeploymentResource{
Client: c,
ETCDEndpoints: endpoints,
DataStoreDriver: dataStore.Spec.Driver,
DataStore: dataStore,
KineContainerImage: tcpReconcilerConfig.KineContainerImage,
},
}
}
func getKubernetesIngressResources(c client.Client, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
func getKubernetesIngressResources(c client.Client) []resources.Resource {
return []resources.Resource{
&resources.KubernetesIngressResource{
Client: c,
@@ -255,7 +207,7 @@ func getKubernetesIngressResources(c client.Client, tenantControlPlane kamajiv1a
}
}
func getKubeadmPhaseResources(c client.Client, log logr.Logger, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
func getKubeadmPhaseResources(c client.Client, log logr.Logger) []resources.Resource {
return []resources.Resource{
&resources.KubeadmPhase{
Name: "upload-config-kubeadm",
@@ -278,7 +230,7 @@ func getKubeadmPhaseResources(c client.Client, log logr.Logger, tenantControlPla
}
}
func getKubeadmAddonResources(c client.Client, log logr.Logger, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
func getKubeadmAddonResources(c client.Client, log logr.Logger) []resources.Resource {
return []resources.Resource{
&resources.KubeadmAddonResource{
Name: "coredns",
@@ -295,7 +247,7 @@ func getKubeadmAddonResources(c client.Client, log logr.Logger, tenantControlPla
}
}
func getExternalKonnectivityResources(c client.Client, log logr.Logger, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
func getExternalKonnectivityResources(c client.Client) []resources.Resource {
return []resources.Resource{
&konnectivity.ServiceAccountResource{
Client: c,
@@ -320,7 +272,7 @@ func getExternalKonnectivityResources(c client.Client, log logr.Logger, tcpRecon
}
}
func getInternalKonnectivityResources(c client.Client, log logr.Logger, tcpReconcilerConfig TenantControlPlaneReconcilerConfig, tenantControlPlane kamajiv1alpha1.TenantControlPlane) []resources.Resource {
func getInternalKonnectivityResources(c client.Client, log logr.Logger) []resources.Resource {
return []resources.Resource{
&konnectivity.EgressSelectorConfigurationResource{
Client: c,

View File

@@ -14,26 +14,10 @@ import (
"github.com/pkg/errors"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/sql"
"github.com/clastix/kamaji/internal/datastore"
)
func (r *TenantControlPlaneReconciler) getStorageConnection(ctx context.Context, ds kamajiv1alpha1.DataStore) (sql.DBConnection, error) {
var driver sql.Driver
var dbName string
// TODO: https://github.com/clastix/kamaji/issues/67
switch ds.Spec.Driver {
case kamajiv1alpha1.EtcdDriver:
return nil, nil
case kamajiv1alpha1.KineMySQLDriver:
driver = sql.MySQL
dbName = "mysql"
case kamajiv1alpha1.KinePostgreSQLDriver:
driver = sql.PostgreSQL
default:
return nil, nil
}
func (r *TenantControlPlaneReconciler) getStorageConnection(ctx context.Context, ds kamajiv1alpha1.DataStore) (datastore.Connection, error) {
ca, err := ds.Spec.TLSConfig.CertificateAuthority.Certificate.GetContent(ctx, r.Client)
if err != nil {
return nil, err
@@ -74,29 +58,47 @@ func (r *TenantControlPlaneReconciler) getStorageConnection(ctx context.Context,
password = string(p)
}
host, stringPort, err := net.SplitHostPort(ds.Spec.Endpoints[0])
if err != nil {
return nil, errors.Wrap(err, "cannot retrieve host-port pair from DataStore endpoints")
eps := make([]datastore.ConnectionEndpoint, 0, len(ds.Spec.Endpoints))
for _, ep := range ds.Spec.Endpoints {
host, stringPort, err := net.SplitHostPort(ep)
if err != nil {
return nil, errors.Wrap(err, "cannot retrieve host-port pair from DataStore endpoints")
}
port, err := strconv.Atoi(stringPort)
if err != nil {
return nil, errors.Wrap(err, "cannot convert port from string for the given DataStore")
}
eps = append(eps, datastore.ConnectionEndpoint{
Host: host,
Port: port,
})
}
port, err := strconv.Atoi(stringPort)
if err != nil {
return nil, errors.Wrap(err, "cannot convert port from string for the given DataStore")
}
return sql.GetDBConnection(
sql.ConnectionConfig{
SQLDriver: driver,
User: user,
Password: password,
Host: host,
Port: port,
DBName: dbName,
TLSConfig: &tls.Config{
ServerName: host,
RootCAs: rootCAs,
Certificates: []tls.Certificate{certificate},
},
cc := datastore.ConnectionConfig{
User: user,
Password: password,
Endpoints: eps,
TLSConfig: &tls.Config{
RootCAs: rootCAs,
Certificates: []tls.Certificate{certificate},
},
)
}
switch ds.Spec.Driver {
case kamajiv1alpha1.KineMySQLDriver:
cc.TLSConfig.ServerName = cc.Endpoints[0].Host
return datastore.NewMySQLConnection(cc)
case kamajiv1alpha1.KinePostgreSQLDriver:
cc.TLSConfig.ServerName = cc.Endpoints[0].Host
//nolint:contextcheck
return datastore.NewPostgreSQLConnection(cc)
case kamajiv1alpha1.EtcdDriver:
return datastore.NewETCDConnection(cc)
default:
return nil, fmt.Errorf("%s is not a valid driver", ds.Spec.Driver)
}
}

View File

@@ -50,11 +50,11 @@ type TenantControlPlaneReconcilerConfig struct {
//+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
//+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)
@@ -80,17 +80,11 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
return ctrl.Result{}, errors.Wrap(err, "cannot retrieve kamajiv1alpha.DataStore object")
}
dbConnection, err := r.getStorageConnection(ctx, ds)
dsConnection, err := r.getStorageConnection(ctx, ds)
if err != nil {
return ctrl.Result{}, err
}
defer func() {
// TODO: Currently, etcd is not accessed using this dbConnection. For that reason we need this check
// Check: https://github.com/clastix/kamaji/issues/67
if dbConnection != nil {
dbConnection.Close()
}
}()
defer dsConnection.Close()
if markedToBeDeleted {
log.Info("marked for deletion, performing clean-up")
@@ -100,12 +94,12 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
log: log,
tcpReconcilerConfig: r.Config,
tenantControlPlane: *tenantControlPlane,
DBConnection: dbConnection,
connection: dsConnection,
}
registeredDeletableResources := GetDeletableResources(groupDeleteableResourceBuilderConfiguration, ds)
registeredDeletableResources := GetDeletableResources(groupDeleteableResourceBuilderConfiguration)
for _, resource := range registeredDeletableResources {
if err := resources.HandleDeletion(ctx, resource, tenantControlPlane); err != nil {
if err = resources.HandleDeletion(ctx, resource, tenantControlPlane); err != nil {
return ctrl.Result{}, err
}
}
@@ -113,7 +107,7 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
if hasFinalizer {
log.Info("removing finalizer")
if err := r.RemoveFinalizer(ctx, tenantControlPlane); err != nil {
if err = r.RemoveFinalizer(ctx, tenantControlPlane); err != nil {
return ctrl.Result{}, err
}
}
@@ -133,9 +127,9 @@ func (r *TenantControlPlaneReconciler) Reconcile(ctx context.Context, req ctrl.R
tcpReconcilerConfig: r.Config,
tenantControlPlane: *tenantControlPlane,
DataStore: ds,
DBConnection: dbConnection,
Connection: dsConnection,
}
registeredResources := GetResources(groupResourceBuilderConfiguration, ds)
registeredResources := GetResources(groupResourceBuilderConfiguration)
for _, resource := range registeredResources {
result, err := resources.Handle(ctx, resource, tenantControlPlane)

View File

@@ -55,7 +55,6 @@ var _ = BeforeSuite(func() {
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())

View File

@@ -18,7 +18,6 @@ import (
var _ = Describe("Deploy a TenantControlPlane resource", func() {
// Fill TenantControlPlane object
tcp := kamajiv1alpha1.TenantControlPlane{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "tcp-clusterip",
Namespace: "default",

View File

@@ -7,7 +7,7 @@ import (
"bytes"
"context"
"fmt"
"io/ioutil"
"io"
"os/exec"
. "github.com/onsi/ginkgo"
@@ -96,7 +96,7 @@ func PrintKamajiLogs() {
defer podLogs.Close()
podBytes, err := ioutil.ReadAll(podLogs)
podBytes, err := io.ReadAll(podLogs)
Expect(err).ToNot(HaveOccurred())
_, _ = fmt.Fprintln(GinkgoWriter, "DEBUG: retrieving Kamaji Pod logs")

View File

@@ -7,7 +7,6 @@ import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
@@ -37,7 +36,6 @@ var _ = Describe("starting a kind worker with kubeadm", func() {
JustBeforeEach(func() {
tcp = kamajiv1alpha1.TenantControlPlane{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "worker-nodes-join",
Namespace: "default",
@@ -84,7 +82,7 @@ var _ = Describe("starting a kind worker with kubeadm", func() {
})
Expect(err).ToNot(HaveOccurred())
kubeconfigFile, err = ioutil.TempFile("", "kamaji")
kubeconfigFile, err = os.CreateTemp("", "kamaji")
Expect(err).ToNot(HaveOccurred())
})

View File

@@ -6,7 +6,6 @@ package e2e
import (
"context"
"fmt"
"io/ioutil"
"os"
"time"
@@ -66,7 +65,7 @@ var _ = Describe("validating kubeconfig", func() {
var err error
kubeconfigFile, err = ioutil.TempFile("", "kamaji")
kubeconfigFile, err = os.CreateTemp("", "kamaji")
Expect(err).ToNot(HaveOccurred())
})

View File

@@ -42,16 +42,14 @@ const (
const (
apiServerFlagsAnnotation = "kube-apiserver.kamaji.clastix.io/args"
kineContainerName = "kine"
kineVolumeChmod = "kine-config"
dataStoreCerts = "kine-config"
kineVolumeCertName = "kine-certs"
)
type Deployment struct {
Address string
ETCDEndpoints []string
ETCDCompactionInterval string
ETCDStorageType kamajiv1alpha1.Driver
KineContainerImage string
Address string
KineContainerImage string
DataStore kamajiv1alpha1.DataStore
}
func (d *Deployment) SetContainers(podSpec *corev1.PodSpec, tcp *kamajiv1alpha1.TenantControlPlane, address string) {
@@ -116,19 +114,24 @@ func (d *Deployment) buildPKIVolume(podSpec *corev1.PodSpec, tcp *kamajiv1alpha1
},
}
if d.ETCDStorageType == kamajiv1alpha1.EtcdDriver {
sources = append(sources, corev1.VolumeProjection{
Secret: d.secretProjection(tcp.Status.Certificates.ETCD.APIServer.SecretName, constants.APIServerEtcdClientCertName, constants.APIServerEtcdClientKeyName),
})
if d.DataStore.Spec.Driver == kamajiv1alpha1.EtcdDriver {
sources = append(sources, corev1.VolumeProjection{
Secret: &corev1.SecretProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: tcp.Status.Certificates.ETCD.CA.SecretName,
Name: tcp.Status.Storage.Certificate.SecretName,
},
Items: []corev1.KeyToPath{
{
Key: constants.CACertName,
Path: constants.EtcdCACertName,
Key: "ca.crt",
Path: "etcd/ca.crt",
},
{
Key: "server.crt",
Path: "etcd/server.crt",
},
{
Key: "server.key",
Path: "etcd/server.key",
},
},
},
@@ -259,7 +262,7 @@ func (d *Deployment) BuildScheduler(podSpec *corev1.PodSpec, tenantControlPlane
args["--authorization-kubeconfig"] = kubeconfig
args["--bind-address"] = "0.0.0.0"
args["--kubeconfig"] = kubeconfig
args["--leader-elect"] = "true" // nolint:goconst
args["--leader-elect"] = "true" //nolint:goconst
podSpec.Containers[schedulerIndex].Name = "kube-scheduler"
podSpec.Containers[schedulerIndex].Image = fmt.Sprintf("k8s.gcr.io/kube-scheduler:%s", tenantControlPlane.Spec.Kubernetes.Version)
@@ -286,6 +289,7 @@ func (d *Deployment) BuildScheduler(podSpec *corev1.PodSpec, tenantControlPlane
SuccessThreshold: 1,
FailureThreshold: 3,
}
podSpec.Containers[schedulerIndex].StartupProbe = &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
@@ -380,6 +384,7 @@ func (d *Deployment) buildControllerManager(podSpec *corev1.PodSpec, tenantContr
MountPath: "/usr/local/share/ca-certificates",
},
}
podSpec.Containers[controllerManagerIndex].LivenessProbe = &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
@@ -394,6 +399,7 @@ func (d *Deployment) buildControllerManager(podSpec *corev1.PodSpec, tenantContr
SuccessThreshold: 1,
FailureThreshold: 3,
}
podSpec.Containers[controllerManagerIndex].StartupProbe = &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
@@ -530,7 +536,6 @@ func (d *Deployment) buildKubeAPIServerCommand(tenantControlPlane *kamajiv1alpha
"--client-ca-file": path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName),
"--enable-admission-plugins": strings.Join(tenantControlPlane.Spec.Kubernetes.AdmissionControllers.ToSlice(), ","),
"--enable-bootstrap-token-auth": "true",
"--etcd-servers": strings.Join(d.ETCDEndpoints, ","),
"--service-cluster-ip-range": tenantControlPlane.Spec.NetworkProfile.ServiceCIDR,
"--kubelet-client-certificate": path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerKubeletClientCertName),
"--kubelet-client-key": path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerKubeletClientKeyName),
@@ -549,12 +554,23 @@ func (d *Deployment) buildKubeAPIServerCommand(tenantControlPlane *kamajiv1alpha
"--tls-private-key-file": path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerKeyName),
}
if d.ETCDStorageType == kamajiv1alpha1.EtcdDriver {
desiredArgs["--etcd-cafile"] = path.Join(v1beta3.DefaultCertificatesDir, constants.EtcdCACertName)
desiredArgs["--etcd-certfile"] = path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerEtcdClientCertName)
desiredArgs["--etcd-keyfile"] = path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerEtcdClientKeyName)
switch d.DataStore.Spec.Driver {
case kamajiv1alpha1.KineMySQLDriver, kamajiv1alpha1.KinePostgreSQLDriver:
desiredArgs["--etcd-servers"] = "http://127.0.0.1:2379"
case kamajiv1alpha1.EtcdDriver:
httpsEndpoints := make([]string, 0, len(d.DataStore.Spec.Endpoints))
for _, ep := range d.DataStore.Spec.Endpoints {
httpsEndpoints = append(httpsEndpoints, fmt.Sprintf("https://%s", ep))
}
desiredArgs["--etcd-prefix"] = fmt.Sprintf("/%s", tenantControlPlane.GetName())
desiredArgs["--etcd-servers"] = strings.Join(httpsEndpoints, ",")
desiredArgs["--etcd-cafile"] = "/etc/kubernetes/pki/etcd/ca.crt"
desiredArgs["--etcd-certfile"] = "/etc/kubernetes/pki/etcd/server.crt"
desiredArgs["--etcd-keyfile"] = "/etc/kubernetes/pki/etcd/server.key"
}
// Order matters, here: extraArgs could try to overwrite some arguments managed by Kamaji and that would be crucial.
// Adding as first element of the array of maps, we're sure that these overrides will be sanitized by our configuration.
return utilities.MergeMaps(extraArgs, current, desiredArgs)
@@ -579,15 +595,6 @@ func (d *Deployment) secretProjection(secretName, certKeyName, keyName string) *
}
func (d *Deployment) removeKineVolumes(podSpec *corev1.PodSpec) {
if found, index := utilities.HasNamedVolume(podSpec.Volumes, kineVolumeChmod); found {
var volumes []corev1.Volume
volumes = append(volumes, podSpec.Volumes[:index]...)
volumes = append(volumes, podSpec.Volumes[index+1:]...)
podSpec.Volumes = volumes
}
if found, index := utilities.HasNamedVolume(podSpec.Volumes, kineVolumeCertName); found {
var volumes []corev1.Volume
@@ -599,25 +606,25 @@ func (d *Deployment) removeKineVolumes(podSpec *corev1.PodSpec) {
}
func (d *Deployment) buildKineVolume(podSpec *corev1.PodSpec, tcp *kamajiv1alpha1.TenantControlPlane) {
if d.ETCDStorageType == kamajiv1alpha1.EtcdDriver {
d.removeKineVolumes(podSpec)
return
}
// Adding the volume for chmod'ed Kine certificates.
found, index := utilities.HasNamedVolume(podSpec.Volumes, kineVolumeChmod)
found, index := utilities.HasNamedVolume(podSpec.Volumes, dataStoreCerts)
if !found {
podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
index = len(podSpec.Volumes) - 1
}
podSpec.Volumes[index].Name = kineVolumeChmod
podSpec.Volumes[index].Name = dataStoreCerts
podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: tcp.Status.Storage.Kine.Certificate.SecretName,
SecretName: tcp.Status.Storage.Certificate.SecretName,
DefaultMode: pointer.Int32Ptr(420),
},
}
if d.DataStore.Spec.Driver == kamajiv1alpha1.EtcdDriver {
d.removeKineVolumes(podSpec)
return
}
// Adding the volume to read Kine certificates:
// these must be subsequently fixed with a chmod due to pg issues with private key.
if found, index = utilities.HasNamedVolume(podSpec.Volumes, kineVolumeCertName); !found {
@@ -646,7 +653,7 @@ func (d *Deployment) removeKineContainers(podSpec *corev1.PodSpec) {
}
func (d *Deployment) buildKine(podSpec *corev1.PodSpec, tcp *kamajiv1alpha1.TenantControlPlane) {
if d.ETCDStorageType == kamajiv1alpha1.EtcdDriver {
if d.DataStore.Spec.Driver == kamajiv1alpha1.EtcdDriver {
d.removeKineContainers(podSpec)
return
@@ -665,11 +672,11 @@ func (d *Deployment) buildKine(podSpec *corev1.PodSpec, tcp *kamajiv1alpha1.Tena
args = utilities.ArgsFromSliceToMap(tcp.Spec.ControlPlane.Deployment.ExtraArgs.Kine)
}
switch d.ETCDStorageType {
switch d.DataStore.Spec.Driver {
case kamajiv1alpha1.KineMySQLDriver:
args["--endpoint"] = "mysql://$(DB_USER):$(DB_PASSWORD)@tcp($(DB_HOST):$(DB_PORT))/$(DB_SCHEMA)"
args["--endpoint"] = "mysql://$(DB_USER):$(DB_PASSWORD)@tcp($(DB_CONNECTION_STRING))/$(DB_SCHEMA)"
case kamajiv1alpha1.KinePostgreSQLDriver:
args["--endpoint"] = "postgres://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_SCHEMA)"
args["--endpoint"] = "postgres://$(DB_USER):$(DB_PASSWORD)@$(DB_CONNECTION_STRING)/$(DB_SCHEMA)"
}
args["--ca-file"] = "/certs/ca.crt"
@@ -690,7 +697,7 @@ func (d *Deployment) buildKine(podSpec *corev1.PodSpec, tcp *kamajiv1alpha1.Tena
},
VolumeMounts: []corev1.VolumeMount{
{
Name: kineVolumeChmod,
Name: dataStoreCerts,
ReadOnly: true,
MountPath: "/kine",
},
@@ -726,7 +733,7 @@ func (d *Deployment) buildKine(podSpec *corev1.PodSpec, tcp *kamajiv1alpha1.Tena
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: tcp.Status.Storage.Kine.Config.SecretName,
Name: tcp.Status.Storage.Config.SecretName,
},
},
},

View File

@@ -41,6 +41,7 @@ func InitConfig() (*viper.Viper, error) {
flag.String("datastore", defaultDataStore, "The default DataStore that should be used by Kamaji to setup the required storage")
// Setup zap configuration
opts := zap.Options{
Development: true,
}

View File

@@ -60,7 +60,7 @@ func GetPublickKey(pubKey []byte) (*rsa.PublicKey, error) {
return nil, err
}
return pub.(*rsa.PublicKey), nil // nolint:forcetypeassert
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) {
@@ -76,16 +76,18 @@ func GenerateCertificateKeyPairBytes(template *x509.Certificate, bitSize int, ca
certPEM := &bytes.Buffer{}
if err := pem.Encode(certPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
Type: "CERTIFICATE",
Headers: nil,
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),
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
}); err != nil {
return nil, nil, err
}
@@ -132,7 +134,7 @@ func checkCertificateValidity(cert x509.Certificate) bool {
}
func checkCertificateKeyPair(cert x509.Certificate, privKey rsa.PrivateKey) bool {
return checkPublicKeys(*cert.PublicKey.(*rsa.PublicKey), privKey.PublicKey) // nolint:forcetypeassert
return checkPublicKeys(*cert.PublicKey.(*rsa.PublicKey), privKey.PublicKey) //nolint:forcetypeassert
}
func checkPublicKeys(a rsa.PublicKey, b rsa.PublicKey) bool {

View File

@@ -0,0 +1,56 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package datastore
import (
"context"
"crypto/tls"
"fmt"
)
type ConnectionEndpoint struct {
Host string
Port int
}
func (r ConnectionEndpoint) String() string {
return fmt.Sprintf("%s:%d", r.Host, r.Port)
}
type ConnectionConfig struct {
User string
Password string
Endpoints []ConnectionEndpoint
DBName string
TLSConfig *tls.Config
Parameters map[string][]string
}
func (config ConnectionConfig) getDataSourceNameUserPassword() string {
if config.User == "" {
return ""
}
if config.Password == "" {
return fmt.Sprintf("%s@", config.User)
}
return fmt.Sprintf("%s:%s@", config.User, config.Password)
}
type Connection interface {
CreateUser(ctx context.Context, user, password string) error
CreateDB(ctx context.Context, dbName string) error
GrantPrivileges(ctx context.Context, user, dbName string) error
UserExists(ctx context.Context, user string) (bool, error)
DBExists(ctx context.Context, dbName string) (bool, error)
GrantPrivilegesExists(ctx context.Context, user, dbName string) (bool, error)
DeleteUser(ctx context.Context, user string) error
DeleteDB(ctx context.Context, dbName string) error
RevokePrivileges(ctx context.Context, user, dbName string) error
GetConnectionString() string
Close() error
Check(ctx context.Context) error
Driver() string
}

171
internal/datastore/etcd.go Normal file
View File

@@ -0,0 +1,171 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package datastore
import (
"context"
"fmt"
"github.com/pkg/errors"
"go.etcd.io/etcd/api/v3/authpb"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
etcdclient "go.etcd.io/etcd/client/v3"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
const (
// 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 NewETCDConnection(config ConnectionConfig) (Connection, error) {
endpoints := make([]string, 0, len(config.Endpoints))
for _, ep := range config.Endpoints {
endpoints = append(endpoints, ep.String())
}
cfg := etcdclient.Config{
Endpoints: endpoints,
TLS: config.TLSConfig,
}
client, err := etcdclient.New(cfg)
if err != nil {
return nil, err
}
return &EtcdClient{
Client: *client,
}, nil
}
type EtcdClient struct {
Client etcdclient.Client
}
func (e *EtcdClient) CreateUser(ctx context.Context, user, password string) error {
_, err := e.Client.Auth.UserAddWithOptions(ctx, user, password, &etcdclient.UserAddOptions{
NoPassword: true,
})
return err
}
func (e *EtcdClient) CreateDB(ctx context.Context, dbName string) error {
return nil
}
func (e *EtcdClient) GrantPrivileges(ctx context.Context, user, dbName string) error {
_, err := e.Client.Auth.RoleAdd(ctx, dbName)
if err != nil {
return err
}
permission := etcdclient.PermissionType(authpb.READWRITE)
key := e.buildKey(dbName)
if _, err = e.Client.RoleGrantPermission(ctx, user, key, rangeEnd, permission); err != nil {
return err
}
if _, err = e.Client.UserGrantRole(ctx, user, dbName); err != nil {
return err
}
return err
}
func (e *EtcdClient) UserExists(ctx context.Context, user string) (bool, error) {
_, err := e.Client.UserGet(ctx, user)
if err != nil {
if errors.As(err, &rpctypes.ErrGRPCUserNotFound) {
return false, nil
}
return false, err
}
return true, nil
}
func (e *EtcdClient) DBExists(_ context.Context, dbName string) (bool, error) {
return true, nil
}
func (e *EtcdClient) GrantPrivilegesExists(ctx context.Context, username, dbName string) (bool, error) {
_, err := e.Client.RoleGet(ctx, dbName)
if err != nil {
if errors.As(err, &rpctypes.ErrGRPCRoleNotFound) {
return false, nil
}
return false, err
}
user, err := e.Client.UserGet(ctx, username)
if err != nil {
return false, err
}
for _, i := range user.Roles {
if i == dbName {
return true, nil
}
}
return false, nil
}
func (e *EtcdClient) DeleteUser(ctx context.Context, user string) error {
_, err := e.Client.Auth.UserDelete(ctx, user)
return err
}
func (e *EtcdClient) DeleteDB(ctx context.Context, dbName string) error {
withRange := etcdclient.WithRange(rangeEnd)
prefix := e.buildKey(dbName)
_, err := e.Client.Delete(ctx, prefix, withRange)
return err
}
func (e *EtcdClient) RevokePrivileges(ctx context.Context, user, dbName string) error {
_, err := e.Client.Auth.RoleDelete(ctx, dbName)
return err
}
func (e *EtcdClient) GetConnectionString() string {
// There's no need for connection string in etcd client:
// it's not used by Kine
return ""
}
func (e *EtcdClient) Close() error {
return e.Client.Close()
}
func (e *EtcdClient) Check(ctx context.Context) error {
_, err := e.Client.AuthStatus(ctx)
return err
}
func (e *EtcdClient) Driver() string {
return string(kamajiv1alpha1.EtcdDriver)
}
func (e *EtcdClient) buildKey(roleName string) string {
return fmt.Sprintf("/%s/", roleName)
}
type Permission struct {
Type int `json:"type,omitempty"`
Key string `json:"key,omitempty"`
RangeEnd string `json:"rangeEnd,omitempty"`
}

View File

@@ -1,15 +1,23 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package sql
package datastore
import (
"context"
"database/sql"
"fmt"
"net/url"
"github.com/go-pg/pg/v10"
"github.com/go-sql-driver/mysql"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
const (
defaultProtocol = "tcp"
sqlErrorNoRows = "sql: no rows in result set"
)
const (
@@ -25,36 +33,44 @@ const (
)
type MySQLConnection struct {
db *sql.DB
host string
port int
db *sql.DB
connector ConnectionEndpoint
}
func (c *MySQLConnection) Driver() string {
return "MySQL"
return string(kamajiv1alpha1.KineMySQLDriver)
}
func getPostgreSQLDB(config ConnectionConfig) (DBConnection, error) {
func NewPostgreSQLConnection(config ConnectionConfig) (Connection, error) {
opt := &pg.Options{
Addr: fmt.Sprintf("%s:%d", config.Host, config.Port),
Addr: config.Endpoints[0].String(),
Database: config.DBName,
User: config.User,
Password: config.Password,
TLSConfig: config.TLSConfig,
}
return &PostgreSQLConnection{db: pg.Connect(opt), port: config.Port, host: config.Host}, nil
return &PostgreSQLConnection{db: pg.Connect(opt), connection: config.Endpoints[0]}, nil
}
func getMySQLDB(config ConnectionConfig) (DBConnection, error) {
tlsKey := "mysql"
dataSourceName := config.GetDataSourceName()
mysqlConfig, err := mysql.ParseDSN(dataSourceName)
func NewMySQLConnection(config ConnectionConfig) (Connection, error) {
nameDB := fmt.Sprintf("%s(%s)", defaultProtocol, config.Endpoints[0].String())
var parameters string
if len(config.Parameters) > 0 {
parameters = url.Values(config.Parameters).Encode()
}
dsn := fmt.Sprintf("%s%s/%s?%s", config.getDataSourceNameUserPassword(), nameDB, config.DBName, parameters)
mysqlConfig, err := mysql.ParseDSN(dsn)
if err != nil {
return nil, err
}
if err := mysql.RegisterTLSConfig(tlsKey, config.TLSConfig); err != nil {
tlsKey := "mysql"
if err = mysql.RegisterTLSConfig(tlsKey, config.TLSConfig); err != nil {
return nil, err
}
@@ -67,27 +83,19 @@ func getMySQLDB(config ConnectionConfig) (DBConnection, error) {
return nil, err
}
return &MySQLConnection{
db: db,
host: config.Host,
port: config.Port,
}, nil
return &MySQLConnection{db: db, connector: config.Endpoints[0]}, nil
}
func (c *MySQLConnection) GetHost() string {
return c.host
}
func (c *MySQLConnection) GetPort() int {
return c.port
func (c *MySQLConnection) GetConnectionString() string {
return c.connector.String()
}
func (c *MySQLConnection) Close() error {
return c.db.Close()
}
func (c *MySQLConnection) Check() error {
return c.db.Ping()
func (c *MySQLConnection) Check(ctx context.Context) error {
return c.db.PingContext(ctx)
}
func (c *MySQLConnection) CreateUser(ctx context.Context, user, password string) error {
@@ -106,7 +114,7 @@ func (c *MySQLConnection) UserExists(ctx context.Context, user string) (bool, er
checker := func(row *sql.Row) (bool, error) {
var name string
if err := row.Scan(&name); err != nil {
if checkEmptyQueryResult(err) {
if c.checkEmptyQueryResult(err) {
return false, nil
}
@@ -123,7 +131,7 @@ func (c *MySQLConnection) DBExists(ctx context.Context, dbName string) (bool, er
checker := func(row *sql.Row) (bool, error) {
var name string
if err := row.Scan(&name); err != nil {
if checkEmptyQueryResult(err) {
if c.checkEmptyQueryResult(err) {
return false, nil
}
@@ -191,3 +199,7 @@ func (c *MySQLConnection) mutate(ctx context.Context, nonFilledStatement string,
return nil
}
func (c *MySQLConnection) checkEmptyQueryResult(err error) bool {
return err.Error() == sqlErrorNoRows
}

View File

@@ -1,7 +1,7 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package sql
package datastore
import (
"context"
@@ -9,6 +9,8 @@ import (
"strings"
"github.com/go-pg/pg/v10"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
const (
@@ -24,13 +26,12 @@ const (
)
type PostgreSQLConnection struct {
db *pg.DB
host string
port int
db *pg.DB
connection ConnectionEndpoint
}
func (r *PostgreSQLConnection) Driver() string {
return "PostgreSQL"
return string(kamajiv1alpha1.KinePostgreSQLDriver)
}
func (r *PostgreSQLConnection) UserExists(ctx context.Context, user string) (bool, error) {
@@ -109,18 +110,14 @@ func (r *PostgreSQLConnection) RevokePrivileges(ctx context.Context, user, dbNam
return err
}
func (r *PostgreSQLConnection) GetHost() string {
return r.host
}
func (r *PostgreSQLConnection) GetPort() int {
return r.port
func (r *PostgreSQLConnection) GetConnectionString() string {
return r.connection.String()
}
func (r *PostgreSQLConnection) Close() error {
return r.db.Close()
}
func (r *PostgreSQLConnection) Check() error {
return r.db.Ping(context.Background())
func (r *PostgreSQLConnection) Check(ctx context.Context) error {
return r.db.Ping(ctx)
}

View File

@@ -1,177 +0,0 @@
// 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)
}

View File

@@ -1,71 +0,0 @@
// 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
}

View File

@@ -80,6 +80,7 @@ func removeCoreDNSDeployment(ctx context.Context, client kubernetes.Interface) e
func removeCoreDNSConfigMap(ctx context.Context, client kubernetes.Interface) error {
name, _ := getCoreDNSConfigMapName(ctx)
opts := metav1.DeleteOptions{}
return client.CoreV1().ConfigMaps(kubeSystemNamespace).Delete(ctx, name, opts)
@@ -152,6 +153,7 @@ func RemoveKubeProxy(ctx context.Context, client kubernetes.Interface) error {
func removeKubeProxyDaemonSet(ctx context.Context, client kubernetes.Interface) error {
name, _ := getKubeProxyDaemonSetName(ctx)
opts := metav1.DeleteOptions{}
return client.AppsV1().DaemonSets(kubeSystemNamespace).Delete(ctx, name, opts)
@@ -159,6 +161,7 @@ func removeKubeProxyDaemonSet(ctx context.Context, client kubernetes.Interface)
func removeKubeProxyConfigMap(ctx context.Context, client kubernetes.Interface) error {
name, _ := getKubeProxyConfigMapName(ctx)
opts := metav1.DeleteOptions{}
return client.CoreV1().ConfigMaps(kubeSystemNamespace).Delete(ctx, name, opts)
@@ -167,6 +170,7 @@ func removeKubeProxyConfigMap(ctx context.Context, client kubernetes.Interface)
func removeKubeProxyRBAC(ctx context.Context, client kubernetes.Interface) error {
// TODO: Currently, kube-proxy is installed using kubeadm phases, therefore, name is the same.
name, _ := getKubeProxyRBACName(ctx)
opts := metav1.DeleteOptions{}
var result error
@@ -361,6 +365,7 @@ func createKubeProxyAddon(client kubernetes.Interface) error {
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",

View File

@@ -7,7 +7,6 @@ import (
"crypto"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"path/filepath"
@@ -147,7 +146,7 @@ func readCertificateFiles(name string, directory string, extensions ...string) (
for _, extension := range extensions {
fileName := fmt.Sprintf("%s.%s", name, extension)
path := filepath.Join(directory, fileName)
content, err := ioutil.ReadFile(path)
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
@@ -160,6 +159,6 @@ func readCertificateFiles(name string, directory string, extensions ...string) (
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
fmt.Printf("Error removing %s: %s", certificateDirectory, err.Error()) //nolint:forbidigo
}
}

View File

@@ -4,7 +4,6 @@
package kubeadm
import (
"io/ioutil"
"os"
"path"
"path/filepath"
@@ -19,12 +18,12 @@ func buildCertificateDirectoryWithCA(ca CertificatePrivateKeyPair, directory str
}
certPath := path.Join(directory, kubeadmconstants.CACertName)
if err := ioutil.WriteFile(certPath, ca.Certificate, os.FileMode(0o600)); err != nil {
if err := os.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 {
if err := os.WriteFile(keyPath, ca.PrivateKey, os.FileMode(0o600)); err != nil {
return err
}
@@ -44,7 +43,7 @@ func CreateKubeconfig(kubeconfigName string, ca CertificatePrivateKeyPair, confi
path := filepath.Join(config.InitConfiguration.CertificatesDir, kubeconfigName)
return ioutil.ReadFile(path)
return os.ReadFile(path)
}
func IsKubeconfigValid(kubeconfigBytes []byte) bool {

View File

@@ -100,6 +100,7 @@ func (r *APIServerKubeletClientCertificate) mutate(ctx context.Context, tenantCo
}
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

View File

@@ -0,0 +1,148 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package datastore
import (
"bytes"
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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 Certificate struct {
resource *corev1.Secret
Client client.Client
Name string
DataStore kamajiv1alpha1.DataStore
}
func (r *Certificate) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
return tenantControlPlane.Status.Storage.Certificate.Checksum != r.resource.GetAnnotations()["checksum"]
}
func (r *Certificate) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
return false
}
func (r *Certificate) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bool, error) {
return false, nil
}
func (r *Certificate) Define(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
r.resource = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: r.getPrefixedName(tenantControlPlane),
Namespace: tenantControlPlane.GetNamespace(),
},
Data: map[string][]byte{},
}
return nil
}
func (r *Certificate) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
return utilities.AddTenantPrefix(r.GetName(), tenantControlPlane)
}
func (r *Certificate) GetClient() client.Client {
return r.Client
}
func (r *Certificate) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
return utilities.CreateOrUpdateWithConflict(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
}
func (r *Certificate) GetName() string {
return "datastore-certificate"
}
func (r *Certificate) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
tenantControlPlane.Status.Storage.Certificate.SecretName = r.resource.GetName()
tenantControlPlane.Status.Storage.Certificate.Checksum = r.resource.GetAnnotations()["checksum"]
tenantControlPlane.Status.Storage.Certificate.LastUpdate = metav1.Now()
return nil
}
func (r *Certificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
return func() error {
ca, err := r.DataStore.Spec.TLSConfig.CertificateAuthority.Certificate.GetContent(ctx, r.Client)
if err != nil {
return err
}
r.resource.Data["ca.crt"] = ca
if r.resource.GetAnnotations()["checksum"] == utilities.CalculateConfigMapChecksum(r.resource.StringData) {
if r.DataStore.Spec.Driver == kamajiv1alpha1.EtcdDriver {
if isValid, _ := etcd.IsETCDCertificateAndKeyPairValid(r.resource.Data["server.crt"], r.resource.Data["server.key"]); isValid {
return nil
}
}
}
var crt, key *bytes.Buffer
switch r.DataStore.Spec.Driver {
case kamajiv1alpha1.EtcdDriver:
// When dealing with the etcd storage we cannot use the basic authentication, thus the generation of a
// certificate used for authentication is mandatory, along with the CA private key.
privateKey, err := r.DataStore.Spec.TLSConfig.CertificateAuthority.PrivateKey.GetContent(ctx, r.Client)
if err != nil {
return err
}
crt, key, err = etcd.GetETCDCACertificateAndKeyPair(tenantControlPlane.GetName(), ca, privateKey)
if err != nil {
return err
}
case kamajiv1alpha1.KineMySQLDriver, kamajiv1alpha1.KinePostgreSQLDriver:
// For the SQL drivers we just need to copy the certificate, since the basic authentication is used
// to connect to the desired schema and database.
crtBytes, err := r.DataStore.Spec.TLSConfig.ClientCertificate.Certificate.GetContent(ctx, r.Client)
if err != nil {
return err
}
crt = bytes.NewBuffer(crtBytes)
keyBytes, err := r.DataStore.Spec.TLSConfig.ClientCertificate.PrivateKey.GetContent(ctx, r.Client)
if err != nil {
return err
}
key = bytes.NewBuffer(keyBytes)
default:
return fmt.Errorf("unrecognized driver for Certificate generation")
}
r.resource.Data["server.crt"] = crt.Bytes()
r.resource.Data["server.key"] = key.Bytes()
annotations := r.resource.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
r.resource.SetAnnotations(annotations)
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(),
r.resource.GetLabels(),
map[string]string{
"kamaji.clastix.io/name": tenantControlPlane.GetName(),
"kamaji.clastix.io/component": r.GetName(),
},
))
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
}
}

View File

@@ -0,0 +1,237 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package datastore
import (
"context"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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/datastore"
"github.com/clastix/kamaji/internal/resources/utils"
)
type SetupResource struct {
schema string
user string
password string
}
type Setup struct {
resource SetupResource
Client client.Client
Connection datastore.Connection
DataStore kamajiv1alpha1.DataStore
}
func (r *Setup) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
return tenantControlPlane.Status.Storage.Driver != string(r.DataStore.Spec.Driver) &&
tenantControlPlane.Status.Storage.Setup.Checksum != tenantControlPlane.Status.Storage.Config.Checksum
}
func (r *Setup) ShouldCleanup(_ *kamajiv1alpha1.TenantControlPlane) bool {
return false
}
func (r *Setup) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bool, error) {
return false, nil
}
func (r *Setup) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
secret := &corev1.Secret{}
namespacedName := types.NamespacedName{
Namespace: tenantControlPlane.GetNamespace(),
Name: tenantControlPlane.Status.Storage.Config.SecretName,
}
if err := r.Client.Get(ctx, namespacedName, secret); err != nil {
return err
}
r.resource = SetupResource{
schema: string(secret.Data["DB_SCHEMA"]),
user: string(secret.Data["DB_USER"]),
password: string(secret.Data["DB_PASSWORD"]),
}
return nil
}
func (r *Setup) GetClient() client.Client {
return r.Client
}
func (r *Setup) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
if tenantControlPlane.Status.Storage.Setup.Checksum != "" &&
tenantControlPlane.Status.Storage.Setup.Checksum != tenantControlPlane.Status.Storage.Config.Checksum {
if err := r.Delete(ctx, tenantControlPlane); err != nil {
return controllerutil.OperationResultNone, err
}
return controllerutil.OperationResultUpdated, nil
}
reconcilationResult := controllerutil.OperationResultNone
var operationResult controllerutil.OperationResult
var err error
operationResult, err = r.createDB(ctx, tenantControlPlane)
if err != nil {
return reconcilationResult, err
}
reconcilationResult = utils.UpdateOperationResult(reconcilationResult, operationResult)
operationResult, err = r.createUser(ctx, tenantControlPlane)
if err != nil {
return reconcilationResult, err
}
reconcilationResult = utils.UpdateOperationResult(reconcilationResult, operationResult)
operationResult, err = r.createGrantPrivileges(ctx, tenantControlPlane)
if err != nil {
return reconcilationResult, err
}
reconcilationResult = utils.UpdateOperationResult(reconcilationResult, operationResult)
return reconcilationResult, nil
}
func (r *Setup) GetName() string {
return "datastore-setup"
}
func (r *Setup) Delete(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
if err := r.Define(ctx, tenantControlPlane); err != nil {
return err
}
if err := r.revokeGrantPrivileges(ctx, tenantControlPlane); err != nil {
return err
}
if err := r.deleteDB(ctx, tenantControlPlane); err != nil {
return err
}
if err := r.deleteUser(ctx, tenantControlPlane); err != nil {
return err
}
return nil
}
func (r *Setup) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
tenantControlPlane.Status.Storage.Setup.Schema = r.resource.schema
tenantControlPlane.Status.Storage.Setup.User = r.resource.user
tenantControlPlane.Status.Storage.Setup.LastUpdate = metav1.Now()
tenantControlPlane.Status.Storage.Setup.Checksum = tenantControlPlane.Status.Storage.Config.Checksum
return nil
}
func (r *Setup) createDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
exists, err := r.Connection.DBExists(ctx, r.resource.schema)
if err != nil {
return controllerutil.OperationResultNone, err
}
if exists {
return controllerutil.OperationResultNone, nil
}
if err := r.Connection.CreateDB(ctx, r.resource.schema); err != nil {
return controllerutil.OperationResultNone, err
}
return controllerutil.OperationResultCreated, nil
}
func (r *Setup) deleteDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
exists, err := r.Connection.DBExists(ctx, r.resource.schema)
if err != nil {
return err
}
if !exists {
return nil
}
if err := r.Connection.DeleteDB(ctx, r.resource.schema); err != nil {
return err
}
return nil
}
func (r *Setup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
exists, err := r.Connection.UserExists(ctx, r.resource.user)
if err != nil {
return controllerutil.OperationResultNone, err
}
if exists {
return controllerutil.OperationResultNone, nil
}
if err := r.Connection.CreateUser(ctx, r.resource.user, r.resource.password); err != nil {
return controllerutil.OperationResultNone, err
}
return controllerutil.OperationResultCreated, nil
}
func (r *Setup) deleteUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
exists, err := r.Connection.UserExists(ctx, r.resource.user)
if err != nil {
return err
}
if !exists {
return nil
}
if err := r.Connection.DeleteUser(ctx, r.resource.user); err != nil {
return err
}
return nil
}
func (r *Setup) createGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
exists, err := r.Connection.GrantPrivilegesExists(ctx, r.resource.user, r.resource.schema)
if err != nil {
return controllerutil.OperationResultNone, err
}
if exists {
return controllerutil.OperationResultNone, nil
}
if err := r.Connection.GrantPrivileges(ctx, r.resource.user, r.resource.schema); err != nil {
return controllerutil.OperationResultNone, err
}
return controllerutil.OperationResultCreated, nil
}
func (r *Setup) revokeGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
exists, err := r.Connection.GrantPrivilegesExists(ctx, r.resource.user, r.resource.schema)
if err != nil {
return err
}
if !exists {
return nil
}
if err := r.Connection.RevokePrivileges(ctx, r.resource.user, r.resource.schema); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,111 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package datastore
import (
"context"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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/utilities"
)
type Config struct {
resource *corev1.Secret
Client client.Client
ConnString string
Driver string
}
func (r *Config) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
return tenantControlPlane.Status.Storage.Config.Checksum != r.resource.GetAnnotations()["checksum"] ||
tenantControlPlane.Status.Storage.Driver != r.Driver
}
func (r *Config) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
return false
}
func (r *Config) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bool, error) {
return false, nil
}
func (r *Config) Define(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
r.resource = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: r.getPrefixedName(tenantControlPlane),
Namespace: tenantControlPlane.GetNamespace(),
},
}
return nil
}
func (r *Config) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
return utilities.AddTenantPrefix(r.GetName(), tenantControlPlane)
}
func (r *Config) GetClient() client.Client {
return r.Client
}
func (r *Config) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
return utilities.CreateOrUpdateWithConflict(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
}
func (r *Config) GetName() string {
return "datastore-config"
}
func (r *Config) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
tenantControlPlane.Status.Storage.Driver = r.Driver
tenantControlPlane.Status.Storage.Config.SecretName = r.resource.GetName()
tenantControlPlane.Status.Storage.Config.Checksum = r.resource.GetAnnotations()["checksum"]
return nil
}
func (r *Config) mutate(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
return func() error {
var password []byte
savedHash, ok := r.resource.GetAnnotations()["checksum"]
switch {
case ok && savedHash == utilities.CalculateConfigMapChecksum(r.resource.StringData):
password = r.resource.Data["DB_PASSWORD"]
default:
password = []byte(utilities.GenerateUUIDString())
}
r.resource.Data = map[string][]byte{
"DB_CONNECTION_STRING": []byte(r.ConnString),
"DB_SCHEMA": []byte(tenantControlPlane.GetName()),
"DB_USER": []byte(tenantControlPlane.GetName()),
"DB_PASSWORD": password,
}
annotations := r.resource.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
r.resource.SetAnnotations(annotations)
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())
}
}

View File

@@ -1,125 +0,0 @@
// 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/etcd"
"github.com/clastix/kamaji/internal/utilities"
)
type ETCDCACertificatesResource struct {
resource *corev1.Secret
Client client.Client
Log logr.Logger
Name string
DataStore kamajiv1alpha1.DataStore
}
func (r *ETCDCACertificatesResource) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
if tenantControlPlane.Status.Certificates.ETCD == nil {
return true
}
return tenantControlPlane.Status.Certificates.ETCD.CA.Checksum != r.resource.GetAnnotations()["checksum"]
}
func (r *ETCDCACertificatesResource) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
return false
}
func (r *ETCDCACertificatesResource) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bool, error) {
return false, nil
}
func (r *ETCDCACertificatesResource) Define(_ 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 utilities.CreateOrUpdateWithConflict(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
}
func (r *ETCDCACertificatesResource) GetName() string {
return r.Name
}
func (r *ETCDCACertificatesResource) UpdateTenantControlPlaneStatus(_ 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()
tenantControlPlane.Status.Certificates.ETCD.CA.Checksum = r.resource.GetAnnotations()["checksum"]
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 {
if r.DataStore.Spec.TLSConfig.CertificateAuthority.PrivateKey == nil {
return fmt.Errorf("missing private key, cannot generate certificate for the given tenant control plane")
}
if etcdStatus := tenantControlPlane.Status.Certificates.ETCD; etcdStatus != nil && len(etcdStatus.CA.Checksum) > 0 && etcdStatus.CA.Checksum == r.resource.GetAnnotations()["checksum"] {
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 isValid {
return nil
}
}
ca, err := r.DataStore.Spec.TLSConfig.CertificateAuthority.Certificate.GetContent(ctx, r.Client)
if err != nil {
return err
}
key, err := r.DataStore.Spec.TLSConfig.CertificateAuthority.PrivateKey.GetContent(ctx, r.Client)
if err != nil {
return err
}
r.resource.Data = map[string][]byte{
kubeadmconstants.CACertName: ca,
kubeadmconstants.CAKeyName: key,
}
r.resource.SetLabels(utilities.KamajiLabels())
annotations := r.resource.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
r.resource.SetAnnotations(annotations)
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
}
}

View File

@@ -1,130 +0,0 @@
// 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(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
if tenantControlPlane.Status.Certificates.ETCD == nil {
return true
}
return tenantControlPlane.Status.Certificates.ETCD.APIServer.Checksum != r.resource.GetAnnotations()["checksum"]
}
func (r *ETCDCertificatesResource) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
return false
}
func (r *ETCDCertificatesResource) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bool, error) {
return false, nil
}
func (r *ETCDCertificatesResource) Define(_ 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 utilities.CreateOrUpdateWithConflict(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()
tenantControlPlane.Status.Certificates.ETCD.APIServer.Checksum = r.resource.GetAnnotations()["checksum"]
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 {
if tenantControlPlane.Status.Certificates.ETCD == nil {
return fmt.Errorf("etcd is still synchronizing latest changes")
}
if checksum := tenantControlPlane.Status.Certificates.ETCD.APIServer.Checksum; len(checksum) > 0 && checksum == r.resource.GetAnnotations()["checksum"] {
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
}
}
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
}
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(),
}
r.resource.SetLabels(utilities.KamajiLabels())
annotations := r.resource.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
r.resource.SetAnnotations(annotations)
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
}
}

View File

@@ -1,273 +0,0 @@
// 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"
"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"
)
type etcdSetupResource struct {
role etcd.Role
user etcd.User
}
type ETCDSetupResource struct {
resource *etcdSetupResource
Client client.Client
Log logr.Logger
DataStore kamajiv1alpha1.DataStore
}
func (r *ETCDSetupResource) ShouldStatusBeUpdated(_ 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(*kamajiv1alpha1.TenantControlPlane) bool {
return false
}
func (r *ETCDSetupResource) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bool, error) {
return false, nil
}
func (r *ETCDSetupResource) Define(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
r.resource = &etcdSetupResource{
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, _ *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 "etcd-setup"
}
func (r *ETCDSetupResource) UpdateTenantControlPlaneStatus(_ 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
}
defer client.Close()
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) {
ca, err := r.DataStore.Spec.TLSConfig.CertificateAuthority.Certificate.GetContent(ctx, r.Client)
if err != nil {
return nil, err
}
crt, err := r.DataStore.Spec.TLSConfig.ClientCertificate.Certificate.GetContent(ctx, r.Client)
if err != nil {
return nil, err
}
key, err := r.DataStore.Spec.TLSConfig.ClientCertificate.PrivateKey.GetContent(ctx, r.Client)
if err != nil {
return nil, err
}
config := etcd.Config{
ETCDCertificate: crt,
ETCDPrivateKey: key,
ETCDCA: ca,
Endpoints: r.DataStore.Spec.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
}

View File

@@ -20,8 +20,7 @@ import (
type KubernetesDeploymentResource struct {
resource *appsv1.Deployment
Client client.Client
DataStoreDriver kamajiv1alpha1.Driver
ETCDEndpoints []string
DataStore kamajiv1alpha1.DataStore
Name string
KineContainerImage string
}
@@ -64,8 +63,7 @@ func (r *KubernetesDeploymentResource) mutate(ctx context.Context, tenantControl
d := builder.Deployment{
Address: address,
ETCDEndpoints: r.ETCDEndpoints,
ETCDStorageType: r.DataStoreDriver,
DataStore: r.DataStore,
KineContainerImage: r.KineContainerImage,
}
d.SetLabels(r.resource, utilities.MergeMaps(utilities.CommonLabels(tenantControlPlane.GetName()), tenantControlPlane.Spec.ControlPlane.Deployment.AdditionalMetadata.Labels))
@@ -114,10 +112,10 @@ func (r *KubernetesDeploymentResource) UpdateTenantControlPlaneStatus(_ context.
}
func (r *KubernetesDeploymentResource) deploymentTemplateLabels(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (labels map[string]string) {
hash := func(ctx context.Context, namespace, secretName string) (hash string) {
hash, _ = utilities.SecretHashValue(ctx, r.Client, namespace, secretName)
hash := func(ctx context.Context, namespace, secretName string) string {
h, _ := utilities.SecretHashValue(ctx, r.Client, namespace, secretName)
return
return h
}
labels = map[string]string{
@@ -132,11 +130,6 @@ func (r *KubernetesDeploymentResource) deploymentTemplateLabels(ctx context.Cont
"component.kamaji.clastix.io/scheduler-kubeconfig": hash(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.KubeConfig.Scheduler.SecretName),
}
if r.DataStoreDriver == kamajiv1alpha1.EtcdDriver {
labels["component.kamaji.clastix.io/etcd-ca-certificates"] = hash(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.ETCD.CA.SecretName)
labels["component.kamaji.clastix.io/etcd-certificates"] = hash(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.ETCD.APIServer.SecretName)
}
return labels
}

View File

@@ -22,7 +22,6 @@ import (
type KubernetesServiceResource struct {
resource *corev1.Service
Client client.Client
Name string
}
func (r *KubernetesServiceResource) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
@@ -63,8 +62,6 @@ func (r *KubernetesServiceResource) Define(_ context.Context, tenantControlPlane
},
}
r.Name = "service"
return nil
}
@@ -124,5 +121,5 @@ func (r *KubernetesServiceResource) mutate(ctx context.Context, tenantControlPla
}
func (r *KubernetesServiceResource) GetName() string {
return r.Name
return "service"
}

View File

@@ -70,7 +70,7 @@ func (r *Agent) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.T
}
func (r *Agent) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
return controllerutil.CreateOrUpdate(ctx, r.tenantClient, r.resource, r.mutate(ctx, tenantControlPlane))
return controllerutil.CreateOrUpdate(ctx, r.tenantClient, r.resource, r.mutate(tenantControlPlane))
}
func (r *Agent) GetName() string {
@@ -96,8 +96,8 @@ func (r *Agent) UpdateTenantControlPlaneStatus(ctx context.Context, tenantContro
return nil
}
func (r *Agent) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
return func() (err error) {
func (r *Agent) mutate(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
return func() error {
address, _, err := tenantControlPlane.AssignedControlPlaneAddress()
if err != nil {
return err

View File

@@ -62,7 +62,7 @@ func (r *ClusterRoleBindingResource) Define(ctx context.Context, tenantControlPl
}
func (r *ClusterRoleBindingResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
return controllerutil.CreateOrUpdate(ctx, r.tenantClient, r.resource, r.mutate(ctx, tenantControlPlane))
return controllerutil.CreateOrUpdate(ctx, r.tenantClient, r.resource, r.mutate())
}
func (r *ClusterRoleBindingResource) GetName() string {
@@ -86,7 +86,7 @@ func (r *ClusterRoleBindingResource) UpdateTenantControlPlaneStatus(ctx context.
return nil
}
func (r *ClusterRoleBindingResource) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
func (r *ClusterRoleBindingResource) mutate() controllerutil.MutateFn {
return func() error {
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(),

View File

@@ -18,7 +18,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/types"
"github.com/clastix/kamaji/internal/utilities"
)
@@ -33,15 +32,10 @@ const (
)
type KubernetesDeploymentResource struct {
resource *appsv1.Deployment
Client client.Client
ETCDStorageType types.ETCDStorageType
ETCDEndpoints []string
Name string
}
func (r *KubernetesDeploymentResource) isStatusEqual(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
return r.resource.Status.String() == tenantControlPlane.Status.Kubernetes.Deployment.DeploymentStatus.String()
resource *appsv1.Deployment
Client client.Client
ETCDEndpoints []string
Name string
}
func (r *KubernetesDeploymentResource) ShouldStatusBeUpdated(context.Context, *kamajiv1alpha1.TenantControlPlane) bool {
@@ -109,7 +103,7 @@ func (r *KubernetesDeploymentResource) Define(ctx context.Context, tenantControl
return nil
}
func (r *KubernetesDeploymentResource) syncContainer(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
func (r *KubernetesDeploymentResource) syncContainer(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) {
found, index := utilities.HasNamedContainer(r.resource.Spec.Template.Spec.Containers, konnectivityServerName)
if !found {
r.resource.Spec.Template.Spec.Containers = append(r.resource.Spec.Template.Spec.Containers, corev1.Container{})
@@ -194,8 +188,6 @@ func (r *KubernetesDeploymentResource) syncContainer(tenantControlPlane *kamajiv
if resources := tenantControlPlane.Spec.Addons.Konnectivity.Resources; resources != nil {
r.resource.Spec.Template.Spec.Containers[index].Resources = *resources
}
return nil
}
func (r *KubernetesDeploymentResource) mutate(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
@@ -210,15 +202,13 @@ func (r *KubernetesDeploymentResource) mutate(_ context.Context, tenantControlPl
return fmt.Errorf("the Deployment resource is not ready to be mangled for Konnectivity server enrichment")
}
if err = r.syncContainer(tenantControlPlane); err != nil {
return errors.Wrap(err, "cannot sync konnectivity-server container")
}
r.syncContainer(tenantControlPlane)
if err = r.patchKubeAPIServerContainer(); err != nil {
return errors.Wrap(err, "cannot sync patch kube-apiserver container")
}
if err = r.syncVolumes(tenantControlPlane); err != nil {
return errors.Wrap(err, "cannot patch required konnectivity volumes")
}
r.syncVolumes(tenantControlPlane)
return nil
}
@@ -247,9 +237,7 @@ func (r *KubernetesDeploymentResource) patchKubeAPIServerContainer() error {
// Adding the egress selector config file flag
args := utilities.ArgsFromSliceToMap(r.resource.Spec.Template.Spec.Containers[index].Args)
if utilities.ArgsAddFlagValue(args, "--egress-selector-config-file", konnectivityEgressSelectorConfigurationPath) {
// LOG
}
utilities.ArgsAddFlagValue(args, "--egress-selector-config-file", konnectivityEgressSelectorConfigurationPath)
r.resource.Spec.Template.Spec.Containers[index].Args = utilities.ArgsFromMapToSlice(args)
@@ -276,7 +264,7 @@ func (r *KubernetesDeploymentResource) patchKubeAPIServerContainer() error {
return nil
}
func (r *KubernetesDeploymentResource) syncVolumes(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
func (r *KubernetesDeploymentResource) syncVolumes(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) {
found, index := false, 0
// Defining volumes for the UDS socket
found, index = utilities.HasNamedVolume(r.resource.Spec.Template.Spec.Volumes, konnectivityUDSVolume)
@@ -321,6 +309,4 @@ func (r *KubernetesDeploymentResource) syncVolumes(tenantControlPlane *kamajiv1a
DefaultMode: pointer.Int32Ptr(420),
},
}
return nil
}

View File

@@ -77,7 +77,7 @@ func (r *EgressSelectorConfigurationResource) UpdateTenantControlPlaneStatus(ctx
return nil
}
func (r *EgressSelectorConfigurationResource) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) func() error {
func (r *EgressSelectorConfigurationResource) mutate(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) func() error {
return func() error {
r.resource.SetLabels(utilities.MergeMaps(r.resource.GetLabels(), utilities.KamajiLabels()))

View File

@@ -61,8 +61,8 @@ func (r *ServiceAccountResource) Define(ctx context.Context, tenantControlPlane
return nil
}
func (r *ServiceAccountResource) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
return controllerutil.CreateOrUpdate(ctx, r.tenantClient, r.resource, r.mutate(ctx, tenantControlPlane))
func (r *ServiceAccountResource) CreateOrUpdate(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
return controllerutil.CreateOrUpdate(ctx, r.tenantClient, r.resource, r.mutate())
}
func (r *ServiceAccountResource) GetName() string {
@@ -87,7 +87,7 @@ func (r *ServiceAccountResource) UpdateTenantControlPlaneStatus(ctx context.Cont
return nil
}
func (r *ServiceAccountResource) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
func (r *ServiceAccountResource) mutate() controllerutil.MutateFn {
return func() error {
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(),

View File

@@ -125,7 +125,7 @@ func (r *ServiceResource) CreateOrUpdate(ctx context.Context, tenantControlPlane
}
func (r *ServiceResource) mutate(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) func() error {
return func() (err error) {
return func() error {
switch len(r.resource.Spec.Ports) {
case 0:
return fmt.Errorf("current state of the Service is not ready to be mangled for Konnectivity")

View File

@@ -15,6 +15,7 @@ import (
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
"github.com/clastix/kamaji/internal/kubeadm"
"github.com/clastix/kamaji/internal/resources/utils"
)
type kubeadmPhase int
@@ -104,7 +105,7 @@ func enrichBootstrapToken(bootstrapToken *bootstraptokenv1.BootstrapToken) {
}
if bootstrapToken.Token.ID == "" {
bootstrapToken.Token.ID = fmt.Sprintf("%s.%s", randomString(6), randomString(16))
bootstrapToken.Token.ID = fmt.Sprintf("%s.%s", utils.RandomString(6), utils.RandomString(16))
}
}

View File

@@ -146,6 +146,7 @@ func (r *KubeconfigResource) mutate(ctx context.Context, tenantControlPlane *kam
kubeconfig, err := kubeadm.CreateKubeconfig(
r.KubeConfigFileName,
kubeadm.CertificatePrivateKeyPair{
Certificate: apiServerCertificatesSecret.Data[kubeadmconstants.CACertName],
PrivateKey: apiServerCertificatesSecret.Data[kubeadmconstants.CAKeyName],

View File

@@ -1,123 +0,0 @@
// 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"
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/types"
"github.com/clastix/kamaji/internal/utilities"
)
type SQLCertificate struct {
resource *corev1.Secret
Client client.Client
Name string
StorageType types.ETCDStorageType
DataStore kamajiv1alpha1.DataStore
}
func (r *SQLCertificate) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
return tenantControlPlane.Status.Storage.Kine.Certificate.Checksum != r.resource.GetAnnotations()["checksum"]
}
func (r *SQLCertificate) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
return false
}
func (r *SQLCertificate) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bool, error) {
return false, nil
}
func (r *SQLCertificate) Define(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
r.resource = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: r.getPrefixedName(tenantControlPlane),
Namespace: tenantControlPlane.GetNamespace(),
},
Data: map[string][]byte{},
}
return nil
}
func (r *SQLCertificate) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
return utilities.AddTenantPrefix(r.GetName(), tenantControlPlane)
}
func (r *SQLCertificate) GetClient() client.Client {
return r.Client
}
func (r *SQLCertificate) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
return utilities.CreateOrUpdateWithConflict(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
}
func (r *SQLCertificate) GetName() string {
return r.Name
}
func (r *SQLCertificate) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
if tenantControlPlane.Status.Storage.Kine == nil {
tenantControlPlane.Status.Storage.Kine = &kamajiv1alpha1.KineStatus{}
}
tenantControlPlane.Status.Storage.Kine.Certificate.SecretName = r.resource.GetName()
tenantControlPlane.Status.Storage.Kine.Certificate.Checksum = r.resource.GetAnnotations()["checksum"]
tenantControlPlane.Status.Storage.Kine.Certificate.LastUpdate = metav1.Now()
return nil
}
func (r *SQLCertificate) mutate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
return func() error {
ca, err := r.DataStore.Spec.TLSConfig.CertificateAuthority.Certificate.GetContent(ctx, r.Client)
if err != nil {
return nil
}
crt, err := r.DataStore.Spec.TLSConfig.ClientCertificate.Certificate.GetContent(ctx, r.Client)
if err != nil {
return nil
}
key, err := r.DataStore.Spec.TLSConfig.ClientCertificate.PrivateKey.GetContent(ctx, r.Client)
if err != nil {
return nil
}
r.resource.Data = map[string][]byte{
"ca.crt": ca,
"server.crt": crt,
"server.key": key,
}
annotations := r.resource.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
r.resource.SetAnnotations(annotations)
r.resource.SetLabels(utilities.MergeMaps(
utilities.KamajiLabels(),
r.resource.GetLabels(),
map[string]string{
"kamaji.clastix.io/name": tenantControlPlane.GetName(),
"kamaji.clastix.io/component": r.GetName(),
},
))
return ctrl.SetControllerReference(tenantControlPlane, r.resource, r.Client.Scheme())
}
}

View File

@@ -1,245 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package resources
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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/sql"
)
type sqlSetupResource struct {
schema string
user string
password string
}
type SQLSetup struct {
resource sqlSetupResource
Client client.Client
DBConnection sql.DBConnection
Driver string
}
func (r *SQLSetup) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
if tenantControlPlane.Status.Storage.Kine == nil {
return true
}
return tenantControlPlane.Status.Storage.Kine.Driver != r.Driver &&
tenantControlPlane.Status.Storage.Kine.Setup.Checksum != tenantControlPlane.Status.Storage.Kine.Config.Checksum
}
func (r *SQLSetup) ShouldCleanup(_ *kamajiv1alpha1.TenantControlPlane) bool {
return false
}
func (r *SQLSetup) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bool, error) {
return false, nil
}
func (r *SQLSetup) Define(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
secret := &corev1.Secret{}
namespacedName := types.NamespacedName{
Namespace: tenantControlPlane.GetNamespace(),
Name: tenantControlPlane.Status.Storage.Kine.Config.SecretName,
}
if err := r.Client.Get(ctx, namespacedName, secret); err != nil {
return err
}
r.resource = sqlSetupResource{
schema: string(secret.Data["DB_SCHEMA"]),
user: string(secret.Data["DB_USER"]),
password: string(secret.Data["DB_PASSWORD"]),
}
return nil
}
func (r *SQLSetup) GetClient() client.Client {
return r.Client
}
func (r *SQLSetup) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
if tenantControlPlane.Status.Storage.Kine.Setup.Checksum != "" &&
tenantControlPlane.Status.Storage.Kine.Setup.Checksum != tenantControlPlane.Status.Storage.Kine.Config.Checksum {
if err := r.Delete(ctx, tenantControlPlane); err != nil {
return controllerutil.OperationResultNone, err
}
return controllerutil.OperationResultUpdated, nil
}
reconcilationResult := controllerutil.OperationResultNone
var operationResult controllerutil.OperationResult
var err error
operationResult, err = r.createDB(ctx, tenantControlPlane)
if err != nil {
return reconcilationResult, err
}
reconcilationResult = updateOperationResult(reconcilationResult, operationResult)
operationResult, err = r.createUser(ctx, tenantControlPlane)
if err != nil {
return reconcilationResult, err
}
reconcilationResult = updateOperationResult(reconcilationResult, operationResult)
operationResult, err = r.createGrantPrivileges(ctx, tenantControlPlane)
if err != nil {
return reconcilationResult, err
}
reconcilationResult = updateOperationResult(reconcilationResult, operationResult)
return reconcilationResult, nil
}
func (r *SQLSetup) GetName() string {
return "sql-setup"
}
func (r *SQLSetup) Delete(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
if err := r.Define(ctx, tenantControlPlane); err != nil {
return err
}
if err := r.revokeGrantPrivileges(ctx, tenantControlPlane); err != nil {
return err
}
if err := r.deleteDB(ctx, tenantControlPlane); err != nil {
return err
}
if err := r.deleteUser(ctx, tenantControlPlane); err != nil {
return err
}
return nil
}
func (r *SQLSetup) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
if tenantControlPlane.Status.Storage.Kine == nil {
return fmt.Errorf("sql configuration is not ready")
}
tenantControlPlane.Status.Storage.Kine.Setup.Schema = r.resource.schema
tenantControlPlane.Status.Storage.Kine.Setup.User = r.resource.user
tenantControlPlane.Status.Storage.Kine.Setup.LastUpdate = metav1.Now()
tenantControlPlane.Status.Storage.Kine.Setup.Checksum = tenantControlPlane.Status.Storage.Kine.Config.Checksum
return nil
}
func (r *SQLSetup) createDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
exists, err := r.DBConnection.DBExists(ctx, r.resource.schema)
if err != nil {
return controllerutil.OperationResultNone, err
}
if exists {
return controllerutil.OperationResultNone, nil
}
if err := r.DBConnection.CreateDB(ctx, r.resource.schema); err != nil {
return controllerutil.OperationResultNone, err
}
return controllerutil.OperationResultCreated, nil
}
func (r *SQLSetup) deleteDB(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
exists, err := r.DBConnection.DBExists(ctx, r.resource.schema)
if err != nil {
return err
}
if !exists {
return nil
}
if err := r.DBConnection.DeleteDB(ctx, r.resource.schema); err != nil {
return err
}
return nil
}
func (r *SQLSetup) createUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
exists, err := r.DBConnection.UserExists(ctx, r.resource.user)
if err != nil {
return controllerutil.OperationResultNone, err
}
if exists {
return controllerutil.OperationResultNone, nil
}
if err := r.DBConnection.CreateUser(ctx, r.resource.user, r.resource.password); err != nil {
return controllerutil.OperationResultNone, err
}
return controllerutil.OperationResultCreated, nil
}
func (r *SQLSetup) deleteUser(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
exists, err := r.DBConnection.UserExists(ctx, r.resource.user)
if err != nil {
return err
}
if !exists {
return nil
}
if err := r.DBConnection.DeleteUser(ctx, r.resource.user); err != nil {
return err
}
return nil
}
func (r *SQLSetup) createGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
exists, err := r.DBConnection.GrantPrivilegesExists(ctx, r.resource.user, r.resource.schema)
if err != nil {
return controllerutil.OperationResultNone, err
}
if exists {
return controllerutil.OperationResultNone, nil
}
if err := r.DBConnection.GrantPrivileges(ctx, r.resource.user, r.resource.schema); err != nil {
return controllerutil.OperationResultNone, err
}
return controllerutil.OperationResultCreated, nil
}
func (r *SQLSetup) revokeGrantPrivileges(ctx context.Context, _ *kamajiv1alpha1.TenantControlPlane) error {
exists, err := r.DBConnection.GrantPrivilegesExists(ctx, r.resource.user, r.resource.schema)
if err != nil {
return err
}
if !exists {
return nil
}
if err := r.DBConnection.RevokePrivileges(ctx, r.resource.user, r.resource.schema); err != nil {
return err
}
return nil
}

View File

@@ -1,123 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package resources
import (
"context"
"strconv"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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/utilities"
)
type SQLStorageConfig struct {
resource *corev1.Secret
Client client.Client
Name string
Host string
Port int
Driver string
}
func (r *SQLStorageConfig) ShouldStatusBeUpdated(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) bool {
if tenantControlPlane.Status.Storage.Kine == nil {
return true
}
return tenantControlPlane.Status.Storage.Kine.Config.Checksum != r.resource.GetAnnotations()["checksum"] ||
tenantControlPlane.Status.Storage.Kine.Driver != r.Driver
}
func (r *SQLStorageConfig) ShouldCleanup(*kamajiv1alpha1.TenantControlPlane) bool {
return false
}
func (r *SQLStorageConfig) CleanUp(context.Context, *kamajiv1alpha1.TenantControlPlane) (bool, error) {
return false, nil
}
func (r *SQLStorageConfig) Define(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
r.resource = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: r.getPrefixedName(tenantControlPlane),
Namespace: tenantControlPlane.GetNamespace(),
},
}
return nil
}
func (r *SQLStorageConfig) getPrefixedName(tenantControlPlane *kamajiv1alpha1.TenantControlPlane) string {
return utilities.AddTenantPrefix(r.Name, tenantControlPlane)
}
func (r *SQLStorageConfig) GetClient() client.Client {
return r.Client
}
func (r *SQLStorageConfig) CreateOrUpdate(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (controllerutil.OperationResult, error) {
return utilities.CreateOrUpdateWithConflict(ctx, r.Client, r.resource, r.mutate(ctx, tenantControlPlane))
}
func (r *SQLStorageConfig) GetName() string {
return r.Name
}
func (r *SQLStorageConfig) UpdateTenantControlPlaneStatus(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) error {
if tenantControlPlane.Status.Storage.Kine == nil {
tenantControlPlane.Status.Storage.Kine = &kamajiv1alpha1.KineStatus{}
}
tenantControlPlane.Status.Storage.Kine.Driver = r.Driver
tenantControlPlane.Status.Storage.Kine.Config.SecretName = r.resource.GetName()
tenantControlPlane.Status.Storage.Kine.Config.Checksum = r.resource.GetAnnotations()["checksum"]
return nil
}
func (r *SQLStorageConfig) mutate(_ context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) controllerutil.MutateFn {
return func() error {
var password []byte
savedHash, ok := r.resource.GetAnnotations()["checksum"]
switch {
case ok && savedHash == utilities.CalculateConfigMapChecksum(r.resource.StringData):
password = r.resource.Data["DB_PASSWORD"]
default:
password = []byte(utilities.GenerateUUIDString())
}
r.resource.Data = map[string][]byte{
"DB_HOST": []byte(r.Host),
"DB_PORT": []byte(strconv.Itoa(r.Port)),
"DB_SCHEMA": []byte(tenantControlPlane.GetName()),
"DB_USER": []byte(tenantControlPlane.GetName()),
"DB_PASSWORD": password,
}
annotations := r.resource.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
annotations["checksum"] = utilities.CalculateConfigMapChecksum(r.resource.StringData)
r.resource.SetAnnotations(annotations)
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())
}
}

View File

@@ -1,7 +1,7 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package resources
package utils
import (
"math/rand"
@@ -12,7 +12,7 @@ import (
var letters = []byte("abcdefghijklmnopqrstuvwxyz")
func updateOperationResult(current controllerutil.OperationResult, op controllerutil.OperationResult) controllerutil.OperationResult {
func UpdateOperationResult(current controllerutil.OperationResult, op controllerutil.OperationResult) controllerutil.OperationResult {
if current == controllerutil.OperationResultCreated || op == controllerutil.OperationResultCreated {
return controllerutil.OperationResultCreated
}
@@ -32,7 +32,7 @@ func updateOperationResult(current controllerutil.OperationResult, op controller
return controllerutil.OperationResultNone
}
func randomString(n int) string {
func RandomString(n int) string {
rand.Seed(time.Now().UnixNano())
b := make([]byte, n)
for i := range b {

View File

@@ -1,10 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package sql
const (
defaultProtocol = "tcp"
firstPort = 1024
sqlErrorNoRows = "sql: no rows in result set"
)

View File

@@ -1,110 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package sql
import (
"context"
"crypto/tls"
"fmt"
"net/url"
)
type Driver int
const (
MySQL Driver = iota
PostgreSQL
)
func (d Driver) ToString() string {
switch d {
case MySQL:
return "mysql"
case PostgreSQL:
return "postgresql"
default:
return ""
}
}
type ConnectionConfig struct {
SQLDriver Driver
User string
Password string
Host string
Port int
DBName string
TLSConfig *tls.Config
Parameters map[string][]string
}
func (config ConnectionConfig) GetDataSourceName() string {
userPassword := config.getDataSourceNameUserPassword()
db := config.getDataSourceNameDB()
dataSourceName := fmt.Sprintf("%s%s/%s?%s", userPassword, db, config.DBName, config.formatParameters())
return dataSourceName
}
func (config ConnectionConfig) getDataSourceNameUserPassword() string {
if config.User == "" {
return ""
}
if config.Password == "" {
return fmt.Sprintf("%s@", config.User)
}
return fmt.Sprintf("%s:%s@", config.User, config.Password)
}
func (config ConnectionConfig) getDataSourceNameDB() string {
if config.Host == "" || config.Port < firstPort {
return ""
}
return fmt.Sprintf("%s(%s:%d)", defaultProtocol, config.Host, config.Port)
}
func (config ConnectionConfig) formatParameters() string {
if len(config.Parameters) == 0 {
return ""
}
values := url.Values(config.Parameters)
return values.Encode()
}
type DBConnection interface {
CreateUser(ctx context.Context, user, password string) error
CreateDB(ctx context.Context, dbName string) error
GrantPrivileges(ctx context.Context, user, dbName string) error
UserExists(ctx context.Context, user string) (bool, error)
DBExists(ctx context.Context, dbName string) (bool, error)
GrantPrivilegesExists(ctx context.Context, user, dbName string) (bool, error)
DeleteUser(ctx context.Context, user string) error
DeleteDB(ctx context.Context, dbName string) error
RevokePrivileges(ctx context.Context, user, dbName string) error
GetHost() string
GetPort() int
Close() error
Check() error
Driver() string
}
func GetDBConnection(config ConnectionConfig) (DBConnection, error) {
switch config.SQLDriver {
case MySQL:
return getMySQLDB(config)
case PostgreSQL:
return getPostgreSQLDB(config)
default:
return nil, fmt.Errorf("%s is not a valid driver", config.SQLDriver.ToString())
}
}
func checkEmptyQueryResult(err error) bool {
return err.Error() == sqlErrorNoRows
}

View File

@@ -1,45 +0,0 @@
// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package types
import (
"fmt"
"github.com/spf13/viper"
)
type ETCDStorageType int
const (
ETCD ETCDStorageType = iota
KineMySQL
KinePostgreSQL
)
var etcdStorageTypeString = map[string]ETCDStorageType{"etcd": ETCD, "kine-mysql": KineMySQL, "kine-postgresql": KinePostgreSQL}
func (s ETCDStorageType) String() string {
return [...]string{"etcd", "kine-mysql", "kine-postgresql"}[s]
}
// ParseETCDStorageType returns the ETCDStorageType given a string representation of the type.
func ParseETCDStorageType(s string) ETCDStorageType {
if storageType, ok := etcdStorageTypeString[s]; ok {
return storageType
}
panic(fmt.Errorf("unsupported storage type %s", s))
}
// ParseETCDEndpoint returns the default ETCD endpoints used to interact with the Tenant Control Plane backing storage.
func ParseETCDEndpoint(conf *viper.Viper) string {
switch ParseETCDStorageType(conf.GetString("etcd-storage-type")) {
case ETCD:
return conf.GetString("etcd-endpoints")
case KineMySQL, KinePostgreSQL:
return "127.0.0.1:2379"
default:
panic("unsupported storage type")
}
}

View File

@@ -63,5 +63,5 @@ func ArgsAddFlagValue(args map[string]string, flag, value string) bool {
args[flag] = value
return ok == false
return !ok
}

View File

@@ -6,6 +6,7 @@ package utilities
import (
"context"
"k8s.io/apimachinery/pkg/api/errors"
k8stypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -17,8 +18,10 @@ import (
// without enqueuing back the request in order to get the latest changes of the resource.
func CreateOrUpdateWithConflict(ctx context.Context, client client.Client, resource client.Object, f controllerutil.MutateFn) (res controllerutil.OperationResult, err error) {
err = retry.RetryOnConflict(retry.DefaultRetry, func() (scopeErr error) {
if scopeErr = client.Get(ctx, k8stypes.NamespacedName{Namespace: resource.GetNamespace(), Name: resource.GetName()}, resource); err != nil {
return err
if scopeErr = client.Get(ctx, k8stypes.NamespacedName{Namespace: resource.GetNamespace(), Name: resource.GetName()}, resource); scopeErr != nil {
if !errors.IsNotFound(scopeErr) {
return scopeErr
}
}
res, scopeErr = controllerutil.CreateOrUpdate(ctx, client, resource, f)

View File

@@ -43,7 +43,6 @@ func GetTenantRESTClient(ctx context.Context, client client.Client, tenantContro
func GetKubeconfigSecret(ctx context.Context, client client.Client, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (*corev1.Secret, error) {
secret := &corev1.Secret{}
if err := client.Get(ctx, k8stypes.NamespacedName{Namespace: tenantControlPlane.GetNamespace(), Name: tenantControlPlane.Status.KubeConfig.Admin.SecretName}, secret); err != nil {
return nil, err
}

View File

@@ -99,7 +99,6 @@ func GenerateUUIDString() string {
// SecretHashValue function returns the md5 value for the secret of the given name and namespace.
func SecretHashValue(ctx context.Context, client client.Client, namespace, name string) (string, error) {
secret := &corev1.Secret{}
if err := client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret); err != nil {
return "", errors.Wrap(err, "cannot retrieve *corev1.Secret for resource version retrieval")
}