Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add backup and restore methods for kibana dashboard
Browse files Browse the repository at this point in the history
Signed-off-by: Md. Ishtiaq Islam <[email protected]>
ishtiaqhimel committed Jan 25, 2024
1 parent 649baaf commit 7b66378
Showing 73 changed files with 33,413 additions and 3,970 deletions.
2 changes: 2 additions & 0 deletions elasticsearchdashboard/api.go
Original file line number Diff line number Diff line change
@@ -23,4 +23,6 @@ import (
type EDClient interface {
GetHealthStatus() (*Health, error)
GetStateFromHealthResponse(health *Health) (esapi.DashboardServerState, error)
ExportSavedObjects() (*Response, error)
ImportSavedObjects(filepath string) (*Response, error)
}
17 changes: 16 additions & 1 deletion elasticsearchdashboard/client.go
Original file line number Diff line number Diff line change
@@ -29,6 +29,14 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
SavedObjectsReqBody = `{"type": ["dashboard", "config", "index-pattern", "url", "query", "tag", "canvas-element", "canvas-workpad", "action", "alert", "visualization",
"graph-workspace", "map", "lens", "cases", "search", "osquery-saved-query", "osquery-pack", "uptime-dynamic-settings", "infrastructure-ui-source", "metrics-explorer-view",
"inventory-view", "apm-indices"]}`
SavedObjectsExportURL = "/api/saved_objects/_export"
SavedObjectsImportURL = "/api/saved_objects/_import"
)

type Client struct {
EDClient
}
@@ -42,13 +50,20 @@ type ClientOptions struct {
Secret *core.Secret
}

type DbVersionInfo struct {
Name string
Version string
AuthPlugin catalog.ElasticsearchAuthPlugin
}

type Config struct {
host string
api string
username string
password string
connectionScheme string
transport *http.Transport
dbVersionInfo *DbVersionInfo
}

type Health struct {
@@ -60,7 +75,7 @@ type Health struct {
type Response struct {
Code int
header http.Header
body io.ReadCloser
Body io.ReadCloser
}

type ResponseBody struct {
44 changes: 41 additions & 3 deletions elasticsearchdashboard/ed_client_v7.go
Original file line number Diff line number Diff line change
@@ -47,7 +47,7 @@ func (h *EDClientV7) GetHealthStatus() (*Health, error) {
ConnectionResponse: Response{
Code: res.StatusCode(),
header: res.Header(),
body: res.RawBody(),
Body: res.RawBody(),
},
StateFailedReason: statesList,
}
@@ -69,10 +69,10 @@ func (h *EDClientV7) GetStateFromHealthResponse(health *Health) (esapi.Dashboard
}
return
}
}(resStatus.body)
}(resStatus.Body)

var responseBody ResponseBody
body, _ := io.ReadAll(resStatus.body)
body, _ := io.ReadAll(resStatus.Body)
err := json.Unmarshal(body, &responseBody)
if err != nil {
return "", errors.Wrap(err, "Failed to parse response body")
@@ -106,3 +106,41 @@ func (h *EDClientV7) GetStateFromHealthResponse(health *Health) (esapi.Dashboard

return esapi.DashboardServerState(health.OverallState), nil
}

func (h *EDClientV7) ExportSavedObjects() (*Response, error) {
req := h.Client.R().
SetDoNotParseResponse(true).
SetHeaders(map[string]string{
"Content-Type": "application/json",
"kbn-xsrf": "true",
}).
SetBody([]byte(SavedObjectsReqBody))
res, err := req.Post(SavedObjectsExportURL)
if err != nil {
klog.Error(err, "Failed to send http request")
return nil, err
}

return &Response{
Code: res.StatusCode(),
Body: res.RawBody(),
}, nil
}

func (h *EDClientV7) ImportSavedObjects(filepath string) (*Response, error) {
req := h.Client.R().
SetDoNotParseResponse(true).
SetHeader("kbn-xsrf", "true").
SetFile("file", filepath).
SetQueryParam("overwrite", "true")
res, err := req.Post(SavedObjectsImportURL)
if err != nil {
klog.Error(err, "Failed to send http request")
return nil, err
}

return &Response{
Code: res.StatusCode(),
Body: res.RawBody(),
}, nil
}
44 changes: 41 additions & 3 deletions elasticsearchdashboard/ed_client_v8.go
Original file line number Diff line number Diff line change
@@ -47,7 +47,7 @@ func (h *EDClientV8) GetHealthStatus() (*Health, error) {
ConnectionResponse: Response{
Code: res.StatusCode(),
header: res.Header(),
body: res.RawBody(),
Body: res.RawBody(),
},
StateFailedReason: statesList,
}
@@ -69,10 +69,10 @@ func (h *EDClientV8) GetStateFromHealthResponse(health *Health) (esapi.Dashboard
}
return
}
}(resStatus.body)
}(resStatus.Body)

var responseBody ResponseBody
body, _ := io.ReadAll(resStatus.body)
body, _ := io.ReadAll(resStatus.Body)
err := json.Unmarshal(body, &responseBody)
if err != nil {
return "", errors.Wrap(err, "failed to parse response body")
@@ -104,3 +104,41 @@ func (h *EDClientV8) GetStateFromHealthResponse(health *Health) (esapi.Dashboard

return esapi.DashboardServerState(health.OverallState), nil
}

func (h *EDClientV8) ExportSavedObjects() (*Response, error) {
req := h.Client.R().
SetDoNotParseResponse(true).
SetHeaders(map[string]string{
"Content-Type": "application/json",
"kbn-xsrf": "true",
}).
SetBody([]byte(SavedObjectsReqBody))
res, err := req.Post(SavedObjectsExportURL)
if err != nil {
klog.Error(err, "Failed to send http request")
return nil, err
}

return &Response{
Code: res.StatusCode(),
Body: res.RawBody(),
}, nil
}

func (h *EDClientV8) ImportSavedObjects(filepath string) (*Response, error) {
req := h.Client.R().
SetDoNotParseResponse(true).
SetHeader("kbn-xsrf", "true").
SetFile("file", filepath).
SetQueryParam("overwrite", "true")
res, err := req.Post(SavedObjectsImportURL)
if err != nil {
klog.Error(err, "Failed to send http request")
return nil, err
}

return &Response{
Code: res.StatusCode(),
Body: res.RawBody(),
}, nil
}
45 changes: 32 additions & 13 deletions elasticsearchdashboard/kubedb-client-builder.go
Original file line number Diff line number Diff line change
@@ -39,14 +39,15 @@ import (
)

type KubeDBClientBuilder struct {
kc client.Client
dashboard *esapi.ElasticsearchDashboard
db *v1alpha2.Elasticsearch
dbVersion *catalog.ElasticsearchVersion
authSecret *core.Secret
url string
podName string
ctx context.Context
kc client.Client
dashboard *esapi.ElasticsearchDashboard
db *v1alpha2.Elasticsearch
dbVersion *catalog.ElasticsearchVersion
dbVersionInfo *DbVersionInfo
authSecret *core.Secret
url string
podName string
ctx context.Context
}

func NewKubeDBClientBuilder(kc client.Client, db *esapi.ElasticsearchDashboard) *KubeDBClientBuilder {
@@ -81,6 +82,11 @@ func (o *KubeDBClientBuilder) WithDbVersion(version *catalog.ElasticsearchVersio
return o
}

func (o *KubeDBClientBuilder) WithDbVersionInfo(versionInfo *DbVersionInfo) *KubeDBClientBuilder {
o.dbVersionInfo = versionInfo
return o
}

func (o *KubeDBClientBuilder) WithContext(ctx context.Context) *KubeDBClientBuilder {
o.ctx = ctx
return o
@@ -97,6 +103,7 @@ func (o *KubeDBClientBuilder) GetElasticsearchDashboardClient() (*Client, error)
}).DialContext,
},
connectionScheme: o.dashboard.GetConnectionScheme(),
dbVersionInfo: o.getDbVersionInfo(),
}
// If EnableSSL is true set tls config,
// provide client certs and root CA
@@ -157,15 +164,15 @@ func (o *KubeDBClientBuilder) GetElasticsearchDashboardClient() (*Client, error)
}

// parse version
version, err := semver.NewVersion(o.dbVersion.Spec.Version)
version, err := semver.NewVersion(config.dbVersionInfo.Version)
if err != nil {
return nil, errors.Wrap(err, "failed to parse version")
}

switch {
// for Elasticsearch 7.x.x and OpenSearch 1.x.x
case (o.dbVersion.Spec.AuthPlugin == catalog.ElasticsearchAuthPluginXpack && version.Major() <= 7) ||
(o.dbVersion.Spec.AuthPlugin == catalog.ElasticsearchAuthPluginOpenSearch && (version.Major() == 1 || version.Major() == 2)):
case (config.dbVersionInfo.AuthPlugin == catalog.ElasticsearchAuthPluginXpack && version.Major() <= 7) ||
(config.dbVersionInfo.AuthPlugin == catalog.ElasticsearchAuthPluginOpenSearch && (version.Major() == 1 || version.Major() == 2)):
newClient := resty.New()
newClient.SetTransport(config.transport).SetScheme(config.connectionScheme).SetBaseURL(config.host)
newClient.SetHeader("Accept", "application/json")
@@ -179,7 +186,7 @@ func (o *KubeDBClientBuilder) GetElasticsearchDashboardClient() (*Client, error)
},
}, nil

case o.dbVersion.Spec.AuthPlugin == catalog.ElasticsearchAuthPluginXpack && version.Major() == 8:
case config.dbVersionInfo.AuthPlugin == catalog.ElasticsearchAuthPluginXpack && version.Major() == 8:
newClient := resty.New()
newClient.SetTransport(config.transport).SetScheme(config.connectionScheme).SetBaseURL(config.host)
newClient.SetHeader("Accept", "application/json")
@@ -194,7 +201,19 @@ func (o *KubeDBClientBuilder) GetElasticsearchDashboardClient() (*Client, error)
}, nil
}

return nil, fmt.Errorf("unknown version: %s", o.dbVersion.Name)
return nil, fmt.Errorf("unknown version: %s", config.dbVersionInfo.Name)
}

func (o *KubeDBClientBuilder) getDbVersionInfo() *DbVersionInfo {
if o.dbVersionInfo == nil {
return &DbVersionInfo{
Name: o.dbVersion.Name,
Version: o.dbVersion.Spec.Version,
AuthPlugin: o.dbVersion.Spec.AuthPlugin,
}
}

return o.dbVersionInfo
}

