diff --git a/Makefile b/Makefile index 14795f8..9ef3ba8 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) rbac:roleName=plugin-barman-cloud crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 396ce20..6780b95 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -29,7 +29,7 @@ func (in *ObjectStore) DeepCopyInto(out *ObjectStore) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +86,7 @@ func (in *ObjectStoreList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ObjectStoreSpec) DeepCopyInto(out *ObjectStoreSpec) { *out = *in + in.Configuration.DeepCopyInto(&out.Configuration) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreSpec. diff --git a/cmd/operator/main.go b/cmd/operator/main.go index 44b5ad9..e40128d 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -2,17 +2,14 @@ package main import ( - "context" "fmt" "os" "github.com/cloudnative-pg/machinery/pkg/log" - "github.com/sourcegraph/conc/pool" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator" - "github.com/cloudnative-pg/plugin-barman-cloud/internal/operator/manager" ) func main() { @@ -20,7 +17,15 @@ func main() { logFlags := &log.Flags{} rootCmd := &cobra.Command{ - Use: "plugin-barman-cloud", + Use: "plugin-barman-cloud", + Short: "Starts the BarmanObjectStore reconciler and the Barman Cloud CNPG-i plugin", + RunE: func(cmd *cobra.Command, _ []string) error { + if len(viper.GetString("sidecar-image")) == 0 { + return fmt.Errorf("missing required SIDECAR_IMAGE environment variable") + } + + return operator.Start(cmd.Context()) + }, PersistentPreRunE: func(_ *cobra.Command, _ []string) error { logFlags.ConfigureLogging() return nil @@ -28,61 +33,66 @@ func main() { } logFlags.AddFlags(rootCmd.PersistentFlags()) - rootCmd.AddCommand(newOperatorCommand()) + + rootCmd.Flags().String("metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ + "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") + _ = viper.BindPFlag("metrics-bind-address", rootCmd.Flags().Lookup("metrics-bind-address")) + + rootCmd.Flags().String("health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + _ = viper.BindPFlag("health-probe-bind-address", rootCmd.Flags().Lookup("health-probe-bind-address")) + + rootCmd.Flags().Bool("leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + _ = viper.BindPFlag("leader-elect", rootCmd.Flags().Lookup("leader-elect")) + + rootCmd.Flags().Bool("metrics-secure", true, + "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") + _ = viper.BindPFlag("metrics-secure", rootCmd.Flags().Lookup("metrics-secure")) + + rootCmd.Flags().Bool("enable-http2", false, + "If set, HTTP/2 will be enabled for the metrics and webhook servers") + _ = viper.BindPFlag("enable-http2", rootCmd.Flags().Lookup("enable-http2")) + + rootCmd.Flags().String( + "plugin-path", + "", + "The plugins socket path", + ) + _ = viper.BindPFlag("plugin-path", rootCmd.Flags().Lookup("plugin-path")) + + rootCmd.Flags().String( + "server-cert", + "", + "The public key to be used for the server process", + ) + _ = viper.BindPFlag("server-cert", rootCmd.Flags().Lookup("server-cert")) + + rootCmd.Flags().String( + "server-key", + "", + "The key to be used for the server process", + ) + _ = viper.BindPFlag("server-key", rootCmd.Flags().Lookup("server-key")) + + rootCmd.Flags().String( + "client-cert", + "", + "The client public key to verify the connection", + ) + _ = viper.BindPFlag("client-cert", rootCmd.Flags().Lookup("client-cert")) + + rootCmd.Flags().String( + "server-address", + "", + "The address where to listen (i.e. 0:9090)", + ) + _ = viper.BindPFlag("server-address", rootCmd.Flags().Lookup("server-address")) + + _ = viper.BindEnv("sidecar-image", "SIDECAR_IMAGE") if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } - -func newOperatorCommand() *cobra.Command { - cmd := operator.NewCommand() - cmd.Use = "operator" - cmd.Short = "Starts the BarmanObjectStore reconciler and the Barman Cloud CNPG-i plugin" - grpcServer := cmd.RunE - - cmd.RunE = func(cmd *cobra.Command, args []string) error { - operatorPool := pool. - New(). - WithContext(cmd.Context()). - WithCancelOnError(). - WithFirstError() - operatorPool.Go(func(ctx context.Context) error { - cmd.SetContext(ctx) - - if len(viper.GetString("sidecar-image")) == 0 { - return fmt.Errorf("missing required SIDECAR_IMAGE environment variable") - } - - err := grpcServer(cmd, args) - return err - }) - operatorPool.Go(manager.Start) - return operatorPool.Wait() - } - - cmd.Flags().String("metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ - "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") - _ = viper.BindPFlag("metrics-bind-address", cmd.Flags().Lookup("metrics-bind-address")) - - cmd.Flags().String("health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - _ = viper.BindPFlag("health-probe-bind-address", cmd.Flags().Lookup("health-probe-bind-address")) - - cmd.Flags().Bool("leader-elect", false, - "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") - _ = viper.BindPFlag("leader-elect", cmd.Flags().Lookup("leader-elect")) - - cmd.Flags().Bool("metrics-secure", true, - "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") - _ = viper.BindPFlag("metrics-secure", cmd.Flags().Lookup("metrics-secure")) - - cmd.Flags().Bool("enable-http2", false, - "If set, HTTP/2 will be enabled for the metrics and webhook servers") - _ = viper.BindPFlag("enable-http2", cmd.Flags().Lookup("enable-http2")) - - _ = viper.BindEnv("sidecar-image", "SIDECAR_IMAGE") - - return cmd -} diff --git a/config/crd/bases/barmancloud.cnpg.io_objectstores.yaml b/config/crd/bases/barmancloud.cnpg.io_objectstores.yaml index 63f8c2a..5e0d8cd 100644 --- a/config/crd/bases/barmancloud.cnpg.io_objectstores.yaml +++ b/config/crd/bases/barmancloud.cnpg.io_objectstores.yaml @@ -17,7 +17,7 @@ spec: - name: v1 schema: openAPIV3Schema: - description: ObjectStore is the Schema for the objectstores API + description: ObjectStore is the Schema for the objectstores API. properties: apiVersion: description: |- @@ -37,7 +37,7 @@ spec: metadata: type: object spec: - description: ObjectStoreSpec defines the desired state of ObjectStore + description: ObjectStoreSpec defines the desired state of ObjectStore. properties: configuration: description: |- @@ -382,7 +382,7 @@ spec: - configuration type: object status: - description: ObjectStoreStatus defines the observed state of ObjectStore + description: ObjectStoreStatus defines the observed state of ObjectStore. type: object type: object served: true diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 4ade04d..78bfda0 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -2,7 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: manager-role + name: plugin-barman-cloud rules: - apiGroups: - barmancloud.cnpg.io @@ -30,3 +30,15 @@ rules: - get - patch - update +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + - roles + verbs: + - create + - get + - list + - patch + - update + - watch diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index 99e9a5e..3e03bda 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -4,11 +4,11 @@ metadata: labels: app.kubernetes.io/name: plugin-barman-cloud app.kubernetes.io/managed-by: kustomize - name: manager-rolebinding + name: plugin-barman-cloud-binding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: manager-role + name: plugin-barman-cloud subjects: - kind: ServiceAccount name: plugin-barman-cloud diff --git a/config/samples/barmancloud_v1_objectstore.yaml b/config/samples/barmancloud_v1_objectstore.yaml deleted file mode 100644 index 70e982f..0000000 --- a/config/samples/barmancloud_v1_objectstore.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: barmancloud.cnpg.io/v1 -kind: ObjectStore -metadata: - labels: - app.kubernetes.io/name: plugin-barman-cloud - app.kubernetes.io/managed-by: kustomize - name: objectstore-sample -spec: - # TODO(user): Add fields here diff --git a/docs/examples/cluster-example.yaml b/docs/examples/cluster-example.yaml index 33983b8..de6c774 100644 --- a/docs/examples/cluster-example.yaml +++ b/docs/examples/cluster-example.yaml @@ -8,7 +8,7 @@ spec: plugins: - name: barman-cloud.cloudnative-pg.io parameters: - barmanObjectStore: minio-store + barmanObjectName: minio-store storage: size: 1Gi diff --git a/docs/examples/minio-store.yaml b/docs/examples/minio-store.yaml new file mode 100644 index 0000000..0193713 --- /dev/null +++ b/docs/examples/minio-store.yaml @@ -0,0 +1,23 @@ +apiVersion: barmancloud.cnpg.io/v1 +kind: ObjectStore +metadata: + name: minio-store +spec: + configuration: + destinationPath: s3://backups/ + endpointURL: http://minio:9000 + s3Credentials: + accessKeyId: + name: minio + key: ACCESS_KEY_ID + secretAccessKey: + name: minio + key: ACCESS_SECRET_KEY + wal: + compression: gzip + data: + additionalCommandArgs: + - "--min-chunk-size=5MB" + - "--read-timeout=60" + - "-vv" + diff --git a/docs/minio/minio-client.yaml b/docs/minio/minio-client.yaml new file mode 100644 index 0000000..4d8ebb8 --- /dev/null +++ b/docs/minio/minio-client.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + run: mc + name: mc +spec: + containers: + - env: + - name: MC_HOST_minio + value: http://chooJeiroroo2noquomei2uuceisheth:ongeiqueitohL0queeLohkiur2quaing@minio:9000 + image: minio/mc + name: mc + resources: {} + # Keep the pod up to exec stuff on it + command: + - sleep + - "3600" + dnsPolicy: ClusterFirst + restartPolicy: Always diff --git a/docs/minio/minio-delete.sh b/docs/minio/minio-delete.sh new file mode 100644 index 0000000..c098b3c --- /dev/null +++ b/docs/minio/minio-delete.sh @@ -0,0 +1 @@ +kubectl exec -ti mc -- mc rm -r --force minio/backups diff --git a/docs/minio/minio-deployment.yaml b/docs/minio/minio-deployment.yaml new file mode 100644 index 0000000..315ea6e --- /dev/null +++ b/docs/minio/minio-deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: minio + labels: + app: minio +spec: + replicas: 1 + selector: + matchLabels: + app: minio + template: + metadata: + labels: + app: minio + spec: + containers: + - name: minio + image: minio/minio + ports: + - containerPort: 9000 + volumeMounts: + - mountPath: /data + name: data + args: + - server + - /data + env: + - name: MINIO_ACCESS_KEY + valueFrom: + secretKeyRef: + name: minio + key: ACCESS_KEY_ID + - name: MINIO_SECRET_KEY + valueFrom: + secretKeyRef: + name: minio + key: ACCESS_SECRET_KEY + volumes: + - name: data + persistentVolumeClaim: + claimName: minio diff --git a/docs/minio/minio-pvc.yaml b/docs/minio/minio-pvc.yaml new file mode 100644 index 0000000..6a402cf --- /dev/null +++ b/docs/minio/minio-pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: minio +spec: + accessModes: + - ReadWriteOnce + volumeMode: Filesystem + resources: + requests: + storage: 1Gi diff --git a/docs/minio/minio-secret.yaml b/docs/minio/minio-secret.yaml new file mode 100644 index 0000000..0189d9a --- /dev/null +++ b/docs/minio/minio-secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + ACCESS_KEY_ID: Y2hvb0plaXJvcm9vMm5vcXVvbWVpMnV1Y2Vpc2hldGg= + ACCESS_SECRET_KEY: b25nZWlxdWVpdG9oTDBxdWVlTG9oa2l1cjJxdWFpbmc= +kind: Secret +metadata: + name: minio diff --git a/docs/minio/minio-service.yaml b/docs/minio/minio-service.yaml new file mode 100644 index 0000000..c2b356b --- /dev/null +++ b/docs/minio/minio-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: minio +spec: + selector: + app: minio + ports: + - protocol: TCP + port: 9000 + targetPort: 9000 diff --git a/go.mod b/go.mod index 732001d..01a96c5 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/cloudnative-pg/machinery v0.0.0-20241001075747-34c8797af80f github.com/onsi/ginkgo/v2 v2.20.2 github.com/onsi/gomega v1.34.2 - github.com/sourcegraph/conc v0.3.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 google.golang.org/grpc v1.67.1 @@ -80,6 +79,7 @@ require ( github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/snorwin/jsonpatch v1.5.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/internal/cnpgi/operator/config/config.go b/internal/cnpgi/operator/config/config.go new file mode 100644 index 0000000..830e52e --- /dev/null +++ b/internal/cnpgi/operator/config/config.go @@ -0,0 +1,71 @@ +package config + +import ( + "strings" + + cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" + + "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata" +) + +// ConfigurationError represents a mistake in the plugin configuration +type ConfigurationError struct { + messages []string +} + +// Error implements the error interface +func (e *ConfigurationError) Error() string { + return strings.Join(e.messages, ",") +} + +// NewConfigurationError creates a new empty configuration error +func NewConfigurationError() *ConfigurationError { + return &ConfigurationError{} +} + +// WithMessage adds a new error message to a potentially empty +// ConfigurationError +func (e *ConfigurationError) WithMessage(msg string) *ConfigurationError { + if e == nil { + return &ConfigurationError{ + messages: []string{msg}, + } + } + + return &ConfigurationError{ + messages: append(e.messages, msg), + } +} + +// IsEmpty returns true if there's no error messages +func (e *ConfigurationError) IsEmpty() bool { + return len(e.messages) == 0 +} + +// PluginConfiguration is the configuration of the plugin +type PluginConfiguration struct { + BarmanObjectName string +} + +// NewFromCluster extracts the configuration from the cluster +func NewFromCluster(cluster *cnpgv1.Cluster) (*PluginConfiguration, error) { + helper := common.NewPlugin( + *cluster, + metadata.PluginName, + ) + + result := &PluginConfiguration{ + BarmanObjectName: helper.Parameters["barmanObjectName"], + } + + err := NewConfigurationError() + if len(result.BarmanObjectName) == 0 { + err = err.WithMessage("Missing barmanObjectName parameter") + } + + if err.IsEmpty() { + return result, nil + } + return result, err +} diff --git a/internal/cnpgi/operator/config/doc.go b/internal/cnpgi/operator/config/doc.go new file mode 100644 index 0000000..703d477 --- /dev/null +++ b/internal/cnpgi/operator/config/doc.go @@ -0,0 +1,2 @@ +// Package config contains the functions to parse the plugin configuration +package config diff --git a/internal/cnpgi/operator/lifecycle.go b/internal/cnpgi/operator/lifecycle.go index 8781cdd..3c44eb7 100644 --- a/internal/cnpgi/operator/lifecycle.go +++ b/internal/cnpgi/operator/lifecycle.go @@ -4,7 +4,6 @@ import ( "context" "errors" - "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/decoder" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/object" "github.com/cloudnative-pg/cnpg-i/pkg/lifecycle" @@ -12,7 +11,7 @@ import ( "github.com/spf13/viper" corev1 "k8s.io/api/core/v1" - "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata" + "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config" ) // LifecycleImplementation is the implementation of the lifecycle handler @@ -65,12 +64,10 @@ func (impl LifecycleImplementation) LifecycleHook( return nil, err } - helper := common.NewPlugin( - *cluster, - metadata.PluginName, - ) - - // TODO: Validation of the plugin configuration + pluginConfiguration, err := config.NewFromCluster(cluster) + if err != nil { + return nil, err + } mutatedPod := pod.DeepCopy() err = object.InjectPluginSidecar(mutatedPod, &corev1.Container{ @@ -79,7 +76,7 @@ func (impl LifecycleImplementation) LifecycleHook( Env: []corev1.EnvVar{ { Name: "BARMAN_OBJECT_NAME", - Value: helper.Parameters["barmanObjectStore"], + Value: pluginConfiguration.BarmanObjectName, }, { // TODO: should we really use this one? diff --git a/internal/operator/manager/manager.go b/internal/cnpgi/operator/manager.go similarity index 90% rename from internal/operator/manager/manager.go rename to internal/cnpgi/operator/manager.go index ad37848..f7ca78a 100644 --- a/internal/operator/manager/manager.go +++ b/internal/cnpgi/operator/manager.go @@ -14,15 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package manager contains the implementation of the ObjectStore controller manager -package manager +package operator import ( "context" "crypto/tls" - "flag" // +kubebuilder:scaffold:imports + cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1" "github.com/cloudnative-pg/machinery/pkg/log" "github.com/spf13/viper" "k8s.io/apimachinery/pkg/runtime" @@ -30,13 +29,12 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/metrics/filters" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1" - "github.com/cloudnative-pg/plugin-barman-cloud/internal/operator/controller" + "github.com/cloudnative-pg/plugin-barman-cloud/internal/controller" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. @@ -47,8 +45,8 @@ var scheme = runtime.NewScheme() func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - utilruntime.Must(barmancloudv1.AddToScheme(scheme)) + utilruntime.Must(cnpgv1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -58,14 +56,6 @@ func Start(ctx context.Context) error { var tlsOpts []func(*tls.Config) - opts := zap.Options{ - Development: true, - } - opts.BindFlags(flag.CommandLine) - flag.Parse() - - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - // if the enable-http2 flag is false (the default), http/2 should be disabled // due to its vulnerabilities. More specifically, disabling http/2 will // prevent from being vulnerable to the HTTP/2 Stream Cancellation and @@ -151,6 +141,18 @@ func Start(ctx context.Context) error { return err } + if err := mgr.Add(&CNPGI{ + Client: mgr.GetClient(), + PluginPath: viper.GetString("plugin-path"), + ServerCertPath: viper.GetString("server-cert"), + ServerKeyPath: viper.GetString("server-key"), + ClientCertPath: viper.GetString("client-cert"), + ServerAddress: viper.GetString("server-address"), + }); err != nil { + setupLog.Error(err, "unable to create CNPGI runnable") + return err + } + setupLog.Info("starting manager") if err := mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running manager") diff --git a/internal/cnpgi/operator/reconciler.go b/internal/cnpgi/operator/reconciler.go index b0e3063..9fabee8 100644 --- a/internal/cnpgi/operator/reconciler.go +++ b/internal/cnpgi/operator/reconciler.go @@ -2,12 +2,24 @@ package operator import ( "context" + "fmt" + cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/decoder" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/object" "github.com/cloudnative-pg/cnpg-i/pkg/reconciler" + rbacv1 "k8s.io/api/rbac/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config" ) // ReconcilerImplementation implements the Reconciler capability type ReconcilerImplementation struct { + Client client.Client reconciler.UnimplementedReconcilerHooksServer } @@ -30,9 +42,37 @@ func (r ReconcilerImplementation) GetCapabilities( // Pre implements the reconciler interface func (r ReconcilerImplementation) Pre( - _ context.Context, - _ *reconciler.ReconcilerHooksRequest, + ctx context.Context, + request *reconciler.ReconcilerHooksRequest, ) (*reconciler.ReconcilerHooksResult, error) { + reconciledKind, err := object.GetKind(request.GetResourceDefinition()) + if err != nil { + return nil, err + } + if reconciledKind != "Cluster" { + return &reconciler.ReconcilerHooksResult{ + Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE, + }, nil + } + + cluster, err := decoder.DecodeClusterJSON(request.GetResourceDefinition()) + if err != nil { + return nil, err + } + + pluginConfiguration, err := config.NewFromCluster(cluster) + if err != nil { + return nil, err + } + + if err := r.ensureRole(ctx, cluster, pluginConfiguration.BarmanObjectName); err != nil { + return nil, err + } + + if err := r.ensureRoleBinding(ctx, cluster); err != nil { + return nil, err + } + return &reconciler.ReconcilerHooksResult{ Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE, }, nil @@ -47,3 +87,126 @@ func (r ReconcilerImplementation) Post( Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE, }, nil } + +func (r ReconcilerImplementation) ensureRole( + ctx context.Context, + cluster *cnpgv1.Cluster, + barmanObjectName string, +) error { + var role rbacv1.Role + if err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: cluster.Namespace, + Name: getRBACName(cluster.Name), + }, &role); err != nil { + if apierrs.IsNotFound(err) { + return r.createRole(ctx, cluster, barmanObjectName) + } + return err + } + + // TODO: patch existing role + return nil +} + +func (r ReconcilerImplementation) ensureRoleBinding( + ctx context.Context, + cluster *cnpgv1.Cluster, +) error { + var role rbacv1.RoleBinding + if err := r.Client.Get(ctx, client.ObjectKey{ + Namespace: cluster.Namespace, + Name: getRBACName(cluster.Name), + }, &role); err != nil { + if apierrs.IsNotFound(err) { + return r.createRoleBinding(ctx, cluster) + } + return err + } + + // TODO: patch existing role binding + return nil +} + +func (r ReconcilerImplementation) createRole( + ctx context.Context, + cluster *cnpgv1.Cluster, + barmanObjectName string, +) error { + role := buildRole(cluster, barmanObjectName) + if err := ctrl.SetControllerReference(cluster, role, r.Client.Scheme()); err != nil { + return err + } + return r.Client.Create(ctx, role) +} + +func (r ReconcilerImplementation) createRoleBinding( + ctx context.Context, + cluster *cnpgv1.Cluster, +) error { + roleBinding := buildRoleBinding(cluster) + if err := ctrl.SetControllerReference(cluster, roleBinding, r.Client.Scheme()); err != nil { + return err + } + return r.Client.Create(ctx, roleBinding) +} + +func buildRole( + cluster *cnpgv1.Cluster, + barmanObjectName string, +) *rbacv1.Role { + return &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: cluster.Namespace, + Name: getRBACName(cluster.Name), + }, + + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{ + "barmancloud.cnpg.io", + }, + Verbs: []string{ + "get", + "watch", + "list", + }, + Resources: []string{ + "objectstores", + }, + ResourceNames: []string{ + barmanObjectName, + }, + }, + }, + } +} + +func buildRoleBinding( + cluster *cnpgv1.Cluster, +) *rbacv1.RoleBinding { + return &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: cluster.Namespace, + Name: getRBACName(cluster.Name), + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + APIGroup: "", + Name: cluster.Name, + Namespace: cluster.Namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: getRBACName(cluster.Name), + }, + } +} + +// getRBACName returns the name of the RBAC entities for the +// barman cloud plugin +func getRBACName(clusterName string) string { + return fmt.Sprintf("%s-barman", clusterName) +} diff --git a/internal/cnpgi/operator/start.go b/internal/cnpgi/operator/start.go index 21cd29c..3c8eff6 100644 --- a/internal/cnpgi/operator/start.go +++ b/internal/cnpgi/operator/start.go @@ -1,21 +1,45 @@ package operator import ( + "context" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/http" "github.com/cloudnative-pg/cnpg-i/pkg/lifecycle" "github.com/cloudnative-pg/cnpg-i/pkg/reconciler" - "github.com/spf13/cobra" "google.golang.org/grpc" + "sigs.k8s.io/controller-runtime/pkg/client" ) -// NewCommand creates the command to start the GRPC server +// CNPGI is the implementation of the CNPG-i server +type CNPGI struct { + Client client.Client + PluginPath string + ServerCertPath string + ServerKeyPath string + ClientCertPath string + ServerAddress string +} + +// Start starts the GRPC server // of the operator plugin -func NewCommand() *cobra.Command { - cmd := http.CreateMainCmd(IdentityImplementation{}, func(server *grpc.Server) error { - reconciler.RegisterReconcilerHooksServer(server, ReconcilerImplementation{}) +func (c *CNPGI) Start(ctx context.Context) error { + enrich := func(server *grpc.Server) error { + reconciler.RegisterReconcilerHooksServer(server, ReconcilerImplementation{ + Client: c.Client, + }) lifecycle.RegisterOperatorLifecycleServer(server, LifecycleImplementation{}) return nil - }) - cmd.Use = "plugin" - return cmd + } + + srv := http.Server{ + IdentityImpl: IdentityImplementation{}, + Enrichers: []http.ServerEnricher{enrich}, + PluginPath: c.PluginPath, + ServerCertPath: c.ServerCertPath, + ServerKeyPath: c.ServerKeyPath, + ClientCertPath: c.ClientCertPath, + ServerAddress: c.ServerAddress, + } + + return srv.Start(ctx) } diff --git a/internal/operator/controller/doc.go b/internal/controller/doc.go similarity index 100% rename from internal/operator/controller/doc.go rename to internal/controller/doc.go diff --git a/internal/operator/controller/objectstore_controller.go b/internal/controller/objectstore_controller.go similarity index 91% rename from internal/operator/controller/objectstore_controller.go rename to internal/controller/objectstore_controller.go index 6f24a1a..b16a5c4 100644 --- a/internal/operator/controller/objectstore_controller.go +++ b/internal/controller/objectstore_controller.go @@ -34,6 +34,8 @@ type ObjectStoreReconciler struct { Scheme *runtime.Scheme } +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=create;patch;update;get;list;watch +// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=create;patch;update;get;list;watch // +kubebuilder:rbac:groups=barmancloud.cnpg.io,resources=objectstores,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=barmancloud.cnpg.io,resources=objectstores/status,verbs=get;update;patch // +kubebuilder:rbac:groups=barmancloud.cnpg.io,resources=objectstores/finalizers,verbs=update diff --git a/internal/operator/controller/objectstore_controller_test.go b/internal/controller/objectstore_controller_test.go similarity index 100% rename from internal/operator/controller/objectstore_controller_test.go rename to internal/controller/objectstore_controller_test.go diff --git a/internal/operator/controller/suite_test.go b/internal/controller/suite_test.go similarity index 96% rename from internal/operator/controller/suite_test.go rename to internal/controller/suite_test.go index 2cbe841..d58ef20 100644 --- a/internal/operator/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -61,7 +61,7 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, ErrorIfCRDPathMissing: true, // The BinaryAssetsDirectory is only required if you want to run the tests directly diff --git a/kubernetes/deployment.yaml b/kubernetes/deployment.yaml index 29820bb..61900f7 100644 --- a/kubernetes/deployment.yaml +++ b/kubernetes/deployment.yaml @@ -29,7 +29,6 @@ spec: key: SIDECAR_IMAGE name: plugin-barman-cloud args: - - operator - --server-cert=/server/tls.crt - --server-key=/server/tls.key - --client-cert=/client/tls.crt diff --git a/kubernetes/kustomization.yaml b/kubernetes/kustomization.yaml index 31d8603..94f9b5d 100644 --- a/kubernetes/kustomization.yaml +++ b/kubernetes/kustomization.yaml @@ -12,8 +12,8 @@ resources: images: - name: plugin-barman-cloud newName: kind.local/github.com/cloudnative-pg/plugin-barman-cloud/cmd/operator - newTag: 634ab82d4f8b68ffada6033b7dff817bbed61a2fec8e05f9ebf74f5bedafb0dd + newTag: 7e901b38eaf33b047dcf2eb044c9c8ca85535d8041a3144d25f7e1a4690ea071 secretGenerator: - literals: - - SIDECAR_IMAGE=kind.local/github.com/cloudnative-pg/plugin-barman-cloud/cmd/instance:7173a2dcf3ce74e982ca3d35053de40fcec67b31d607a0d12547b4f9d09c535c + - SIDECAR_IMAGE=kind.local/github.com/cloudnative-pg/plugin-barman-cloud/cmd/instance:ca1fd58413940a247bc52cdb44f4a6909192d781b1767dc7ee9625368ee9d7e2 name: plugin-barman-cloud