From da1fa48fbd4090fc8bbe60ec9860f0dd0a8701ab Mon Sep 17 00:00:00 2001 From: Nicolas Bigler Date: Wed, 29 May 2024 15:25:18 +0200 Subject: [PATCH] Migrate PostgreSQL from P&T to comp-functions Signed-off-by: Nicolas Bigler --- Makefile | 13 + apis/stackgres/v1/groupversion_info.go | 2 + apis/stackgres/v1/sgdbops.go | 6 +- apis/stackgres/v1/sginstanceprofile.go | 25 + apis/stackgres/v1/sginstanceprofile.yaml | 205 ++++++ apis/stackgres/v1/sginstanceprofile_types.go | 88 +++ apis/stackgres/v1/zz_generated.deepcopy.go | 157 +++++ apis/stackgres/v1beta1/groupversion_info.go | 28 + apis/stackgres/v1beta1/sgobjectstorage.gen.go | 209 +++++++ apis/stackgres/v1beta1/sgobjectstorage.go | 25 + apis/stackgres/v1beta1/sgobjectstorage.yaml | 248 ++++++++ .../v1beta1/sgobjectstorage_crd.yaml | 271 ++++++++ .../v1beta1/zz_generated.deepcopy.go | 445 +++++++++++++ apis/vshn/v1/dbaas_vshn_postgresql.go | 2 +- apis/vshn/v1/zz_generated.deepcopy.go | 6 +- pkg/common/utils/sidecars.go | 11 + .../functions/common/instance_namespace.go | 12 +- .../functions/vshnpostgres/delay_cluster.go | 4 +- .../functions/vshnpostgres/extensions.go | 2 +- .../functions/vshnpostgres/maintenance.go | 2 +- .../vshnpostgres/postgresql_deploy.go | 585 +++++++++++++++++- .../vshnpostgres/postgresql_deploy_test.go | 156 +++++ .../functions/vshnpostgres/register.go | 4 - .../functions/vshnpostgres/replication.go | 39 +- .../vshnpostgres/replication_test.go | 34 +- .../vshnpostgres/scripts/copy-pg-backup.sh | 14 + pkg/scheme.go | 2 + .../vshn-postgres/deploy/01_default.yaml | 301 +++++++++ .../deploy/02_with_pg_config.yaml | 305 +++++++++ .../vshn-postgres/deploy/03_with_restore.yaml | 309 +++++++++ 30 files changed, 3426 insertions(+), 84 deletions(-) create mode 100644 apis/stackgres/v1/sginstanceprofile.go create mode 100644 apis/stackgres/v1/sginstanceprofile.yaml create mode 100644 apis/stackgres/v1/sginstanceprofile_types.go create mode 100644 apis/stackgres/v1beta1/groupversion_info.go create mode 100644 apis/stackgres/v1beta1/sgobjectstorage.gen.go create mode 100644 apis/stackgres/v1beta1/sgobjectstorage.go create mode 100644 apis/stackgres/v1beta1/sgobjectstorage.yaml create mode 100644 apis/stackgres/v1beta1/sgobjectstorage_crd.yaml create mode 100644 apis/stackgres/v1beta1/zz_generated.deepcopy.go create mode 100644 pkg/comp-functions/functions/vshnpostgres/postgresql_deploy_test.go create mode 100644 pkg/comp-functions/functions/vshnpostgres/scripts/copy-pg-backup.sh create mode 100644 test/functions/vshn-postgres/deploy/01_default.yaml create mode 100644 test/functions/vshn-postgres/deploy/02_with_pg_config.yaml create mode 100644 test/functions/vshn-postgres/deploy/03_with_restore.yaml diff --git a/Makefile b/Makefile index e038ad676..695a29ece 100644 --- a/Makefile +++ b/Makefile @@ -93,6 +93,12 @@ generate-stackgres-crds: go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=v1 -generate=types -o apis/stackgres/v1/sgcluster.gen.go apis/stackgres/v1/sgcluster.yaml perl -i -0pe 's/\*struct\s\{\n\s\sAdditionalProperties\smap\[string\]string\s`json:"-"`\n\s}/map\[string\]string/gms' apis/stackgres/v1/sgcluster.gen.go + # The generator for the pool config CRD unfortunately produces a broken result. However if we ever need to regenerate it in the future, please uncomment this. + # curl ${STACKGRES_CRD_URL}/SGInstanceProfile.yaml?inline=false -o apis/stackgres/v1/sginstanceprofile_crd.yaml + # yq -i e apis/stackgres/v1/sginstanceprofile.yaml --expression ".components.schemas.SGInstanceProfileSpec=load(\"apis/stackgres/v1/sginstanceprofile_crd.yaml\").spec.versions[0].schema.openAPIV3Schema.properties.spec" + # go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=v1 -generate=types -o apis/stackgres/v1/sginstanceprofile.gen.go apis/stackgres/v1/sginstanceprofile.yaml + # perl -i -0pe 's/\*struct\s\{\n\s\sAdditionalProperties\smap\[string\]string\s`json:"-"`\n\s}/map\[string\]string/gms' apis/stackgres/v1/sginstanceprofile.gen.go + # The generator for the pool config CRD unfortunately produces a broken result. However if we ever need to regenerate it in the future, please uncomment this. # curl ${STACKGRES_CRD_URL}/SGPoolingConfig.yaml?inline=false -o apis/stackgres/v1/sgpoolconfigs_crd.yaml # yq -i e apis/stackgres/v1/sgpoolconfigs.yaml --expression ".components.schemas.SGPoolingConfigSpec=load(\"apis/stackgres/v1/sgpoolconfigs_crd.yaml\").spec.versions[0].schema.openAPIV3Schema.properties.spec" @@ -100,7 +106,14 @@ generate-stackgres-crds: # go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=v1 -generate=types -o apis/stackgres/v1/sgpoolconfigs.gen.go apis/stackgres/v1/sgpoolconfigs.yaml # perl -i -0pe 's/\*struct\s\{\n\s\sAdditionalProperties\smap\[string\]string\s`json:"-"`\n\s}/map\[string\]string/gms' apis/stackgres/v1/sgpoolconfigs.gen.go + curl ${STACKGRES_CRD_URL}/SGObjectStorage.yaml?inline=false -o apis/stackgres/v1beta1/sgobjectstorage_crd.yaml + yq -i e apis/stackgres/v1beta1/sgobjectstorage.yaml --expression ".components.schemas.SGObjectStorageSpec=load(\"apis/stackgres/v1beta1/sgobjectstorage_crd.yaml\").spec.versions[0].schema.openAPIV3Schema.properties.spec" + go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=v1beta1 -generate=types -o apis/stackgres/v1beta1/sgobjectstorage.gen.go apis/stackgres/v1beta1/sgobjectstorage.yaml + perl -i -0pe 's/\*struct\s\{\n\s\sAdditionalProperties\smap\[string\]string\s`json:"-"`\n\s}/map\[string\]string/gms' apis/stackgres/v1beta1/sgobjectstorage.gen.go + + go run sigs.k8s.io/controller-tools/cmd/controller-gen object paths=./apis/stackgres/v1/... + go run sigs.k8s.io/controller-tools/cmd/controller-gen object paths=./apis/stackgres/v1beta1/... rm apis/stackgres/v1/*_crd.yaml .PHONY: fmt diff --git a/apis/stackgres/v1/groupversion_info.go b/apis/stackgres/v1/groupversion_info.go index 5af3b7f5e..d3d1f992a 100644 --- a/apis/stackgres/v1/groupversion_info.go +++ b/apis/stackgres/v1/groupversion_info.go @@ -30,5 +30,7 @@ func init() { &SGPostgesConfigList{}, &SGPoolingConfigList{}, &SGPoolingConfig{}, + &SGInstanceProfile{}, + &SGPInstanceProfileList{}, ) } diff --git a/apis/stackgres/v1/sgdbops.go b/apis/stackgres/v1/sgdbops.go index 76c2fa513..0f186e955 100644 --- a/apis/stackgres/v1/sgdbops.go +++ b/apis/stackgres/v1/sgdbops.go @@ -11,15 +11,15 @@ const SGDbOpsRestartMethodInPlace = "InPlace" // +kubebuilder:object:root=true -// VSHNPostgreSQL is the API for creating Postgresql clusters. +// SGDbOps is the API for creating SGDbOps objects. type SGDbOps struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec defines the desired state of a VSHNPostgreSQL. + // Spec defines the desired state of a SGDbOps. Spec SGDbOpsSpec `json:"spec"` - // Status reflects the observed state of a VSHNPostgreSQL. + // Status reflects the observed state of a SGDbOps. Status SGDbOpsStatus `json:"status,omitempty"` } diff --git a/apis/stackgres/v1/sginstanceprofile.go b/apis/stackgres/v1/sginstanceprofile.go new file mode 100644 index 000000000..777453572 --- /dev/null +++ b/apis/stackgres/v1/sginstanceprofile.go @@ -0,0 +1,25 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +kubebuilder:object:root=true + +// SGInstanceProfile is the API for creating instance profiles for SgClusters. +type SGInstanceProfile struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec contains the custom configurations for the SgInstanceProfile. + Spec SGInstanceProfileSpec `json:"spec"` +} + +// +kubebuilder:object:root=true + +type SGPInstanceProfileList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []SGInstanceProfile `json:"items"` +} diff --git a/apis/stackgres/v1/sginstanceprofile.yaml b/apis/stackgres/v1/sginstanceprofile.yaml new file mode 100644 index 000000000..ab13269fd --- /dev/null +++ b/apis/stackgres/v1/sginstanceprofile.yaml @@ -0,0 +1,205 @@ +openapi: "3.0.2" +paths: + "/spec": + get: + responses: + "200": + content: + "application/json": + schema: + "$ref": "#/components/schemas/SGInstanceProfileSpec" +components: + schemas: + SGInstanceProfileSpec: + type: object + properties: + cpu: + type: string + pattern: '^[1-9][0-9]*[m]?$' + description: | + CPU(s) (cores) used for every instance of a SGCluster. The suffix `m` + specifies millicpus (where 1000m is equals to 1). + + The number of cores set is assigned to the patroni container (that runs both Patroni and PostgreSQL). + + A minimum of 2 cores is recommended. + memory: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated to every instance of a SGCluster. The suffix `Mi` or `Gi` + specifies Mebibytes or Gibibytes, respectively. + + The amount of RAM set is assigned to the patroni container (that runs both Patroni and PostgreSQL). + + A minimum of 2-4Gi is recommended. + hugePages: + type: object + description: | + RAM allocated for huge pages + properties: + hugepages-2Mi: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated for huge pages with a size of 2Mi. The suffix `Mi` or `Gi` + specifies Mebibytes or Gibibytes, respectively. + + By default the amount of RAM set is assigned to patroni container + (that runs both Patroni and PostgreSQL). + hugepages-1Gi: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated for huge pages with a size of 1Gi. The suffix `Mi` or `Gi` + specifies Mebibytes or Gibibytes, respectively. + + By default the amount of RAM set is assigned to patroni container + (that runs both Patroni and PostgreSQL). + containers: + type: object + description: | + The CPU(s) (cores) and RAM assigned to containers other than patroni container. + + This section, if left empty, will be filled automatically by the operator with + some defaults that can be proportional to the resources assigned to patroni + container (except for the huge pages that are always left empty). + additionalProperties: + type: object + properties: + cpu: + type: string + pattern: '^[1-9][0-9]*[m]?$' + description: | + CPU(s) (cores) used for the specified Pod container. The suffix `m` + specifies millicpus (where 1000m is equals to 1). + memory: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated to the specified Pod container. The suffix `Mi` or `Gi` + specifies Mebibytes or Gibibytes, respectively. + hugePages: + type: object + description: | + RAM allocated for huge pages + properties: + hugepages-2Mi: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated for huge pages with a size of 2Mi. The suffix `Mi` + or `Gi` specifies Mebibytes or Gibibytes, respectively. + + The amount of RAM is assigned to the specified container. + hugepages-1Gi: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated for huge pages with a size of 1Gi. The suffix `Mi` + or `Gi` specifies Mebibytes or Gibibytes, respectively. + + The amount of RAM is assigned to the specified container. + required: ["cpu", "memory"] + initContainers: + type: object + description: The CPU(s) (cores) and RAM assigned to init containers. + additionalProperties: + type: object + properties: + cpu: + type: string + pattern: '^[1-9][0-9]*[m]?$' + description: | + CPU(s) (cores) used for the specified Pod init container. The suffix + `m` specifies millicpus (where 1000m is equals to 1). + memory: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated to the specified Pod init container. The suffix `Mi` + or `Gi` specifies Mebibytes or Gibibytes, respectively. + hugePages: + type: object + description: | + RAM allocated for huge pages + properties: + hugepages-2Mi: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated for huge pages with a size of 2Mi. The suffix `Mi` + or `Gi` specifies Mebibytes or Gibibytes, respectively. + + The amount of RAM is assigned to the specified init container. + hugepages-1Gi: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated for huge pages with a size of 1Gi. The suffix `Mi` or `Gi` + specifies Mebibytes or Gibibytes, respectively. + + The amount of RAM is assigned to the specified init container. + required: ["cpu", "memory"] + requests: + type: object + description: | + On containerized environments, when running production workloads, enforcing container's resources requirements request to be equals to the limits allow to achieve the highest level of performance. Doing so, reduces the chances of leaving + the workload with less resources than it requires. It also allow to set [static CPU management policy](https://kubernetes.io/docs/tasks/administer-cluster/cpu-management-policies/#static-policy) that allows to guarantee a pod the usage exclusive CPUs on the node. + + There are cases where you may need to set resource requirement request to a different value than limit. This section allow to do so but requires to enable such feature in the `SGCluster` and `SGDistributedLogs` (see `.spec.nonProductionOptions` section for each of those custom resources). + properties: + cpu: + type: string + pattern: '^[1-9][0-9]*[m]?$' + description: | + CPU(s) (cores) used for every instance of a SGCluster. The suffix `m` + specifies millicpus (where 1000m is equals to 1). + + The number of cores set is assigned to the patroni container (that runs both Patroni and PostgreSQL). + memory: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated to every instance of a SGCluster. The suffix `Mi` or `Gi` + specifies Mebibytes or Gibibytes, respectively. + + The amount of RAM set is assigned to the patroni container (that runs both Patroni and PostgreSQL). + containers: + type: object + description: | + The CPU(s) (cores) and RAM assigned to containers other than patroni container. + additionalProperties: + type: object + properties: + cpu: + type: string + pattern: '^[1-9][0-9]*[m]?$' + description: | + CPU(s) (cores) used for the specified Pod container. The suffix `m` + specifies millicpus (where 1000m is equals to 1). + memory: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated to the specified Pod container. The suffix `Mi` or `Gi` + specifies Mebibytes or Gibibytes, respectively. + initContainers: + type: object + description: The CPU(s) (cores) and RAM assigned to init containers. + additionalProperties: + type: object + properties: + cpu: + type: string + pattern: '^[1-9][0-9]*[m]?$' + description: | + CPU(s) (cores) used for the specified Pod init container. The suffix + `m` specifies millicpus (where 1000m is equals to 1). + memory: + type: string + pattern: '^[0-9]+(\.[0-9]+)?(Mi|Gi)$' + description: | + RAM allocated to the specified Pod init container. The suffix `Mi` + or `Gi` specifies Mebibytes or Gibibytes, respectively. + required: ["cpu", "memory"] diff --git a/apis/stackgres/v1/sginstanceprofile_types.go b/apis/stackgres/v1/sginstanceprofile_types.go new file mode 100644 index 000000000..fde881681 --- /dev/null +++ b/apis/stackgres/v1/sginstanceprofile_types.go @@ -0,0 +1,88 @@ +// Package v1 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v0.0.0-00010101000000-000000000000 DO NOT EDIT. +package v1 + +import "k8s.io/apimachinery/pkg/runtime" + +// SGInstanceProfileSpec defines model for SGInstanceProfileSpec. +type SGInstanceProfileSpec struct { + // The CPU(s) (cores) and RAM assigned to containers other than patroni container. + // + // This section, if left empty, will be filled automatically by the operator with + // some defaults that can be proportional to the resources assigned to patroni + // container (except for the huge pages that are always left empty). + Containers runtime.RawExtension `json:"containers,omitempty"` + + // CPU(s) (cores) used for every instance of a SGCluster. The suffix `m` + // specifies millicpus (where 1000m is equals to 1). + // + // The number of cores set is assigned to the patroni container (that runs both Patroni and PostgreSQL). + // + // A minimum of 2 cores is recommended. + Cpu string `json:"cpu"` + + // RAM allocated for huge pages + HugePages *SGInstanceProfileSpecHugePages `json:"hugePages,omitempty"` + + // The CPU(s) (cores) and RAM assigned to init containers. + InitContainers runtime.RawExtension `json:"initContainers,omitempty"` + + // RAM allocated to every instance of a SGCluster. The suffix `Mi` or `Gi` + // specifies Mebibytes or Gibibytes, respectively. + // + // The amount of RAM set is assigned to the patroni container (that runs both Patroni and PostgreSQL). + // + // A minimum of 2-4Gi is recommended. + Memory string `json:"memory"` + + // On containerized environments, when running production workloads, enforcing container's resources requirements request to be equals to the limits allow to achieve the highest level of performance. Doing so, reduces the chances of leaving + // the workload with less resources than it requires. It also allow to set [static CPU management policy](https://kubernetes.io/docs/tasks/administer-cluster/cpu-management-policies/#static-policy) that allows to guarantee a pod the usage exclusive CPUs on the node. + // + // There are cases where you may need to set resource requirement request to a different value than limit. This section allow to do so but requires to enable such feature in the `SGCluster` and `SGDistributedLogs` (see `.spec.nonProductionOptions` section for each of those custom resources). + Requests *SGInstanceProfileSpecRequests `json:"requests,omitempty"` +} + +// SGInstanceProfileSpecHugePages defines model for SGInstanceProfileSpecHugePages. +type SGInstanceProfileSpecHugePages struct { + // RAM allocated for huge pages with a size of 1Gi. The suffix `Mi` or `Gi` + // specifies Mebibytes or Gibibytes, respectively. + // + // By default the amount of RAM set is assigned to patroni container + // (that runs both Patroni and PostgreSQL). + Hugepages1Gi *string `json:"hugepages-1Gi,omitempty"` + + // RAM allocated for huge pages with a size of 2Mi. The suffix `Mi` or `Gi` + // specifies Mebibytes or Gibibytes, respectively. + // + // By default the amount of RAM set is assigned to patroni container + // (that runs both Patroni and PostgreSQL). + Hugepages2Mi *string `json:"hugepages-2Mi,omitempty"` +} + +// SGInstanceProfileSpecRequests defines model for SGInstanceProfileSpecRequests. +type SGInstanceProfileSpecRequests struct { + // The CPU(s) (cores) and RAM assigned to containers other than patroni container. + Containers runtime.RawExtension `json:"containers,omitempty"` + + // CPU(s) (cores) used for every instance of a SGCluster. The suffix `m` + // specifies millicpus (where 1000m is equals to 1). + // + // The number of cores set is assigned to the patroni container (that runs both Patroni and PostgreSQL). + Cpu *string `json:"cpu,omitempty"` + + // The CPU(s) (cores) and RAM assigned to init containers. + InitContainers runtime.RawExtension `json:"initContainers,omitempty"` + + // RAM allocated to every instance of a SGCluster. The suffix `Mi` or `Gi` + // specifies Mebibytes or Gibibytes, respectively. + // + // The amount of RAM set is assigned to the patroni container (that runs both Patroni and PostgreSQL). + Memory *string `json:"memory,omitempty"` +} + +type SGInstanceProfileContainer struct { + Cpu string `json:"cpu,omitempty"` + Memory string `json:"memory,omitempty"` + Hugepages *SGInstanceProfileSpecHugePages `json:"hugePages,omitempty"` +} diff --git a/apis/stackgres/v1/zz_generated.deepcopy.go b/apis/stackgres/v1/zz_generated.deepcopy.go index 24fda188a..d26a42061 100644 --- a/apis/stackgres/v1/zz_generated.deepcopy.go +++ b/apis/stackgres/v1/zz_generated.deepcopy.go @@ -9903,6 +9903,163 @@ func (in *SGDbOpsStatusSecurityUpgrade) DeepCopy() *SGDbOpsStatusSecurityUpgrade return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGInstanceProfile) DeepCopyInto(out *SGInstanceProfile) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGInstanceProfile. +func (in *SGInstanceProfile) DeepCopy() *SGInstanceProfile { + if in == nil { + return nil + } + out := new(SGInstanceProfile) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SGInstanceProfile) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGInstanceProfileContainer) DeepCopyInto(out *SGInstanceProfileContainer) { + *out = *in + if in.Hugepages != nil { + in, out := &in.Hugepages, &out.Hugepages + *out = new(SGInstanceProfileSpecHugePages) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGInstanceProfileContainer. +func (in *SGInstanceProfileContainer) DeepCopy() *SGInstanceProfileContainer { + if in == nil { + return nil + } + out := new(SGInstanceProfileContainer) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGInstanceProfileSpec) DeepCopyInto(out *SGInstanceProfileSpec) { + *out = *in + in.Containers.DeepCopyInto(&out.Containers) + if in.HugePages != nil { + in, out := &in.HugePages, &out.HugePages + *out = new(SGInstanceProfileSpecHugePages) + (*in).DeepCopyInto(*out) + } + in.InitContainers.DeepCopyInto(&out.InitContainers) + if in.Requests != nil { + in, out := &in.Requests, &out.Requests + *out = new(SGInstanceProfileSpecRequests) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGInstanceProfileSpec. +func (in *SGInstanceProfileSpec) DeepCopy() *SGInstanceProfileSpec { + if in == nil { + return nil + } + out := new(SGInstanceProfileSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGInstanceProfileSpecHugePages) DeepCopyInto(out *SGInstanceProfileSpecHugePages) { + *out = *in + if in.Hugepages1Gi != nil { + in, out := &in.Hugepages1Gi, &out.Hugepages1Gi + *out = new(string) + **out = **in + } + if in.Hugepages2Mi != nil { + in, out := &in.Hugepages2Mi, &out.Hugepages2Mi + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGInstanceProfileSpecHugePages. +func (in *SGInstanceProfileSpecHugePages) DeepCopy() *SGInstanceProfileSpecHugePages { + if in == nil { + return nil + } + out := new(SGInstanceProfileSpecHugePages) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGInstanceProfileSpecRequests) DeepCopyInto(out *SGInstanceProfileSpecRequests) { + *out = *in + in.Containers.DeepCopyInto(&out.Containers) + if in.Cpu != nil { + in, out := &in.Cpu, &out.Cpu + *out = new(string) + **out = **in + } + in.InitContainers.DeepCopyInto(&out.InitContainers) + if in.Memory != nil { + in, out := &in.Memory, &out.Memory + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGInstanceProfileSpecRequests. +func (in *SGInstanceProfileSpecRequests) DeepCopy() *SGInstanceProfileSpecRequests { + if in == nil { + return nil + } + out := new(SGInstanceProfileSpecRequests) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGPInstanceProfileList) DeepCopyInto(out *SGPInstanceProfileList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SGInstanceProfile, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGPInstanceProfileList. +func (in *SGPInstanceProfileList) DeepCopy() *SGPInstanceProfileList { + if in == nil { + return nil + } + out := new(SGPInstanceProfileList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SGPInstanceProfileList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SGPoolingConfig) DeepCopyInto(out *SGPoolingConfig) { *out = *in diff --git a/apis/stackgres/v1beta1/groupversion_info.go b/apis/stackgres/v1beta1/groupversion_info.go new file mode 100644 index 000000000..0bc574356 --- /dev/null +++ b/apis/stackgres/v1beta1/groupversion_info.go @@ -0,0 +1,28 @@ +// +kubebuilder:object:generate=true +// +groupName=stackgres.io +// +versionName=v1beta1 + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "stackgres.io", Version: "v1beta1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) + +func init() { + SchemeBuilder.Register( + &SGObjectStorage{}, + &SGObjectStorageList{}, + ) +} diff --git a/apis/stackgres/v1beta1/sgobjectstorage.gen.go b/apis/stackgres/v1beta1/sgobjectstorage.gen.go new file mode 100644 index 000000000..1e86a3c8b --- /dev/null +++ b/apis/stackgres/v1beta1/sgobjectstorage.gen.go @@ -0,0 +1,209 @@ +// Package v1beta1 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v0.0.0-00010101000000-000000000000 DO NOT EDIT. +package v1beta1 + +// SGObjectStorageSpec defines model for SGObjectStorageSpec. +type SGObjectStorageSpec struct { + // Azure Blob Storage configuration. + AzureBlob *SGObjectStorageSpecAzureBlob `json:"azureBlob,omitempty"` + + // Google Cloud Storage configuration. + Gcs *SGObjectStorageSpecGcs `json:"gcs,omitempty"` + + // Amazon Web Services S3 configuration. + S3 *SGObjectStorageSpecS3 `json:"s3,omitempty"` + + // AWS S3-Compatible API configuration + S3Compatible *SGObjectStorageSpecS3Compatible `json:"s3Compatible,omitempty"` + + // Determine the type of object storage used for storing the base backups and WAL segments. + // Possible values: + // * `s3`: Amazon Web Services S3 (Simple Storage Service). + // * `s3Compatible`: non-AWS services that implement a compatibility API with AWS S3. + // * `gcs`: Google Cloud Storage. + // * `azureBlob`: Microsoft Azure Blob Storage. + Type string `json:"type"` +} + +// SGObjectStorageSpecAzureBlob defines model for SGObjectStorageSpecAzureBlob. +type SGObjectStorageSpecAzureBlob struct { + // The credentials to access Azure Blob Storage for writing and reading. + AzureCredentials SGObjectStorageSpecAzureBlobAzureCredentials `json:"azureCredentials"` + + // Azure Blob Storage bucket name. + Bucket string `json:"bucket"` +} + +// SGObjectStorageSpecAzureBlobAzureCredentials defines model for SGObjectStorageSpecAzureBlobAzureCredentials. +type SGObjectStorageSpecAzureBlobAzureCredentials struct { + // Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core)(s) to reference the Secret(s) that contain the information about the `azureCredentials`. . Note that you may use the same or different Secrets for the `storageAccount` and the `accessKey`. In the former case, the `keys` that identify each must be, obviously, different. + SecretKeySelectors *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectors `json:"secretKeySelectors,omitempty"` +} + +// SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectors defines model for SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectors. +type SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectors struct { + // The [storage account access key](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage?tabs=azure-portal). + AccessKey SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsAccessKey `json:"accessKey"` + + // The [Storage Account](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-overview?toc=/azure/storage/blobs/toc.json) that contains the Blob bucket to be used. + StorageAccount SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsStorageAccount `json:"storageAccount"` +} + +// SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsAccessKey defines model for SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsAccessKey. +type SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsAccessKey struct { + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key"` + + // Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + Name string `json:"name"` +} + +// SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsStorageAccount defines model for SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsStorageAccount. +type SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsStorageAccount struct { + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key"` + + // Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + Name string `json:"name"` +} + +// SGObjectStorageSpecGcs defines model for SGObjectStorageSpecGcs. +type SGObjectStorageSpecGcs struct { + // GCS bucket name. + Bucket string `json:"bucket"` + + // The credentials to access GCS for writing and reading. + GcpCredentials SGObjectStorageSpecGcsGcpCredentials `json:"gcpCredentials"` +} + +// SGObjectStorageSpecGcsGcpCredentials defines model for SGObjectStorageSpecGcsGcpCredentials. +type SGObjectStorageSpecGcsGcpCredentials struct { + // If true, the credentials will be fetched from the GCE/GKE metadata service and the field `secretKeySelectors` have to be set to null or omitted. + // + // This is useful when running StackGres inside a GKE cluster using [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity). + FetchCredentialsFromMetadataService *bool `json:"fetchCredentialsFromMetadataService,omitempty"` + + // A Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core) to reference the Secrets that contain the information about the Service Account to access GCS. + SecretKeySelectors *SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectors `json:"secretKeySelectors,omitempty"` +} + +// SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectors defines model for SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectors. +type SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectors struct { + // A service account key from GCP. In JSON format, as downloaded from the GCP Console. + ServiceAccountJSON SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectorsServiceAccountJSON `json:"serviceAccountJSON"` +} + +// SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectorsServiceAccountJSON defines model for SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectorsServiceAccountJSON. +type SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectorsServiceAccountJSON struct { + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key"` + + // Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + Name string `json:"name"` +} + +// SGObjectStorageSpecS3 defines model for SGObjectStorageSpecS3. +type SGObjectStorageSpecS3 struct { + // The credentials to access AWS S3 for writing and reading. + AwsCredentials SGObjectStorageSpecS3AwsCredentials `json:"awsCredentials"` + + // AWS S3 bucket name. + Bucket string `json:"bucket"` + + // The AWS S3 region. The Region may be detected using s3:GetBucketLocation, but if you wish to avoid giving permissions to this API call or forbid it from the applicable IAM policy, you must then specify this property. + Region *string `json:"region,omitempty"` + + // The [Amazon S3 Storage Class](https://aws.amazon.com/s3/storage-classes/) to use for the backup object storage. By default, the `STANDARD` storage class is used. Other supported values include `STANDARD_IA` for Infrequent Access and `REDUCED_REDUNDANCY`. + StorageClass *string `json:"storageClass,omitempty"` +} + +// SGObjectStorageSpecS3AwsCredentials defines model for SGObjectStorageSpecS3AwsCredentials. +type SGObjectStorageSpecS3AwsCredentials struct { + // Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core)(s) to reference the Secrets that contain the information about the `awsCredentials`. Note that you may use the same or different Secrets for the `accessKeyId` and the `secretAccessKey`. In the former case, the `keys` that identify each must be, obviously, different. + SecretKeySelectors SGObjectStorageSpecS3AwsCredentialsSecretKeySelectors `json:"secretKeySelectors"` +} + +// SGObjectStorageSpecS3AwsCredentialsSecretKeySelectors defines model for SGObjectStorageSpecS3AwsCredentialsSecretKeySelectors. +type SGObjectStorageSpecS3AwsCredentialsSecretKeySelectors struct { + // AWS [access key ID](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `AKIAIOSFODNN7EXAMPLE`. + AccessKeyId SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsAccessKeyId `json:"accessKeyId"` + + // AWS [secret access key](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`. + SecretAccessKey SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsSecretAccessKey `json:"secretAccessKey"` +} + +// SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsAccessKeyId defines model for SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsAccessKeyId. +type SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsAccessKeyId struct { + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key"` + + // Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + Name string `json:"name"` +} + +// SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsSecretAccessKey defines model for SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsSecretAccessKey. +type SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsSecretAccessKey struct { + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key"` + + // Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + Name string `json:"name"` +} + +// SGObjectStorageSpecS3Compatible defines model for SGObjectStorageSpecS3Compatible. +type SGObjectStorageSpecS3Compatible struct { + // The credentials to access AWS S3 for writing and reading. + AwsCredentials SGObjectStorageSpecS3CompatibleAwsCredentials `json:"awsCredentials"` + + // Bucket name. + Bucket string `json:"bucket"` + + // Enable path-style addressing (i.e. `http://s3.amazonaws.com/BUCKET/KEY`) when connecting to an S3-compatible service that lacks support for sub-domain style bucket URLs (i.e. `http://BUCKET.s3.amazonaws.com/KEY`). + // + // Defaults to false. + EnablePathStyleAddressing *bool `json:"enablePathStyleAddressing,omitempty"` + + // Overrides the default url to connect to an S3-compatible service. + // For example: `http://s3-like-service:9000`. + Endpoint *string `json:"endpoint,omitempty"` + + // The AWS S3 region. The Region may be detected using s3:GetBucketLocation, but if you wish to avoid giving permissions to this API call or forbid it from the applicable IAM policy, you must then specify this property. + Region *string `json:"region,omitempty"` + + // The [Amazon S3 Storage Class](https://aws.amazon.com/s3/storage-classes/) to use for the backup object storage. By default, the `STANDARD` storage class is used. Other supported values include `STANDARD_IA` for Infrequent Access and `REDUCED_REDUNDANCY`. + StorageClass *string `json:"storageClass,omitempty"` +} + +// SGObjectStorageSpecS3CompatibleAwsCredentials defines model for SGObjectStorageSpecS3CompatibleAwsCredentials. +type SGObjectStorageSpecS3CompatibleAwsCredentials struct { + // Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core)(s) to reference the Secret(s) that contain the information about the `awsCredentials`. Note that you may use the same or different Secrets for the `accessKeyId` and the `secretAccessKey`. In the former case, the `keys` that identify each must be, obviously, different. + SecretKeySelectors SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectors `json:"secretKeySelectors"` +} + +// SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectors defines model for SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectors. +type SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectors struct { + // AWS [access key ID](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `AKIAIOSFODNN7EXAMPLE`. + AccessKeyId SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsAccessKeyId `json:"accessKeyId"` + + // AWS [secret access key](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`. + SecretAccessKey SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsSecretAccessKey `json:"secretAccessKey"` +} + +// SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsAccessKeyId defines model for SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsAccessKeyId. +type SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsAccessKeyId struct { + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key"` + + // Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + Name string `json:"name"` +} + +// SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsSecretAccessKey defines model for SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsSecretAccessKey. +type SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsSecretAccessKey struct { + // The key of the secret to select from. Must be a valid secret key. + Key string `json:"key"` + + // Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + Name string `json:"name"` +} diff --git a/apis/stackgres/v1beta1/sgobjectstorage.go b/apis/stackgres/v1beta1/sgobjectstorage.go new file mode 100644 index 000000000..53da62529 --- /dev/null +++ b/apis/stackgres/v1beta1/sgobjectstorage.go @@ -0,0 +1,25 @@ +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +kubebuilder:object:root=true + +// SGObjectStorage is the API for creating SgObjectStorage objects. +type SGObjectStorage struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of a SGObjectStorage. + Spec SGObjectStorageSpec `json:"spec"` +} + +// +kubebuilder:object:root=true + +type SGObjectStorageList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []SGObjectStorage `json:"items"` +} diff --git a/apis/stackgres/v1beta1/sgobjectstorage.yaml b/apis/stackgres/v1beta1/sgobjectstorage.yaml new file mode 100644 index 000000000..52275e66a --- /dev/null +++ b/apis/stackgres/v1beta1/sgobjectstorage.yaml @@ -0,0 +1,248 @@ +openapi: "3.0.2" +paths: + "/spec": + get: + responses: + "200": + content: + "application/json": + schema: + "$ref": "#/components/schemas/SGObjectStorageSpec" +components: + schemas: + SGObjectStorageSpec: + type: object + description: | + Object Storage configuration + properties: + type: + type: string + enum: ["s3", "s3Compatible", "gcs", "azureBlob"] + description: | + Determine the type of object storage used for storing the base backups and WAL segments. + Possible values: + * `s3`: Amazon Web Services S3 (Simple Storage Service). + * `s3Compatible`: non-AWS services that implement a compatibility API with AWS S3. + * `gcs`: Google Cloud Storage. + * `azureBlob`: Microsoft Azure Blob Storage. + s3: + type: object + description: | + Amazon Web Services S3 configuration. + properties: + bucket: + type: string + pattern: '^((s3|https?)://)?[^/]+(/[^/]*)*$' + description: | + AWS S3 bucket name. + region: + type: string + description: | + The AWS S3 region. The Region may be detected using s3:GetBucketLocation, but if you wish to avoid giving permissions to this API call or forbid it from the applicable IAM policy, you must then specify this property. + storageClass: + type: string + description: | + The [Amazon S3 Storage Class](https://aws.amazon.com/s3/storage-classes/) to use for the backup object storage. By default, the `STANDARD` storage class is used. Other supported values include `STANDARD_IA` for Infrequent Access and `REDUCED_REDUNDANCY`. + awsCredentials: + type: object + description: | + The credentials to access AWS S3 for writing and reading. + properties: + secretKeySelectors: + type: object + description: | + Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core)(s) to reference the Secrets that contain the information about the `awsCredentials`. Note that you may use the same or different Secrets for the `accessKeyId` and the `secretAccessKey`. In the former case, the `keys` that identify each must be, obviously, different. + properties: + accessKeyId: + type: object + description: | + AWS [access key ID](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `AKIAIOSFODNN7EXAMPLE`. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: ["key", "name"] + secretAccessKey: + type: object + description: | + AWS [secret access key](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: ["key", "name"] + required: ["accessKeyId", "secretAccessKey"] + required: ["secretKeySelectors"] + required: ["bucket", "awsCredentials"] + s3Compatible: + type: object + description: "AWS S3-Compatible API configuration" + properties: + bucket: + type: string + pattern: '^((s3|https?)://)?[^/]+(/[^/]*)*$' + description: | + Bucket name. + enablePathStyleAddressing: + type: boolean + description: | + Enable path-style addressing (i.e. `http://s3.amazonaws.com/BUCKET/KEY`) when connecting to an S3-compatible service that lacks support for sub-domain style bucket URLs (i.e. `http://BUCKET.s3.amazonaws.com/KEY`). + + Defaults to false. + endpoint: + type: string + description: | + Overrides the default url to connect to an S3-compatible service. + For example: `http://s3-like-service:9000`. + region: + type: string + description: | + The AWS S3 region. The Region may be detected using s3:GetBucketLocation, but if you wish to avoid giving permissions to this API call or forbid it from the applicable IAM policy, you must then specify this property. + storageClass: + type: string + description: | + The [Amazon S3 Storage Class](https://aws.amazon.com/s3/storage-classes/) to use for the backup object storage. By default, the `STANDARD` storage class is used. Other supported values include `STANDARD_IA` for Infrequent Access and `REDUCED_REDUNDANCY`. + awsCredentials: + type: object + description: | + The credentials to access AWS S3 for writing and reading. + properties: + secretKeySelectors: + type: object + description: | + Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core)(s) to reference the Secret(s) that contain the information about the `awsCredentials`. Note that you may use the same or different Secrets for the `accessKeyId` and the `secretAccessKey`. In the former case, the `keys` that identify each must be, obviously, different. + properties: + accessKeyId: + type: object + description: | + AWS [access key ID](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `AKIAIOSFODNN7EXAMPLE`. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: ["key", "name"] + secretAccessKey: + type: object + description: | + AWS [secret access key](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: ["key", "name"] + required: ["accessKeyId", "secretAccessKey"] + required: ["secretKeySelectors"] + required: ["bucket", "awsCredentials"] + gcs: + type: object + description: | + Google Cloud Storage configuration. + properties: + bucket: + type: string + pattern: "^(gs://)?[^/]+(/[^/]*)*$" + description: | + GCS bucket name. + gcpCredentials: + type: object + description: | + The credentials to access GCS for writing and reading. + properties: + fetchCredentialsFromMetadataService: + type: boolean + description: | + If true, the credentials will be fetched from the GCE/GKE metadata service and the field `secretKeySelectors` have to be set to null or omitted. + + This is useful when running StackGres inside a GKE cluster using [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity). + secretKeySelectors: + type: object + description: | + A Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core) to reference the Secrets that contain the information about the Service Account to access GCS. + properties: + serviceAccountJSON: + type: object + description: | + A service account key from GCP. In JSON format, as downloaded from the GCP Console. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: ["key", "name"] + required: ["serviceAccountJSON"] + required: ["bucket", "gcpCredentials"] + azureBlob: + type: object + description: | + Azure Blob Storage configuration. + properties: + bucket: + type: string + pattern: "^(azure://)?[^/]+(/[^/]*)*$" + description: | + Azure Blob Storage bucket name. + azureCredentials: + type: object + description: | + The credentials to access Azure Blob Storage for writing and reading. + properties: + secretKeySelectors: + type: object + description: | + Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core)(s) to reference the Secret(s) that contain the information about the `azureCredentials`. . Note that you may use the same or different Secrets for the `storageAccount` and the `accessKey`. In the former case, the `keys` that identify each must be, obviously, different. + properties: + storageAccount: + type: object + description: | + The [Storage Account](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-overview?toc=/azure/storage/blobs/toc.json) that contains the Blob bucket to be used. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: ["key", "name"] + accessKey: + type: object + description: | + The [storage account access key](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage?tabs=azure-portal). + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: ["key", "name"] + required: ["storageAccount", "accessKey"] + required: ["bucket", "azureCredentials"] + required: ["type"] diff --git a/apis/stackgres/v1beta1/sgobjectstorage_crd.yaml b/apis/stackgres/v1beta1/sgobjectstorage_crd.yaml new file mode 100644 index 000000000..a2bd2d522 --- /dev/null +++ b/apis/stackgres/v1beta1/sgobjectstorage_crd.yaml @@ -0,0 +1,271 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: sgobjectstorages.stackgres.io +spec: + group: stackgres.io + scope: Namespaced + names: + kind: SGObjectStorage + listKind: SGObjectStorageList + plural: sgobjectstorages + singular: sgobjectstorage + shortNames: + - sgobjs + versions: + - name: v1beta1 + served: true + storage: true + additionalPrinterColumns: + - name: type + type: string + jsonPath: .spec.type + schema: + openAPIV3Schema: + type: object + required: ["metadata", "spec"] + properties: + metadata: + type: object + properties: + name: + type: string + description: | + Name of the Object Storage configuration. + The name must be unique across all object storage configurations in the same namespace. + spec: + type: object + description: | + Object Storage configuration + properties: + type: + type: string + enum: ["s3", "s3Compatible", "gcs", "azureBlob"] + description: | + Determine the type of object storage used for storing the base backups and WAL segments. + Possible values: + * `s3`: Amazon Web Services S3 (Simple Storage Service). + * `s3Compatible`: non-AWS services that implement a compatibility API with AWS S3. + * `gcs`: Google Cloud Storage. + * `azureBlob`: Microsoft Azure Blob Storage. + s3: + type: object + description: | + Amazon Web Services S3 configuration. + properties: + bucket: + type: string + pattern: '^((s3|https?)://)?[^/]+(/[^/]*)*$' + description: | + AWS S3 bucket name. + region: + type: string + description: | + The AWS S3 region. The Region may be detected using s3:GetBucketLocation, but if you wish to avoid giving permissions to this API call or forbid it from the applicable IAM policy, you must then specify this property. + storageClass: + type: string + description: | + The [Amazon S3 Storage Class](https://aws.amazon.com/s3/storage-classes/) to use for the backup object storage. By default, the `STANDARD` storage class is used. Other supported values include `STANDARD_IA` for Infrequent Access and `REDUCED_REDUNDANCY`. + awsCredentials: + type: object + description: | + The credentials to access AWS S3 for writing and reading. + properties: + secretKeySelectors: + type: object + description: | + Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core)(s) to reference the Secrets that contain the information about the `awsCredentials`. Note that you may use the same or different Secrets for the `accessKeyId` and the `secretAccessKey`. In the former case, the `keys` that identify each must be, obviously, different. + properties: + accessKeyId: + type: object + description: | + AWS [access key ID](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `AKIAIOSFODNN7EXAMPLE`. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: ["key", "name"] + secretAccessKey: + type: object + description: | + AWS [secret access key](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: ["key", "name"] + required: ["accessKeyId", "secretAccessKey"] + required: ["secretKeySelectors"] + required: ["bucket", "awsCredentials"] + s3Compatible: + type: object + description: "AWS S3-Compatible API configuration" + properties: + bucket: + type: string + pattern: '^((s3|https?)://)?[^/]+(/[^/]*)*$' + description: | + Bucket name. + enablePathStyleAddressing: + type: boolean + description: | + Enable path-style addressing (i.e. `http://s3.amazonaws.com/BUCKET/KEY`) when connecting to an S3-compatible service that lacks support for sub-domain style bucket URLs (i.e. `http://BUCKET.s3.amazonaws.com/KEY`). + + Defaults to false. + endpoint: + type: string + description: | + Overrides the default url to connect to an S3-compatible service. + For example: `http://s3-like-service:9000`. + region: + type: string + description: | + The AWS S3 region. The Region may be detected using s3:GetBucketLocation, but if you wish to avoid giving permissions to this API call or forbid it from the applicable IAM policy, you must then specify this property. + storageClass: + type: string + description: | + The [Amazon S3 Storage Class](https://aws.amazon.com/s3/storage-classes/) to use for the backup object storage. By default, the `STANDARD` storage class is used. Other supported values include `STANDARD_IA` for Infrequent Access and `REDUCED_REDUNDANCY`. + awsCredentials: + type: object + description: | + The credentials to access AWS S3 for writing and reading. + properties: + secretKeySelectors: + type: object + description: | + Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core)(s) to reference the Secret(s) that contain the information about the `awsCredentials`. Note that you may use the same or different Secrets for the `accessKeyId` and the `secretAccessKey`. In the former case, the `keys` that identify each must be, obviously, different. + properties: + accessKeyId: + type: object + description: | + AWS [access key ID](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `AKIAIOSFODNN7EXAMPLE`. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: ["key", "name"] + secretAccessKey: + type: object + description: | + AWS [secret access key](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). For example, `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: ["key", "name"] + required: ["accessKeyId", "secretAccessKey"] + required: ["secretKeySelectors"] + required: ["bucket", "awsCredentials"] + gcs: + type: object + description: | + Google Cloud Storage configuration. + properties: + bucket: + type: string + pattern: "^(gs://)?[^/]+(/[^/]*)*$" + description: | + GCS bucket name. + gcpCredentials: + type: object + description: | + The credentials to access GCS for writing and reading. + properties: + fetchCredentialsFromMetadataService: + type: boolean + description: | + If true, the credentials will be fetched from the GCE/GKE metadata service and the field `secretKeySelectors` have to be set to null or omitted. + + This is useful when running StackGres inside a GKE cluster using [Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity). + secretKeySelectors: + type: object + description: | + A Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core) to reference the Secrets that contain the information about the Service Account to access GCS. + properties: + serviceAccountJSON: + type: object + description: | + A service account key from GCP. In JSON format, as downloaded from the GCP Console. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: [ "key", "name" ] + required: [ "serviceAccountJSON" ] + required: [ "bucket", "gcpCredentials" ] + azureBlob: + type: object + description: | + Azure Blob Storage configuration. + properties: + bucket: + type: string + pattern: "^(azure://)?[^/]+(/[^/]*)*$" + description: | + Azure Blob Storage bucket name. + azureCredentials: + type: object + description: | + The credentials to access Azure Blob Storage for writing and reading. + properties: + secretKeySelectors: + type: object + description: | + Kubernetes [SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#secretkeyselector-v1-core)(s) to reference the Secret(s) that contain the information about the `azureCredentials`. . Note that you may use the same or different Secrets for the `storageAccount` and the `accessKey`. In the former case, the `keys` that identify each must be, obviously, different. + properties: + storageAccount: + type: object + description: | + The [Storage Account](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-overview?toc=/azure/storage/blobs/toc.json) that contains the Blob bucket to be used. + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: [ "key", "name" ] + accessKey: + type: object + description: | + The [storage account access key](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage?tabs=azure-portal). + properties: + key: + type: string + description: | + The key of the secret to select from. Must be a valid secret key. + name: + type: string + description: | + Name of the referent. [More information](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + required: [ "key", "name" ] + required: [ "storageAccount", "accessKey" ] + required: [ "bucket", "azureCredentials" ] + required: [ "type" ] diff --git a/apis/stackgres/v1beta1/zz_generated.deepcopy.go b/apis/stackgres/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 000000000..ab8bc88f7 --- /dev/null +++ b/apis/stackgres/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,445 @@ +//go:build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorage) DeepCopyInto(out *SGObjectStorage) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorage. +func (in *SGObjectStorage) DeepCopy() *SGObjectStorage { + if in == nil { + return nil + } + out := new(SGObjectStorage) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SGObjectStorage) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageList) DeepCopyInto(out *SGObjectStorageList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SGObjectStorage, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageList. +func (in *SGObjectStorageList) DeepCopy() *SGObjectStorageList { + if in == nil { + return nil + } + out := new(SGObjectStorageList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SGObjectStorageList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpec) DeepCopyInto(out *SGObjectStorageSpec) { + *out = *in + if in.AzureBlob != nil { + in, out := &in.AzureBlob, &out.AzureBlob + *out = new(SGObjectStorageSpecAzureBlob) + (*in).DeepCopyInto(*out) + } + if in.Gcs != nil { + in, out := &in.Gcs, &out.Gcs + *out = new(SGObjectStorageSpecGcs) + (*in).DeepCopyInto(*out) + } + if in.S3 != nil { + in, out := &in.S3, &out.S3 + *out = new(SGObjectStorageSpecS3) + (*in).DeepCopyInto(*out) + } + if in.S3Compatible != nil { + in, out := &in.S3Compatible, &out.S3Compatible + *out = new(SGObjectStorageSpecS3Compatible) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpec. +func (in *SGObjectStorageSpec) DeepCopy() *SGObjectStorageSpec { + if in == nil { + return nil + } + out := new(SGObjectStorageSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecAzureBlob) DeepCopyInto(out *SGObjectStorageSpecAzureBlob) { + *out = *in + in.AzureCredentials.DeepCopyInto(&out.AzureCredentials) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecAzureBlob. +func (in *SGObjectStorageSpecAzureBlob) DeepCopy() *SGObjectStorageSpecAzureBlob { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecAzureBlob) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecAzureBlobAzureCredentials) DeepCopyInto(out *SGObjectStorageSpecAzureBlobAzureCredentials) { + *out = *in + if in.SecretKeySelectors != nil { + in, out := &in.SecretKeySelectors, &out.SecretKeySelectors + *out = new(SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectors) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecAzureBlobAzureCredentials. +func (in *SGObjectStorageSpecAzureBlobAzureCredentials) DeepCopy() *SGObjectStorageSpecAzureBlobAzureCredentials { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecAzureBlobAzureCredentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectors) DeepCopyInto(out *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectors) { + *out = *in + out.AccessKey = in.AccessKey + out.StorageAccount = in.StorageAccount +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectors. +func (in *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectors) DeepCopy() *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectors { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectors) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsAccessKey) DeepCopyInto(out *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsAccessKey) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsAccessKey. +func (in *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsAccessKey) DeepCopy() *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsAccessKey { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsAccessKey) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsStorageAccount) DeepCopyInto(out *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsStorageAccount) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsStorageAccount. +func (in *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsStorageAccount) DeepCopy() *SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsStorageAccount { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecAzureBlobAzureCredentialsSecretKeySelectorsStorageAccount) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecGcs) DeepCopyInto(out *SGObjectStorageSpecGcs) { + *out = *in + in.GcpCredentials.DeepCopyInto(&out.GcpCredentials) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecGcs. +func (in *SGObjectStorageSpecGcs) DeepCopy() *SGObjectStorageSpecGcs { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecGcs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecGcsGcpCredentials) DeepCopyInto(out *SGObjectStorageSpecGcsGcpCredentials) { + *out = *in + if in.FetchCredentialsFromMetadataService != nil { + in, out := &in.FetchCredentialsFromMetadataService, &out.FetchCredentialsFromMetadataService + *out = new(bool) + **out = **in + } + if in.SecretKeySelectors != nil { + in, out := &in.SecretKeySelectors, &out.SecretKeySelectors + *out = new(SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectors) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecGcsGcpCredentials. +func (in *SGObjectStorageSpecGcsGcpCredentials) DeepCopy() *SGObjectStorageSpecGcsGcpCredentials { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecGcsGcpCredentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectors) DeepCopyInto(out *SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectors) { + *out = *in + out.ServiceAccountJSON = in.ServiceAccountJSON +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectors. +func (in *SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectors) DeepCopy() *SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectors { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectors) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectorsServiceAccountJSON) DeepCopyInto(out *SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectorsServiceAccountJSON) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectorsServiceAccountJSON. +func (in *SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectorsServiceAccountJSON) DeepCopy() *SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectorsServiceAccountJSON { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecGcsGcpCredentialsSecretKeySelectorsServiceAccountJSON) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecS3) DeepCopyInto(out *SGObjectStorageSpecS3) { + *out = *in + out.AwsCredentials = in.AwsCredentials + if in.Region != nil { + in, out := &in.Region, &out.Region + *out = new(string) + **out = **in + } + if in.StorageClass != nil { + in, out := &in.StorageClass, &out.StorageClass + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecS3. +func (in *SGObjectStorageSpecS3) DeepCopy() *SGObjectStorageSpecS3 { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecS3) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecS3AwsCredentials) DeepCopyInto(out *SGObjectStorageSpecS3AwsCredentials) { + *out = *in + out.SecretKeySelectors = in.SecretKeySelectors +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecS3AwsCredentials. +func (in *SGObjectStorageSpecS3AwsCredentials) DeepCopy() *SGObjectStorageSpecS3AwsCredentials { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecS3AwsCredentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectors) DeepCopyInto(out *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectors) { + *out = *in + out.AccessKeyId = in.AccessKeyId + out.SecretAccessKey = in.SecretAccessKey +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecS3AwsCredentialsSecretKeySelectors. +func (in *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectors) DeepCopy() *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectors { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecS3AwsCredentialsSecretKeySelectors) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsAccessKeyId) DeepCopyInto(out *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsAccessKeyId) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsAccessKeyId. +func (in *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsAccessKeyId) DeepCopy() *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsAccessKeyId { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsAccessKeyId) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsSecretAccessKey) DeepCopyInto(out *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsSecretAccessKey) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsSecretAccessKey. +func (in *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsSecretAccessKey) DeepCopy() *SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsSecretAccessKey { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecS3AwsCredentialsSecretKeySelectorsSecretAccessKey) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecS3Compatible) DeepCopyInto(out *SGObjectStorageSpecS3Compatible) { + *out = *in + out.AwsCredentials = in.AwsCredentials + if in.EnablePathStyleAddressing != nil { + in, out := &in.EnablePathStyleAddressing, &out.EnablePathStyleAddressing + *out = new(bool) + **out = **in + } + if in.Endpoint != nil { + in, out := &in.Endpoint, &out.Endpoint + *out = new(string) + **out = **in + } + if in.Region != nil { + in, out := &in.Region, &out.Region + *out = new(string) + **out = **in + } + if in.StorageClass != nil { + in, out := &in.StorageClass, &out.StorageClass + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecS3Compatible. +func (in *SGObjectStorageSpecS3Compatible) DeepCopy() *SGObjectStorageSpecS3Compatible { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecS3Compatible) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecS3CompatibleAwsCredentials) DeepCopyInto(out *SGObjectStorageSpecS3CompatibleAwsCredentials) { + *out = *in + out.SecretKeySelectors = in.SecretKeySelectors +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecS3CompatibleAwsCredentials. +func (in *SGObjectStorageSpecS3CompatibleAwsCredentials) DeepCopy() *SGObjectStorageSpecS3CompatibleAwsCredentials { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecS3CompatibleAwsCredentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectors) DeepCopyInto(out *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectors) { + *out = *in + out.AccessKeyId = in.AccessKeyId + out.SecretAccessKey = in.SecretAccessKey +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectors. +func (in *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectors) DeepCopy() *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectors { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectors) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsAccessKeyId) DeepCopyInto(out *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsAccessKeyId) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsAccessKeyId. +func (in *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsAccessKeyId) DeepCopy() *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsAccessKeyId { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsAccessKeyId) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsSecretAccessKey) DeepCopyInto(out *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsSecretAccessKey) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsSecretAccessKey. +func (in *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsSecretAccessKey) DeepCopy() *SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsSecretAccessKey { + if in == nil { + return nil + } + out := new(SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsSecretAccessKey) + in.DeepCopyInto(out) + return out +} diff --git a/apis/vshn/v1/dbaas_vshn_postgresql.go b/apis/vshn/v1/dbaas_vshn_postgresql.go index 493a89140..bbca2c06a 100644 --- a/apis/vshn/v1/dbaas_vshn_postgresql.go +++ b/apis/vshn/v1/dbaas_vshn_postgresql.go @@ -60,7 +60,7 @@ type VSHNPostgreSQLParameters struct { Backup VSHNPostgreSQLBackup `json:"backup,omitempty"` // Restore contains settings to control the restore of an instance. - Restore VSHNPostgreSQLRestore `json:"restore,omitempty"` + Restore *VSHNPostgreSQLRestore `json:"restore,omitempty"` // Monitoring contains settings to control monitoring. Monitoring VSHNMonitoring `json:"monitoring,omitempty"` diff --git a/apis/vshn/v1/zz_generated.deepcopy.go b/apis/vshn/v1/zz_generated.deepcopy.go index 5363619e7..adb0ebdf9 100644 --- a/apis/vshn/v1/zz_generated.deepcopy.go +++ b/apis/vshn/v1/zz_generated.deepcopy.go @@ -845,7 +845,11 @@ func (in *VSHNPostgreSQLParameters) DeepCopyInto(out *VSHNPostgreSQLParameters) in.Scheduling.DeepCopyInto(&out.Scheduling) in.Network.DeepCopyInto(&out.Network) in.Backup.DeepCopyInto(&out.Backup) - out.Restore = in.Restore + if in.Restore != nil { + in, out := &in.Restore, &out.Restore + *out = new(VSHNPostgreSQLRestore) + **out = **in + } in.Monitoring.DeepCopyInto(&out.Monitoring) out.Encryption = in.Encryption out.UpdateStrategy = in.UpdateStrategy diff --git a/pkg/common/utils/sidecars.go b/pkg/common/utils/sidecars.go index 68dde7b4b..b5a916b54 100644 --- a/pkg/common/utils/sidecars.go +++ b/pkg/common/utils/sidecars.go @@ -69,6 +69,17 @@ func FetchSidecarsFromConfig(ctx context.Context, svc *runtime.ServiceRuntime) ( return s, nil } +func FetchInitContainersFromConfig(ctx context.Context, svc *runtime.ServiceRuntime) (*Sidecars, error) { + s := &Sidecars{} + + err := json.Unmarshal([]byte(svc.Config.Data["initContainers"]), s) + if err != nil { + return &Sidecars{}, err + } + + return s, nil +} + // FetchSidecarFromCluster will fetch the specified sidecar from the current PLANS_NAMESPACE namespace and parse it into Resources. // By default PLANS_NAMESPACE should be the same namespace where the controller pod is running. func FetchSidecarFromCluster(ctx context.Context, c client.Client, name, sidecar string) (Resources, error) { diff --git a/pkg/comp-functions/functions/common/instance_namespace.go b/pkg/comp-functions/functions/common/instance_namespace.go index 6177de478..c9cb23c3f 100644 --- a/pkg/comp-functions/functions/common/instance_namespace.go +++ b/pkg/comp-functions/functions/common/instance_namespace.go @@ -31,19 +31,19 @@ func BootstrapInstanceNs(ctx context.Context, comp Composite, serviceName, names } l.Info("creating namespace observer for " + serviceName + " claim namespace") - err := createNamespaceObserver(ctx, claimNs, compositionName, svc) + err := createNamespaceObserver(claimNs, compositionName, svc) if err != nil { return fmt.Errorf("cannot create namespace observer for claim namespace: %w", err) } l.Info("Creating namespace for " + serviceName + " instance") - err = createInstanceNamespace(ctx, serviceName, compositionName, claimNs, instanceNs, namespaceResName, claimName, svc) + err = createInstanceNamespace(serviceName, compositionName, claimNs, instanceNs, namespaceResName, claimName, svc) if err != nil { return fmt.Errorf("cannot create %s namespace: %w", serviceName, err) } l.Info("Creating rbac rules for " + serviceName + " instance") - err = createNamespacePermissions(ctx, compositionName, instanceNs, namespaceResName, svc) + err = createNamespacePermissions(compositionName, instanceNs, namespaceResName, svc) if err != nil { return fmt.Errorf("cannot create rbac rules for %s instance: %w", serviceName, err) } @@ -73,7 +73,7 @@ func getOrg(instance string, svc *runtime.ServiceRuntime) (string, error) { return ns.GetLabels()[utils.OrgLabelName], nil } -func createNamespaceObserver(ctx context.Context, claimNs string, instance string, svc *runtime.ServiceRuntime) error { +func createNamespaceObserver(claimNs string, instance string, svc *runtime.ServiceRuntime) error { ns := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: claimNs, @@ -84,7 +84,7 @@ func createNamespaceObserver(ctx context.Context, claimNs string, instance strin } // Create the namespace for the service instance -func createInstanceNamespace(ctx context.Context, serviceName, compName, claimNamespace, instanceNamespace, namespaceResName, claimName string, svc *runtime.ServiceRuntime) error { +func createInstanceNamespace(serviceName, compName, claimNamespace, instanceNamespace, namespaceResName, claimName string, svc *runtime.ServiceRuntime) error { org, err := getOrg(compName, svc) if err != nil { @@ -126,7 +126,7 @@ func createInstanceNamespace(ctx context.Context, serviceName, compName, claimNa return svc.SetDesiredKubeObjectWithName(ns, instanceNamespace, namespaceResName) } -func createNamespacePermissions(ctx context.Context, instance string, instanceNs string, namespaceResName string, svc *runtime.ServiceRuntime) error { +func createNamespacePermissions(instance string, instanceNs string, namespaceResName string, svc *runtime.ServiceRuntime) error { ns := &corev1.Namespace{} err := svc.GetObservedKubeObject(ns, namespaceResName) if err != nil { diff --git a/pkg/comp-functions/functions/vshnpostgres/delay_cluster.go b/pkg/comp-functions/functions/vshnpostgres/delay_cluster.go index 90f213376..0e382c65c 100644 --- a/pkg/comp-functions/functions/vshnpostgres/delay_cluster.go +++ b/pkg/comp-functions/functions/vshnpostgres/delay_cluster.go @@ -30,13 +30,13 @@ func DelayClusterDeployment(_ context.Context, svc *runtime.ServiceRuntime) *xfn // We're done here, the sgcluster has been applied. return nil } else if err != runtime.ErrNotFound { - return runtime.NewFatalResult(fmt.Errorf("Cannot get cluster: %w", err)) + return runtime.NewWarningResult(fmt.Errorf("Cannot get cluster: %w", err).Error()) } desiredCluster := &stackgresv1.SGCluster{} err = svc.GetDesiredKubeObject(desiredCluster, "cluster") if err != nil { - return runtime.NewFatalResult(fmt.Errorf("cannot get desired cluster: %w", err)) + return runtime.NewWarningResult(fmt.Errorf("cannot get desired cluster: %w", err).Error()) } svc.DeleteDesiredCompososedResource("cluster") diff --git a/pkg/comp-functions/functions/vshnpostgres/extensions.go b/pkg/comp-functions/functions/vshnpostgres/extensions.go index 53bf2c17e..b0d9e14c3 100644 --- a/pkg/comp-functions/functions/vshnpostgres/extensions.go +++ b/pkg/comp-functions/functions/vshnpostgres/extensions.go @@ -62,7 +62,7 @@ func AddExtensions(ctx context.Context, svc *runtime.ServiceRuntime) *xfnproto.R cluster := &stackgresv1.SGCluster{} err = svc.GetDesiredKubeObject(cluster, "cluster") if err != nil { - return runtime.NewFatalResult(fmt.Errorf("not able to get cluster: %w", err)) + return runtime.NewWarningResult(fmt.Errorf("not able to get cluster: %w", err).Error()) } finalExtensions := []stackgresv1.SGClusterSpecPostgresExtensionsItem{} diff --git a/pkg/comp-functions/functions/vshnpostgres/maintenance.go b/pkg/comp-functions/functions/vshnpostgres/maintenance.go index 07c1f43bc..7fc132c8b 100644 --- a/pkg/comp-functions/functions/vshnpostgres/maintenance.go +++ b/pkg/comp-functions/functions/vshnpostgres/maintenance.go @@ -116,7 +116,7 @@ func addSchedules(ctx context.Context, svc *runtime.ServiceRuntime) *xfnproto.Re cluster := &stackgresv1.SGCluster{} err = svc.GetDesiredKubeObject(cluster, "cluster") if err != nil { - return runtime.NewFatalResult(fmt.Errorf("cannot get cluster object: %w", err)) + return runtime.NewWarningResult(fmt.Errorf("cannot get cluster object: %w", err).Error()) } additionalVars := append(extraEnvVars, []corev1.EnvVar{ diff --git a/pkg/comp-functions/functions/vshnpostgres/postgresql_deploy.go b/pkg/comp-functions/functions/vshnpostgres/postgresql_deploy.go index b96119577..934476d28 100644 --- a/pkg/comp-functions/functions/vshnpostgres/postgresql_deploy.go +++ b/pkg/comp-functions/functions/vshnpostgres/postgresql_deploy.go @@ -2,31 +2,608 @@ package vshnpostgres import ( "context" + _ "embed" + "encoding/json" "fmt" + "strings" + "time" + cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + xkubev1 "github.com/crossplane-contrib/provider-kubernetes/apis/object/v1alpha2" + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1" + promv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + sgv1 "github.com/vshn/appcat/v4/apis/stackgres/v1" + sgv1beta1 "github.com/vshn/appcat/v4/apis/stackgres/v1beta1" + appcatv1 "github.com/vshn/appcat/v4/apis/v1" vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" + + "github.com/vshn/appcat/v4/pkg/common/utils" "github.com/vshn/appcat/v4/pkg/comp-functions/functions/common" "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" + batchv1 "k8s.io/api/batch/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" +) + +const ( + certificateSecretName = "tls-certificate" ) +//go:embed scripts/copy-pg-backup.sh +var postgresqlCopyJobScript string + func DeployPostgreSQL(ctx context.Context, svc *runtime.ServiceRuntime) *xfnproto.Result { l := svc.Log comp := &vshnv1.VSHNPostgreSQL{} err := svc.GetObservedComposite(comp) if err != nil { - err = fmt.Errorf("cannot get observed composite: %w", err) - return runtime.NewFatalResult(err) + return runtime.NewFatalResult(fmt.Errorf("cannot get observed composite: %w", err)) } l.Info("Bootstrapping instance namespace and rbac rules") err = common.BootstrapInstanceNs(ctx, comp, "postgresql", "namespace-conditions", svc) if err != nil { - err = fmt.Errorf("cannot bootstrap instance namespace: %w", err) - return runtime.NewWarningResult(err.Error()) + return runtime.NewWarningResult(fmt.Errorf("cannot bootstrap instance namespace: %w", err).Error()) + } + + l.Info("Create tls certificate") + err = createCerts(comp, svc) + if err != nil { + return runtime.NewWarningResult(fmt.Errorf("cannot create tls certificate: %w", err).Error()) + } + + l.Info("Create Stackgres objects") + err = createStackgresObjects(ctx, comp, svc) + if err != nil { + return runtime.NewWarningResult(fmt.Errorf("cannot create stackgres objects: %w", err).Error()) + } + + l.Info("Create ObjectBucket") + err = createObjectBucket(comp, svc) + if err != nil { + return runtime.NewWarningResult(fmt.Errorf("cannot create xObjectBucket object: %w", err).Error()) + } + + l.Info("Create SgObjectStorage") + err = createSgObjectStorage(comp, svc) + if err != nil { + return runtime.NewWarningResult(fmt.Errorf("cannot create sgObjectStorage object: %w", err).Error()) + } + + l.Info("Create podMonitor") + err = createPodMonitor(comp, svc) + if err != nil { + return runtime.NewWarningResult(fmt.Errorf("cannot create podMonitor object: %w", err).Error()) + } + + if comp.Spec.Parameters.Restore != nil { + l.Info("Create copy job") + err = createCopyJob(comp, svc) + if err != nil { + return runtime.NewWarningResult(fmt.Errorf("cannot create copyJob object: %w", err).Error()) + } + } + return nil +} + +func createCerts(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) error { + selfSignedIssuer := &cmv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName(), + Namespace: comp.GetInstanceNamespace(), + }, + Spec: cmv1.IssuerSpec{ + IssuerConfig: cmv1.IssuerConfig{ + SelfSigned: &cmv1.SelfSignedIssuer{ + CRLDistributionPoints: []string{}, + }, + }, + }, + } + + err := svc.SetDesiredKubeObjectWithName(selfSignedIssuer, comp.GetName()+"-localca", "local-ca") + if err != nil { + err = fmt.Errorf("cannot create local ca object: %w", err) + return err + } + + certificate := &cmv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName(), + Namespace: comp.GetInstanceNamespace(), + }, + Spec: cmv1.CertificateSpec{ + SecretName: certificateSecretName, + Duration: &metav1.Duration{ + Duration: time.Duration(87600 * time.Hour), + }, + RenewBefore: &metav1.Duration{ + Duration: time.Duration(2400 * time.Hour), + }, + Subject: &cmv1.X509Subject{ + Organizations: []string{ + "vshn-appcat", + }, + }, + IsCA: false, + PrivateKey: &cmv1.CertificatePrivateKey{ + Algorithm: cmv1.RSAKeyAlgorithm, + Encoding: cmv1.PKCS1, + Size: 4096, + }, + Usages: []cmv1.KeyUsage{"server auth", "client auth"}, + DNSNames: []string{ + comp.GetName() + "." + comp.GetInstanceNamespace() + ".svc.cluster.local", + comp.GetName() + "." + comp.GetInstanceNamespace() + ".svc", + }, + IssuerRef: certmgrv1.ObjectReference{ + Name: comp.GetName(), + Kind: selfSignedIssuer.GetObjectKind().GroupVersionKind().Kind, + Group: selfSignedIssuer.GetObjectKind().GroupVersionKind().Group, + }, + }, + } + + err = svc.SetDesiredKubeObjectWithName(certificate, comp.GetName()+"-certificate", "certificate") + if err != nil { + err = fmt.Errorf("cannot create local ca object: %w", err) + return err } return nil +} + +func createStackgresObjects(ctx context.Context, comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) error { + + err := createSgInstanceProfile(ctx, comp, svc) + + if err != nil { + return err + } + + err = createSgPostgresConfig(comp, svc) + if err != nil { + return err + } + + err = createSgCluster(ctx, comp, svc) + + if err != nil { + return err + } + + return nil +} + +func createSgInstanceProfile(ctx context.Context, comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) error { + plan := comp.Spec.Parameters.Size.GetPlan(svc.Config.Data["defaultPlan"]) + + resources, err := utils.FetchPlansFromConfig(ctx, svc, plan) + if err != nil { + err = fmt.Errorf("cannot fetch plans from the composition config, maybe they are not set: %w", err) + return err + } + + containers, err := utils.FetchSidecarsFromConfig(ctx, svc) + + if err != nil { + err = fmt.Errorf("cannot get sideCars from config: %w", err) + return err + } + + initContainers, err := utils.FetchInitContainersFromConfig(ctx, svc) + + if err != nil { + err = fmt.Errorf("cannot get initContainers from config: %w", err) + return err + } + + sideCarMap := map[string]string{ + "createBackup": "backup.create-backup", + "clusterController": "cluster-controller", + "runDbops": "dbops.run-dbops", + "setDbopsResult": "dbops.set-dbops-result", + "envoy": "envoy", + "pgbouncer": "pgbouncer", + "postgresUtil": "postgres-util", + "prometheusPostgresExporter": "prometheus-postgres-exporter", + } + + initContainerMap := map[string]string{ + "pgbouncerAuthFile": "pgbouncer-auth-file", + "relocateBinaries": "relocate-binaries", + "setupScripts": "setup-scripts", + "setupArbitraryUser": "setup-arbitrary-user", + "clusterReconciliationCycle": "cluster-reconciliation-cycle", + "setDbopsRunning": "dbops.set-dbops-running", + } + + res := common.GetResources(&comp.Spec.Parameters.Size, resources) + containersRequests := generateContainers(*containers, sideCarMap, false) + + containersRequestsBytes, err := json.Marshal(containersRequests) + if err != nil { + return err + } + containersLimits := generateContainers(*containers, sideCarMap, true) + containersLimitsBytes, err := json.Marshal(containersLimits) + if err != nil { + return err + } + + initContainersRequests := generateContainers(*initContainers, initContainerMap, false) + initContainersRequestsBytes, err := json.Marshal(initContainersRequests) + if err != nil { + return err + } + + initContainersLimits := generateContainers(*initContainers, initContainerMap, true) + initContainersLimitsBytes, err := json.Marshal(initContainersLimits) + if err != nil { + return err + } + + sgInstanceProfile := &sgv1.SGInstanceProfile{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName(), + Namespace: comp.GetInstanceNamespace(), + }, + Spec: sgv1.SGInstanceProfileSpec{ + Cpu: res.CPU, + Memory: res.Mem, + Requests: &sgv1.SGInstanceProfileSpecRequests{ + Cpu: &res.ReqCPU, + Memory: &res.ReqMem, + Containers: k8sruntime.RawExtension{ + Raw: containersRequestsBytes, + }, + InitContainers: k8sruntime.RawExtension{ + Raw: initContainersRequestsBytes, + }, + }, + Containers: k8sruntime.RawExtension{ + Raw: containersLimitsBytes, + }, + InitContainers: k8sruntime.RawExtension{ + Raw: initContainersLimitsBytes, + }, + }, + } + + err = svc.SetDesiredKubeObjectWithName(sgInstanceProfile, comp.GetName()+"-profile", "profile") + if err != nil { + err = fmt.Errorf("cannot create sgInstanceProfile: %w", err) + return err + } + return nil +} + +func generateContainers(s utils.Sidecars, containerMap map[string]string, limits bool) map[string]sgv1.SGInstanceProfileContainer { + + containers := map[string]sgv1.SGInstanceProfileContainer{} + + if limits { + for k, v := range containerMap { + containers[v] = sgv1.SGInstanceProfileContainer{ + Cpu: s[k].Limits.CPU, + Memory: s[k].Limits.Memory, + } + } + } else { + for k, v := range containerMap { + containers[v] = sgv1.SGInstanceProfileContainer{ + Cpu: s[k].Requests.CPU, + Memory: s[k].Requests.Memory, + } + } + } + + return containers +} + +func createSgPostgresConfig(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) error { + + pgConfBytes := comp.Spec.Parameters.Service.PostgreSQLSettings + + pgConf := map[string]string{} + if pgConfBytes.Raw != nil { + err := json.Unmarshal(pgConfBytes.Raw, &pgConf) + if err != nil { + return fmt.Errorf("cannot unmarshall pgConf: %w", err) + } + } + + sgPostgresConfig := &sgv1.SGPostgresConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName(), + Namespace: comp.GetInstanceNamespace(), + }, + Spec: sgv1.SGPostgresConfigSpec{ + PostgresVersion: comp.Spec.Parameters.Service.MajorVersion, + PostgresqlConf: pgConf, + }, + } + + err := svc.SetDesiredKubeObjectWithName(sgPostgresConfig, comp.GetName()+"-pgconf", "pg-conf") + if err != nil { + err = fmt.Errorf("cannot create sgInstanceProfile: %w", err) + return err + } + + return nil +} + +func createSgCluster(ctx context.Context, comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) error { + + plan := comp.Spec.Parameters.Size.GetPlan(svc.Config.Data["defaultPlan"]) + + resources, err := utils.FetchPlansFromConfig(ctx, svc, plan) + if err != nil { + err = fmt.Errorf("cannot fetch plans from the composition config, maybe they are not set: %w", err) + return err + } + + res := common.GetResources(&comp.Spec.Parameters.Size, resources) + nodeSelector, err := utils.FetchNodeSelectorFromConfig(ctx, svc, plan, comp.Spec.Parameters.Scheduling.NodeSelector) + + if err != nil { + return fmt.Errorf("cannot fetch nodeSelector from the composition config: %w", err) + } + + initialData := &sgv1.SGClusterSpecInitialData{} + backupRef := xkubev1.Reference{} + if comp.Spec.Parameters.Restore != nil { + initialData = &sgv1.SGClusterSpecInitialData{ + Restore: &sgv1.SGClusterSpecInitialDataRestore{ + FromBackup: &sgv1.SGClusterSpecInitialDataRestoreFromBackup{ + Name: &comp.Spec.Parameters.Restore.BackupName, + TargetTimeline: &comp.Spec.Parameters.Restore.RecoveryTimeStamp, + }, + }, + } + backupRef = xkubev1.Reference{ + DependsOn: &xkubev1.DependsOn{ + APIVersion: "stackgres.io/v1", + Kind: "SGBackup", + Name: comp.Spec.Parameters.Restore.BackupName, + Namespace: comp.GetInstanceNamespace(), + }, + } + } + + sgCluster := &sgv1.SGCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName(), + Namespace: comp.GetInstanceNamespace(), + }, + Spec: sgv1.SGClusterSpec{ + Instances: comp.Spec.Parameters.Instances, + SgInstanceProfile: ptr.To(comp.GetName()), + Configurations: &sgv1.SGClusterSpecConfigurations{ + SgPostgresConfig: ptr.To(comp.GetName()), + Backups: &[]sgv1.SGClusterSpecConfigurationsBackupsItem{ + { + SgObjectStorage: "sgbackup-" + comp.GetName(), + Retention: &comp.Spec.Parameters.Backup.Retention, + }, + }, + }, + InitialData: initialData, + Postgres: sgv1.SGClusterSpecPostgres{ + Version: comp.Spec.Parameters.Service.MajorVersion, + Ssl: &sgv1.SGClusterSpecPostgresSsl{ + Enabled: ptr.To(true), + CertificateSecretKeySelector: &sgv1.SGClusterSpecPostgresSslCertificateSecretKeySelector{ + Name: certificateSecretName, + Key: "tls.crt", + }, + PrivateKeySecretKeySelector: &sgv1.SGClusterSpecPostgresSslPrivateKeySecretKeySelector{ + Name: certificateSecretName, + Key: "tls.key", + }, + }, + }, + Pods: sgv1.SGClusterSpecPods{ + PersistentVolume: sgv1.SGClusterSpecPodsPersistentVolume{ + Size: res.Disk, + }, + Resources: &sgv1.SGClusterSpecPodsResources{ + EnableClusterLimitsRequirements: ptr.To(true), + }, + Scheduling: &sgv1.SGClusterSpecPodsScheduling{ + NodeSelector: nodeSelector, + }, + }, + NonProductionOptions: &sgv1.SGClusterSpecNonProductionOptions{ + EnableSetPatroniCpuRequests: ptr.To(true), + EnableSetPatroniMemoryRequests: ptr.To(true), + EnableSetClusterCpuRequests: ptr.To(true), + EnableSetClusterMemoryRequests: ptr.To(true), + }, + }, + } + configureReplication(comp, sgCluster) + + err = svc.SetDesiredKubeObjectWithName(sgCluster, comp.GetName()+"-cluster", "cluster", runtime.KubeOptionAddRefs(backupRef)) + if err != nil { + err = fmt.Errorf("cannot create sgInstanceProfile: %w", err) + return err + } + + return nil + +} + +func createObjectBucket(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) error { + + xObjectBucket := &appcatv1.XObjectBucket{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName(), + }, + Spec: appcatv1.XObjectBucketSpec{ + Parameters: appcatv1.ObjectBucketParameters{ + BucketName: comp.GetName(), + Region: svc.Config.Data["bucketRegion"], + }, + ResourceSpec: xpv1.ResourceSpec{ + WriteConnectionSecretToReference: &xpv1.SecretReference{ + Name: "pgbucket-" + comp.GetName(), + Namespace: comp.GetInstanceNamespace(), + }, + }, + }, + } + + err := svc.SetDesiredComposedResourceWithName(xObjectBucket, "pg-bucket") + if err != nil { + err = fmt.Errorf("cannot create xObjectBucket: %w", err) + return err + } + + return nil +} + +func createSgObjectStorage(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) error { + + sgObjectStorage := &sgv1beta1.SGObjectStorage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sgbackup-" + comp.GetName(), + Namespace: comp.GetInstanceNamespace(), + }, + Spec: sgv1beta1.SGObjectStorageSpec{ + Type: "s3Compatible", + S3Compatible: &sgv1beta1.SGObjectStorageSpecS3Compatible{ + Bucket: comp.GetName(), + EnablePathStyleAddressing: ptr.To(true), + Region: ptr.To(svc.Config.Data["bucketRegion"]), + Endpoint: ptr.To(svc.Config.Data["bucketEndpoint"]), + AwsCredentials: sgv1beta1.SGObjectStorageSpecS3CompatibleAwsCredentials{ + SecretKeySelectors: sgv1beta1.SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectors{ + AccessKeyId: sgv1beta1.SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsAccessKeyId{ + Name: "pgbucket-" + comp.GetName(), + Key: "AWS_ACCESS_KEY_ID", + }, + SecretAccessKey: sgv1beta1.SGObjectStorageSpecS3CompatibleAwsCredentialsSecretKeySelectorsSecretAccessKey{ + Name: "pgbucket-" + comp.GetName(), + Key: "AWS_SECRET_ACCESS_KEY", + }, + }, + }, + }, + }, + } + err := svc.SetDesiredKubeObjectWithName(sgObjectStorage, comp.GetName()+"-object-storage", "sg-backup") + if err != nil { + err = fmt.Errorf("cannot create xObjectBucket: %w", err) + return err + } + + return nil +} + +func createPodMonitor(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) error { + var keepMetrics []string + err := json.Unmarshal([]byte(svc.Config.Data["keepMetrics"]), &keepMetrics) + if err != nil { + return fmt.Errorf("cannot unmarshall keepMetrics: %w", err) + } + + podMonitor := &promv1.PodMonitor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "postgresql-podmonitor", + Namespace: comp.GetInstanceNamespace(), + }, + Spec: promv1.PodMonitorSpec{ + PodMetricsEndpoints: []promv1.PodMetricsEndpoint{ + { + Port: "pgexporter", + MetricRelabelConfigs: []*promv1.RelabelConfig{ + { + Action: "keep", + SourceLabels: []promv1.LabelName{ + "__name__", + }, + Regex: "(" + strings.Join(keepMetrics[:], "|") + ")", + }, + }, + }, + }, + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "StackGresCluster", + "stackgres.io/cluster-name": comp.GetName(), + }, + }, + NamespaceSelector: promv1.NamespaceSelector{ + MatchNames: []string{ + comp.GetInstanceNamespace(), + }, + }, + }, + } + + err = svc.SetDesiredKubeObjectWithName(podMonitor, comp.GetName()+"-podmonitor", "podmonitor") + if err != nil { + err = fmt.Errorf("cannot create xObjectBucket: %w", err) + return err + } + return nil +} + +func createCopyJob(comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) error { + + copyJob := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: comp.GetName() + "-copyjob", + Namespace: svc.Config.Data["controlNamespace"], + }, + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ + Spec: v1.PodSpec{ + RestartPolicy: "Never", + ServiceAccountName: "copyserviceaccount", + Containers: []v1.Container{ + { + Name: "copyjob", + Image: "bitnami/kubectl:latest", + Command: []string{"sh", "-c"}, + Args: []string{postgresqlCopyJobScript}, + Env: []v1.EnvVar{ + { + Name: "CLAIM_NAMESPACE", + Value: comp.GetClaimNamespace(), + }, + { + Name: "CLAIM_NAME", + Value: comp.Spec.Parameters.Restore.ClaimName, + }, + { + Name: "BACKUP_NAME", + Value: comp.Spec.Parameters.Restore.BackupName, + }, + { + Name: "TARGET_NAMESPACE", + Value: comp.GetInstanceNamespace(), + }, + }, + }, + }, + }, + }, + }, + } + + err := svc.SetDesiredKubeObjectWithName(copyJob, comp.GetName()+"-copyjob", "copy-job") + if err != nil { + err = fmt.Errorf("cannot create xObjectBucket: %w", err) + return err + } + + return nil } diff --git a/pkg/comp-functions/functions/vshnpostgres/postgresql_deploy_test.go b/pkg/comp-functions/functions/vshnpostgres/postgresql_deploy_test.go new file mode 100644 index 000000000..c7c984a90 --- /dev/null +++ b/pkg/comp-functions/functions/vshnpostgres/postgresql_deploy_test.go @@ -0,0 +1,156 @@ +package vshnpostgres + +import ( + "context" + "encoding/json" + "testing" + "time" + + cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + promv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + "github.com/stretchr/testify/assert" + sgv1 "github.com/vshn/appcat/v4/apis/stackgres/v1" + vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" + "github.com/vshn/appcat/v4/pkg/common/utils" + "github.com/vshn/appcat/v4/pkg/comp-functions/functions/commontest" + "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" +) + +func TestPostgreSqlDeploy(t *testing.T) { + + svc, comp := getPostgreSqlComp(t, "vshn-postgres/deploy/01_default.yaml") + + ctx := context.TODO() + + assert.Nil(t, DeployPostgreSQL(ctx, svc)) + assert.Nil(t, addSchedules(ctx, svc)) + + ns := &corev1.Namespace{} + assert.NoError(t, svc.GetDesiredKubeObject(ns, "namespace-conditions")) + assert.Equal(t, string("vshn"), ns.GetLabels()[utils.OrgLabelName]) + + roleBinding := &rbacv1.RoleBinding{} + assert.NoError(t, svc.GetDesiredKubeObject(roleBinding, "namespace-permissions")) + + selfSignedIssuer := &cmv1.Issuer{} + assert.NoError(t, svc.GetDesiredKubeObject(selfSignedIssuer, "local-ca")) + assert.Equal(t, comp.GetName(), selfSignedIssuer.Name) + assert.Equal(t, cmv1.SelfSignedIssuer{}, *selfSignedIssuer.Spec.IssuerConfig.SelfSigned) + + certificate := &cmv1.Certificate{} + assert.NoError(t, svc.GetDesiredKubeObject(certificate, "certificate")) + assert.Equal(t, comp.GetName(), certificate.Name) + assert.Equal(t, "tls-certificate", certificate.Spec.SecretName) + assert.Equal(t, "vshn-appcat", certificate.Spec.Subject.Organizations[0]) + dnsNames := []string{ + comp.GetName() + "." + comp.GetInstanceNamespace() + ".svc.cluster.local", + comp.GetName() + "." + comp.GetInstanceNamespace() + ".svc", + } + assert.Equal(t, dnsNames, certificate.Spec.DNSNames) + assert.Equal(t, time.Duration(87600*time.Hour), certificate.Spec.Duration.Duration) + assert.Equal(t, time.Duration(2400*time.Hour), certificate.Spec.RenewBefore.Duration) + assert.Equal(t, comp.GetName(), certificate.Spec.IssuerRef.Name) + assert.Equal(t, selfSignedIssuer.GetObjectKind().GroupVersionKind().Kind, certificate.Spec.IssuerRef.Kind) + assert.Equal(t, selfSignedIssuer.GetObjectKind().GroupVersionKind().Group, certificate.Spec.IssuerRef.Group) + + cluster := &sgv1.SGCluster{} + assert.NoError(t, svc.GetDesiredKubeObject(cluster, "cluster")) + assert.Equal(t, comp.GetName(), cluster.Name) + assert.Equal(t, comp.GetInstanceNamespace(), cluster.Namespace) + assert.Equal(t, comp.Spec.Parameters.Instances, cluster.Spec.Instances) + assert.Equal(t, comp.Spec.Parameters.Service.MajorVersion, cluster.Spec.Postgres.Version) + assert.Nil(t, cluster.Spec.InitialData.Restore) + assert.Equal(t, comp.GetName(), *cluster.Spec.SgInstanceProfile) + assert.Equal(t, comp.GetName(), *cluster.Spec.Configurations.SgPostgresConfig) + backups := *cluster.Spec.Configurations.Backups + assert.Equal(t, "sgbackup-"+comp.GetName(), backups[0].SgObjectStorage) + assert.Equal(t, comp.Spec.Parameters.Backup.Schedule, *(backups[0].CronSchedule)) + assert.Equal(t, comp.Spec.Parameters.Backup.Retention, *(backups[0].Retention)) + + sgInstanceProfile := &sgv1.SGInstanceProfile{} + assert.NoError(t, svc.GetDesiredKubeObject(sgInstanceProfile, "profile")) + assert.Equal(t, comp.GetName(), sgInstanceProfile.Name) + assert.Equal(t, comp.GetInstanceNamespace(), sgInstanceProfile.Namespace) + assert.Equal(t, "250m", sgInstanceProfile.Spec.Cpu) + assert.Equal(t, "1Gi", sgInstanceProfile.Spec.Memory) + containers := map[string]sgv1.SGInstanceProfileContainer{} + assert.NoError(t, json.Unmarshal(sgInstanceProfile.Spec.Containers.Raw, &containers)) + assert.Contains(t, containers, "cluster-controller") + assert.Equal(t, "32m", containers["cluster-controller"].Cpu) + assert.Equal(t, "256Mi", containers["cluster-controller"].Memory) + assert.Nil(t, sgInstanceProfile.Spec.HugePages) + + sgPostgresConfig := &sgv1.SGPostgresConfig{} + assert.NoError(t, svc.GetDesiredKubeObject(sgPostgresConfig, "pg-conf")) + assert.Equal(t, comp.Spec.Parameters.Service.MajorVersion, sgPostgresConfig.Spec.PostgresVersion) + assert.Equal(t, map[string]string{}, sgPostgresConfig.Spec.PostgresqlConf) + + podMonitor := &promv1.PodMonitor{} + assert.NoError(t, svc.GetDesiredKubeObject(podMonitor, "podmonitor")) + assert.Contains(t, podMonitor.Spec.Selector.MatchLabels, "stackgres.io/cluster-name") + assert.Equal(t, comp.GetName(), podMonitor.Spec.Selector.MatchLabels["stackgres.io/cluster-name"]) + assert.Equal(t, "pgexporter", podMonitor.Spec.PodMetricsEndpoints[0].Port) + +} + +func TestPostgreSqlDeployWithPgConfig(t *testing.T) { + + svc, _ := getPostgreSqlComp(t, "vshn-postgres/deploy/02_with_pg_config.yaml") + + ctx := context.TODO() + + assert.Nil(t, DeployPostgreSQL(ctx, svc)) + + ns := &corev1.Namespace{} + assert.NoError(t, svc.GetDesiredKubeObject(ns, "namespace-conditions")) + assert.Equal(t, string("vshn"), ns.GetLabels()[utils.OrgLabelName]) + + roleBinding := &rbacv1.RoleBinding{} + assert.NoError(t, svc.GetDesiredKubeObject(roleBinding, "namespace-permissions")) + + cluster := &sgv1.SGCluster{} + assert.NoError(t, svc.GetDesiredKubeObject(cluster, "cluster")) + + sgPostgresConfig := &sgv1.SGPostgresConfig{} + assert.NoError(t, svc.GetDesiredKubeObject(sgPostgresConfig, "pg-conf")) + assert.Contains(t, sgPostgresConfig.Spec.PostgresqlConf, "timezone") + assert.Equal(t, "Europe/Zurich", sgPostgresConfig.Spec.PostgresqlConf["timezone"]) +} + +func TestPostgreSqlDeployWithRestore(t *testing.T) { + + svc, comp := getPostgreSqlComp(t, "vshn-postgres/deploy/03_with_restore.yaml") + + ctx := context.TODO() + + assert.Nil(t, DeployPostgreSQL(ctx, svc)) + + ns := &corev1.Namespace{} + assert.NoError(t, svc.GetDesiredKubeObject(ns, "namespace-conditions")) + assert.Equal(t, string("vshn"), ns.GetLabels()[utils.OrgLabelName]) + + roleBinding := &rbacv1.RoleBinding{} + assert.NoError(t, svc.GetDesiredKubeObject(roleBinding, "namespace-permissions")) + + cluster := &sgv1.SGCluster{} + assert.NoError(t, svc.GetDesiredKubeObject(cluster, "cluster")) + assert.Equal(t, comp.Spec.Parameters.Restore.BackupName, *cluster.Spec.InitialData.Restore.FromBackup.Name) + + copyJob := &batchv1.Job{} + assert.NoError(t, svc.GetDesiredKubeObject(copyJob, "copy-job")) + assert.Equal(t, "CLAIM_NAMESPACE", copyJob.Spec.Template.Spec.Containers[0].Env[0].Name) + assert.Equal(t, comp.GetClaimNamespace(), copyJob.Spec.Template.Spec.Containers[0].Env[0].Value) +} + +func getPostgreSqlComp(t *testing.T, file string) (*runtime.ServiceRuntime, *vshnv1.VSHNPostgreSQL) { + svc := commontest.LoadRuntimeFromFile(t, file) + + comp := &vshnv1.VSHNPostgreSQL{} + err := svc.GetObservedComposite(comp) + assert.NoError(t, err) + + return svc, comp +} diff --git a/pkg/comp-functions/functions/vshnpostgres/register.go b/pkg/comp-functions/functions/vshnpostgres/register.go index 9e3aa95f8..74bc0e4cc 100644 --- a/pkg/comp-functions/functions/vshnpostgres/register.go +++ b/pkg/comp-functions/functions/vshnpostgres/register.go @@ -49,10 +49,6 @@ func init() { Name: "extensions", Execute: AddExtensions, }, - { - Name: "replication", - Execute: ConfigureReplication, - }, { Name: "load-balancer", Execute: AddPrimaryService, diff --git a/pkg/comp-functions/functions/vshnpostgres/replication.go b/pkg/comp-functions/functions/vshnpostgres/replication.go index 82caec727..239bf1901 100644 --- a/pkg/comp-functions/functions/vshnpostgres/replication.go +++ b/pkg/comp-functions/functions/vshnpostgres/replication.go @@ -1,49 +1,22 @@ package vshnpostgres import ( - "context" - "fmt" - - xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1" stackgresv1 "github.com/vshn/appcat/v4/apis/stackgres/v1" vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" - "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" ) const replicationModeAsync = "async" -// ConfigureReplication configures the stackgres replication based on the claim -func ConfigureReplication(ctx context.Context, svc *runtime.ServiceRuntime) *xfnproto.Result { - comp := &vshnv1.VSHNPostgreSQL{} - err := svc.GetObservedComposite(comp) - if err != nil { - return runtime.NewFatalResult(fmt.Errorf("Cannot get composite from function io: %w", err)) - } - cluster := &stackgresv1.SGCluster{} - err = svc.GetDesiredKubeObject(cluster, "cluster") - if err != nil { - return runtime.NewFatalResult(fmt.Errorf("not able to get cluster: %w", err)) - } - - cluster = configureReplication(ctx, comp, cluster) - - err = svc.SetDesiredKubeObjectWithName(cluster, comp.GetName()+"-cluster", "cluster") - if err != nil { - return runtime.NewFatalResult(fmt.Errorf("cannot save cluster to functionIO: %w", err)) - } - return nil -} - -func configureReplication(ctx context.Context, comp *vshnv1.VSHNPostgreSQL, cluster *stackgresv1.SGCluster) *stackgresv1.SGCluster { +func configureReplication(comp *vshnv1.VSHNPostgreSQL, cluster *stackgresv1.SGCluster) *stackgresv1.SGCluster { cluster.Spec.Replication = &stackgresv1.SGClusterSpecReplication{ - Mode: pointer.String(replicationModeAsync), - SyncInstances: pointer.Int(1), + Mode: ptr.To(replicationModeAsync), + SyncInstances: ptr.To(1), } cluster.Spec.Instances = comp.Spec.Parameters.Instances if comp.Spec.Parameters.Instances > 1 && comp.Spec.Parameters.Replication.Mode != replicationModeAsync && comp.Spec.Parameters.Replication.Mode != "" { - cluster.Spec.Replication.Mode = pointer.String(comp.Spec.Parameters.Replication.Mode) - cluster.Spec.Replication.SyncInstances = pointer.Int(comp.Spec.Parameters.Instances - 1) + cluster.Spec.Replication.Mode = ptr.To(comp.Spec.Parameters.Replication.Mode) + cluster.Spec.Replication.SyncInstances = ptr.To(comp.Spec.Parameters.Instances - 1) } return cluster } diff --git a/pkg/comp-functions/functions/vshnpostgres/replication_test.go b/pkg/comp-functions/functions/vshnpostgres/replication_test.go index b641f9c1a..0b15d14df 100644 --- a/pkg/comp-functions/functions/vshnpostgres/replication_test.go +++ b/pkg/comp-functions/functions/vshnpostgres/replication_test.go @@ -1,31 +1,14 @@ package vshnpostgres import ( - "context" "testing" "github.com/stretchr/testify/assert" stackgresv1 "github.com/vshn/appcat/v4/apis/stackgres/v1" vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" - "github.com/vshn/appcat/v4/pkg/comp-functions/functions/commontest" ) -func Test_ConfigureReplication(t *testing.T) { - ctx := context.TODO() - svc := commontest.LoadRuntimeFromFile(t, "vshn-postgres/replication/01-GivenMulitInstance.yaml") - - res := ConfigureReplication(ctx, svc) - assert.Nil(t, res) - - cluster := &stackgresv1.SGCluster{} - assert.NoError(t, svc.GetDesiredKubeObject(cluster, "cluster")) - assert.Equal(t, 3, cluster.Spec.Instances) - assert.Equal(t, "sync", *cluster.Spec.Replication.Mode) - assert.Equal(t, 2, *cluster.Spec.Replication.SyncInstances) -} - func Test_configureReplication_SingleInstance(t *testing.T) { - ctx := context.Background() comp := &vshnv1.VSHNPostgreSQL{ Spec: vshnv1.VSHNPostgreSQLSpec{ Parameters: vshnv1.VSHNPostgreSQLParameters{ @@ -36,14 +19,13 @@ func Test_configureReplication_SingleInstance(t *testing.T) { } cluster := &stackgresv1.SGCluster{} - cluster = configureReplication(ctx, comp, cluster) + cluster = configureReplication(comp, cluster) assert.Equal(t, 1, cluster.Spec.Instances) assert.Equal(t, "async", *cluster.Spec.Replication.Mode) } func Test_configureReplication_SingleInstance_Sync(t *testing.T) { - ctx := context.Background() comp := &vshnv1.VSHNPostgreSQL{ Spec: vshnv1.VSHNPostgreSQLSpec{ Parameters: vshnv1.VSHNPostgreSQLParameters{ @@ -56,14 +38,13 @@ func Test_configureReplication_SingleInstance_Sync(t *testing.T) { } cluster := &stackgresv1.SGCluster{} - cluster = configureReplication(ctx, comp, cluster) + cluster = configureReplication(comp, cluster) assert.Equal(t, 1, cluster.Spec.Instances) assert.Equal(t, "async", *cluster.Spec.Replication.Mode) } func Test_configureReplication_MultiInstance_Async(t *testing.T) { - ctx := context.Background() comp := &vshnv1.VSHNPostgreSQL{ Spec: vshnv1.VSHNPostgreSQLSpec{ Parameters: vshnv1.VSHNPostgreSQLParameters{ @@ -76,14 +57,13 @@ func Test_configureReplication_MultiInstance_Async(t *testing.T) { } cluster := &stackgresv1.SGCluster{} - cluster = configureReplication(ctx, comp, cluster) + cluster = configureReplication(comp, cluster) assert.Equal(t, 2, cluster.Spec.Instances) assert.Equal(t, "async", *cluster.Spec.Replication.Mode) } func Test_configureReplication_MultiInstance_Sync(t *testing.T) { - ctx := context.Background() comp := &vshnv1.VSHNPostgreSQL{ Spec: vshnv1.VSHNPostgreSQLSpec{ Parameters: vshnv1.VSHNPostgreSQLParameters{ @@ -96,7 +76,7 @@ func Test_configureReplication_MultiInstance_Sync(t *testing.T) { } cluster := &stackgresv1.SGCluster{} - cluster = configureReplication(ctx, comp, cluster) + cluster = configureReplication(comp, cluster) assert.Equal(t, 2, cluster.Spec.Instances) assert.Equal(t, "sync", *cluster.Spec.Replication.Mode) @@ -104,7 +84,6 @@ func Test_configureReplication_MultiInstance_Sync(t *testing.T) { } func Test_configureReplication_MultiInstance_StrictSync(t *testing.T) { - ctx := context.Background() comp := &vshnv1.VSHNPostgreSQL{ Spec: vshnv1.VSHNPostgreSQLSpec{ Parameters: vshnv1.VSHNPostgreSQLParameters{ @@ -117,7 +96,7 @@ func Test_configureReplication_MultiInstance_StrictSync(t *testing.T) { } cluster := &stackgresv1.SGCluster{} - cluster = configureReplication(ctx, comp, cluster) + cluster = configureReplication(comp, cluster) assert.Equal(t, 3, cluster.Spec.Instances) assert.Equal(t, "strict-sync", *cluster.Spec.Replication.Mode) @@ -125,7 +104,6 @@ func Test_configureReplication_MultiInstance_StrictSync(t *testing.T) { } func Test_configureReplication_MultiInstance_Default(t *testing.T) { - ctx := context.Background() comp := &vshnv1.VSHNPostgreSQL{ Spec: vshnv1.VSHNPostgreSQLSpec{ Parameters: vshnv1.VSHNPostgreSQLParameters{ @@ -135,7 +113,7 @@ func Test_configureReplication_MultiInstance_Default(t *testing.T) { } cluster := &stackgresv1.SGCluster{} - cluster = configureReplication(ctx, comp, cluster) + cluster = configureReplication(comp, cluster) assert.Equal(t, 3, cluster.Spec.Instances) assert.Equal(t, "async", *cluster.Spec.Replication.Mode) diff --git a/pkg/comp-functions/functions/vshnpostgres/scripts/copy-pg-backup.sh b/pkg/comp-functions/functions/vshnpostgres/scripts/copy-pg-backup.sh new file mode 100644 index 000000000..2d6e6988f --- /dev/null +++ b/pkg/comp-functions/functions/vshnpostgres/scripts/copy-pg-backup.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + +xrdname=$(kubectl -n "${CLAIM_NAMESPACE}" get vshnpostgresqls "${CLAIM_NAME}" -ojson | jq -r '.spec.resourceRef.name') + +source_namespace=$(kubectl -n "${CLAIM_NAMESPACE}" get vshnpostgresqls "${CLAIM_NAME}" -ojson | jq -r '.status.instanceNamespace') + +echo "copy secret" +kubectl -n "${source_namespace}" get secret "pgbucket-${xrdname}" -ojson | jq 'del(.metadata.namespace) | del(.metadata.ownerReferences)' | kubectl -n "${TARGET_NAMESPACE}" apply -f - +echo "copy sgObjectStorage" +kubectl -n "${source_namespace}" get sgobjectstorages.stackgres.io "sgbackup-${xrdname}" -ojson | jq 'del(.metadata.namespace) | del(.metadata.ownerReferences)' | kubectl -n "$TARGET_NAMESPACE" apply -f - +echo "copy sgBackup" +kubectl -n "${source_namespace}" get sgbackups.stackgres.io "${BACKUP_NAME}" -ojson | jq '.spec.sgCluster = .metadata.namespace + "." + .spec.sgCluster | del(.metadata.namespace) | del(.metadata.ownerReferences)' | kubectl -n "${TARGET_NAMESPACE}" apply -f - diff --git a/pkg/scheme.go b/pkg/scheme.go index 7406d0b5c..34e0515a0 100644 --- a/pkg/scheme.go +++ b/pkg/scheme.go @@ -13,6 +13,7 @@ import ( alertmanagerv1alpha1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1alpha1" pgv1alpha1 "github.com/vshn/appcat/v4/apis/sql/postgresql/v1alpha1" stackgresv1 "github.com/vshn/appcat/v4/apis/stackgres/v1" + stackgresv1beta1 "github.com/vshn/appcat/v4/apis/stackgres/v1beta1" appcatv1 "github.com/vshn/appcat/v4/apis/v1" vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" miniov1 "github.com/vshn/provider-minio/apis/minio/v1" @@ -38,6 +39,7 @@ func AddToScheme(s *runtime.Scheme) { _ = xkube.SchemeBuilder.AddToScheme(s) _ = vshnv1.SchemeBuilder.SchemeBuilder.AddToScheme(s) _ = stackgresv1.SchemeBuilder.AddToScheme(s) + _ = stackgresv1beta1.SchemeBuilder.AddToScheme(s) _ = rbacv1.SchemeBuilder.AddToScheme(s) _ = appcatv1.SchemeBuilder.AddToScheme(s) _ = batchv1.SchemeBuilder.AddToScheme(s) diff --git a/test/functions/vshn-postgres/deploy/01_default.yaml b/test/functions/vshn-postgres/deploy/01_default.yaml new file mode 100644 index 000000000..9957ad5ab --- /dev/null +++ b/test/functions/vshn-postgres/deploy/01_default.yaml @@ -0,0 +1,301 @@ +desired: + composite: + resource: + apiVersion: vshn.appcat.vshn.io/v1 + kind: XVSHNPostgreSQL + metadata: + creationTimestamp: "2023-03-21T16:52:31Z" + finalizers: + - composite.apiextensions.crossplane.io + generateName: pgsql- + generation: 13 + labels: + appuio.io/organization: vshn + crossplane.io/claim-name: pgsql + crossplane.io/claim-namespace: unit-test + crossplane.io/composite: pgsql-gc9x4 + name: pgsql-gc9x4 + spec: + parameters: + service: + majorVersion: "15" + instances: 1 + backup: + schedule: 44 20 * * * + maintenance: + schedule: 12 34 * * * + writeConnectionSecretToRef: {} + status: + instanceNamespace: vshn-postgresql-pgsql-gc9x4 + +input: + apiVersion: v1 + data: + defaultPlan: standard-1 + controlNamespace: appcat-control + plans: + '{"standard-1": {"size": {"cpu": "250m", "disk": "16Gi", "enabled": true, + "memory": "1Gi"}}}' + imageTag: master + initContainers: '{"clusterReconciliationCycle": {"limits": {"cpu": "300m", + "memory": "200Mi"}, "requests": {"cpu": "100m", "memory": "100Mi"}}, "pgbouncerAuthFile": + {"limits": {"cpu": "300m", "memory": "500Mi"}, "requests": {"cpu": "100m", + "memory": "100Mi"}}, "relocateBinaries": {"limits": {"cpu": "300m", "memory": + "500Mi"}, "requests": {"cpu": "100m", "memory": "100Mi"}}, "setDbopsRunning": + {"limits": {"cpu": "250m", "memory": "256Mi"}, "requests": {"cpu": "250m", + "memory": "256Mi"}}, "setupArbitraryUser": {"limits": {"cpu": "300m", + "memory": "500Mi"}, "requests": {"cpu": "100m", "memory": "500Mi"}}, "setupScripts": + {"limits": {"cpu": "300m", "memory": "500Mi"}, "requests": {"cpu": "100m", + "memory": "500Mi"}}}' + keepMetrics: '["pg_locks_count", "pg_postmaster_start_time_seconds", "pg_replication_lag", + "pg_settings_effective_cache_size_bytes", "pg_settings_maintenance_work_mem_bytes", + "pg_settings_max_connections", "pg_settings_max_parallel_workers", "pg_settings_max_wal_size_bytes", + "pg_settings_max_worker_processes", "pg_settings_shared_buffers_bytes", + "pg_settings_work_mem_bytes", "pg_stat_activity_count", "pg_stat_bgwriter_buffers_alloc_total", + "pg_stat_bgwriter_buffers_backend_fsync_total", "pg_stat_bgwriter_buffers_backend_total", + "pg_stat_bgwriter_buffers_checkpoint_total", "pg_stat_bgwriter_buffers_clean_total", + "pg_stat_database_blks_hit", "pg_stat_database_blks_read", "pg_stat_database_conflicts", + "pg_stat_database_deadlocks", "pg_stat_database_temp_bytes", "pg_stat_database_xact_commit", + "pg_stat_database_xact_rollback", "pg_static", "pg_up", "pgbouncer_show_stats_total_xact_count", + "pgbouncer_show_stats_totals_bytes_received", "pgbouncer_show_stats_totals_bytes_sent"]' + serviceName: postgresql + sgNamespace: stackgres + sideCars: '{"clusterController": {"limits": {"cpu": "32m", "memory": "256Mi"}, + "requests": {"cpu": "32m", "memory": "128Mi"}}, "createBackup": {"limits": + {"cpu": "400m", "memory": "500Mi"}, "requests": {"cpu": "100m", "memory": + "64Mi"}}, "envoy": {"limits": {"cpu": "64m", "memory": "64Mi"}, "requests": + {"cpu": "32m", "memory": "64Mi"}}, "pgbouncer": {"limits": {"cpu": "32m", + "memory": "20Mi"}, "requests": {"cpu": "16m", "memory": "4Mi"}}, "postgresUtil": + {"limits": {"cpu": "20m", "memory": "20Mi"}, "requests": {"cpu": "10m", + "memory": "4Mi"}}, "prometheusPostgresExporter": {"limits": {"cpu": "150m", + "memory": "256Mi"}, "requests": {"cpu": "10m", "memory": "16Mi"}}, "runDbops": + {"limits": {"cpu": "250m", "memory": "256Mi"}, "requests": {"cpu": "100m", + "memory": "64Mi"}}, "setDbopsResult": {"limits": {"cpu": "250m", "memory": + "256Mi"}, "requests": {"cpu": "100m", "memory": "64Mi"}}}' + providerEnabled: "true" + kind: ConfigMap + metadata: + annotations: {} + labels: + name: xfn-config + name: xfn-config +observed: + composite: + resource: + apiVersion: vshn.appcat.vshn.io/v1 + kind: XVSHNPostgreSQL + metadata: + annotations: null + creationTimestamp: "2023-03-21T16:52:31Z" + finalizers: + - composite.apiextensions.crossplane.io + generateName: pgsql- + generation: 13 + labels: + appuio.io/organization: vshn + crossplane.io/claim-name: pgsql + crossplane.io/claim-namespace: unit-test + crossplane.io/composite: pgsql-gc9x4 + name: pgsql-gc9x4 + spec: + claimRef: + apiVersion: vshn.appcat.vshn.io/v1 + kind: VSHNPostgreSQL + name: pgsql + namespace: unit-test + compositionRef: + name: vshnpostgres.vshn.appcat.vshn.io + compositionRevisionRef: + name: vshnpostgres.vshn.appcat.vshn.io-ce52f13 + compositionUpdatePolicy: Automatic + parameters: + service: + majorVersion: "15" + instances: 1 + backup: + schedule: 44 20 * * * + retention: 6 + maintenance: + dayOfWeek: wednesday + timeOfDay: '12:34:56' + status: + instanceNamespace: vshn-postgresql-pgsql-gc9x4 + resources: + namespace-conditions: + resource: + apiVersion: kubernetes.crossplane.io/v1alpha2 + kind: Object + metadata: + name: vshn-postgresql-pgsql-gc9x4 + spec: + forProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: vshn-postgresql-pgsql-gc9x4 + status: + atProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: vshn-postgresql-pgsql-gc9x4 + pgsql-gc9x4-claim-ns-observer: + resource: + apiVersion: kubernetes.crossplane.io/v1alpha2 + kind: Object + metadata: + name: pgsql-gc9x4-claim-ns-observer + spec: + forProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: unit-test + labels: + 'appuio.io/organization': 'vshn' + status: + atProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: unit-test + labels: + 'appuio.io/organization': 'vshn' + cluster: + resource: + apiVersion: kubernetes.crossplane.io/v1alpha2 + kind: Object + metadata: + name: pgsql-gc9x4-cluster + spec: + forProvider: + manifest: + apiVersion: stackgres.io/v1 + kind: SGCluster + metadata: + name: psql-gc9x4 + namespace: vshn-postgresql-psql-gc9x4 + spec: + configurations: + backups: + - cronSchedule: 44 20 * * * + retention: 6 + sgObjectStorage: sgbackup-psql-gc9x4 + sgPostgresConfig: psql-gc9x4 + instances: 1 + nonProductionOptions: + enableSetPatroniCpuRequests: true + enableSetPatroniMemoryRequests: true + pods: + persistentVolume: + size: 20Gi + scheduling: + nodeSelector: {} + postgres: + ssl: + certificateSecretKeySelector: + key: tls.crt + name: tls-certificate + enabled: true + privateKeySecretKeySelector: + key: tls.key + name: tls-certificate + version: "15" + sgInstanceProfile: psql-gc9x4 + managementPolicy: Default + providerConfigRef: + name: kubernetes + status: + atProvider: + manifest: + apiVersion: stackgres.io/v1 + kind: SGCluster + metadata: + annotations: null + creationTimestamp: "2023-04-27T09:21:42Z" + generation: 8 + name: psql-gc9x4 + namespace: vshn-postgresql-psql-gc9x4 + resourceVersion: "583272583" + uid: 44ead047-98de-4e73-9cc0-d99454090a36 + spec: + configurations: + backups: + - cronSchedule: 44 20 * * * + path: sgbackups.stackgres.io/vshn-postgresql-psql-gc9x4/buzz-qvgrd/15 + retention: 6 + sgObjectStorage: sgbackup-psql-gc9x4 + sgPoolingConfig: generated-from-default-1682587302016 + sgPostgresConfig: psql-gc9x4 + instances: 1 + managedSql: + scripts: + - id: 0 + sgScript: psql-gc9x4-default + nonProductionOptions: + enableSetPatroniCpuRequests: true + enableSetPatroniMemoryRequests: true + pods: + persistentVolume: + size: 20Gi + scheduling: + nodeSelector: {} + postgres: + flavor: vanilla + ssl: + certificateSecretKeySelector: + key: tls.crt + name: tls-certificate + enabled: true + privateKeySecretKeySelector: + key: tls.key + name: tls-certificate + version: "15.1" + postgresServices: + primary: + enabled: true + type: ClusterIP + replicas: + enabled: true + type: ClusterIP + replication: + mode: async + role: ha-read + sgInstanceProfile: psql-gc9x4 + toInstallPostgresExtensions: [] + status: + arch: x86_64 + conditions: + - lastTransitionTime: "2023-04-27T09:22:22.200237Z" + reason: FalseFailed + status: "False" + type: Failed + - lastTransitionTime: "2023-04-27T09:22:22.226244Z" + reason: FalsePendingRestart + status: "False" + type: PendingRestart + - lastTransitionTime: "2023-04-27T09:22:22.226273Z" + reason: FalsePendingUpgrade + status: "False" + type: PendingUpgrade + managedSql: + scripts: + - completedAt: "2023-04-27T09:22:52.795696Z" + id: 0 + scripts: + - id: 0 + version: 0 + startedAt: "2023-04-27T09:22:52.631407Z" + updatedAt: "2023-04-27T09:22:52.631418Z" + os: linux + podStatuses: + - installedPostgresExtensions: [] + name: psql-gc9x4-0 + pendingRestart: false + primary: true + replicationGroup: 0 + diff --git a/test/functions/vshn-postgres/deploy/02_with_pg_config.yaml b/test/functions/vshn-postgres/deploy/02_with_pg_config.yaml new file mode 100644 index 000000000..e6ae7e67a --- /dev/null +++ b/test/functions/vshn-postgres/deploy/02_with_pg_config.yaml @@ -0,0 +1,305 @@ +desired: + composite: + resource: + apiVersion: vshn.appcat.vshn.io/v1 + kind: XVSHNPostgreSQL + metadata: + creationTimestamp: "2023-03-21T16:52:31Z" + finalizers: + - composite.apiextensions.crossplane.io + generateName: pgsql- + generation: 13 + labels: + appuio.io/organization: vshn + crossplane.io/claim-name: pgsql + crossplane.io/claim-namespace: unit-test + crossplane.io/composite: pgsql-gc9x4 + name: pgsql-gc9x4 + spec: + parameters: + service: + majorVersion: "15" + pgSettings: + timezone: "Europe/Zurich" + instances: 1 + backup: + schedule: 44 20 * * * + maintenance: + schedule: 12 34 * * * + writeConnectionSecretToRef: {} + status: + instanceNamespace: vshn-postgresql-pgsql-gc9x4 + +input: + apiVersion: v1 + data: + defaultPlan: standard-1 + controlNamespace: appcat-control + plans: + '{"standard-1": {"size": {"cpu": "250m", "disk": "16Gi", "enabled": true, + "memory": "1Gi"}}}' + imageTag: master + initContainers: '{"clusterReconciliationCycle": {"limits": {"cpu": "300m", + "memory": "200Mi"}, "requests": {"cpu": "100m", "memory": "100Mi"}}, "pgbouncerAuthFile": + {"limits": {"cpu": "300m", "memory": "500Mi"}, "requests": {"cpu": "100m", + "memory": "100Mi"}}, "relocateBinaries": {"limits": {"cpu": "300m", "memory": + "500Mi"}, "requests": {"cpu": "100m", "memory": "100Mi"}}, "setDbopsRunning": + {"limits": {"cpu": "250m", "memory": "256Mi"}, "requests": {"cpu": "250m", + "memory": "256Mi"}}, "setupArbitraryUser": {"limits": {"cpu": "300m", + "memory": "500Mi"}, "requests": {"cpu": "100m", "memory": "500Mi"}}, "setupScripts": + {"limits": {"cpu": "300m", "memory": "500Mi"}, "requests": {"cpu": "100m", + "memory": "500Mi"}}}' + keepMetrics: '["pg_locks_count", "pg_postmaster_start_time_seconds", "pg_replication_lag", + "pg_settings_effective_cache_size_bytes", "pg_settings_maintenance_work_mem_bytes", + "pg_settings_max_connections", "pg_settings_max_parallel_workers", "pg_settings_max_wal_size_bytes", + "pg_settings_max_worker_processes", "pg_settings_shared_buffers_bytes", + "pg_settings_work_mem_bytes", "pg_stat_activity_count", "pg_stat_bgwriter_buffers_alloc_total", + "pg_stat_bgwriter_buffers_backend_fsync_total", "pg_stat_bgwriter_buffers_backend_total", + "pg_stat_bgwriter_buffers_checkpoint_total", "pg_stat_bgwriter_buffers_clean_total", + "pg_stat_database_blks_hit", "pg_stat_database_blks_read", "pg_stat_database_conflicts", + "pg_stat_database_deadlocks", "pg_stat_database_temp_bytes", "pg_stat_database_xact_commit", + "pg_stat_database_xact_rollback", "pg_static", "pg_up", "pgbouncer_show_stats_total_xact_count", + "pgbouncer_show_stats_totals_bytes_received", "pgbouncer_show_stats_totals_bytes_sent"]' + serviceName: postgresql + sgNamespace: stackgres + sideCars: '{"clusterController": {"limits": {"cpu": "32m", "memory": "256Mi"}, + "requests": {"cpu": "32m", "memory": "128Mi"}}, "createBackup": {"limits": + {"cpu": "400m", "memory": "500Mi"}, "requests": {"cpu": "100m", "memory": + "64Mi"}}, "envoy": {"limits": {"cpu": "64m", "memory": "64Mi"}, "requests": + {"cpu": "32m", "memory": "64Mi"}}, "pgbouncer": {"limits": {"cpu": "32m", + "memory": "20Mi"}, "requests": {"cpu": "16m", "memory": "4Mi"}}, "postgresUtil": + {"limits": {"cpu": "20m", "memory": "20Mi"}, "requests": {"cpu": "10m", + "memory": "4Mi"}}, "prometheusPostgresExporter": {"limits": {"cpu": "150m", + "memory": "256Mi"}, "requests": {"cpu": "10m", "memory": "16Mi"}}, "runDbops": + {"limits": {"cpu": "250m", "memory": "256Mi"}, "requests": {"cpu": "100m", + "memory": "64Mi"}}, "setDbopsResult": {"limits": {"cpu": "250m", "memory": + "256Mi"}, "requests": {"cpu": "100m", "memory": "64Mi"}}}' + providerEnabled: "true" + kind: ConfigMap + metadata: + annotations: {} + labels: + name: xfn-config + name: xfn-config +observed: + composite: + resource: + apiVersion: vshn.appcat.vshn.io/v1 + kind: XVSHNPostgreSQL + metadata: + annotations: null + creationTimestamp: "2023-03-21T16:52:31Z" + finalizers: + - composite.apiextensions.crossplane.io + generateName: pgsql- + generation: 13 + labels: + appuio.io/organization: vshn + crossplane.io/claim-name: pgsql + crossplane.io/claim-namespace: unit-test + crossplane.io/composite: pgsql-gc9x4 + name: pgsql-gc9x4 + spec: + claimRef: + apiVersion: vshn.appcat.vshn.io/v1 + kind: VSHNPostgreSQL + name: pgsql + namespace: unit-test + compositionRef: + name: vshnpostgres.vshn.appcat.vshn.io + compositionRevisionRef: + name: vshnpostgres.vshn.appcat.vshn.io-ce52f13 + compositionUpdatePolicy: Automatic + parameters: + service: + majorVersion: "15" + pgSettings: + timezone: "Europe/Zurich" + instances: 1 + backup: + schedule: 44 20 * * * + retention: 6 + maintenance: + dayOfWeek: wednesday + timeOfDay: '12:34:56' + status: + instanceNamespace: vshn-postgresql-pgsql-gc9x4 + resources: + namespace-conditions: + resource: + apiVersion: kubernetes.crossplane.io/v1alpha2 + kind: Object + metadata: + name: vshn-postgresql-pgsql-gc9x4 + spec: + forProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: vshn-postgresql-pgsql-gc9x4 + status: + atProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: vshn-postgresql-pgsql-gc9x4 + pgsql-gc9x4-claim-ns-observer: + resource: + apiVersion: kubernetes.crossplane.io/v1alpha2 + kind: Object + metadata: + name: pgsql-gc9x4-claim-ns-observer + spec: + forProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: unit-test + labels: + 'appuio.io/organization': 'vshn' + status: + atProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: unit-test + labels: + 'appuio.io/organization': 'vshn' + cluster: + resource: + apiVersion: kubernetes.crossplane.io/v1alpha2 + kind: Object + metadata: + name: pgsql-gc9x4-cluster + spec: + forProvider: + manifest: + apiVersion: stackgres.io/v1 + kind: SGCluster + metadata: + name: psql-gc9x4 + namespace: vshn-postgresql-psql-gc9x4 + spec: + configurations: + backups: + - cronSchedule: 44 20 * * * + retention: 6 + sgObjectStorage: sgbackup-psql-gc9x4 + sgPostgresConfig: psql-gc9x4 + instances: 1 + nonProductionOptions: + enableSetPatroniCpuRequests: true + enableSetPatroniMemoryRequests: true + pods: + persistentVolume: + size: 20Gi + scheduling: + nodeSelector: {} + postgres: + ssl: + certificateSecretKeySelector: + key: tls.crt + name: tls-certificate + enabled: true + privateKeySecretKeySelector: + key: tls.key + name: tls-certificate + version: "15" + sgInstanceProfile: psql-gc9x4 + managementPolicy: Default + providerConfigRef: + name: kubernetes + status: + atProvider: + manifest: + apiVersion: stackgres.io/v1 + kind: SGCluster + metadata: + annotations: null + creationTimestamp: "2023-04-27T09:21:42Z" + generation: 8 + name: psql-gc9x4 + namespace: vshn-postgresql-psql-gc9x4 + resourceVersion: "583272583" + uid: 44ead047-98de-4e73-9cc0-d99454090a36 + spec: + configurations: + backups: + - cronSchedule: 44 20 * * * + path: sgbackups.stackgres.io/vshn-postgresql-psql-gc9x4/buzz-qvgrd/15 + retention: 6 + sgObjectStorage: sgbackup-psql-gc9x4 + sgPoolingConfig: generated-from-default-1682587302016 + sgPostgresConfig: psql-gc9x4 + instances: 1 + managedSql: + scripts: + - id: 0 + sgScript: psql-gc9x4-default + nonProductionOptions: + enableSetPatroniCpuRequests: true + enableSetPatroniMemoryRequests: true + pods: + persistentVolume: + size: 20Gi + scheduling: + nodeSelector: {} + postgres: + flavor: vanilla + ssl: + certificateSecretKeySelector: + key: tls.crt + name: tls-certificate + enabled: true + privateKeySecretKeySelector: + key: tls.key + name: tls-certificate + version: "15.1" + postgresServices: + primary: + enabled: true + type: ClusterIP + replicas: + enabled: true + type: ClusterIP + replication: + mode: async + role: ha-read + sgInstanceProfile: psql-gc9x4 + toInstallPostgresExtensions: [] + status: + arch: x86_64 + conditions: + - lastTransitionTime: "2023-04-27T09:22:22.200237Z" + reason: FalseFailed + status: "False" + type: Failed + - lastTransitionTime: "2023-04-27T09:22:22.226244Z" + reason: FalsePendingRestart + status: "False" + type: PendingRestart + - lastTransitionTime: "2023-04-27T09:22:22.226273Z" + reason: FalsePendingUpgrade + status: "False" + type: PendingUpgrade + managedSql: + scripts: + - completedAt: "2023-04-27T09:22:52.795696Z" + id: 0 + scripts: + - id: 0 + version: 0 + startedAt: "2023-04-27T09:22:52.631407Z" + updatedAt: "2023-04-27T09:22:52.631418Z" + os: linux + podStatuses: + - installedPostgresExtensions: [] + name: psql-gc9x4-0 + pendingRestart: false + primary: true + replicationGroup: 0 + diff --git a/test/functions/vshn-postgres/deploy/03_with_restore.yaml b/test/functions/vshn-postgres/deploy/03_with_restore.yaml new file mode 100644 index 000000000..d3c756142 --- /dev/null +++ b/test/functions/vshn-postgres/deploy/03_with_restore.yaml @@ -0,0 +1,309 @@ +desired: + composite: + resource: + apiVersion: vshn.appcat.vshn.io/v1 + kind: XVSHNPostgreSQL + metadata: + creationTimestamp: "2023-03-21T16:52:31Z" + finalizers: + - composite.apiextensions.crossplane.io + generateName: pgsql- + generation: 13 + labels: + appuio.io/organization: vshn + crossplane.io/claim-name: pgsql-restore + crossplane.io/claim-namespace: unit-test + crossplane.io/composite: pgsql-gc9x4 + name: pgsql-gc9x4 + spec: + parameters: + restore: + claimName: pgsql + backupName: "pgsql-gc9x4-2024-06-11-16-01-02" + service: + majorVersion: "15" + instances: 1 + backup: + schedule: 44 20 * * * + maintenance: + schedule: 12 34 * * * + writeConnectionSecretToRef: {} + status: + instanceNamespace: vshn-postgresql-pgsql-gc9x4 + +input: + apiVersion: v1 + data: + defaultPlan: standard-1 + controlNamespace: appcat-control + plans: + '{"standard-1": {"size": {"cpu": "250m", "disk": "16Gi", "enabled": true, + "memory": "1Gi"}}}' + imageTag: master + initContainers: '{"clusterReconciliationCycle": {"limits": {"cpu": "300m", + "memory": "200Mi"}, "requests": {"cpu": "100m", "memory": "100Mi"}}, "pgbouncerAuthFile": + {"limits": {"cpu": "300m", "memory": "500Mi"}, "requests": {"cpu": "100m", + "memory": "100Mi"}}, "relocateBinaries": {"limits": {"cpu": "300m", "memory": + "500Mi"}, "requests": {"cpu": "100m", "memory": "100Mi"}}, "setDbopsRunning": + {"limits": {"cpu": "250m", "memory": "256Mi"}, "requests": {"cpu": "250m", + "memory": "256Mi"}}, "setupArbitraryUser": {"limits": {"cpu": "300m", + "memory": "500Mi"}, "requests": {"cpu": "100m", "memory": "500Mi"}}, "setupScripts": + {"limits": {"cpu": "300m", "memory": "500Mi"}, "requests": {"cpu": "100m", + "memory": "500Mi"}}}' + keepMetrics: '["pg_locks_count", "pg_postmaster_start_time_seconds", "pg_replication_lag", + "pg_settings_effective_cache_size_bytes", "pg_settings_maintenance_work_mem_bytes", + "pg_settings_max_connections", "pg_settings_max_parallel_workers", "pg_settings_max_wal_size_bytes", + "pg_settings_max_worker_processes", "pg_settings_shared_buffers_bytes", + "pg_settings_work_mem_bytes", "pg_stat_activity_count", "pg_stat_bgwriter_buffers_alloc_total", + "pg_stat_bgwriter_buffers_backend_fsync_total", "pg_stat_bgwriter_buffers_backend_total", + "pg_stat_bgwriter_buffers_checkpoint_total", "pg_stat_bgwriter_buffers_clean_total", + "pg_stat_database_blks_hit", "pg_stat_database_blks_read", "pg_stat_database_conflicts", + "pg_stat_database_deadlocks", "pg_stat_database_temp_bytes", "pg_stat_database_xact_commit", + "pg_stat_database_xact_rollback", "pg_static", "pg_up", "pgbouncer_show_stats_total_xact_count", + "pgbouncer_show_stats_totals_bytes_received", "pgbouncer_show_stats_totals_bytes_sent"]' + serviceName: postgresql + sgNamespace: stackgres + sideCars: '{"clusterController": {"limits": {"cpu": "32m", "memory": "256Mi"}, + "requests": {"cpu": "32m", "memory": "128Mi"}}, "createBackup": {"limits": + {"cpu": "400m", "memory": "500Mi"}, "requests": {"cpu": "100m", "memory": + "64Mi"}}, "envoy": {"limits": {"cpu": "64m", "memory": "64Mi"}, "requests": + {"cpu": "32m", "memory": "64Mi"}}, "pgbouncer": {"limits": {"cpu": "32m", + "memory": "20Mi"}, "requests": {"cpu": "16m", "memory": "4Mi"}}, "postgresUtil": + {"limits": {"cpu": "20m", "memory": "20Mi"}, "requests": {"cpu": "10m", + "memory": "4Mi"}}, "prometheusPostgresExporter": {"limits": {"cpu": "150m", + "memory": "256Mi"}, "requests": {"cpu": "10m", "memory": "16Mi"}}, "runDbops": + {"limits": {"cpu": "250m", "memory": "256Mi"}, "requests": {"cpu": "100m", + "memory": "64Mi"}}, "setDbopsResult": {"limits": {"cpu": "250m", "memory": + "256Mi"}, "requests": {"cpu": "100m", "memory": "64Mi"}}}' + providerEnabled: "true" + kind: ConfigMap + metadata: + annotations: {} + labels: + name: xfn-config + name: xfn-config +observed: + composite: + resource: + apiVersion: vshn.appcat.vshn.io/v1 + kind: XVSHNPostgreSQL + metadata: + annotations: null + creationTimestamp: "2023-03-21T16:52:31Z" + finalizers: + - composite.apiextensions.crossplane.io + generateName: pgsql- + generation: 13 + labels: + appuio.io/organization: vshn + crossplane.io/claim-name: pgsql + crossplane.io/claim-namespace: unit-test + crossplane.io/composite: pgsql-gc9x4 + name: pgsql-gc9x4 + spec: + claimRef: + apiVersion: vshn.appcat.vshn.io/v1 + kind: VSHNPostgreSQL + name: pgsql + namespace: unit-test + compositionRef: + name: vshnpostgres.vshn.appcat.vshn.io + compositionRevisionRef: + name: vshnpostgres.vshn.appcat.vshn.io-ce52f13 + compositionUpdatePolicy: Automatic + parameters: + restore: + claimName: pgsql + backupName: "pgsql-gc9x4-2024-06-11-16-01-02" + service: + majorVersion: "15" + pgSettings: + timezone: "Europe/Zurich" + instances: 1 + backup: + schedule: 44 20 * * * + retention: 6 + maintenance: + dayOfWeek: wednesday + timeOfDay: '12:34:56' + status: + instanceNamespace: vshn-postgresql-pgsql-gc9x4 + resources: + namespace-conditions: + resource: + apiVersion: kubernetes.crossplane.io/v1alpha2 + kind: Object + metadata: + name: vshn-postgresql-pgsql-gc9x4 + spec: + forProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: vshn-postgresql-pgsql-gc9x4 + status: + atProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: vshn-postgresql-pgsql-gc9x4 + pgsql-gc9x4-claim-ns-observer: + resource: + apiVersion: kubernetes.crossplane.io/v1alpha2 + kind: Object + metadata: + name: pgsql-gc9x4-claim-ns-observer + spec: + forProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: unit-test + labels: + 'appuio.io/organization': 'vshn' + status: + atProvider: + manifest: + apiVersion: v1 + kind: Namespace + metadata: + name: unit-test + labels: + 'appuio.io/organization': 'vshn' + cluster: + resource: + apiVersion: kubernetes.crossplane.io/v1alpha2 + kind: Object + metadata: + name: pgsql-gc9x4-cluster + spec: + forProvider: + manifest: + apiVersion: stackgres.io/v1 + kind: SGCluster + metadata: + name: psql-gc9x4 + namespace: vshn-postgresql-psql-gc9x4 + spec: + configurations: + backups: + - cronSchedule: 44 20 * * * + retention: 6 + sgObjectStorage: sgbackup-psql-gc9x4 + sgPostgresConfig: psql-gc9x4 + instances: 1 + nonProductionOptions: + enableSetPatroniCpuRequests: true + enableSetPatroniMemoryRequests: true + pods: + persistentVolume: + size: 20Gi + scheduling: + nodeSelector: {} + postgres: + ssl: + certificateSecretKeySelector: + key: tls.crt + name: tls-certificate + enabled: true + privateKeySecretKeySelector: + key: tls.key + name: tls-certificate + version: "15" + sgInstanceProfile: psql-gc9x4 + managementPolicy: Default + providerConfigRef: + name: kubernetes + status: + atProvider: + manifest: + apiVersion: stackgres.io/v1 + kind: SGCluster + metadata: + annotations: null + creationTimestamp: "2023-04-27T09:21:42Z" + generation: 8 + name: psql-gc9x4 + namespace: vshn-postgresql-psql-gc9x4 + resourceVersion: "583272583" + uid: 44ead047-98de-4e73-9cc0-d99454090a36 + spec: + configurations: + backups: + - cronSchedule: 44 20 * * * + path: sgbackups.stackgres.io/vshn-postgresql-psql-gc9x4/buzz-qvgrd/15 + retention: 6 + sgObjectStorage: sgbackup-psql-gc9x4 + sgPoolingConfig: generated-from-default-1682587302016 + sgPostgresConfig: psql-gc9x4 + instances: 1 + managedSql: + scripts: + - id: 0 + sgScript: psql-gc9x4-default + nonProductionOptions: + enableSetPatroniCpuRequests: true + enableSetPatroniMemoryRequests: true + pods: + persistentVolume: + size: 20Gi + scheduling: + nodeSelector: {} + postgres: + flavor: vanilla + ssl: + certificateSecretKeySelector: + key: tls.crt + name: tls-certificate + enabled: true + privateKeySecretKeySelector: + key: tls.key + name: tls-certificate + version: "15.1" + postgresServices: + primary: + enabled: true + type: ClusterIP + replicas: + enabled: true + type: ClusterIP + replication: + mode: async + role: ha-read + sgInstanceProfile: psql-gc9x4 + toInstallPostgresExtensions: [] + status: + arch: x86_64 + conditions: + - lastTransitionTime: "2023-04-27T09:22:22.200237Z" + reason: FalseFailed + status: "False" + type: Failed + - lastTransitionTime: "2023-04-27T09:22:22.226244Z" + reason: FalsePendingRestart + status: "False" + type: PendingRestart + - lastTransitionTime: "2023-04-27T09:22:22.226273Z" + reason: FalsePendingUpgrade + status: "False" + type: PendingUpgrade + managedSql: + scripts: + - completedAt: "2023-04-27T09:22:52.795696Z" + id: 0 + scripts: + - id: 0 + version: 0 + startedAt: "2023-04-27T09:22:52.631407Z" + updatedAt: "2023-04-27T09:22:52.631418Z" + os: linux + podStatuses: + - installedPostgresExtensions: [] + name: psql-gc9x4-0 + pendingRestart: false + primary: true + replicationGroup: 0 +