// return host path in
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ require (
k8s.io/klog/v2 v2.110.1
kmodules.xyz/client-go v0.29.6
kmodules.xyz/custom-resources v0.29.0
kubedb.dev/apimachinery v0.41.0-beta.0.0.20240118063916-9d6870498d68
kubedb.dev/apimachinery v0.41.0-beta.1.0.20240124061503-ce4799bb0e5c
sigs.k8s.io/controller-runtime v0.16.3
xorm.io/xorm v1.3.6
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -580,8 +580,8 @@ kmodules.xyz/monitoring-agent-api v0.29.0 h1:gpFl6OZrlMLb/ySMHdREI9EwGtnJ91oZBn9
kmodules.xyz/monitoring-agent-api v0.29.0/go.mod h1:iNbvaMTgVFOI5q2LJtGK91j4Dmjv4ZRiRdasGmWLKQI=
kmodules.xyz/offshoot-api v0.29.0 h1:GHLhxxT9jU1N8+FvOCCeJNyU5g0duYS46UGrs6AHNLY=
kmodules.xyz/offshoot-api v0.29.0/go.mod h1:5NxhBblXoDHWStx9HCDJR2KFTwYjEZ7i1Id3jelIunw=
kubedb.dev/apimachinery v0.41.0-beta.0.0.20240118063916-9d6870498d68 h1:2J1DMU9OiiMzQMKfv+gTGkmcN34JTARqS5CA0pjctMc=
kubedb.dev/apimachinery v0.41.0-beta.0.0.20240118063916-9d6870498d68/go.mod h1:9iHsSfX02yJ6eA9IXSCruVjckH8wWgHB93ivpNVc1EY=
kubedb.dev/apimachinery v0.41.0-beta.1.0.20240124061503-ce4799bb0e5c h1:QdnEBPdmd/Z3JOFJKMQ3nCFiJYiWxsuIAqMLgXep880=
kubedb.dev/apimachinery v0.41.0-beta.1.0.20240124061503-ce4799bb0e5c/go.mod h1:IqIwU4I/UfmcMi4X7G01M7XAEFUPehtiFAKZNKwH8Ek=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
Original file line number Diff line number Diff line change
@@ -56,14 +56,9 @@ type DruidVersionSpec struct {
// Deprecated versions usable but regarded as obsolete and best avoided, typically due to having been superseded.
// +optional
Deprecated bool `json:"deprecated,omitempty"`
// PSP names
// +optional
PodSecurityPolicies DruidVersionPodSecurityPolicy `json:"podSecurityPolicies"`
// update constraints
UpdateConstraints UpdateConstraints `json:"updateConstraints,omitempty"`
// SecurityContext is for the additional security information for the Druid container
// +optional
SecurityContext DruidSecurityContext `json:"securityContext"`
SecurityContext SecurityContext `json:"securityContext"`
}

// DruidVersionDatabase is the Druid Database image
@@ -76,22 +71,6 @@ type DruidInitContainer struct {
Image string `json:"image"`
}

// SinglestoreVersionPodSecurityPolicy is the Singlestore pod security policies
type DruidVersionPodSecurityPolicy struct {
DatabasePolicyName string `json:"databasePolicyName"`
}

// DruidSecurityContext provides additional securityContext settings for the Druid Image
type DruidSecurityContext struct {
// RunAsUser is default UID for the DB container. It defaults to 1000.
RunAsUser *int64 `json:"runAsUser,omitempty"`

RunAsGroup *int64 `json:"runAsGroup,omitempty"`

// RunAsAnyNonRoot will be true if user can change the default UID to other than 1000.
RunAsAnyNonRoot bool `json:"runAsAnyNonRoot,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// DruidVersionList contains a list of DruidVersion
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright AppsCode Inc. and Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"fmt"

"kubedb.dev/apimachinery/apis"
"kubedb.dev/apimachinery/apis/catalog"
"kubedb.dev/apimachinery/crds"

"kmodules.xyz/client-go/apiextensions"
)

func (f FerretDBVersion) CustomResourceDefinition() *apiextensions.CustomResourceDefinition {
return crds.MustCustomResourceDefinition(SchemeGroupVersion.WithResource(ResourcePluralFerretDBVersion))
}

var _ apis.ResourceInfo = &FerretDBVersion{}

func (f FerretDBVersion) ResourceFQN() string {
return fmt.Sprintf("%s.%s", ResourcePluralFerretDBVersion, catalog.GroupName)
}

func (f FerretDBVersion) ResourceShortCode() string {
return ResourceCodeFerretDBVersion
}

func (f FerretDBVersion) ResourceKind() string {
return ResourceKindFerretDBVersion
}

func (f FerretDBVersion) ResourceSingular() string {
return ResourceSingularFerretDBVersion
}

func (f FerretDBVersion) ResourcePlural() string {
return ResourcePluralFerretDBVersion
}

func (f FerretDBVersion) ValidateSpecs() error {
if f.Spec.Version == "" ||
f.Spec.DB.Image == "" {
return fmt.Errorf(`atleast one of the following specs is not set for ferretdbVersion "%v":
spec.version,
spec.db.image`, f.Name)
}
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
Copyright AppsCode Inc. and Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

const (
ResourceCodeFerretDBVersion = "frversion"
ResourceKindFerretDBVersion = "FerretDBVersion"
ResourceSingularFerretDBVersion = "ferretdbversion"
ResourcePluralFerretDBVersion = "ferretdbversions"
)

// +genclient
// +genclient:nonNamespaced
// +genclient:skipVerbs=updateStatus
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// +kubebuilder:object:root=true
// +kubebuilder:resource:path=ferretdbversions,singular=ferretdbversion,scope=Cluster,shortName=frversion,categories={datastore,kubedb,appscode}
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.version"
// +kubebuilder:printcolumn:name="DB_IMAGE",type="string",JSONPath=".spec.db.image"
// +kubebuilder:printcolumn:name="Deprecated",type="boolean",JSONPath=".spec.deprecated"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
type FerretDBVersion struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec FerretDBVersionSpec `json:"spec,omitempty"`
}

// FerretDBVersionSpec defines the desired state of FerretDBVersion
type FerretDBVersionSpec struct {
// Version
Version string `json:"version"`

// Database Image
DB FerretDBVersionDatabase `json:"db"`

// Deprecated versions usable but regarded as obsolete and best avoided, typically due to having been superseded.
// +optional
Deprecated bool `json:"deprecated,omitempty"`

// update constraints
// +optional
UpdateConstraints UpdateConstraints `json:"updateConstraints,omitempty"`

// SecurityContext is for the additional security information for the FerretDB container
// +optional
SecurityContext SecurityContext `json:"securityContext"`
}

// FerretDBVersionDatabase is the FerretDB Database image
type FerretDBVersionDatabase struct {
Image string `json:"image"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// FerretDBVersionList contains a list of FerretDBVersion
type FerretDBVersionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []FerretDBVersion `json:"items,omitempty"`
}
Original file line number Diff line number Diff line change
@@ -58,9 +58,6 @@ type KafkaConnectorVersionSpec struct {
// Deprecated versions usable but regarded as obsolete and best avoided, typically due to having been superseded.
// +optional
Deprecated bool `json:"deprecated,omitempty"`
// PSP names
// +optional
PodSecurityPolicies ConnectorPluginPodSecurityPolicy `json:"podSecurityPolicies"`
// SecurityContext is for the additional config for the init container
// +optional
SecurityContext SecurityContext `json:"securityContext"`
@@ -71,11 +68,6 @@ type ConnectorPlugin struct {
Image string `json:"image"`
}

// ConnectorPluginPodSecurityPolicy is the Kafka init container security policies
type ConnectorPluginPodSecurityPolicy struct {
DatabasePolicyName string `json:"databasePolicyName"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// KafkaConnectorVersionList is a list of KafkaConnectorVersion
678 changes: 545 additions & 133 deletions vendor/kubedb.dev/apimachinery/apis/catalog/v1alpha1/openapi_generated.go

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright AppsCode Inc. and Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"fmt"

"kubedb.dev/apimachinery/apis"
"kubedb.dev/apimachinery/apis/catalog"
"kubedb.dev/apimachinery/crds"

"kmodules.xyz/client-go/apiextensions"
)

func (_ RabbitMQVersion) CustomResourceDefinition() *apiextensions.CustomResourceDefinition {
return crds.MustCustomResourceDefinition(SchemeGroupVersion.WithResource(ResourcePluralRabbitMQVersion))
}

var _ apis.ResourceInfo = &RabbitMQVersion{}

func (r RabbitMQVersion) ResourceFQN() string {
return fmt.Sprintf("%s.%s", ResourcePluralRabbitMQVersion, catalog.GroupName)
}

func (r RabbitMQVersion) ResourceShortCode() string {
return ResourceCodeRabbitMQVersion
}

func (r RabbitMQVersion) ResourceKind() string {
return ResourceKindRabbitMQVersion
}

func (r RabbitMQVersion) ResourceSingular() string {
return ResourceSingularRabbitMQVersion
}

func (r RabbitMQVersion) ResourcePlural() string {
return ResourcePluralRabbitMQVersion
}

func (r RabbitMQVersion) ValidateSpecs() error {
if r.Spec.Version == "" ||
r.Spec.DB.Image == "" {
return fmt.Errorf(`atleast one of the following specs is not set for RabbitMQVersion "%v":
spec.version,
spec.db.image`, r.Name)
}
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
Copyright 2023.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
ResourceCodeRabbitMQVersion = "rmversion"
ResourceKindRabbitMQVersion = "RabbitMQVersion"
ResourceSingularRabbitMQVersion = "rabbitmqversion"
ResourcePluralRabbitMQVersion = "rabbitmqversions"
)

// RabbitMQVersion defines a RabbitMQ database version.

// +genclient
// +genclient:nonNamespaced
// +genclient:skipVerbs=updateStatus
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// +kubebuilder:object:root=true
// +kubebuilder:resource:path=rabbitmqversions,singular=rabbitmqversion,scope=Cluster,shortName=rmversion,categories={datastore,kubedb,appscode}
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.version"
// +kubebuilder:printcolumn:name="DB_IMAGE",type="string",JSONPath=".spec.db.image"
// +kubebuilder:printcolumn:name="Deprecated",type="boolean",JSONPath=".spec.deprecated"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
type RabbitMQVersion struct {
metav1.TypeMeta `json:",inline,omitempty"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec RabbitMQVersionSpec `json:"spec,omitempty"`
}

// RabbitMQVersionSpec is the spec for RabbitMQ version
type RabbitMQVersionSpec struct {
// Version
Version string `json:"version"`
// Database Image
DB RabbitMQVersionDatabase `json:"db"`
// Database Image
InitContainer RabbitMQInitContainer `json:"initContainer"`
// Deprecated versions usable but regarded as obsolete and best avoided, typically due to having been superseded.
// +optional
Deprecated bool `json:"deprecated,omitempty"`
// SecurityContext is for the additional config for the DB container
// +optional
SecurityContext SecurityContext `json:"securityContext"`
}

// RabbitMQVersionDatabase is the RabbitMQ Database image
type RabbitMQVersionDatabase struct {
Image string `json:"image"`
}

// RabbitMQInitContainer is the RabbitMQ init Container image
type RabbitMQInitContainer struct {
Image string `json:"image"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// RabbitMQVersionList is a list of RabbitmqVersions
type RabbitMQVersionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
// Items is a list of RedisVersion CRD objects
Items []RabbitMQVersion `json:"items,omitempty"`
}
Original file line number Diff line number Diff line change
@@ -60,6 +60,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&ElasticsearchVersionList{},
&EtcdVersion{},
&EtcdVersionList{},
&FerretDBVersion{},
&FerretDBVersionList{},
&KafkaVersion{},
&KafkaVersionList{},
&KafkaConnectorVersion{},
@@ -80,10 +82,14 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&PostgresVersionList{},
&ProxySQLVersion{},
&ProxySQLVersionList{},
&RabbitMQVersion{},
&RabbitMQVersionList{},
&RedisVersion{},
&RedisVersionList{},
&SinglestoreVersion{},
&SinglestoreVersionList{},
&SolrVersion{},
&SolrVersionList{},
&ZooKeeperVersion{},
&ZooKeeperVersionList{},
)
Original file line number Diff line number Diff line change
@@ -59,8 +59,8 @@ func (s SinglestoreVersion) ValidateSpecs() error {
s.Spec.Standalone.Image == "" {
return fmt.Errorf(`atleast one of the following specs is not set for singlestoreVersion "%v":
spec.version,
spec.db.image,
spec.exporter.image.`, s.Name)
spec.coordinator.image,
spec.standalone.image.`, s.Name)
}
return nil
}
Original file line number Diff line number Diff line change
@@ -61,9 +61,6 @@ type SinglestoreVersionSpec struct {
// Deprecated versions usable but regarded as obsolete and best avoided, typically due to having been superseded.
// +optional
Deprecated bool `json:"deprecated,omitempty"`
// PSP names
// +optional
PodSecurityPolicies SinglestoreVersionPodSecurityPolicy `json:"podSecurityPolicies"`
// Stash defines backup and restore task definitions.
// +optional
Stash appcat.StashAddonSpec `json:"stash,omitempty"`
@@ -100,11 +97,6 @@ type SinglestoreInitContainer struct {
Image string `json:"image"`
}

// SinglestoreVersionPodSecurityPolicy is the Singlestore pod security policies
type SinglestoreVersionPodSecurityPolicy struct {
DatabasePolicyName string `json:"databasePolicyName"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// SinglestoreVersionList contains a list of SinglestoreVersions
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright AppsCode Inc. and Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"fmt"

"kubedb.dev/apimachinery/apis"
"kubedb.dev/apimachinery/apis/catalog"
"kubedb.dev/apimachinery/crds"

"kmodules.xyz/client-go/apiextensions"
)

func (s SolrVersion) CustomResourceDefinition() *apiextensions.CustomResourceDefinition {
return crds.MustCustomResourceDefinition(SchemeGroupVersion.WithResource(ResourcePluralSolrVersion))
}

var _ apis.ResourceInfo = &SolrVersion{}

func (s SolrVersion) ResourceFQN() string {
return fmt.Sprintf("%s.%s", ResourcePluralSolrVersion, catalog.GroupName)
}

func (s SolrVersion) ResourceShortCode() string {
return ResourceCodeSolrVersion
}

func (s SolrVersion) ResourceKind() string {
return ResourceKindSolrVersion
}

func (s SolrVersion) ResourceSingular() string {
return ResourceSingularSolrVersion
}

func (s SolrVersion) ResourcePlural() string {
return ResourcePluralSolrVersion
}

func (s SolrVersion) ValidateSpecs() error {
if s.Spec.Version == "" ||
s.Spec.DB.Image == "" {
return fmt.Errorf(`atleast one of the following specs is not set for solrVersion "%v":
spec.version,
spec.db.image`, s.Name)
}
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
Copyright 2023.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

const (
ResourceCodeSolrVersion = "slversion"
ResourceKindSolrVersion = "SolrVersion"
ResourceSingularSolrVersion = "Solrversion"
ResourcePluralSolrVersion = "solrversions"
)

// SolrVersion defines a Solr database version.

// +genclient
// +genclient:nonNamespaced
// +genclient:skipVerbs=updateStatus
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// +kubebuilder:object:root=true
// +kubebuilder:resource:path=solrversions,singular=solrversion,scope=Cluster,shortName=slversion,categories={datastore,kubedb,appscode}
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.version"
// +kubebuilder:printcolumn:name="DB_IMAGE",type="string",JSONPath=".spec.db.image"
// +kubebuilder:printcolumn:name="Deprecated",type="boolean",JSONPath=".spec.deprecated"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
type SolrVersion struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec SolrVersionSpec `json:"spec,omitempty"`
}

// SolrVersionSpec defines the desired state of SolrVersion
type SolrVersionSpec struct {
// Version
Version string `json:"version"`
// Database Image
DB SolrVersionDatabase `json:"db"`
// Database Image
InitContainer SolrInitContainer `json:"initContainer"`
// Deprecated versions usable but regarded as obsolete and best avoided, typically due to having been superseded.
// +optional
Deprecated bool `json:"deprecated,omitempty"`
// SecurityContext is for the additional security information for the Solr container
// +optional
SecurityContext SecurityContext `json:"securityContext"`
}

// SolrVersionDatabase is the Solr Database image
type SolrVersionDatabase struct {
Image string `json:"image"`
}

// SolrInitContainer is the Solr init Container image
type SolrInitContainer struct {
Image string `json:"image"`
}

// SolrVersionList contains a list of SolrVersion

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type SolrVersionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []SolrVersion `json:"items"`
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ const (
OS_PASSWORD_KEY = "opensearch.password"

ElasticsearchDashboardPortServer = "server"
ElasticsearchDashboardConfigMergeCommand = "/usr/local/bin/elasticsearch-config-merger.sh"
ElasticsearchDashboardConfigMergeCommand = "/usr/local/bin/dashboard-config-merger.sh"

KibanaConfigDir = "/usr/share/kibana/config"
KibanaTempConfigDir = "/kibana/temp-config"
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ import (
"path/filepath"
"strings"

"kubedb.dev/apimachinery/apis"
catalog "kubedb.dev/apimachinery/apis/catalog/v1alpha1"
"kubedb.dev/apimachinery/apis/kafka"
api "kubedb.dev/apimachinery/apis/kubedb/v1alpha2"
@@ -246,6 +247,11 @@ func (k *ConnectCluster) SetDefaults() {
k.setDefaultContainerSecurityContext(&kfVersion, &k.Spec.PodTemplate)
k.setDefaultInitContainerSecurityContext(&k.Spec.PodTemplate)

dbContainer := coreutil.GetContainerByName(k.Spec.PodTemplate.Spec.Containers, ConnectClusterContainerName)
if dbContainer != nil {
apis.SetDefaultResourceLimits(&dbContainer.Resources, api.DefaultResources)
}

k.Spec.Monitor.SetDefaults()
if k.Spec.Monitor != nil && k.Spec.Monitor.Prometheus != nil && k.Spec.Monitor.Prometheus.Exporter.SecurityContext.RunAsUser == nil {
k.Spec.Monitor.Prometheus.Exporter.SecurityContext.RunAsUser = kfVersion.Spec.SecurityContext.RunAsUser
Original file line number Diff line number Diff line change
@@ -173,23 +173,13 @@ func validateEnvVars(connect *ConnectCluster) error {
return nil
}

var availableVersions = []string{
"3.3.0",
"3.3.2",
"3.4.0",
"3.4.1",
"3.5.1",
"3.6.0",
}

func validateVersion(connect *ConnectCluster) error {
version := connect.Spec.Version
for _, v := range availableVersions {
if v == version {
return nil
}
kccVersion := &catalog.KafkaVersion{}
err := DefaultClient.Get(context.TODO(), types.NamespacedName{Name: connect.Spec.Version}, kccVersion)
if err != nil {
return errors.New("version not supported")
}
return errors.New("version not supported")
return nil
}

var reservedVolumes = []string{
Original file line number Diff line number Diff line change
@@ -17,11 +17,48 @@ limitations under the License.
package v1alpha1

import (
"fmt"

"kubedb.dev/apimachinery/apis/kafka"
"kubedb.dev/apimachinery/crds"

meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"kmodules.xyz/client-go/apiextensions"
)

func (_ *Connector) CustomResourceDefinition() *apiextensions.CustomResourceDefinition {
return crds.MustCustomResourceDefinition(SchemeGroupVersion.WithResource(ResourcePluralConnector))
}

func (k *Connector) AsOwner() *meta.OwnerReference {
return meta.NewControllerRef(k, SchemeGroupVersion.WithKind(ResourceKindConnector))
}

func (k *Connector) ResourceShortCode() string {
return ResourceCodeConnector
}

func (k *Connector) ResourceKind() string {
return ResourceKindConnector
}

func (k *Connector) ResourceSingular() string {
return ResourceSingularConnector
}

func (k *Connector) ResourcePlural() string {
return ResourcePluralConnector
}

func (k *Connector) ResourceFQN() string {
return fmt.Sprintf("%s.%s", k.ResourcePlural(), kafka.GroupName)
}

// Owner returns owner reference to resources
func (k *Connector) Owner() *meta.OwnerReference {
return meta.NewControllerRef(k, SchemeGroupVersion.WithKind(k.ResourceKind()))
}

func (k *Connector) OffshootName() string {
return k.Name
}
144 changes: 130 additions & 14 deletions vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/constants.go
Original file line number Diff line number Diff line change
@@ -322,20 +322,24 @@ const (
MariaDBDataVolumeName = "data"

// =========================== SingleStore Constants ============================
SinglestoreDatabasePortName = "db"
SinglestorePrimaryServicePortName = "primary"
SinglestoreDatabasePort = 3306
SinglestoreRootUserName = "ROOT_USERNAME"
SinglestoreRootPassword = "ROOT_PASSWORD"
SinglestoreRootUser = "root"
DatabasePodMaster = "Master"
DatabasePodAggregator = "Aggregator"
DatabasePodLeaf = "Leaf"
StatefulSetTypeMasterAggregator = "master-aggregator"
StatefulSetTypeLeaf = "leaf"
SinglestoreCoordinatorContainerName = "singlestore-coordinator"
SinglestoreContainerName = "singlestore"
SinglestoreInitContainerName = "singlestore-init"
SinglestoreDatabasePortName = "db"
SinglestorePrimaryServicePortName = "primary"
SinglestoreDatabasePort = 3306
SinglestoreRootUserName = "ROOT_USERNAME"
SinglestoreRootPassword = "ROOT_PASSWORD"
SinglestoreRootUser = "root"
DatabasePodMaster = "Master"
DatabasePodAggregator = "Aggregator"
DatabasePodLeaf = "Leaf"
StatefulSetTypeMasterAggregator = "master-aggregator"
StatefulSetTypeLeaf = "leaf"
SinglestoreDatabaseHealth = "singlestore_health"
SinglestoreTableHealth = "singlestore_health_table"

SinglestoreCoordinatorContainerName = "singlestore-coordinator"
SinglestoreContainerName = "singlestore"
SinglestoreInitContainerName = "singlestore-init"

SinglestoreVolumeNameUserInitScript = "initial-script"
SinglestoreVolumeMountPathUserInitScript = "/docker-entrypoint-initdb.d"
SinglestoreVolumeNameCustomConfig = "custom-config"
@@ -730,6 +734,74 @@ const (
KafkaAdminTopicConfigProvider = "com.linkedin.kafka.cruisecontrol.config.KafkaAdminTopicConfigProvider"
KafkaCCMetricReporter = "com.linkedin.kafka.cruisecontrol.metricsreporter.CruiseControlMetricsReporter"
KafkaJMXMetricReporter = "org.apache.kafka.common.metrics.JmxReporter"

// =========================== Solr Constants ============================
ResourceCodeSolr = "sl"
ResourceKindSolr = "Solr"
ResourceSingularSolr = "solr"
ResourcePluralSolr = "solrs"
SolrPortName = "http"
SolrRestPort = 8983
SolrSecretKey = "solr.xml"
SolrContainerName = "solr"
SolrInitContainerName = "init-solr"
SolrAdmin = "admin"
SecurityJSON = "security.json"

SolrVolumeDefaultConfig = "default-config"
SolrVolumeCustomConfig = "custom-config"
SolrVolumeAuthConfig = "auth-config"
SolrVolumeData = "data"
SolrVolumeConfig = "slconfig"

DistLibs = "/opt/solr/dist"
ContribLibs = "/opt/solr/contrib/%s/lib"
SysPropLibPlaceholder = "${solr.sharedLib:}"
SolrHomeDir = "/var/solr"
SolrDataDir = "/var/solr/data"
SolrTempConfigDir = "/temp-config"
SolrCustomConfigDir = "/custom-config"
SolrSecurityConfigDir = "/var/security"

SolrCloudHostKey = "host"
SolrCloudHostValue = ""
SolrCloudHostPortKey = "hostPort"
SolrCloudHostPortValue = 80
SolrCloudHostContextKey = "hostContext"
SolrCloudHostContextValue = "solr"
SolrCloudGenericCoreNodeNamesKey = "genericCoreNodeNames"
SolrCloudGenericCoreNodeNamesValue = true
SolrCloudZKClientTimeoutKey = "zkClientTimeout"
SolrCloudZKClientTimeoutValue = 30000
SolrCloudDistribUpdateSoTimeoutKey = "distribUpdateSoTimeout"
SolrCloudDistribUpdateSoTimeoutValue = 600000
SolrCloudDistribUpdateConnTimeoutKey = "distribUpdateConnTimeout"
SolrCloudDistribUpdateConnTimeoutValue = 60000
SolrCloudZKCredentialProviderKey = "zkCredentialsProvider"
SolrCloudZKCredentialProviderValue = "org.apache.solr.common.cloud.DefaultZkCredentialsProvider"
SolrCloudZKAclProviderKey = "zkACLProvider"
SolrCloudZKAclProviderValue = "org.apache.solr.common.cloud.DefaultZkACLProvider"

ShardHandlerFactorySocketTimeoutKey = "socketTimeout"
ShardHandlerFactorySocketTimeoutValue = 600000
ShardHandlerFactoryConnTimeoutKey = "connTimeout"
ShardHandlerFactoryConnTimeoutValue = 60000

SolrKeysMaxBooleanClausesKey = "maxBooleanClauses"
SolrKeysMaxBooleanClausesValue = "solr.max.booleanClauses"
SolrKeysSharedLibKey = "sharedLib"
SolrKeysShardLibValue = "solr.sharedLib"
SolrKeysHostPortKey = "hostPort"
SolrKeysHostPortValue = "solr.port.advertise"
SolrKeysAllowPathsKey = "allowPaths"
SolrKeysAllowPathsValue = "solr.allowPaths"

SolrConfMaxBooleanClausesKey = "maxBooleanClauses"
SolrConfMaxBooleanClausesValue = 1024
SolrConfAllowPathsKey = "allowPaths"
SolrConfAllowPathsValue = ""
SolrConfSolrCloudKey = "solrcloud"
SolrConfShardHandlerFactoryKey = "shardHandlerFactory"
)

// =========================== Druid Constants ============================
@@ -932,6 +1004,50 @@ const (
DruidDeepStorageHDFS DruidDeepStorageType = "hdfs"
)

const (
RabbitMQAMQPPort = 5672
RabbitMQPeerDiscoveryPort = 4369
RabbitMQManagementUIPort = 15672
RabbitMQInterNodePort = 25672

RabbitMQVolumeData = "data"
RabbitMQVolumeConfig = "rabbitmqconfig"
RabbitMQVolumeTempConfig = "temp-config"
RabbitMQVolumeCustomConfig = "custom-config"

RabbitMQDataDir = "/var/lib/rabbitmq/mnesia"
RabbitMQPluginsDir = "/etc/rabbitmq/"
RabbitMQCertDir = "/var/private/ssl"
RabbitMQConfigDir = "/config/"
RabbitMQTempConfigDir = "/tmp/config/"
)

// =========================== FerretDB Constants ============================
const (

// envs
EnvFerretDBUser = "FERRETDB_PG_USER"
EnvFerretDBPassword = "FERRETDB_PG_PASSWORD"
EnvFerretDBHandler = "FERRETDB_HANDLER"
EnvFerretDBPgURL = "FERRETDB_POSTGRESQL_URL"
EnvFerretDBTLSPort = "FERRETDB_LISTEN_TLS"
EnvFerretDBCAPath = "FERRETDB_LISTEN_TLS_CA_FILE"
EnvFerretDBCertPath = "FERRETDB_LISTEN_TLS_CERT_FILE"
EnvFerretDBKeyPath = "FERRETDB_LISTEN_TLS_KEY_FILE"

FerretDBContainerName = "ferretdb"
FerretDBMainImage = "ghcr.io/ferretdb/ferretdb"
FerretDBUser = "postgres"

FerretDBServerPath = "/etc/certs/server"

FerretDBDefaultPort = 27017
FerretDBMetricsPort = 8080
FerretDBTLSPort = 27018

FerretDBMetricsPath = "/debug/metrics"
)

// Resource kind related constants
const (
ResourceKindStatefulSet = "StatefulSet"
Original file line number Diff line number Diff line change
@@ -25,19 +25,26 @@ import (
"kubedb.dev/apimachinery/apis"
catalog "kubedb.dev/apimachinery/apis/catalog/v1alpha1"
"kubedb.dev/apimachinery/apis/kubedb"
"kubedb.dev/apimachinery/crds"

"gomodules.xyz/pointer"
v1 "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
appslister "k8s.io/client-go/listers/apps/v1"
"k8s.io/klog/v2"
"kmodules.xyz/client-go/apiextensions"
coreutil "kmodules.xyz/client-go/core/v1"
meta_util "kmodules.xyz/client-go/meta"
"kmodules.xyz/client-go/policy/secomp"
appcat "kmodules.xyz/custom-resources/apis/appcatalog/v1alpha1"
ofst "kmodules.xyz/offshoot-api/api/v2"
)

func (d *Druid) CustomResourceDefinition() *apiextensions.CustomResourceDefinition {
return crds.MustCustomResourceDefinition(SchemeGroupVersion.WithResource(ResourcePluralDruid))
}

func (d *Druid) Owner() *meta.OwnerReference {
return meta.NewControllerRef(d, SchemeGroupVersion.WithKind(d.ResourceKind()))
}
@@ -264,6 +271,15 @@ func (d *Druid) OffshootSelectors(extraSelectors ...map[string]string) map[strin
return meta_util.OverwriteKeys(selector, extraSelectors...)
}

func (d Druid) OffshootLabels() map[string]string {
return d.offshootLabels(d.OffshootSelectors(), nil)
}

func (e Druid) offshootLabels(selector, override map[string]string) map[string]string {
selector[meta_util.ComponentLabelKey] = ComponentDatabase
return meta_util.FilterKeys(kubedb.GroupName, selector, meta_util.OverwriteKeys(nil, e.Labels, override))
}

func (d *Druid) SetDefaults() {
if d.Spec.TerminationPolicy == "" {
d.Spec.TerminationPolicy = TerminationPolicyDelete
@@ -401,26 +417,23 @@ func (d *Druid) setDefaultContainerSecurityContext(druidVersion *catalog.DruidVe
}

func (d *Druid) assignDefaultContainerSecurityContext(druidVersion *catalog.DruidVersion, sc *v1.SecurityContext) {
if sc.AllowPrivilegeEscalation == nil {
sc.AllowPrivilegeEscalation = pointer.BoolP(false)
}
if sc.Capabilities == nil {
sc.Capabilities = &v1.Capabilities{
Drop: []v1.Capability{"ALL"},
}
}
if sc.RunAsNonRoot == nil {
sc.RunAsNonRoot = pointer.BoolP(true)
}
if sc.RunAsUser == nil {
sc.RunAsUser = druidVersion.Spec.SecurityContext.RunAsUser
}
if sc.RunAsGroup == nil {
sc.RunAsGroup = druidVersion.Spec.SecurityContext.RunAsGroup
}
if sc.SeccompProfile == nil {
sc.SeccompProfile = secomp.DefaultSeccompProfile()
}
//if sc.AllowPrivilegeEscalation == nil {
// sc.AllowPrivilegeEscalation = pointer.BoolP(false)
//}
//if sc.Capabilities == nil {
// sc.Capabilities = &v1.Capabilities{
// Drop: []v1.Capability{"ALL"},
// }
//}
//if sc.RunAsNonRoot == nil {
// sc.RunAsNonRoot = pointer.BoolP(true)
//}
//if sc.RunAsUser == nil {
// sc.RunAsUser = druidVersion.Spec.SecurityContext.RunAsUser
//}
//if sc.SeccompProfile == nil {
// sc.SeccompProfile = secomp.DefaultSeccompProfile()
//}
}

func (d *Druid) GetPersistentSecrets() []string {
@@ -434,3 +447,18 @@ func (d *Druid) GetPersistentSecrets() []string {
}
return secrets
}

func (d *Druid) ReplicasAreReady(lister appslister.StatefulSetLister) (bool, string, error) {
// Desire number of statefulSets
expectedItems := 1
if d.Spec.Topology != nil {
expectedItems = 4
}
if d.Spec.Topology.Routers != nil {
expectedItems++
}
if d.Spec.Topology.Overlords != nil {
expectedItems++
}
return checkReplicas(lister.StatefulSets(d.Namespace), labels.SelectorFromSet(d.OffshootLabels()), expectedItems)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
/*
Copyright AppsCode Inc. and Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
"fmt"

"kubedb.dev/apimachinery/apis"
"kubedb.dev/apimachinery/apis/kubedb"
"kubedb.dev/apimachinery/crds"

"github.com/fatih/structs"
v1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"gomodules.xyz/pointer"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
appslister "k8s.io/client-go/listers/apps/v1"
kmapi "kmodules.xyz/client-go/api/v1"
"kmodules.xyz/client-go/apiextensions"
coreutil "kmodules.xyz/client-go/core/v1"
meta_util "kmodules.xyz/client-go/meta"
appcat "kmodules.xyz/custom-resources/apis/appcatalog/v1alpha1"
mona "kmodules.xyz/monitoring-agent-api/api/v1"
ofst "kmodules.xyz/offshoot-api/api/v2"
)

func (f *FerretDB) CustomResourceDefinition() *apiextensions.CustomResourceDefinition {
return crds.MustCustomResourceDefinition(SchemeGroupVersion.WithResource(ResourcePluralFerretDB))
}

func (f *FerretDB) ResourcePlural() string {
return ResourcePluralFerretDB
}

func (f *FerretDB) ResourceFQN() string {
return fmt.Sprintf("%s.%s", f.ResourcePlural(), "kubedb.com")
}

func (f *FerretDB) ServiceName() string {
return f.Name
}

type FerretDBApp struct {
*FerretDB
}

func (f FerretDBApp) Name() string {
return f.FerretDB.Name
}

func (f FerretDBApp) Type() appcat.AppType {
return appcat.AppType(fmt.Sprintf("%s/%s", kubedb.GroupName, ResourceSingularFerretDB))
}

func (f *FerretDB) AppBindingMeta() appcat.AppBindingMeta {
return &FerretDBApp{f}
}

func (f *FerretDB) OffshootName() string {
return f.Name
}

func (f *FerretDB) OffshootSelectors() map[string]string {
selector := map[string]string{
meta_util.NameLabelKey: f.ResourceFQN(),
meta_util.InstanceLabelKey: f.Name,
meta_util.ManagedByLabelKey: "kubedb.com",
}
return selector
}

func (f *FerretDB) PodControllerLabels(podControllerLabels map[string]string, extraLabels ...map[string]string) map[string]string {
return f.offshootLabels(meta_util.OverwriteKeys(f.OffshootSelectors(), extraLabels...), podControllerLabels)
}

func (f *FerretDB) OffshootLabels() map[string]string {
return f.offshootLabels(f.OffshootSelectors(), nil)
}

func (f *FerretDB) offshootLabels(selector, override map[string]string) map[string]string {
selector[meta_util.ComponentLabelKey] = ComponentDatabase
return meta_util.FilterKeys("kubedb.com", selector, meta_util.OverwriteKeys(nil, f.Labels, override))
}

func (f *FerretDB) GetAuthSecretName() string {
if f.Spec.AuthSecret != nil && f.Spec.AuthSecret.Name != "" {
return f.Spec.AuthSecret.Name
}
return meta_util.NameWithSuffix(f.PgBackendName(), "auth")
}

// AsOwner returns owner reference to resources
func (f *FerretDB) AsOwner() *meta.OwnerReference {
return meta.NewControllerRef(f, SchemeGroupVersion.WithKind(f.ResourceKind()))
}

func (f *FerretDB) ResourceKind() string {
return ResourceKindFerretDB
}

func (f *FerretDB) PgBackendName() string {
return f.OffshootName() + "-pg-backend"
}

func (f *FerretDB) PodLabels(podTemplateLabels map[string]string, extraLabels ...map[string]string) map[string]string {
return f.offshootLabels(meta_util.OverwriteKeys(f.OffshootSelectors(), extraLabels...), podTemplateLabels)
}

func (f *FerretDB) CertificateName(alias FerretDBCertificateAlias) string {
return meta_util.NameWithSuffix(f.Name, fmt.Sprintf("%s-cert", string(alias)))
}

func (f *FerretDB) GetCertSecretName(alias FerretDBCertificateAlias) string {
name, ok := kmapi.GetCertificateSecretName(f.Spec.TLS.Certificates, string(alias))
if ok {
return name
}

return f.CertificateName(alias)
}

func (f *FerretDB) SetHealthCheckerDefaults() {
if f.Spec.HealthChecker.PeriodSeconds == nil {
f.Spec.HealthChecker.PeriodSeconds = pointer.Int32P(10)
}
if f.Spec.HealthChecker.TimeoutSeconds == nil {
f.Spec.HealthChecker.TimeoutSeconds = pointer.Int32P(10)
}
if f.Spec.HealthChecker.FailureThreshold == nil {
f.Spec.HealthChecker.FailureThreshold = pointer.Int32P(2)
}
}

func (f *FerretDB) SetDefaults() {
if f == nil {
return
}
if f.Spec.StorageType == "" {
f.Spec.StorageType = StorageTypeDurable
}

if f.Spec.TerminationPolicy == "" {
f.Spec.TerminationPolicy = TerminationPolicyDelete
}

if f.Spec.SSLMode == "" {
f.Spec.SSLMode = SSLModeDisabled
}

if f.Spec.Replicas == nil {
f.Spec.Replicas = pointer.Int32P(1)
}

if f.Spec.PodTemplate == nil {
f.Spec.PodTemplate = &ofst.PodTemplateSpec{}
}

dbContainer := coreutil.GetContainerByName(f.Spec.PodTemplate.Spec.Containers, FerretDBContainerName)
if dbContainer == nil {
dbContainer = &core.Container{
Name: FerretDBContainerName,
}
f.Spec.PodTemplate.Spec.Containers = append(f.Spec.PodTemplate.Spec.Containers, *dbContainer)
}
if structs.IsZero(dbContainer.Resources) {
apis.SetDefaultResourceLimits(&dbContainer.Resources, DefaultResources)
}

if f.Spec.Backend.LinkedDB == "" {
if f.Spec.Backend.ExternallyManaged {
f.Spec.Backend.LinkedDB = "postgres"
} else {
f.Spec.Backend.LinkedDB = "ferretdb"
}
}
if f.Spec.Monitor != nil && f.Spec.Monitor.Prometheus.Exporter.Port == 0 {
// 56790 is default port for Prometheus operator.
f.Spec.Monitor.Prometheus.Exporter.Port = 56790
}
defaultVersion := "13.13"
if !f.Spec.Backend.ExternallyManaged && f.Spec.Backend.Postgres == nil {
f.Spec.Backend.Postgres = &PostgresRef{
Version: &defaultVersion,
}
}
f.SetTLSDefaults()
f.SetHealthCheckerDefaults()
}

func (f *FerretDB) SetTLSDefaults() {
if f.Spec.TLS == nil || f.Spec.TLS.IssuerRef == nil {
return
}

defaultServerOrg := []string{KubeDBOrganization}
defaultServerOrgUnit := []string{string(FerretDBServerCert)}

_, cert := kmapi.GetCertificate(f.Spec.TLS.Certificates, string(FerretDBServerCert))
if cert != nil && cert.Subject != nil {
if cert.Subject.Organizations != nil {
defaultServerOrg = cert.Subject.Organizations
}
if cert.Subject.OrganizationalUnits != nil {
defaultServerOrgUnit = cert.Subject.OrganizationalUnits
}
}
f.Spec.TLS.Certificates = kmapi.SetMissingSpecForCertificate(f.Spec.TLS.Certificates, kmapi.CertificateSpec{
Alias: string(FerretDBServerCert),
SecretName: f.GetCertSecretName(FerretDBServerCert),
Subject: &kmapi.X509Subject{
Organizations: defaultServerOrg,
OrganizationalUnits: defaultServerOrgUnit,
},
})

// Client-cert
defaultClientOrg := []string{KubeDBOrganization}
defaultClientOrgUnit := []string{string(FerretDBClientCert)}
_, cert = kmapi.GetCertificate(f.Spec.TLS.Certificates, string(FerretDBClientCert))
if cert != nil && cert.Subject != nil {
if cert.Subject.Organizations != nil {
defaultClientOrg = cert.Subject.Organizations
}
if cert.Subject.OrganizationalUnits != nil {
defaultClientOrgUnit = cert.Subject.OrganizationalUnits
}
}
f.Spec.TLS.Certificates = kmapi.SetMissingSpecForCertificate(f.Spec.TLS.Certificates, kmapi.CertificateSpec{
Alias: string(FerretDBClientCert),
SecretName: f.GetCertSecretName(FerretDBClientCert),
Subject: &kmapi.X509Subject{
Organizations: defaultClientOrg,
OrganizationalUnits: defaultClientOrgUnit,
},
})
}

type FerretDBStatsService struct {
*FerretDB
}

func (fs FerretDBStatsService) ServiceMonitorName() string {
return fs.OffshootName() + "-stats"
}

func (fs FerretDBStatsService) ServiceMonitorAdditionalLabels() map[string]string {
return fs.OffshootLabels()
}

func (fs FerretDBStatsService) Path() string {
return FerretDBMetricsPath
}

func (fs FerretDBStatsService) Scheme() string {
return ""
}

func (fs FerretDBStatsService) TLSConfig() *v1.TLSConfig {
return nil
}

func (fs FerretDBStatsService) ServiceName() string {
return fs.OffshootName() + "-stats"
}

func (f *FerretDB) StatsService() mona.StatsAccessor {
return &FerretDBStatsService{f}
}

func (f *FerretDB) ServiceLabels(alias ServiceAlias, extraLabels ...map[string]string) map[string]string {
svcTemplate := GetServiceTemplate(f.Spec.ServiceTemplates, alias)
return f.offshootLabels(meta_util.OverwriteKeys(f.OffshootSelectors(), extraLabels...), svcTemplate.Labels)
}

func (f *FerretDB) StatsServiceLabels() map[string]string {
return f.ServiceLabels(StatsServiceAlias, map[string]string{LabelRole: RoleStats})
}

func (f *FerretDB) ReplicasAreReady(lister appslister.StatefulSetLister) (bool, string, error) {
// Desire number of statefulSets
expectedItems := 1
return checkReplicas(lister.StatefulSets(f.Namespace), labels.SelectorFromSet(f.OffshootLabels()), expectedItems)
}
164 changes: 164 additions & 0 deletions vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/ferretdb_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
Copyright 2023.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
core "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kmapi "kmodules.xyz/client-go/api/v1"
mona "kmodules.xyz/monitoring-agent-api/api/v1"
ofst "kmodules.xyz/offshoot-api/api/v2"
)

const (
ResourceCodeFerretDB = "fr"
ResourceKindFerretDB = "FerretDB"
ResourceSingularFerretDB = "ferretdb"
ResourcePluralFerretDB = "ferretdbs"
)

// +genclient
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=fr,scope=Namespaced
// +kubebuilder:printcolumn:name="Namespace",type="string",JSONPath=".metadata.namespace"
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.version"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.phase"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
type FerretDB struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec FerretDBSpec `json:"spec,omitempty"`
Status FerretDBStatus `json:"status,omitempty"`
}

type FerretDBSpec struct {
// Version of FerretDB to be deployed.
Version string `json:"version"`

// Number of instances to deploy for a FerretDB database.
Replicas *int32 `json:"replicas,omitempty"`

// Database authentication secret
// +optional
AuthSecret *SecretReference `json:"authSecret,omitempty"`

// See more options: https://docs.ferretdb.io/security/tls-connections/
// +optional
SSLMode SSLMode `json:"sslMode,omitempty"`

// PodTemplate is an optional configuration for pods used to expose database
// +optional
PodTemplate *ofst.PodTemplateSpec `json:"podTemplate,omitempty"`

// ServiceTemplates is an optional configuration for services used to expose database
// +optional
ServiceTemplates []NamedServiceTemplateSpec `json:"serviceTemplates,omitempty"`

// TLS contains tls configurations for client and server.
// +optional
TLS *kmapi.TLSConfig `json:"tls,omitempty"`

// Indicates that the database is halted and all offshoot Kubernetes resources except PVCs are deleted.
// +optional
Halted bool `json:"halted,omitempty"`

// StorageType can be durable (default) or ephemeral for KubeDB Backend
// +optional
StorageType StorageType `json:"storageType,omitempty"`

// Storage to specify how storage shall be used for KubeDB Backend.
Storage *core.PersistentVolumeClaimSpec `json:"storage,omitempty"`

// TerminationPolicy controls the delete operation for database and KubeDB Backend
// +optional
TerminationPolicy TerminationPolicy `json:"terminationPolicy,omitempty"`

// HealthChecker defines attributes of the health checker
// +optional
// +kubebuilder:default={periodSeconds: 10, timeoutSeconds: 10, failureThreshold: 1}
HealthChecker kmapi.HealthCheckSpec `json:"healthChecker"`

// Monitor is used monitor database instance and KubeDB Backend
// +optional
Monitor *mona.AgentSpec `json:"monitor,omitempty"`

Backend *Backend `json:"backend"`
}

type FerretDBStatus struct {
// Specifies the current phase of the database
// +optional
Phase DatabasePhase `json:"phase,omitempty"`
// observedGeneration is the most recent generation observed for this resource. It corresponds to the
// resource's generation, which is updated on mutation by the API Server.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Conditions applied to the database, such as approval or denial.
// +optional
Conditions []kmapi.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:validation:Enum=server;client;
type FerretDBCertificateAlias string

const (
FerretDBServerCert FerretDBCertificateAlias = "server"
FerretDBClientCert FerretDBCertificateAlias = "client"
)

type Backend struct {
// +optional
Postgres *PostgresRef `json:"postgres,omitempty"`
// A DB inside backend specifically made for ferretdb
// +optional
LinkedDB string `json:"linkedDB,omitempty"`
ExternallyManaged bool `json:"externallyManaged,omitempty"`
}

type PostgresRef struct {
// Postgres URL address
// +optional
URL *string `json:"url,omitempty"`
// Service information for Postgres
// +optional
Service *PostgresServiceRef `json:"service,omitempty"`
// Which versions pg will be used as backend of ferretdb
// +optional
Version *string `json:"version"`
}

type PostgresServiceRef struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
// PgPort is used because the service referred to the
// pg pod can have any port between 1 and 65535, inclusive
// but targetPort is fixed to 5432
PgPort string `json:"pgPort"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type FerretDBList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []FerretDB `json:"items"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/*
Copyright 2023.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
"context"
"errors"
"fmt"

"kubedb.dev/apimachinery/apis/catalog/v1alpha1"

"gomodules.xyz/x/arrays"
core "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// log is for logging in this package.
var ferretdblog = logf.Log.WithName("ferretdb-resource")

func (f *FerretDB) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(f).
Complete()
}

//+kubebuilder:webhook:path=/mutate-kubedb-com-v1alpha2-ferretdb,mutating=true,failurePolicy=fail,sideEffects=None,groups=kubedb.com,resources=ferretdbs,verbs=create;update,versions=v1alpha2,name=mferretdb.kb.io,admissionReviewVersions=v1

var _ webhook.Defaulter = &FerretDB{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (f *FerretDB) Default() {
if f == nil {
return
}
ferretdblog.Info("default", "name", f.Name)
f.SetDefaults()
}

//+kubebuilder:webhook:path=/validate-kubedb-com-v1alpha2-ferretdb,mutating=false,failurePolicy=fail,sideEffects=None,groups=kubedb.com,resources=ferretdbs,verbs=create;update;delete,versions=v1alpha2,name=vferretdb.kb.io,admissionReviewVersions=v1

var _ webhook.Validator = &FerretDB{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (f *FerretDB) ValidateCreate() (admission.Warnings, error) {
ferretdblog.Info("validate create", "name", f.Name)

allErr := f.ValidateCreateOrUpdate()
if len(allErr) == 0 {
return nil, nil
}
return nil, apierrors.NewInvalid(schema.GroupKind{Group: "kubedb.com", Kind: "FerretDB"}, f.Name, allErr)
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (f *FerretDB) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
ferretdblog.Info("validate update", "name", f.Name)

_ = old.(*FerretDB)
allErr := f.ValidateCreateOrUpdate()
if len(allErr) == 0 {
return nil, nil
}
return nil, apierrors.NewInvalid(schema.GroupKind{Group: "kubedb.com", Kind: "FerretDB"}, f.Name, allErr)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (f *FerretDB) ValidateDelete() (admission.Warnings, error) {
ferretdblog.Info("validate delete", "name", f.Name)

var allErr field.ErrorList
if f.Spec.TerminationPolicy == TerminationPolicyDoNotTerminate {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("terminationPolicy"),
f.Name,
"Can not delete as terminationPolicy is set to \"DoNotTerminate\""))
return nil, apierrors.NewInvalid(schema.GroupKind{Group: "kubedb.com", Kind: "FerretDB"}, f.Name, allErr)
}
return nil, nil
}

func (f *FerretDB) ValidateCreateOrUpdate() field.ErrorList {
var allErr field.ErrorList

err := f.validateFerretDBVersion()
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("version"),
f.Name,
err.Error()))
}
if f.Spec.Replicas == nil || *f.Spec.Replicas < 1 {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("replicas"),
f.Name,
fmt.Sprintf(`spec.replicas "%v" invalid. Must be greater than zero`, f.Spec.Replicas)))
}

if f.Spec.PodTemplate != nil {
if err := FerretDBValidateEnvVar(getMainContainerEnvs(f), forbiddenEnvVars, f.ResourceKind()); err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("podTemplate"),
f.Name,
err.Error()))
}
}

if f.Spec.StorageType == "" {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("storageType"),
f.Name,
`'spec.storageType' is missing`))
}
if f.Spec.StorageType != StorageTypeDurable && f.Spec.StorageType != StorageTypeEphemeral {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("storageType"),
f.Name,
fmt.Sprintf(`'spec.storageType' %s is invalid`, f.Spec.StorageType)))
}
if f.Spec.StorageType == StorageTypeEphemeral && f.Spec.Storage != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("storageType"),
f.Name,
`'spec.storageType' is set to Ephemeral, so 'spec.storage' needs to be empty`))
}

if f.Spec.AuthSecret != nil && f.Spec.AuthSecret.ExternallyManaged != f.Spec.Backend.ExternallyManaged {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("authSecret"),
f.Name,
`when 'spec.backend' is internally manager, 'spec.authSecret' can't be externally managed and vice versa`))
}

if f.Spec.AuthSecret != nil && f.Spec.AuthSecret.ExternallyManaged && f.Spec.AuthSecret.Name == "" {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("authSecret"),
f.Name,
`for externallyManaged auth secret, user must configure "spec.authSecret.name"`))
}
if f.Spec.StorageType == StorageTypeEphemeral && f.Spec.TerminationPolicy == TerminationPolicyHalt {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("storageType"),
f.Name,
`'spec.terminationPolicy: Halt' can not be used for 'Ephemeral' storage`))
}
if f.Spec.TerminationPolicy == "" {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("terminationPolicy"),
f.Name,
`'spec.terminationPolicy' is missing`))
}
if f.Spec.TerminationPolicy == TerminationPolicyHalt || f.Spec.TerminationPolicy == TerminationPolicyDelete {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("terminationPolicy"),
f.Name,
`'spec.terminationPolicy' value 'Halt' or 'Delete' is not supported yet for FerretDB`))
}

if f.Spec.Backend.ExternallyManaged {
if f.Spec.Backend.Postgres == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
f.Name,
`'spec.postgres' is missing when backend is externally managed`))
}
}
if f.Spec.SSLMode == SSLModeAllowSSL || f.Spec.SSLMode == SSLModePreferSSL {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("sslMode"),
f.Name,
`'spec.sslMode' value 'allowSSL' or 'preferSSL' is not supported yet for FerretDB`))
}
if !f.Spec.Backend.ExternallyManaged && f.Spec.Backend.Postgres != nil && f.Spec.Backend.Postgres.Version != nil {
err := f.validatePostgresVersion()
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("backend"),
f.Name,
err.Error()))
}
}

return allErr
}

func FerretDBValidateEnvVar(envs []core.EnvVar, forbiddenEnvs []string, resourceType string) error {
for _, env := range envs {
present, _ := arrays.Contains(forbiddenEnvs, env.Name)
if present {
return fmt.Errorf("environment variable %s is forbidden to use in %s spec", env.Name, resourceType)
}
}
return nil
}

var forbiddenEnvVars = []string{
EnvFerretDBUser, EnvFerretDBPassword, EnvFerretDBHandler, EnvFerretDBPgURL,
EnvFerretDBTLSPort, EnvFerretDBCAPath, EnvFerretDBCertPath, EnvFerretDBKeyPath,
}

func getMainContainerEnvs(f *FerretDB) []core.EnvVar {
for _, container := range f.Spec.PodTemplate.Spec.Containers {
if container.Name == FerretDBContainerName {
return container.Env
}
}
return []core.EnvVar{}
}

func (f *FerretDB) validateFerretDBVersion() error {
frVersion := v1alpha1.FerretDBVersion{}
err := DefaultClient.Get(context.TODO(), types.NamespacedName{Name: f.Spec.Version}, &frVersion)
if err != nil {
return errors.New("version not supported")
}
return nil
}

func (f *FerretDB) validatePostgresVersion() error {
pgVersion := v1alpha1.PostgresVersion{}
err := DefaultClient.Get(context.TODO(), types.NamespacedName{Name: *f.Spec.Backend.Postgres.Version}, &pgVersion)
if err != nil {
return errors.New("postgres version not supported in KubeDB")
}
return nil
}
Original file line number Diff line number Diff line change
@@ -31,15 +31,18 @@ import (
"gomodules.xyz/pointer"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
appslister "k8s.io/client-go/listers/apps/v1"
"k8s.io/klog/v2"
kmapi "kmodules.xyz/client-go/api/v1"
"kmodules.xyz/client-go/apiextensions"
coreutil "kmodules.xyz/client-go/core/v1"
meta_util "kmodules.xyz/client-go/meta"
"kmodules.xyz/client-go/policy/secomp"
appcat "kmodules.xyz/custom-resources/apis/appcatalog/v1alpha1"
mona "kmodules.xyz/monitoring-agent-api/api/v1"
ofst "kmodules.xyz/offshoot-api/api/v1"
ofst "kmodules.xyz/offshoot-api/api/v2"
)

func (k *Kafka) CustomResourceDefinition() *apiextensions.CustomResourceDefinition {
@@ -293,6 +296,23 @@ func (k *Kafka) SetDefaults() {
k.Spec.StorageType = StorageTypeDurable
}

var kfVersion catalog.KafkaVersion
err := DefaultClient.Get(context.TODO(), types.NamespacedName{Name: k.Spec.Version}, &kfVersion)
if err != nil {
klog.Errorf("can't get the kafka version object %s for %s \n", err.Error(), k.Spec.Version)
return
}

k.setDefaultContainerSecurityContext(&kfVersion, &k.Spec.PodTemplate)
if k.Spec.CruiseControl != nil {
k.setDefaultContainerSecurityContext(&kfVersion, &k.Spec.CruiseControl.PodTemplate)
}

k.Spec.Monitor.SetDefaults()
if k.Spec.Monitor != nil && k.Spec.Monitor.Prometheus != nil && k.Spec.Monitor.Prometheus.Exporter.SecurityContext.RunAsUser == nil {
k.Spec.Monitor.Prometheus.Exporter.SecurityContext.RunAsUser = kfVersion.Spec.SecurityContext.RunAsUser
}

if k.Spec.Topology != nil {
if k.Spec.Topology.Controller != nil {
if k.Spec.Topology.Controller.Suffix == "" {
@@ -314,29 +334,15 @@ func (k *Kafka) SetDefaults() {
apis.SetDefaultResourceLimits(&k.Spec.Topology.Broker.Resources, DefaultResources)
}
} else {
apis.SetDefaultResourceLimits(&k.Spec.PodTemplate.Spec.Resources, DefaultResources)
dbContainer := coreutil.GetContainerByName(k.Spec.PodTemplate.Spec.Containers, KafkaContainerName)
if dbContainer != nil {
apis.SetDefaultResourceLimits(&dbContainer.Resources, DefaultResources)
}
if k.Spec.Replicas == nil {
k.Spec.Replicas = pointer.Int32P(1)
}
}

var kfVersion catalog.KafkaVersion
err := DefaultClient.Get(context.TODO(), types.NamespacedName{Name: k.Spec.Version}, &kfVersion)
if err != nil {
klog.Errorf("can't get the kafka version object %s for %s \n", err.Error(), k.Spec.Version)
return
}

k.setDefaultContainerSecurityContext(&kfVersion, &k.Spec.PodTemplate)
if k.Spec.CruiseControl != nil {
k.setDefaultContainerSecurityContext(&kfVersion, &k.Spec.CruiseControl.PodTemplate)
}

k.Spec.Monitor.SetDefaults()
if k.Spec.Monitor != nil && k.Spec.Monitor.Prometheus != nil && k.Spec.Monitor.Prometheus.Exporter.SecurityContext.RunAsUser == nil {
k.Spec.Monitor.Prometheus.Exporter.SecurityContext.RunAsUser = kfVersion.Spec.SecurityContext.RunAsUser
}

if k.Spec.EnableSSL {
k.SetTLSDefaults()
}
@@ -347,16 +353,24 @@ func (k *Kafka) setDefaultContainerSecurityContext(kfVersion *catalog.KafkaVersi
if podTemplate == nil {
return
}
if podTemplate.Spec.ContainerSecurityContext == nil {
podTemplate.Spec.ContainerSecurityContext = &core.SecurityContext{}
}

if podTemplate.Spec.SecurityContext == nil {
podTemplate.Spec.SecurityContext = &core.PodSecurityContext{}
}
if podTemplate.Spec.SecurityContext.FSGroup == nil {
podTemplate.Spec.SecurityContext.FSGroup = kfVersion.Spec.SecurityContext.RunAsUser
}
k.assignDefaultContainerSecurityContext(kfVersion, podTemplate.Spec.ContainerSecurityContext)
dbContainer := coreutil.GetContainerByName(podTemplate.Spec.Containers, KafkaContainerName)
if dbContainer == nil {
dbContainer = &core.Container{
Name: KafkaContainerName,
}
podTemplate.Spec.Containers = append(podTemplate.Spec.Containers, *dbContainer)
}
if dbContainer.SecurityContext == nil {
dbContainer.SecurityContext = &core.SecurityContext{}
}
k.assignDefaultContainerSecurityContext(kfVersion, dbContainer.SecurityContext)
}

func (k *Kafka) assignDefaultContainerSecurityContext(kfVersion *catalog.KafkaVersion, sc *core.SecurityContext) {
@@ -430,3 +444,12 @@ func (k *Kafka) GetConnectionScheme() string {
func (k *Kafka) GetCruiseControlClientID() string {
return meta_util.NameWithSuffix(k.Name, "cruise-control")
}

func (k *Kafka) ReplicasAreReady(lister appslister.StatefulSetLister) (bool, string, error) {
// Desire number of statefulSets
expectedItems := 1
if k.Spec.Topology != nil {
expectedItems = 2
}
return checkReplicas(lister.StatefulSets(k.Namespace), labels.SelectorFromSet(k.OffshootLabels()), expectedItems)
}
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kmapi "kmodules.xyz/client-go/api/v1"
mona "kmodules.xyz/monitoring-agent-api/api/v1"
ofst "kmodules.xyz/offshoot-api/api/v1"
ofst "kmodules.xyz/offshoot-api/api/v2"
)

const (
Original file line number Diff line number Diff line change
@@ -17,14 +17,19 @@ limitations under the License.
package v1alpha2

import (
"context"
"errors"

catalog "kubedb.dev/apimachinery/apis/catalog/v1alpha1"

errors2 "github.com/pkg/errors"
"gomodules.xyz/pointer"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
ofst "kmodules.xyz/offshoot-api/api/v2"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
@@ -122,7 +127,7 @@ func (k *Kafka) ValidateCreateOrUpdate() error {
k.Name,
"doesn't support spec.storage when spec.topology is set"))
}
if k.Spec.PodTemplate.Spec.Resources.Size() != 0 {
if k.Spec.PodTemplate.Spec.Containers[0].Resources.Size() != 0 {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("podTemplate").Child("spec").Child("resources"),
k.Name,
"doesn't support spec.podTemplate.spec.resources when spec.topology is set"))
@@ -141,15 +146,15 @@ func (k *Kafka) ValidateCreateOrUpdate() error {
}

// validate that multiple nodes don't have same suffixes
err := validateNodeSuffix(k.Spec.Topology)
err := k.validateNodeSuffix(k.Spec.Topology)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology"),
k.Name,
err.Error()))
}

// validate that node replicas are not 0 or negative
err = validateNodeReplicas(k.Spec.Topology)
err = k.validateNodeReplicas(k.Spec.Topology)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology"),
k.Name,
@@ -164,21 +169,21 @@ func (k *Kafka) ValidateCreateOrUpdate() error {
}
}

err := validateVersion(k)
err := k.validateVersion(k)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("version"),
k.Name,
err.Error()))
}

err = validateVolumes(k)
err = k.validateVolumes(k)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("podTemplate").Child("spec").Child("volumes"),
k.Name,
err.Error()))
}

err = validateVolumesMountPaths(k)
err = k.validateVolumesMountPaths(&k.Spec.PodTemplate)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("podTemplate").Child("spec").Child("volumeMounts"),
k.Name,
@@ -203,26 +208,16 @@ func (k *Kafka) ValidateCreateOrUpdate() error {
return apierrors.NewInvalid(schema.GroupKind{Group: "kafka.kubedb.com", Kind: "Kafka"}, k.Name, allErr)
}

var availableVersions = []string{
"3.3.0",
"3.3.2",
"3.4.0",
"3.4.1",
"3.5.1",
"3.6.0",
}

func validateVersion(db *Kafka) error {
version := db.Spec.Version
for _, v := range availableVersions {
if v == version {
return nil
}
func (k *Kafka) validateVersion(db *Kafka) error {
kfVersion := &catalog.KafkaVersion{}
err := DefaultClient.Get(context.TODO(), types.NamespacedName{Name: db.Spec.Version}, kfVersion)
if err != nil {
return errors.New("version not supported")
}
return errors.New("version not supported")
return nil
}

func validateNodeSuffix(topology *KafkaClusterTopology) error {
func (k *Kafka) validateNodeSuffix(topology *KafkaClusterTopology) error {
tMap := topology.ToMap()
names := make(map[string]bool)
for _, value := range tMap {
@@ -234,7 +229,7 @@ func validateNodeSuffix(topology *KafkaClusterTopology) error {
return nil
}

func validateNodeReplicas(topology *KafkaClusterTopology) error {
func (k *Kafka) validateNodeReplicas(topology *KafkaClusterTopology) error {
tMap := topology.ToMap()
for key, node := range tMap {
if pointer.Int32(node.Replicas) <= 0 {
@@ -244,18 +239,18 @@ func validateNodeReplicas(topology *KafkaClusterTopology) error {
return nil
}

var reservedVolumes = []string{
var kafkaReservedVolumes = []string{
KafkaVolumeData,
KafkaVolumeConfig,
KafkaVolumeTempConfig,
}

func validateVolumes(db *Kafka) error {
func (k *Kafka) validateVolumes(db *Kafka) error {
if db.Spec.PodTemplate.Spec.Volumes == nil {
return nil
}
rsv := make([]string, len(reservedVolumes))
copy(rsv, reservedVolumes)
rsv := make([]string, len(kafkaReservedVolumes))
copy(rsv, kafkaReservedVolumes)
if db.Spec.TLS != nil && db.Spec.TLS.Certificates != nil {
for _, c := range db.Spec.TLS.Certificates {
rsv = append(rsv, db.CertSecretVolumeName(KafkaCertificateAlias(c.Alias)))
@@ -272,26 +267,49 @@ func validateVolumes(db *Kafka) error {
return nil
}

var reservedVolumeMountPaths = []string{
var kafkaReservedVolumeMountPaths = []string{
KafkaConfigDir,
KafkaTempConfigDir,
KafkaDataDir,
KafkaMetaDataDir,
KafkaCertDir,
}

func validateVolumesMountPaths(db *Kafka) error {
if db.Spec.PodTemplate.Spec.VolumeMounts == nil {
func (k *Kafka) validateVolumesMountPaths(podTemplate *ofst.PodTemplateSpec) error {
if podTemplate == nil {
return nil
}
if podTemplate.Spec.Containers == nil {
return nil
}
rPaths := reservedVolumeMountPaths
volumeMountPaths := db.Spec.PodTemplate.Spec.VolumeMounts
for _, rvm := range rPaths {
for _, ugv := range volumeMountPaths {
if ugv.Name == rvm {
return errors.New("Cannot use a reserve volume name: " + rvm)

for _, rvmp := range kafkaReservedVolumeMountPaths {
containerList := podTemplate.Spec.Containers
for i := range containerList {
mountPathList := containerList[i].VolumeMounts
for j := range mountPathList {
if mountPathList[j].MountPath == rvmp {
return errors.New("Can't use a reserve volume mount path name: " + rvmp)
}
}
}
}

if podTemplate.Spec.InitContainers == nil {
return nil
}

for _, rvmp := range kafkaReservedVolumeMountPaths {
containerList := podTemplate.Spec.InitContainers
for i := range containerList {
mountPathList := containerList[i].VolumeMounts
for j := range mountPathList {
if mountPathList[j].MountPath == rvmp {
return errors.New("Can't use a reserve volume mount path name: " + rvmp)
}
}
}
}

return nil
}
3,219 changes: 2,151 additions & 1,068 deletions vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/openapi_generated.go

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -27,7 +27,9 @@ import (
"gomodules.xyz/pointer"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
appslister "k8s.io/client-go/listers/apps/v1"
"k8s.io/klog/v2"
"kmodules.xyz/client-go/apiextensions"
meta_util "kmodules.xyz/client-go/meta"
@@ -189,16 +191,16 @@ func (p *Pgpool) SetDefaults() {
}
p.SetHealthCheckerDefaults()

ppVersion := &catalog.PgpoolVersion{}
ppVersion := catalog.PgpoolVersion{}
err := DefaultClient.Get(context.TODO(), types.NamespacedName{
Name: p.Spec.Version,
}, ppVersion)
}, &ppVersion)
if err != nil {
klog.Errorf("can't get the pgpool version object %s for %s \n", err.Error(), p.Spec.Version)
return
}
if p.Spec.PodTemplate != nil {
p.SetSecurityContext(ppVersion)
p.SetSecurityContext(&ppVersion)
}
}

@@ -210,3 +212,9 @@ func (p *Pgpool) GetPersistentSecrets() []string {
}
return secrets
}

func (p *Pgpool) ReplicasAreReady(lister appslister.StatefulSetLister) (bool, string, error) {
// Desire number of statefulSets
expectedItems := 1
return checkReplicas(lister.StatefulSets(p.Namespace), labels.SelectorFromSet(p.OffshootLabels()), expectedItems)
}
Original file line number Diff line number Diff line change
@@ -96,7 +96,7 @@ type PgpoolSpec struct {
// HealthChecker defines attributes of the health checker
// +optional
// +kubebuilder:default={periodSeconds: 10, timeoutSeconds: 10, failureThreshold: 1}
HealthChecker *kmapi.HealthCheckSpec `json:"healthChecker"`
HealthChecker kmapi.HealthCheckSpec `json:"healthChecker"`

// TerminationPolicy controls the delete operation for Pgpool
// +optional
Original file line number Diff line number Diff line change
@@ -17,14 +17,18 @@ limitations under the License.
package v1alpha2

import (
"context"
"fmt"

catalog "kubedb.dev/apimachinery/apis/catalog/v1alpha1"

"github.com/pkg/errors"
"gomodules.xyz/x/arrays"
core "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
kmapi "kmodules.xyz/client-go/api/v1"
ofst "kmodules.xyz/offshoot-api/api/v2"
@@ -152,7 +156,7 @@ func (p *Pgpool) ValidateCreateOrUpdate() field.ErrorList {
}
}

if err := p.ValidateHealth(p.Spec.HealthChecker); err != nil {
if err := p.ValidateHealth(&p.Spec.HealthChecker); err != nil {
errorList = append(errorList, field.Invalid(field.NewPath("spec").Child("healthChecker"),
p.Name,
err.Error(),
@@ -191,17 +195,14 @@ func (p *Pgpool) ValidateHealth(health *kmapi.HealthCheckSpec) error {
}

func PgpoolValidateVersion(p *Pgpool) error {
version := p.Spec.Version
for _, v := range PgpoolAvailableVersions {
if v == version {
return nil
}
ppVersion := catalog.PgpoolVersion{}
err := DefaultClient.Get(context.TODO(), types.NamespacedName{
Name: p.Spec.Version,
}, &ppVersion)
if err != nil {
return errors.New("version not supported")
}
return errors.New("version not supported")
}

var PgpoolAvailableVersions = []string{
"4.4.2",
return nil
}

var PgpoolReservedVolumes = []string{
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
/*
Copyright AppsCode Inc. and Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
"fmt"
"path/filepath"
"strings"

"kubedb.dev/apimachinery/apis/kubedb"
"kubedb.dev/apimachinery/crds"

promapi "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"gomodules.xyz/pointer"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
appslister "k8s.io/client-go/listers/apps/v1"
kmapi "kmodules.xyz/client-go/api/v1"
"kmodules.xyz/client-go/apiextensions"
meta_util "kmodules.xyz/client-go/meta"
appcat "kmodules.xyz/custom-resources/apis/appcatalog/v1alpha1"
mona "kmodules.xyz/monitoring-agent-api/api/v1"
)

func (r *RabbitMQ) CustomResourceDefinition() *apiextensions.CustomResourceDefinition {
return crds.MustCustomResourceDefinition(SchemeGroupVersion.WithResource(ResourcePluralRabbitmq))
}

type RabbitmqApp struct {
*RabbitMQ
}

func (r RabbitmqApp) Name() string {
return r.RabbitMQ.Name
}

func (r RabbitmqApp) Type() appcat.AppType {
return appcat.AppType(fmt.Sprintf("%s/%s", kubedb.GroupName, ResourceSingularRabbitmq))
}

func (r *RabbitMQ) AppBindingMeta() appcat.AppBindingMeta {
return &RabbitmqApp{r}
}

func (r *RabbitMQ) GetConnectionScheme() string {
scheme := "http"
if r.Spec.EnableSSL {
scheme = "https"
}
return scheme
}

func (r *RabbitMQ) ResourceShortCode() string {
return ResourceCodeRabbitmq
}

func (r *RabbitMQ) ResourceKind() string {
return ResourceKindRabbitmq
}

func (r *RabbitMQ) ResourceSingular() string {
return ResourceSingularRabbitmq
}

func (r *RabbitMQ) ResourcePlural() string {
return ResourcePluralRabbitmq
}

func (r *RabbitMQ) AsOwner() *meta.OwnerReference {
return meta.NewControllerRef(r, SchemeGroupVersion.WithKind(ResourceKindRabbitmq))
}

func (r *RabbitMQ) ResourceFQN() string {
return fmt.Sprintf("%s.%s", r.ResourcePlural(), kubedb.GroupName)
}

// Owner returns owner reference to resources
func (r *RabbitMQ) Owner() *meta.OwnerReference {
return meta.NewControllerRef(r, SchemeGroupVersion.WithKind(r.ResourceKind()))
}

func (r *RabbitMQ) OffshootName() string {
return r.Name
}

func (r *RabbitMQ) ServiceName() string {
return r.OffshootName()
}

func (r *RabbitMQ) GoverningServiceName() string {
return meta_util.NameWithSuffix(r.ServiceName(), "pods")
}

func (r *RabbitMQ) StandbyServiceName() string {
return meta_util.NameWithPrefix(r.ServiceName(), KafkaStandbyServiceSuffix)
}

func (r *RabbitMQ) offshootLabels(selector, override map[string]string) map[string]string {
selector[meta_util.ComponentLabelKey] = ComponentDatabase
return meta_util.FilterKeys(kubedb.GroupName, selector, meta_util.OverwriteKeys(nil, r.Labels, override))
}

func (r *RabbitMQ) OffshootSelectors(extraSelectors ...map[string]string) map[string]string {
selector := map[string]string{
meta_util.NameLabelKey: r.ResourceFQN(),
meta_util.InstanceLabelKey: r.Name,
meta_util.ManagedByLabelKey: kubedb.GroupName,
}
return meta_util.OverwriteKeys(selector, extraSelectors...)
}

func (r *RabbitMQ) OffshootLabels() map[string]string {
return r.offshootLabels(r.OffshootSelectors(), nil)
}

func (r *RabbitMQ) ServiceLabels(alias ServiceAlias, extraLabels ...map[string]string) map[string]string {
svcTemplate := GetServiceTemplate(r.Spec.ServiceTemplates, alias)
return r.offshootLabels(meta_util.OverwriteKeys(r.OffshootSelectors(), extraLabels...), svcTemplate.Labels)
}

type RabbitmqStatsService struct {
*RabbitMQ
}

func (ks RabbitmqStatsService) TLSConfig() *promapi.TLSConfig {
return nil
}

func (ks RabbitmqStatsService) GetNamespace() string {
return ks.RabbitMQ.GetNamespace()
}

func (ks RabbitmqStatsService) ServiceName() string {
return ks.OffshootName() + "-stats"
}

func (ks RabbitmqStatsService) ServiceMonitorName() string {
return ks.ServiceName()
}

func (ks RabbitmqStatsService) ServiceMonitorAdditionalLabels() map[string]string {
return ks.OffshootLabels()
}

func (ks RabbitmqStatsService) Path() string {
return DefaultStatsPath
}

func (ks RabbitmqStatsService) Scheme() string {
return ""
}

func (r *RabbitMQ) StatsService() mona.StatsAccessor {
return &RabbitmqStatsService{r}
}

func (r *RabbitMQ) StatsServiceLabels() map[string]string {
return r.ServiceLabels(StatsServiceAlias, map[string]string{LabelRole: RoleStats})
}

func (r *RabbitMQ) PodControllerLabels(extraLabels ...map[string]string) map[string]string {
return r.offshootLabels(meta_util.OverwriteKeys(r.OffshootSelectors(), extraLabels...), r.Spec.PodTemplate.Controller.Labels)
}

func (r *RabbitMQ) PodLabels(extraLabels ...map[string]string) map[string]string {
return r.offshootLabels(meta_util.OverwriteKeys(r.OffshootSelectors(), extraLabels...), r.Spec.PodTemplate.Labels)
}

func (r *RabbitMQ) StatefulSetName() string {
return r.OffshootName()
}

func (r *RabbitMQ) ServiceAccountName() string {
return r.OffshootName()
}

func (r *RabbitMQ) DefaultPodRoleName() string {
return meta_util.NameWithSuffix(r.OffshootName(), "role")
}

func (r *RabbitMQ) DefaultPodRoleBindingName() string {
return meta_util.NameWithSuffix(r.OffshootName(), "rolebinding")
}

func (r *RabbitMQ) ConfigSecretName() string {
return meta_util.NameWithSuffix(r.OffshootName(), "config")
}

func (r *RabbitMQ) DefaultUserCredSecretName(username string) string {
return meta_util.NameWithSuffix(r.Name, strings.ReplaceAll(fmt.Sprintf("%s-cred", username), "_", "-"))
}

func (r *RabbitMQ) DefaultErlangCookieSecretName() string {
return meta_util.NameWithSuffix(r.OffshootName(), "erlang-cookie")
}

// CertificateName returns the default certificate name and/or certificate secret name for a certificate alias
func (r *RabbitMQ) CertificateName(alias RabbitMQCertificateAlias) string {
return meta_util.NameWithSuffix(r.Name, fmt.Sprintf("%s-cert", string(alias)))
}

// ClientCertificateCN returns the CN for a client certificate
func (r *RabbitMQ) ClientCertificateCN(alias RabbitMQCertificateAlias) string {
return fmt.Sprintf("%s-%s", r.Name, string(alias))
}

// GetCertSecretName returns the secret name for a certificate alias if any,
// otherwise returns default certificate secret name for the given alias.
func (r *RabbitMQ) GetCertSecretName(alias RabbitMQCertificateAlias) string {
if r.Spec.TLS != nil {
name, ok := kmapi.GetCertificateSecretName(r.Spec.TLS.Certificates, string(alias))
if ok {
return name
}
}
return r.CertificateName(alias)
}

// returns the CertSecretVolumeName
// Values will be like: client-certs, server-certs etc.
func (r *RabbitMQ) CertSecretVolumeName(alias RabbitMQCertificateAlias) string {
return string(alias) + "-certs"
}

// returns CertSecretVolumeMountPath
// if configDir is "/opt/kafka/config",
// mountPath will be, "/opt/kafka/config/<alias>".
func (r *RabbitMQ) CertSecretVolumeMountPath(configDir string, cert string) string {
return filepath.Join(configDir, cert)
}

func (r *RabbitMQ) PVCName(alias string) string {
return meta_util.NameWithSuffix(r.Name, alias)
}

func (r *RabbitMQ) SetHealthCheckerDefaults() {
if r.Spec.HealthChecker.PeriodSeconds == nil {
r.Spec.HealthChecker.PeriodSeconds = pointer.Int32P(10)
}
if r.Spec.HealthChecker.TimeoutSeconds == nil {
r.Spec.HealthChecker.TimeoutSeconds = pointer.Int32P(10)
}
if r.Spec.HealthChecker.FailureThreshold == nil {
r.Spec.HealthChecker.FailureThreshold = pointer.Int32P(3)
}
}

func (r *RabbitMQ) SetDefaults() {
if r.Spec.TerminationPolicy == "" {
r.Spec.TerminationPolicy = TerminationPolicyDelete
}

if r.Spec.StorageType == "" {
r.Spec.StorageType = StorageTypeDurable
}

r.SetHealthCheckerDefaults()
}

func (r *RabbitMQ) SetTLSDefaults() {
if r.Spec.TLS == nil || r.Spec.TLS.IssuerRef == nil {
return
}
r.Spec.TLS.Certificates = kmapi.SetMissingSecretNameForCertificate(r.Spec.TLS.Certificates, string(RabbitmqServerCert), r.CertificateName(RabbitmqServerCert))
r.Spec.TLS.Certificates = kmapi.SetMissingSecretNameForCertificate(r.Spec.TLS.Certificates, string(RabbitmqClientCert), r.CertificateName(RabbitmqClientCert))
}

func (r *RabbitMQ) ReplicasAreReady(lister appslister.StatefulSetLister) (bool, string, error) {
// Desire number of statefulSets
expectedItems := 1
return checkReplicas(lister.StatefulSets(r.Namespace), labels.SelectorFromSet(r.OffshootLabels()), expectedItems)
}
160 changes: 160 additions & 0 deletions vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/rabbitmq_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
Copyright AppsCode Inc. and Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
kmapi "kmodules.xyz/client-go/api/v1"
ofst "kmodules.xyz/offshoot-api/api/v2"
)

const (
ResourceCodeRabbitmq = "rm"
ResourceKindRabbitmq = "RabbitMQ"
ResourceSingularRabbitmq = "rabbitmq"
ResourcePluralRabbitmq = "rabbitmqs"
)

// RabbitMQ is the Schema for the RabbitMQ API

// +genclient
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=rm,scope=Namespaced
// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".apiVersion"
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.version"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.phase"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
type RabbitMQ struct {
meta.TypeMeta `json:",inline"`
meta.ObjectMeta `json:"metadata,omitempty"`

Spec RabbitMQSpec `json:"spec,omitempty"`
Status RabbitMQStatus `json:"status,omitempty"`
}

// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// RabbitMQSpec defines the desired state of RabbitMQ
type RabbitMQSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Version of RabbitMQ to be deployed.
Version string `json:"version"`

// Number of instances to deploy for a RabbitMQ database.
// +optional
Replicas *int32 `json:"replicas,omitempty"`

// StorageType can be durable (default) or ephemeral
StorageType StorageType `json:"storageType,omitempty"`

// Storage to specify how storage shall be used.
Storage *core.PersistentVolumeClaimSpec `json:"storage,omitempty"`

// To enable ssl for http layer
EnableSSL bool `json:"enableSSL,omitempty"`

// disable security. It disables authentication security of user.
// If unset, default is false
// +optional
DisableSecurity bool `json:"disableSecurity,omitempty"`

// Database authentication secret
// +optional
AuthSecret *SecretReference `json:"authSecret,omitempty"`

// ConfigSecret is an optional field to provide custom configuration file for database (i.e config.properties).
// If specified, this file will be used as configuration file otherwise default configuration file will be used.
// +optional
ConfigSecret *core.LocalObjectReference `json:"configSecret,omitempty"`

// TLS contains tls configurations
// +optional
TLS *kmapi.TLSConfig `json:"tls,omitempty"`

// PodTemplate is an optional configuration for pods used to expose database
// +optional
PodTemplate ofst.PodTemplateSpec `json:"podTemplate,omitempty"`

// ServiceTemplates is an optional configuration for services used to expose database
// +optional
ServiceTemplates []NamedServiceTemplateSpec `json:"serviceTemplates,omitempty"`

// Indicates that the database is halted and all offshoot Kubernetes resources except PVCs are deleted.
// +optional
Halted bool `json:"halted,omitempty"`

// TerminationPolicy controls the delete operation for database
// +optional
TerminationPolicy TerminationPolicy `json:"terminationPolicy,omitempty"`

// HealthChecker defines attributes of the health checker
// +optional
// +kubebuilder:default={periodSeconds: 20, timeoutSeconds: 10, failureThreshold: 3}
HealthChecker kmapi.HealthCheckSpec `json:"healthChecker"`
}

// RabbitMQStatus defines the observed state of RabbitMQ
type RabbitMQStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Specifies the current phase of the database
// +optional
Phase RabbitMQPhase `json:"phase,omitempty"`
// observedGeneration is the most recent generation observed for this resource. It corresponds to the
// resource's generation, which is updated on mutation by the API Server.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Conditions applied to the database, such as approval or denial.
// +optional
Conditions []kmapi.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:validation:Enum=Provisioning;Ready;NotReady;Critical
type RabbitMQPhase string

const (
RabbitmqProvisioning RabbitMQPhase = "Provisioning"
RabbitmqReady RabbitMQPhase = "Ready"
RabbitmqNotReady RabbitMQPhase = "NotReady"
RabbitmqCritical RabbitMQPhase = "Critical"
)

// +kubebuilder:validation:Enum=ca;client;server
type RabbitMQCertificateAlias string

const (
RabbitmqCACert RabbitMQCertificateAlias = "ca"
RabbitmqClientCert RabbitMQCertificateAlias = "client"
RabbitmqServerCert RabbitMQCertificateAlias = "server"
)

// RabbitMQList contains a list of RabbitMQ

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type RabbitMQList struct {
meta.TypeMeta `json:",inline"`
meta.ListMeta `json:"metadata,omitempty"`
Items []RabbitMQ `json:"items"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
/*
Copyright AppsCode Inc. and Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
"context"
"errors"

catalog "kubedb.dev/apimachinery/apis/catalog/v1alpha1"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
ofst "kmodules.xyz/offshoot-api/api/v2"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// log is for logging in this package.
var rabbitmqlog = logf.Log.WithName("rabbitmq-resource")

//+kubebuilder:webhook:path=/mutate-rabbitmq-kubedb-com-v1alpha1-rabbitmq,mutating=true,failurePolicy=fail,sideEffects=None,groups=kubedb.com,resources=rabbitmqs,verbs=create;update,versions=v1alpha1,name=mrabbitmq.kb.io,admissionReviewVersions={v1,v1beta1}

var _ webhook.Defaulter = &RabbitMQ{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *RabbitMQ) Default() {
if r == nil {
return
}
rabbitmqlog.Info("default", "name", r.Name)
r.SetDefaults()
}

//+kubebuilder:webhook:path=/validate-rabbitmq-kubedb-com-v1alpha1-rabbitmq,mutating=false,failurePolicy=fail,sideEffects=None,groups=kubedb.com,resources=rabbitmqs,verbs=create;update,versions=v1alpha1,name=vrabbitmq.kb.io,admissionReviewVersions={v1,v1beta1}

var _ webhook.Validator = &RabbitMQ{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *RabbitMQ) ValidateCreate() (admission.Warnings, error) {
rabbitmqlog.Info("validate create", "name", r.Name)
return nil, r.ValidateCreateOrUpdate()
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *RabbitMQ) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
rabbitmqlog.Info("validate update", "name", r.Name)
return nil, r.ValidateCreateOrUpdate()
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *RabbitMQ) ValidateDelete() (admission.Warnings, error) {
rabbitmqlog.Info("validate delete", "name", r.Name)

var allErr field.ErrorList
if r.Spec.TerminationPolicy == TerminationPolicyDoNotTerminate {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("teminationPolicy"),
r.Name,
"Can not delete as terminationPolicy is set to \"DoNotTerminate\""))
return nil, apierrors.NewInvalid(schema.GroupKind{Group: "rabbitmq.kubedb.com", Kind: "RabbitMQ"}, r.Name, allErr)
}
return nil, nil
}

func (r *RabbitMQ) ValidateCreateOrUpdate() error {
var allErr field.ErrorList
if r.Spec.EnableSSL {
if r.Spec.TLS == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("enableSSL"),
r.Name,
".spec.tls can't be nil, if .spec.enableSSL is true"))
}
} else {
if r.Spec.TLS != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("enableSSL"),
r.Name,
".spec.tls must be nil, if .spec.enableSSL is disabled"))
}
}

// number of replicas can not be 0 or less
if r.Spec.Replicas != nil && *r.Spec.Replicas <= 0 {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("replicas"),
r.Name,
"number of replicas can not be 0 or less"))
}

err := r.ValidateVersion(r)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("version"),
r.Name,
err.Error()))
}

err = r.validateVolumes(r)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("podTemplate").Child("spec").Child("volumes"),
r.Name,
err.Error()))
}

err = r.validateVolumesMountPaths(&r.Spec.PodTemplate)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("podTemplate").Child("spec").Child("volumeMounts"),
r.Name,
err.Error()))
}

if r.Spec.StorageType == "" {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("storageType"),
r.Name,
"StorageType can not be empty"))
} else {
if r.Spec.StorageType != StorageTypeDurable && r.Spec.StorageType != StorageTypeEphemeral {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("storageType"),
r.Name,
"StorageType should be either durable or ephemeral"))
}
}

if len(allErr) == 0 {
return nil
}
return apierrors.NewInvalid(schema.GroupKind{Group: "rabbitmq.kubedb.com", Kind: "RabbitMQ"}, r.Name, allErr)
}

func (r *RabbitMQ) ValidateVersion(db *RabbitMQ) error {
rmVersion := catalog.RabbitMQVersion{}
err := DefaultClient.Get(context.TODO(), types.NamespacedName{Name: db.Spec.Version}, &rmVersion)
if err != nil {
return errors.New("version not supported")
}
return nil
}

var rabbitmqReservedVolumes = []string{
RabbitMQVolumeData,
RabbitMQVolumeConfig,
RabbitMQVolumeTempConfig,
}

func (r *RabbitMQ) validateVolumes(db *RabbitMQ) error {
if db.Spec.PodTemplate.Spec.Volumes == nil {
return nil
}
rsv := make([]string, len(rabbitmqReservedVolumes))
copy(rsv, rabbitmqReservedVolumes)
if db.Spec.TLS != nil && db.Spec.TLS.Certificates != nil {
for _, c := range db.Spec.TLS.Certificates {
rsv = append(rsv, db.CertSecretVolumeName(RabbitMQCertificateAlias(c.Alias)))
}
}
volumes := db.Spec.PodTemplate.Spec.Volumes
for _, rv := range rsv {
for _, ugv := range volumes {
if ugv.Name == rv {
return errors.New("Cannot use a reserve volume name: " + rv)
}
}
}
return nil
}

var rabbitmqReservedVolumeMountPaths = []string{
RabbitMQConfigDir,
RabbitMQTempConfigDir,
RabbitMQDataDir,
RabbitMQPluginsDir,
RabbitMQCertDir,
}

func (r *RabbitMQ) validateVolumesMountPaths(podTemplate *ofst.PodTemplateSpec) error {
if podTemplate == nil {
return nil
}
if podTemplate.Spec.Containers == nil {
return nil
}

for _, rvmp := range rabbitmqReservedVolumeMountPaths {
containerList := podTemplate.Spec.Containers
for i := range containerList {
mountPathList := containerList[i].VolumeMounts
for j := range mountPathList {
if mountPathList[j].MountPath == rvmp {
return errors.New("Can't use a reserve volume mount path name: " + rvmp)
}
}
}
}

if podTemplate.Spec.InitContainers == nil {
return nil
}

for _, rvmp := range rabbitmqReservedVolumeMountPaths {
containerList := podTemplate.Spec.InitContainers
for i := range containerList {
mountPathList := containerList[i].VolumeMounts
for j := range mountPathList {
if mountPathList[j].MountPath == rvmp {
return errors.New("Can't use a reserve volume mount path name: " + rvmp)
}
}
}
}

return nil
}
Original file line number Diff line number Diff line change
@@ -60,6 +60,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&ElasticsearchList{},
&Etcd{},
&EtcdList{},
&FerretDB{},
&FerretDBList{},
&Kafka{},
&KafkaList{},
&MariaDB{},
@@ -80,6 +82,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&PostgresList{},
&ProxySQL{},
&ProxySQLList{},
&RabbitMQ{},
&RabbitMQList{},
&Redis{},
&RedisList{},
&RedisSentinel{},
@@ -88,6 +92,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&SinglestoreList{},
&ZooKeeper{},
&ZooKeeperList{},
&Solr{},
&SolrList{},
)

scheme.AddKnownTypes(SchemeGroupVersion,
Original file line number Diff line number Diff line change
@@ -28,7 +28,9 @@ import (
"gomodules.xyz/pointer"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
appslister "k8s.io/client-go/listers/apps/v1"
"k8s.io/klog/v2"
kmapi "kmodules.xyz/client-go/api/v1"
"kmodules.xyz/client-go/apiextensions"
@@ -55,10 +57,18 @@ func (s *singlestoreApp) Type() appcat.AppType {
return appcat.AppType(fmt.Sprintf("%s/%s", kubedb.GroupName, ResourceSingularSinglestore))
}

func (s *Singlestore) ResourceShortCode() string {
return ResourceCodeSinglestore
}

func (s *Singlestore) ResourceKind() string {
return ResourceKindSinglestore
}

func (s *Singlestore) ResourceSingular() string {
return ResourceSingularRabbitmq
}

func (s *Singlestore) ResourcePlural() string {
return ResourcePluralSinglestore
}
@@ -373,3 +383,12 @@ func (s *Singlestore) SetTLSDefaults() {
func (s *Singlestore) CertificateName(alias SinglestoreCertificateAlias) string {
return metautil.NameWithSuffix(s.Name, fmt.Sprintf("%s-cert", string(alias)))
}

func (s *Singlestore) ReplicasAreReady(lister appslister.StatefulSetLister) (bool, string, error) {
// Desire number of statefulSets
expectedItems := 1
if s.Spec.Topology != nil {
expectedItems = 2
}
return checkReplicas(lister.StatefulSets(s.Namespace), labels.SelectorFromSet(s.OffshootLabels()), expectedItems)
}
Original file line number Diff line number Diff line change
@@ -73,11 +73,6 @@ type SinglestoreSpec struct {
// To enable ssl for http layer
EnableSSL bool `json:"enableSSL,omitempty"`

// disable security. It disables authentication security of user.
// If unset, default is false
// +optional
DisableSecurity bool `json:"disableSecurity,omitempty"`

// Init is used to initialize database
// +optional
Init *InitSpec `json:"init,omitempty"`
@@ -142,16 +137,6 @@ type SinglestoreNode struct {
// PodTemplate is an optional configuration for pods used to expose database
// +optional
PodTemplate *ofst.PodTemplateSpec `json:"podTemplate,omitempty"`

// NodeSelector is a selector which must be true for the pod to fit on a node.
// Selector which must match a node's labels for the pod to be scheduled on that node.
// More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
// +optional
// +mapType=atomic
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
// If specified, the pod's tolerations.
// +optional
Tolerations []core.Toleration `json:"tolerations,omitempty"`
}

// SinglestoreStatus defines the observed state of Singlestore
Original file line number Diff line number Diff line change
@@ -17,10 +17,15 @@ limitations under the License.
package v1alpha2

import (
"context"

catalog "kubedb.dev/apimachinery/apis/catalog/v1alpha1"

"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
ofst "kmodules.xyz/offshoot-api/api/v2"
logf "sigs.k8s.io/controller-runtime/pkg/log"
@@ -218,10 +223,6 @@ func (s *Singlestore) ValidateCreateOrUpdate() field.ErrorList {
return allErr
}

var sdbAvailableVersions = []string{
"8.1.32",
}

// reserved volume and volumes mounts for singlestore
var sdbReservedVolumes = []string{
SinglestoreVolumeNameUserInitScript,
@@ -238,13 +239,15 @@ var sdbReservedVolumesMountPaths = []string{
}

func sdbValidateVersion(s *Singlestore) error {
version := s.Spec.Version
for _, v := range sdbAvailableVersions {
if v == version {
return nil
}
var sdbVersion catalog.SinglestoreVersion
err := DefaultClient.Get(context.TODO(), types.NamespacedName{
Name: s.Spec.Version,
}, &sdbVersion)
if err != nil {
return errors.New("version not supported")
}
return errors.New("version not supported")

return nil
}

func sdbValidateVolumes(podTemplate *ofst.PodTemplateSpec) error {
359 changes: 359 additions & 0 deletions vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,359 @@
/*
Copyright AppsCode Inc. and Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
"fmt"
"strings"

"kubedb.dev/apimachinery/apis"
catalog "kubedb.dev/apimachinery/apis/catalog/v1alpha1"
"kubedb.dev/apimachinery/apis/kubedb"
"kubedb.dev/apimachinery/crds"

"gomodules.xyz/pointer"
v1 "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
appslister "k8s.io/client-go/listers/apps/v1"
"kmodules.xyz/client-go/apiextensions"
coreutil "kmodules.xyz/client-go/core/v1"
meta_util "kmodules.xyz/client-go/meta"
"kmodules.xyz/client-go/policy/secomp"
appcat "kmodules.xyz/custom-resources/apis/appcatalog/v1alpha1"
ofst "kmodules.xyz/offshoot-api/api/v2"
)

type SolrApp struct {
*Solr
}

func (s *Solr) CustomResourceDefinition() *apiextensions.CustomResourceDefinition {
return crds.MustCustomResourceDefinition(SchemeGroupVersion.WithResource(ResourcePluralSolr))
}

func (s *Solr) StatefulsetName(suffix string) string {
sts := []string{s.Name}
if suffix != "" {
sts = append(sts, suffix)
}
return strings.Join(sts, "-")
}

// Owner returns owner reference to resources
func (s *Solr) Owner() *meta.OwnerReference {
return meta.NewControllerRef(s, SchemeGroupVersion.WithKind(s.ResourceKind()))
}

func (s *Solr) ResourceKind() string {
return ResourceKindSolr
}

func (s *Solr) GoverningServiceName() string {
return meta_util.NameWithSuffix(s.ServiceName(), "pods")
}

func (s *Solr) OverseerDiscoveryServiceName() string {
return meta_util.NameWithSuffix(s.ServiceName(), "overseer")
}

func (s *Solr) ServiceAccountName() string { return s.OffshootName() }

func (s *Solr) DefaultPodRoleName() string {
return meta_util.NameWithSuffix(s.OffshootName(), "role")
}

func (s *Solr) DefaultPodRoleBindingName() string {
return meta_util.NameWithSuffix(s.OffshootName(), "rolebinding")
}

func (s *Solr) ServiceName() string {
return s.OffshootName()
}

func (s *Solr) SolrSecretName(suffix string) string {
return strings.Join([]string{s.Name, suffix}, "-")
}

func (s *Solr) SolrSecretKey() string {
return SolrSecretKey
}

func (s *Solr) Merge(opt map[string]string) map[string]string {
if len(s.Spec.SolrOpts) == 0 {
return opt
}
for _, y := range s.Spec.SolrOpts {
sr := strings.Split(y, "=")
_, ok := opt[sr[0]]
if !ok || sr[0] != "-Dsolr.node.roles" {
opt[sr[0]] = sr[1]
}
}
return opt
}

func (s *Solr) Append(opt map[string]string) string {
fl := 0
as := ""
for x, y := range opt {
if fl == 1 {
as += " "
}
as += fmt.Sprintf("%s=%s", x, y)
fl = 1

}
return as
}

func (s *Solr) OffshootName() string {
return s.Name
}

func (s *Solr) PodControllerLabels(extraLabels ...map[string]string) map[string]string {
return s.offshootLabels(meta_util.OverwriteKeys(s.OffshootSelectors(), extraLabels...), s.Spec.PodTemplate.Controller.Labels)
}

func (s *Solr) PodLabels(extraLabels ...map[string]string) map[string]string {
return s.offshootLabels(meta_util.OverwriteKeys(s.OffshootSelectors(), extraLabels...), s.Spec.PodTemplate.Labels)
}

func (s *Solr) OffshootLabels() map[string]string {
return s.offshootLabels(s.OffshootSelectors(), nil)
}

func (s *Solr) offshootLabels(selector, override map[string]string) map[string]string {
selector[meta_util.ComponentLabelKey] = ComponentDatabase
return meta_util.FilterKeys(kubedb.GroupName, selector, meta_util.OverwriteKeys(nil, s.Labels, override))
}

func (s *Solr) OffshootSelectors(extraSelectors ...map[string]string) map[string]string {
selector := map[string]string{
meta_util.NameLabelKey: s.ResourceFQN(),
meta_util.InstanceLabelKey: s.Name,
meta_util.ManagedByLabelKey: kubedb.GroupName,
}
return meta_util.OverwriteKeys(selector, extraSelectors...)
}

func (s *Solr) ResourceFQN() string {
return fmt.Sprintf("%s.%s", s.ResourcePlural(), kubedb.GroupName)
}

func (s *Solr) ResourcePlural() string {
return ResourcePluralSolr
}

func (s SolrApp) Name() string {
return s.Solr.Name
}

func (s SolrApp) Type() appcat.AppType {
return appcat.AppType(fmt.Sprintf("%s/%s", kubedb.GroupName, ResourceSingularSolr))
}

func (s *Solr) AppBindingMeta() appcat.AppBindingMeta {
return &SolrApp{s}
}

func (s *Solr) GetConnectionScheme() string {
scheme := "http"
if s.Spec.EnableSSL {
scheme = "https"
}
return scheme
}

func (s *Solr) PVCName(alias string) string {
return meta_util.NameWithSuffix(s.Name, alias)
}

func (s *Solr) SetDefaults(slVersion *catalog.SolrVersion) {
if s.Spec.TerminationPolicy == "" {
s.Spec.TerminationPolicy = TerminationPolicyDelete
}

if s.Spec.StorageType == "" {
s.Spec.StorageType = StorageTypeDurable
}

if s.Spec.AuthSecret == nil {
s.Spec.AuthSecret = &v1.LocalObjectReference{
Name: s.SolrSecretName("admin-cred"),
}
}

if s.Spec.AuthConfigSecret == nil {
s.Spec.AuthConfigSecret = &v1.LocalObjectReference{
Name: s.SolrSecretName("auth-config"),
}
}

if s.Spec.Topology != nil {
if s.Spec.Topology.Data != nil {
if s.Spec.Topology.Data.Suffix == "" {
s.Spec.Topology.Data.Suffix = string(SolrNodeRoleData)
}
if s.Spec.Topology.Data.Replicas == nil {
s.Spec.Topology.Data.Replicas = pointer.Int32P(1)
}

if s.Spec.Topology.Data.PodTemplate.Spec.SecurityContext == nil {
s.Spec.Topology.Data.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: slVersion.Spec.SecurityContext.RunAsUser}
}
s.Spec.Topology.Data.PodTemplate.Spec.SecurityContext.FSGroup = slVersion.Spec.SecurityContext.RunAsUser
s.setDefaultContainerSecurityContext(slVersion, &s.Spec.Topology.Data.PodTemplate)
s.setDefaultInitContainerSecurityContext(slVersion, &s.Spec.Topology.Data.PodTemplate)
}

if s.Spec.Topology.Overseer != nil {
if s.Spec.Topology.Overseer.Suffix == "" {
s.Spec.Topology.Overseer.Suffix = string(SolrNodeRoleOverseer)
}
if s.Spec.Topology.Overseer.Replicas == nil {
s.Spec.Topology.Overseer.Replicas = pointer.Int32P(1)
}

if s.Spec.Topology.Overseer.PodTemplate.Spec.SecurityContext == nil {
s.Spec.Topology.Overseer.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: slVersion.Spec.SecurityContext.RunAsUser}
}
s.Spec.Topology.Overseer.PodTemplate.Spec.SecurityContext.FSGroup = slVersion.Spec.SecurityContext.RunAsUser
s.setDefaultContainerSecurityContext(slVersion, &s.Spec.Topology.Overseer.PodTemplate)
s.setDefaultInitContainerSecurityContext(slVersion, &s.Spec.Topology.Overseer.PodTemplate)
}

if s.Spec.Topology.Coordinator != nil {
if s.Spec.Topology.Coordinator.Suffix == "" {
s.Spec.Topology.Coordinator.Suffix = string(SolrNodeRoleCoordinator)
}
if s.Spec.Topology.Coordinator.Replicas == nil {
s.Spec.Topology.Coordinator.Replicas = pointer.Int32P(1)
}

if s.Spec.Topology.Coordinator.PodTemplate.Spec.SecurityContext == nil {
s.Spec.Topology.Coordinator.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: slVersion.Spec.SecurityContext.RunAsUser}
}
s.Spec.Topology.Coordinator.PodTemplate.Spec.SecurityContext.FSGroup = slVersion.Spec.SecurityContext.RunAsUser
s.setDefaultContainerSecurityContext(slVersion, &s.Spec.Topology.Coordinator.PodTemplate)
s.setDefaultInitContainerSecurityContext(slVersion, &s.Spec.Topology.Coordinator.PodTemplate)
}
} else {

if s.Spec.Replicas == nil {
s.Spec.Replicas = pointer.Int32P(1)
}

if s.Spec.PodTemplate.Spec.SecurityContext == nil {
s.Spec.PodTemplate.Spec.SecurityContext = &v1.PodSecurityContext{FSGroup: slVersion.Spec.SecurityContext.RunAsUser}
}

s.Spec.PodTemplate.Spec.SecurityContext.FSGroup = slVersion.Spec.SecurityContext.RunAsUser
s.setDefaultContainerSecurityContext(slVersion, &s.Spec.PodTemplate)

s.setDefaultInitContainerSecurityContext(slVersion, &s.Spec.PodTemplate)
}
}

func (s *Solr) setDefaultInitContainerSecurityContext(slVersion *catalog.SolrVersion, podTemplate *ofst.PodTemplateSpec) {
initContainer := coreutil.GetContainerByName(podTemplate.Spec.InitContainers, SolrInitContainerName)
if initContainer == nil {
initContainer = &v1.Container{
Name: SolrInitContainerName,
}
}
if initContainer.SecurityContext == nil {
initContainer.SecurityContext = &v1.SecurityContext{}
}
apis.SetDefaultResourceLimits(&initContainer.Resources, DefaultResources)
s.assignDefaultContainerSecurityContext(slVersion, initContainer.SecurityContext)
podTemplate.Spec.InitContainers = coreutil.UpsertContainer(podTemplate.Spec.InitContainers, *initContainer)
}

func (s *Solr) setDefaultContainerSecurityContext(slVersion *catalog.SolrVersion, podTemplate *ofst.PodTemplateSpec) {
container := coreutil.GetContainerByName(podTemplate.Spec.Containers, SolrContainerName)
if container == nil {
container = &v1.Container{
Name: SolrContainerName,
}
}
if container.SecurityContext == nil {
container.SecurityContext = &v1.SecurityContext{}
}
apis.SetDefaultResourceLimits(&container.Resources, DefaultResources)
s.assignDefaultContainerSecurityContext(slVersion, container.SecurityContext)
podTemplate.Spec.Containers = coreutil.UpsertContainer(podTemplate.Spec.Containers, *container)
}

func (s *Solr) assignDefaultContainerSecurityContext(slVersion *catalog.SolrVersion, sc *v1.SecurityContext) {
if sc.AllowPrivilegeEscalation == nil {
sc.AllowPrivilegeEscalation = pointer.BoolP(false)
}
if sc.Capabilities == nil {
sc.Capabilities = &v1.Capabilities{
Drop: []v1.Capability{"ALL"},
}
}
if sc.RunAsNonRoot == nil {
sc.RunAsNonRoot = pointer.BoolP(true)
}
if sc.RunAsUser == nil {
sc.RunAsUser = slVersion.Spec.SecurityContext.RunAsUser
}
if sc.SeccompProfile == nil {
sc.SeccompProfile = secomp.DefaultSeccompProfile()
}
}

func (s *Solr) SetHealthCheckerDefaults() {
if s.Spec.HealthChecker.PeriodSeconds == nil {
s.Spec.HealthChecker.PeriodSeconds = pointer.Int32P(20)
}
if s.Spec.HealthChecker.TimeoutSeconds == nil {
s.Spec.HealthChecker.TimeoutSeconds = pointer.Int32P(10)
}
if s.Spec.HealthChecker.FailureThreshold == nil {
s.Spec.HealthChecker.FailureThreshold = pointer.Int32P(3)
}
}

func (s *Solr) GetPersistentSecrets() []string {
if s == nil {
return nil
}

var secrets []string
// Add Admin/Elastic user secret name
if s.Spec.AuthSecret != nil {
secrets = append(secrets, s.Spec.AuthSecret.Name)
}

if s.Spec.AuthConfigSecret != nil {
secrets = append(secrets, s.Spec.AuthConfigSecret.Name)
}

return secrets
}

func (s *Solr) ReplicasAreReady(lister appslister.StatefulSetLister) (bool, string, error) {
// Desire number of statefulSets
expectedItems := 1
if s.Spec.Topology != nil {
expectedItems = 3
}
return checkReplicas(lister.StatefulSets(s.Namespace), labels.SelectorFromSet(s.OffshootLabels()), expectedItems)
}
188 changes: 188 additions & 0 deletions vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
Copyright 2023.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
core "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kmapi "kmodules.xyz/client-go/api/v1"
ofst "kmodules.xyz/offshoot-api/api/v2"
)

// Solr is the schema for the Sole API

// +genclient
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=sl,scope=Namespaced
// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".apiVersion"
// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.version"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.phase"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
type Solr struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec SolrSpec `json:"spec,omitempty"`
Status SolrStatus `json:"status,omitempty"`
}

// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// SolrSpec defines the desired state of Solr c
type SolrSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Version of Solr to be deployed
Version string `json:"version"`

// Number of instances to deploy for a Solr database
// +optional
Replicas *int32 `json:"replicas,omitempty"`

// Solr topology for node specification
// +optional
Topology *SolrClusterTopology `json:"topology,omitempty"`

// StorageType van be durable (default) or ephemeral
StorageType StorageType `json:"storageType,omitempty"`

// Storage to specify how storage shall be used
Storage *core.PersistentVolumeClaimSpec `json:"storage,omitempty"`

ZookeeperRef *core.LocalObjectReference `json:"zookeeperRef"`

// +optional
SolrModules []string `json:"solrModules,omitempty"`

// +optional
SolrOpts []string `json:"solrOpts,omitempty"`

// To enable ssl for http layer
EnableSSL bool `json:"enableSSL,omitempty"`

// TLS contains tls configurations for client and server.
// +optional
TLS *kmapi.TLSConfig `json:"tls,omitempty"`

// Disable security. It disables authentication security of users.
// If unset, default is false
// +optional
DisableSecurity bool `json:"disableSecurity,omitempty"`

// +optional
ConfigSecret *core.LocalObjectReference `json:"configSecret,omitempty"`

// +optional
AuthSecret *core.LocalObjectReference `json:"authSecret,omitempty"`

// +optional
AuthConfigSecret *core.LocalObjectReference `json:"authConfigSecret,omitempty"`

// PodTemplate is an optional configuration for pods used to expose database
// +optional
PodTemplate ofst.PodTemplateSpec `json:"podTemplate,omitempty"`

// ServiceTemplates is an optional configuration for services used to expose database
// +optional
ServiceTemplates []NamedServiceTemplateSpec `json:"serviceTemplates,omitempty"`

// TerminationPolicy controls the delete operation for database
// +optional
TerminationPolicy TerminationPolicy `json:"terminationPolicy,omitempty"`

// HealthChecker defines attributes of the health checker
// +optional
// +kubebuilder:default={periodSeconds: 20, timeoutSeconds: 10, failureThreshold: 3}
HealthChecker kmapi.HealthCheckSpec `json:"healthChecker"`
}

type SolrClusterTopology struct {
Overseer *SolrNode `json:"overseer,omitempty"`
Data *SolrNode `json:"data,omitempty"`
Coordinator *SolrNode `json:"coordinator,omitempty"`
}

type SolrNode struct {
// Replica represents number of replica for this specific type of nodes
// +optional
Replicas *int32 `json:"replicas,omitempty"`

// suffix to append with node name
// +optional
Suffix string `json:"suffix,omitempty"`

// Storage to specify how storage shall be used.
// +optional
Storage *core.PersistentVolumeClaimSpec `json:"storage,omitempty"`

// PodTemplate is an optional configuration for pods used to expose database
// +optional
PodTemplate ofst.PodTemplateSpec `json:"podTemplate,omitempty"`

// NodeSelector is a selector which must be true for the pod to fit on a node.
// Selector which must match a node's labels for the pod to be scheduled on that node.
// More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
// +optional
// +mapType=atomic
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
// If specified, the pod's tolerations.
// +optional
Tolerations []core.Toleration `json:"tolerations,omitempty"`
}

// SolrStatus defines the observed state of Solr
type SolrStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Specifies the current phase of the database
// +optional
Phase DatabasePhase `json:"phase,omitempty"`
// observedGeneration is the most recent generation observed for this resource. It corresponds to the
// resource's generation, which is updated on mutation by the API Server.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Conditions applied to the database, such as approval or denial.
// +optional
Conditions []kmapi.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:validation:Enum=overseer;data;coordinator;combined
type SolrNodeRoleType string

const (
SolrNodeRoleOverseer SolrNodeRoleType = "overseer"
SolrNodeRoleData SolrNodeRoleType = "data"
SolrNodeRoleCoordinator SolrNodeRoleType = "coordinator"
SolrNodeRoleSet = "set"
)

//+kubebuilder:object:root=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// SolrList contains a list of Solr
type SolrList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Solr `json:"items"`
}
363 changes: 363 additions & 0 deletions vendor/kubedb.dev/apimachinery/apis/kubedb/v1alpha2/solr_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
/*
Copyright 2023.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
"context"
"errors"
"fmt"
"strings"

catalog "kubedb.dev/apimachinery/apis/catalog/v1alpha1"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/klog/v2"
ofst "kmodules.xyz/offshoot-api/api/v2"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// log is for logging in this package.
var solrlog = logf.Log.WithName("solr-resource")

var _ webhook.Defaulter = &Solr{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (s *Solr) Default() {
if s == nil {
return
}
solrlog.Info("default", "name", s.Name)

slVersion := catalog.SolrVersion{}
err := DefaultClient.Get(context.TODO(), types.NamespacedName{Name: s.Spec.Version}, &slVersion)
if err != nil {
klog.Errorf("Version does not exist.")
return
}

s.SetDefaults(&slVersion)
}

var _ webhook.Validator = &Solr{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (s *Solr) ValidateCreate() (admission.Warnings, error) {
solrlog.Info("validate create", "name", s.Name)

allErr := s.ValidateCreateOrUpdate()
if len(allErr) == 0 {
return nil, nil
}
return nil, apierrors.NewInvalid(schema.GroupKind{Group: "kubedb.com", Kind: "Solr"}, s.Name, allErr)
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (s *Solr) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
solrlog.Info("validate update", "name", s.Name)

_ = old.(*Solr)
allErr := s.ValidateCreateOrUpdate()

if len(allErr) == 0 {
return nil, nil
}

return nil, apierrors.NewInvalid(schema.GroupKind{Group: "kubedb.com", Kind: "Solr"}, s.Name, allErr)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (s *Solr) ValidateDelete() (admission.Warnings, error) {
solrlog.Info("validate delete", "name", s.Name)

var allErr field.ErrorList
if s.Spec.TerminationPolicy == TerminationPolicyDoNotTerminate {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("terminationPolicy"),
s.Name,
"Can not delete as terminationPolicy is set to \"DoNotTerminate\""))
return nil, apierrors.NewInvalid(schema.GroupKind{Group: "kubedb.com", Kind: "Solr"}, s.Name, allErr)
}
return nil, nil
}

var solrReservedVolumes = []string{
SolrVolumeConfig,
SolrVolumeDefaultConfig,
SolrVolumeCustomConfig,
SolrVolumeAuthConfig,
}

var solrReservedVolumeMountPaths = []string{
SolrHomeDir,
SolrDataDir,
SolrCustomConfigDir,
SolrSecurityConfigDir,
SolrTempConfigDir,
}

var solrAvailableModules = []string{
"analysis-extras", "extraction", "hdfs", "langid", "prometheus-exporter", "sql",
"analytics", "gcs-repository", "jaegertracer-configurator", "ltr", "s3-repository",
"clustering", "hadoop-auth", "jwt-auth", "opentelemetry", "scripting",
}

func (s *Solr) ValidateCreateOrUpdate() field.ErrorList {
var allErr field.ErrorList

if s.Spec.EnableSSL {
if s.Spec.TLS == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("enableSSL"),
s.Name,
".spec.tls can't be nil, if .spec.enableSSL is true"))
}
} else {
if s.Spec.TLS != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("enableSSL"),
s.Name,
".spec.tls must be nil, if .spec.enableSSL is disabled"))
}
}

if s.Spec.Version == "" {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("version"),
s.Name,
"spec.version' is missing"))
} else {
err := solrValidateVersion(s)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("version"),
s.Name,
err.Error()))
}
}

err := solrValidateModules(s)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("solrmodules"),
s.Name,
err.Error()))
}

if s.Spec.Topology == nil {
if s.Spec.Replicas != nil && *s.Spec.Replicas <= 0 {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("replicas"),
s.Name,
"number of replicas can not be less be 0 or less"))
}
err := solrValidateVolumes(&s.Spec.PodTemplate)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("podTemplate").Child("spec").Child("volumes"),
s.Name,
err.Error()))
}
err = solrValidateVolumesMountPaths(&s.Spec.PodTemplate)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("podTemplate").Child("spec").Child("containers"),
s.Name,
err.Error()))
}

} else {
if s.Spec.Topology.Data == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("data"),
s.Name,
".spec.topology.data can't be empty in cluster mode"))
}
if s.Spec.Topology.Data.Replicas != nil && *s.Spec.Topology.Data.Replicas <= 0 {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("data").Child("replicas"),
s.Name,
"number of replicas can not be less be 0 or less"))
}
err := solrValidateVolumes(&s.Spec.Topology.Data.PodTemplate)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("data").Child("podTemplate").Child("spec").Child("volumes"),
s.Name,
err.Error()))
}
err = solrValidateVolumesMountPaths(&s.Spec.Topology.Data.PodTemplate)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("data").Child("podTemplate").Child("spec").Child("containers"),
s.Name,
err.Error()))
}

if s.Spec.Topology.Overseer == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("overseer"),
s.Name,
".spec.topology.overseer can't be empty in cluster mode"))
}
if s.Spec.Topology.Overseer.Replicas != nil && *s.Spec.Topology.Overseer.Replicas <= 0 {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("overseer").Child("replicas"),
s.Name,
"number of replicas can not be less be 0 or less"))
}
err = solrValidateVolumes(&s.Spec.Topology.Overseer.PodTemplate)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("overseer").Child("podTemplate").Child("spec").Child("volumes"),
s.Name,
err.Error()))
}
err = solrValidateVolumesMountPaths(&s.Spec.Topology.Overseer.PodTemplate)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("overseer").Child("podTemplate").Child("spec").Child("containers"),
s.Name,
err.Error()))
}

if s.Spec.Topology.Coordinator == nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("coordinator"),
s.Name,
".spec.topology.coordinator can't be empty in cluster mode"))
}
if s.Spec.Topology.Coordinator.Replicas != nil && *s.Spec.Topology.Coordinator.Replicas <= 0 {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("coordinator").Child("replicas"),
s.Name,
"number of replicas can not be less be 0 or less"))
}
err = solrValidateVolumes(&s.Spec.Topology.Coordinator.PodTemplate)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("coordinator").Child("podTemplate").Child("spec").Child("volumes"),
s.Name,
err.Error()))
}
err = solrValidateVolumesMountPaths(&s.Spec.Topology.Coordinator.PodTemplate)
if err != nil {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("topology").Child("coordinator").Child("podTemplate").Child("spec").Child("containers"),
s.Name,
err.Error()))
}
}

if s.Spec.StorageType == "" {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("storageType"),
s.Name,
"StorageType can not be empty"))
} else {
if s.Spec.StorageType != StorageTypeDurable && s.Spec.StorageType != StorageTypeEphemeral {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("storageType"),
s.Name,
"StorageType should be either durable or ephemeral"))
}
}

for _, x := range s.Spec.SolrOpts {
if strings.Count(x, " ") > 0 {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("solropts"),
s.Name,
"solropt jvm env variables must not contain space"))
}
if x[0] != '-' || x[1] != 'D' {
allErr = append(allErr, field.Invalid(field.NewPath("spec").Child("solropts"),
s.Name,
"solropt jvm env variables must start with -D"))
}
}

if len(allErr) == 0 {
return nil
}
return allErr
}

func solrValidateVersion(s *Solr) error {
slVersion := catalog.SolrVersion{}
err := DefaultClient.Get(context.TODO(), types.NamespacedName{Name: s.Spec.Version}, &slVersion)
if err != nil {
return errors.New("version not supported")
}
return nil
}

func solrValidateModules(s *Solr) error {
modules := s.Spec.SolrModules
for _, mod := range modules {
fl := false
for _, av_mod := range solrAvailableModules {
if mod == av_mod {
fl = true
break
}
}
if !fl {
return fmt.Errorf("%s does not exist in available modules", mod)
}
}
return nil
}

func solrValidateVolumes(podTemplate *ofst.PodTemplateSpec) error {
if podTemplate == nil {
return nil
}
if podTemplate.Spec.Volumes == nil {
return nil
}

for _, rv := range solrReservedVolumes {
for _, ugv := range podTemplate.Spec.Volumes {
if ugv.Name == rv {
return errors.New("Can't use a reserve volume name: " + rv)
}
}
}

return nil
}

func solrValidateVolumesMountPaths(podTemplate *ofst.PodTemplateSpec) error {
if podTemplate == nil {
return nil
}
if podTemplate.Spec.Containers == nil {
return nil
}

for _, rvmp := range solrReservedVolumeMountPaths {
containerList := podTemplate.Spec.Containers
for i := range containerList {
mountPathList := containerList[i].VolumeMounts
for j := range mountPathList {
if mountPathList[j].MountPath == rvmp {
return errors.New("Can't use a reserve volume mount path name: " + rvmp)
}
}
}
}

if podTemplate.Spec.InitContainers == nil {
return nil
}

for _, rvmp := range solrReservedVolumeMountPaths {
containerList := podTemplate.Spec.InitContainers
for i := range containerList {
mountPathList := containerList[i].VolumeMounts
for j := range mountPathList {
if mountPathList[j].MountPath == rvmp {
return errors.New("Can't use a reserve volume mount path name: " + rvmp)
}
}
}
}

return nil
}
Original file line number Diff line number Diff line change
@@ -28,7 +28,9 @@ import (
"gomodules.xyz/pointer"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
appslister "k8s.io/client-go/listers/apps/v1"
"k8s.io/klog/v2"
"kmodules.xyz/client-go/apiextensions"
meta_util "kmodules.xyz/client-go/meta"
@@ -256,3 +258,9 @@ func (z *ZooKeeper) GetConnectionScheme() string {
//}
return scheme
}

func (z *ZooKeeper) ReplicasAreReady(lister appslister.StatefulSetLister) (bool, string, error) {
// Desire number of statefulSets
expectedItems := 1
return checkReplicas(lister.StatefulSets(z.Namespace), labels.SelectorFromSet(z.OffshootLabels()), expectedItems)
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -61,35 +61,12 @@ spec:
required:
- image
type: object
podSecurityPolicies:
properties:
databasePolicyName:
type: string
required:
- databasePolicyName
type: object
securityContext:
properties:
runAsAnyNonRoot:
type: boolean
runAsGroup:
format: int64
type: integer
runAsUser:
format: int64
type: integer
type: object
updateConstraints:
properties:
allowlist:
items:
type: string
type: array
denylist:
items:
type: string
type: array
type: object
version:
type: string
required:
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
creationTimestamp: null
labels:
app.kubernetes.io/name: kubedb
name: ferretdbversions.catalog.kubedb.com
spec:
group: catalog.kubedb.com
names:
categories:
- datastore
- kubedb
- appscode
kind: FerretDBVersion
listKind: FerretDBVersionList
plural: ferretdbversions
shortNames:
- frversion
singular: ferretdbversion
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .spec.version
name: Version
type: string
- jsonPath: .spec.db.image
name: DB_IMAGE
type: string
- jsonPath: .spec.deprecated
name: Deprecated
type: boolean
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
spec:
properties:
db:
properties:
image:
type: string
required:
- image
type: object
deprecated:
type: boolean
securityContext:
properties:
runAsUser:
format: int64
type: integer
type: object
updateConstraints:
properties:
allowlist:
items:
type: string
type: array
denylist:
items:
type: string
type: array
type: object
version:
type: string
required:
- db
- version
type: object
type: object
served: true
storage: true
subresources: {}
Original file line number Diff line number Diff line change
@@ -54,13 +54,6 @@ spec:
type: object
deprecated:
type: boolean
podSecurityPolicies:
properties:
databasePolicyName:
type: string
required:
- databasePolicyName
type: object
securityContext:
properties:
runAsUser:
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
creationTimestamp: null
labels:
app.kubernetes.io/name: kubedb
name: rabbitmqversions.catalog.kubedb.com
spec:
group: catalog.kubedb.com
names:
categories:
- datastore
- kubedb
- appscode
kind: RabbitMQVersion
listKind: RabbitMQVersionList
plural: rabbitmqversions
shortNames:
- rmversion
singular: rabbitmqversion
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .spec.version
name: Version
type: string
- jsonPath: .spec.db.image
name: DB_IMAGE
type: string
- jsonPath: .spec.deprecated
name: Deprecated
type: boolean
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
spec:
properties:
db:
properties:
image:
type: string
required:
- image
type: object
deprecated:
type: boolean
initContainer:
properties:
image:
type: string
required:
- image
type: object
securityContext:
properties:
runAsUser:
format: int64
type: integer
type: object
version:
type: string
required:
- db
- initContainer
- version
type: object
type: object
served: true
storage: true
subresources: {}
Original file line number Diff line number Diff line change
@@ -68,13 +68,6 @@ spec:
required:
- image
type: object
podSecurityPolicies:
properties:
databasePolicyName:
type: string
required:
- databasePolicyName
type: object
securityContext:
properties:
runAsGroup:
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
creationTimestamp: null
labels:
app.kubernetes.io/name: kubedb
name: solrversions.catalog.kubedb.com
spec:
group: catalog.kubedb.com
names:
categories:
- datastore
- kubedb
- appscode
kind: SolrVersion
listKind: SolrVersionList
plural: solrversions
shortNames:
- slversion
singular: solrversion
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .spec.version
name: Version
type: string
- jsonPath: .spec.db.image
name: DB_IMAGE
type: string
- jsonPath: .spec.deprecated
name: Deprecated
type: boolean
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
spec:
properties:
db:
properties:
image:
type: string
required:
- image
type: object
deprecated:
type: boolean
initContainer:
properties:
image:
type: string
required:
- image
type: object
securityContext:
properties:
runAsUser:
format: int64
type: integer
type: object
version:
type: string
required:
- db
- initContainer
- version
type: object
type: object
served: true
storage: true
subresources: {}
3,972 changes: 3,972 additions & 0 deletions vendor/kubedb.dev/apimachinery/crds/kubedb.com_ferretdbs.yaml

Large diffs are not rendered by default.

6,428 changes: 4,083 additions & 2,345 deletions vendor/kubedb.dev/apimachinery/crds/kubedb.com_kafkas.yaml

Large diffs are not rendered by default.

3,756 changes: 3,756 additions & 0 deletions vendor/kubedb.dev/apimachinery/crds/kubedb.com_rabbitmqs.yaml

Large diffs are not rendered by default.

44 changes: 0 additions & 44 deletions vendor/kubedb.dev/apimachinery/crds/kubedb.com_singlestores.yaml
Original file line number Diff line number Diff line change
@@ -55,8 +55,6 @@ spec:
type: string
type: object
x-kubernetes-map-type: atomic
disableSecurity:
type: boolean
enableSSL:
type: boolean
halted:
@@ -4682,11 +4680,6 @@ spec:
properties:
aggregator:
properties:
nodeSelector:
additionalProperties:
type: string
type: object
x-kubernetes-map-type: atomic
podTemplate:
properties:
controller:
@@ -8130,30 +8123,9 @@ spec:
type: object
suffix:
type: string
tolerations:
items:
properties:
effect:
type: string
key:
type: string
operator:
type: string
tolerationSeconds:
format: int64
type: integer
value:
type: string
type: object
type: array
type: object
leaf:
properties:
nodeSelector:
additionalProperties:
type: string
type: object
x-kubernetes-map-type: atomic
podTemplate:
properties:
controller:
@@ -11597,22 +11569,6 @@ spec:
type: object
suffix:
type: string
tolerations:
items:
properties:
effect:
type: string
key:
type: string
operator:
type: string
tolerationSeconds:
format: int64
type: integer
value:
type: string
type: object
type: array
type: object
type: object
version:
14,180 changes: 14,180 additions & 0 deletions vendor/kubedb.dev/apimachinery/crds/kubedb.com_solrs.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -984,6 +984,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -223,6 +223,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -419,6 +419,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -375,6 +375,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -224,6 +224,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -856,6 +856,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -371,6 +371,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -375,6 +375,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -220,6 +220,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -448,6 +448,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -336,6 +336,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -2884,6 +2884,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
Original file line number Diff line number Diff line change
@@ -2836,6 +2836,21 @@ spec:
observedGeneration:
format: int64
type: integer
pausedBackups:
items:
properties:
apiGroup:
type: string
kind:
type: string
name:
type: string
namespace:
type: string
required:
- name
type: object
type: array
phase:
enum:
- Pending
2 changes: 1 addition & 1 deletion vendor/modules.txt
Original file line number Diff line number Diff line change
@@ -1402,7 +1402,7 @@ kmodules.xyz/monitoring-agent-api/api/v1
## explicit; go 1.21.5
kmodules.xyz/offshoot-api/api/v1
kmodules.xyz/offshoot-api/api/v2
# kubedb.dev/apimachinery v0.41.0-beta.0.0.20240118063916-9d6870498d68
# kubedb.dev/apimachinery v0.41.0-beta.1.0.20240124061503-ce4799bb0e5c
## explicit; go 1.21.5
kubedb.dev/apimachinery/apis
kubedb.dev/apimachinery/apis/catalog

0 comments on commit 7b66378

Please sign in to comment.