Merge branch 'main' into dev/instance-backup

This commit is contained in:
Leonardo Cecchi 2024-10-03 17:10:55 +02:00
commit d11fabd2d3
39 changed files with 836 additions and 232 deletions

View File

@ -131,4 +131,4 @@ issues:
- test - test
exclude-files: exclude-files:
- zz_generated.* - zz_generated.*
- internal/controller/suite_test.go - internal/operator/controller/suite_test.go

View File

@ -45,7 +45,7 @@ help: ## Display this help.
.PHONY: manifests .PHONY: manifests
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 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 .PHONY: generate
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.

View File

@ -51,7 +51,7 @@ tasks:
desc: Run go test desc: Run go test
env: env:
# renovate: datasource=docker depName=golang versioning=semver # renovate: datasource=docker depName=golang versioning=semver
GOLANG_IMAGE_VERSION: 1.23.1 GOLANG_IMAGE_VERSION: 1.23.2
# renovate: datasource=git-refs depname=kubernetes packageName=https://github.com/kubernetes/kubernetes versioning=semver # renovate: datasource=git-refs depname=kubernetes packageName=https://github.com/kubernetes/kubernetes versioning=semver
K8S_VERSION: 1.31.0 K8S_VERSION: 1.31.0
# renovate: datasource=git-refs depName=controller-runtime packageName=https://github.com/kubernetes-sigs/controller-runtime versioning=semver # renovate: datasource=git-refs depName=controller-runtime packageName=https://github.com/kubernetes-sigs/controller-runtime versioning=semver

View File

@ -29,7 +29,7 @@ func (in *ObjectStore) DeepCopyInto(out *ObjectStore) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ObjectStoreSpec) DeepCopyInto(out *ObjectStoreSpec) { func (in *ObjectStoreSpec) DeepCopyInto(out *ObjectStoreSpec) {
*out = *in *out = *in
in.Configuration.DeepCopyInto(&out.Configuration)
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStoreSpec.

View File

@ -1,107 +1,58 @@
// Package main is the implementation of the CNPG-i PostgreSQL sidecar // Package main is the entrypoint of operator plugin
package main package main
import ( import (
"errors" "fmt"
"os" "os"
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1" "github.com/cloudnative-pg/machinery/pkg/log"
"k8s.io/apimachinery/pkg/fields" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime" "github.com/spf13/viper"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/instance" "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/instance"
) )
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(barmancloudv1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}
func main() { func main() {
setupLog.Info("Starting barman cloud instance plugin") cobra.EnableTraverseRunHooks = true
namespace := mustGetEnv("NAMESPACE")
boName := mustGetEnv("BARMAN_OBJECT_NAME")
clusterName := mustGetEnv("CLUSTER_NAME")
instanceName := mustGetEnv("INSTANCE_NAME")
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ logFlags := &log.Flags{}
Scheme: scheme, rootCmd := &cobra.Command{
Cache: cache.Options{ Use: "instance",
ByObject: map[client.Object]cache.ByObject{ Short: "Starts the Barman Cloud CNPG-i sidecar plugin",
&barmancloudv1.ObjectStore{}: { PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
Field: fields.OneTermEqualSelector("metadata.name", boName), logFlags.ConfigureLogging()
Namespaces: map[string]cache.Config{ return nil
namespace: {},
}, },
}, RunE: func(cmd *cobra.Command, _ []string) error {
&cnpgv1.Cluster{}: { requiredSettings := []string{
Field: fields.OneTermEqualSelector("metadata.name", clusterName), "namespace",
Namespaces: map[string]cache.Config{ "barman-object-name",
namespace: {}, "cluster-name",
}, "pod-name",
}, "spool-directory",
},
},
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
} }
if err := mgr.Add(&instance.CNPGI{ for _, k := range requiredSettings {
Client: mgr.GetClient(), if len(viper.GetString(k)) == 0 {
ClusterObjectKey: client.ObjectKey{ return fmt.Errorf("missing required %s setting", k)
Namespace: namespace,
Name: clusterName,
},
WALConfigurationKey: client.ObjectKey{
Namespace: namespace,
Name: boName,
},
InstanceName: instanceName,
// TODO: improve
PGDataPath: mustGetEnv("PGDATA"),
PGWALPath: mustGetEnv("PGWAL"),
SpoolDirectory: mustGetEnv("SPOOL_DIRECTORY"),
ServerCertPath: mustGetEnv("SERVER_CERT"),
ServerKeyPath: mustGetEnv("SERVER_KEY"),
ClientCertPath: mustGetEnv("CLIENT_CERT"),
ServerAddress: mustGetEnv("SERVER_ADDRESS"),
PluginPath: mustGetEnv("PLUGIN_PATH"),
}); err != nil {
setupLog.Error(err, "unable to create CNPGI runnable")
os.Exit(1)
}
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
} }
} }
func mustGetEnv(envName string) string { return instance.Start(cmd.Context())
value := os.Getenv(envName) },
if value == "" { }
setupLog.Error(
errors.New("missing required env variable"), logFlags.AddFlags(rootCmd.PersistentFlags())
"while fetching env variables",
"name", _ = viper.BindEnv("namespace", "NAMESPACE")
envName, _ = viper.BindEnv("barman-object-name", "BARMAN_OBJECT_NAME")
) _ = viper.BindEnv("cluster-name", "CLUSTER_NAME")
_ = viper.BindEnv("pod-name", "POD_NAME")
_ = viper.BindEnv("pgdata", "PGDATA")
_ = viper.BindEnv("spool-directory", "SPOOL_DIRECTORY")
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
return value
} }

View File

@ -2,18 +2,14 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"github.com/cloudnative-pg/machinery/pkg/log" "github.com/cloudnative-pg/machinery/pkg/log"
"github.com/sourcegraph/conc/pool"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
ctrl "sigs.k8s.io/controller-runtime"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator" "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/manager"
) )
func main() { func main() {
@ -22,6 +18,14 @@ func main() {
logFlags := &log.Flags{} logFlags := &log.Flags{}
rootCmd := &cobra.Command{ 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 { PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
logFlags.ConfigureLogging() logFlags.ConfigureLogging()
return nil return nil
@ -29,62 +33,66 @@ func main() {
} }
logFlags.AddFlags(rootCmd.PersistentFlags()) 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 { if err := rootCmd.Execute(); err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) 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 {
ctrl.SetupSignalHandler()
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
}

View File

@ -17,7 +17,7 @@ spec:
- name: v1 - name: v1
schema: schema:
openAPIV3Schema: openAPIV3Schema:
description: ObjectStore is the Schema for the objectstores API description: ObjectStore is the Schema for the objectstores API.
properties: properties:
apiVersion: apiVersion:
description: |- description: |-
@ -37,7 +37,7 @@ spec:
metadata: metadata:
type: object type: object
spec: spec:
description: ObjectStoreSpec defines the desired state of ObjectStore description: ObjectStoreSpec defines the desired state of ObjectStore.
properties: properties:
configuration: configuration:
description: |- description: |-
@ -382,7 +382,7 @@ spec:
- configuration - configuration
type: object type: object
status: status:
description: ObjectStoreStatus defines the observed state of ObjectStore description: ObjectStoreStatus defines the observed state of ObjectStore.
type: object type: object
type: object type: object
served: true served: true

View File

@ -2,8 +2,18 @@
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole kind: ClusterRole
metadata: metadata:
name: manager-role name: plugin-barman-cloud
rules: rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- delete
- get
- list
- watch
- apiGroups: - apiGroups:
- barmancloud.cnpg.io - barmancloud.cnpg.io
resources: resources:
@ -30,3 +40,15 @@ rules:
- get - get
- patch - patch
- update - update
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
- roles
verbs:
- create
- get
- list
- patch
- update
- watch

View File

@ -4,11 +4,11 @@ metadata:
labels: labels:
app.kubernetes.io/name: plugin-barman-cloud app.kubernetes.io/name: plugin-barman-cloud
app.kubernetes.io/managed-by: kustomize app.kubernetes.io/managed-by: kustomize
name: manager-rolebinding name: plugin-barman-cloud-binding
roleRef: roleRef:
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
kind: ClusterRole kind: ClusterRole
name: manager-role name: plugin-barman-cloud
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: plugin-barman-cloud name: plugin-barman-cloud

View File

@ -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

View File

@ -7,6 +7,8 @@ spec:
plugins: plugins:
- name: barman-cloud.cloudnative-pg.io - name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: minio-store
storage: storage:
size: 1Gi size: 1Gi

View File

@ -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"

View File

@ -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

View File

@ -0,0 +1 @@
kubectl exec -ti mc -- mc rm -r --force minio/backups

View File

@ -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

11
docs/minio/minio-pvc.yaml Normal file
View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: minio
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 1Gi

View File

@ -0,0 +1,7 @@
apiVersion: v1
data:
ACCESS_KEY_ID: Y2hvb0plaXJvcm9vMm5vcXVvbWVpMnV1Y2Vpc2hldGg=
ACCESS_SECRET_KEY: b25nZWlxdWVpdG9oTDBxdWVlTG9oa2l1cjJxdWFpbmc=
kind: Secret
metadata:
name: minio

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: minio
spec:
selector:
app: minio
ports:
- protocol: TCP
port: 9000
targetPort: 9000

4
go.mod
View File

@ -6,11 +6,10 @@ require (
github.com/cloudnative-pg/barman-cloud v0.0.0-20240924124724-92831d48562a github.com/cloudnative-pg/barman-cloud v0.0.0-20240924124724-92831d48562a
github.com/cloudnative-pg/cloudnative-pg v1.24.1-0.20241001084914-829808376542 github.com/cloudnative-pg/cloudnative-pg v1.24.1-0.20241001084914-829808376542
github.com/cloudnative-pg/cnpg-i v0.0.0-20240924030516-c5636170f248 github.com/cloudnative-pg/cnpg-i v0.0.0-20240924030516-c5636170f248
github.com/cloudnative-pg/cnpg-i-machinery v0.0.0-20241001135556-db88a95a39eb github.com/cloudnative-pg/cnpg-i-machinery v0.0.0-20241002070940-e5495e9c5ed6
github.com/cloudnative-pg/machinery v0.0.0-20241001075747-34c8797af80f github.com/cloudnative-pg/machinery v0.0.0-20241001075747-34c8797af80f
github.com/onsi/ginkgo/v2 v2.20.2 github.com/onsi/ginkgo/v2 v2.20.2
github.com/onsi/gomega v1.34.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/cobra v1.8.1
github.com/spf13/viper v1.19.0 github.com/spf13/viper v1.19.0
google.golang.org/grpc v1.67.1 google.golang.org/grpc v1.67.1
@ -80,6 +79,7 @@ require (
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/snorwin/jsonpatch v1.5.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/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect

4
go.sum
View File

@ -20,8 +20,8 @@ github.com/cloudnative-pg/cloudnative-pg v1.24.1-0.20241001084914-829808376542 h
github.com/cloudnative-pg/cloudnative-pg v1.24.1-0.20241001084914-829808376542/go.mod h1:L8M+kTGpz/eaLZj46+4sARvO/vDYlo/m1xOigI/ghBA= github.com/cloudnative-pg/cloudnative-pg v1.24.1-0.20241001084914-829808376542/go.mod h1:L8M+kTGpz/eaLZj46+4sARvO/vDYlo/m1xOigI/ghBA=
github.com/cloudnative-pg/cnpg-i v0.0.0-20240924030516-c5636170f248 h1:eUGzb7YNjVLilwhgZoe4hDOO70fci3oqb/ZzQFbN3xg= github.com/cloudnative-pg/cnpg-i v0.0.0-20240924030516-c5636170f248 h1:eUGzb7YNjVLilwhgZoe4hDOO70fci3oqb/ZzQFbN3xg=
github.com/cloudnative-pg/cnpg-i v0.0.0-20240924030516-c5636170f248/go.mod h1:K9/4eAT3rh2bKIWyujoN8BIPRXa4d1Ls+eBY8PE8y6w= github.com/cloudnative-pg/cnpg-i v0.0.0-20240924030516-c5636170f248/go.mod h1:K9/4eAT3rh2bKIWyujoN8BIPRXa4d1Ls+eBY8PE8y6w=
github.com/cloudnative-pg/cnpg-i-machinery v0.0.0-20241001135556-db88a95a39eb h1:fDZ4mOSwgEUKaXJI3a37Bw0bPa8bl3DqZ9nPu/6CiJ4= github.com/cloudnative-pg/cnpg-i-machinery v0.0.0-20241002070940-e5495e9c5ed6 h1:C4CU5fBTYTiJBPDqcgHpXSc5IvRTy+5KTaFZzdKHfAQ=
github.com/cloudnative-pg/cnpg-i-machinery v0.0.0-20241001135556-db88a95a39eb/go.mod h1:mHEVy/Guae+rij1qlgwHg+lyFKDX48qjTL4lAqE7OJs= github.com/cloudnative-pg/cnpg-i-machinery v0.0.0-20241002070940-e5495e9c5ed6/go.mod h1:mHEVy/Guae+rij1qlgwHg+lyFKDX48qjTL4lAqE7OJs=
github.com/cloudnative-pg/machinery v0.0.0-20241001075747-34c8797af80f h1:RgPmQJkuSu3eTdfd4T2K95RYQi57LHB2+Jfsu/faKOM= github.com/cloudnative-pg/machinery v0.0.0-20241001075747-34c8797af80f h1:RgPmQJkuSu3eTdfd4T2K95RYQi57LHB2+Jfsu/faKOM=
github.com/cloudnative-pg/machinery v0.0.0-20241001075747-34c8797af80f/go.mod h1:bWp1Es5zlxElg4Z/c5f0RKOkDcyNvDHdYIvNcPQU4WM= github.com/cloudnative-pg/machinery v0.0.0-20241001075747-34c8797af80f/go.mod h1:bWp1Es5zlxElg4Z/c5f0RKOkDcyNvDHdYIvNcPQU4WM=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=

View File

@ -14,7 +14,7 @@ import (
// IdentityImplementation implements IdentityServer // IdentityImplementation implements IdentityServer
type IdentityImplementation struct { type IdentityImplementation struct {
identity.UnimplementedIdentityServer identity.UnimplementedIdentityServer
WALConfigurationKey client.ObjectKey BarmanObjectKey client.ObjectKey
Client client.Client Client client.Client
} }
@ -57,8 +57,8 @@ func (i IdentityImplementation) Probe(
_ *identity.ProbeRequest, _ *identity.ProbeRequest,
) (*identity.ProbeResponse, error) { ) (*identity.ProbeResponse, error) {
var obj barmancloudv1.ObjectStore var obj barmancloudv1.ObjectStore
if err := i.Client.Get(ctx, i.WALConfigurationKey, &obj); err != nil { if err := i.Client.Get(ctx, i.BarmanObjectKey, &obj); err != nil {
return nil, fmt.Errorf("while fetching object store %s: %w", i.WALConfigurationKey.Name, err) return nil, fmt.Errorf("while fetching object store %s: %w", i.BarmanObjectKey.Name, err)
} }
return &identity.ProbeResponse{ return &identity.ProbeResponse{

View File

@ -0,0 +1,98 @@
package instance
import (
"context"
"os"
"path"
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
"github.com/spf13/viper"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
)
var scheme = runtime.NewScheme()
func init() {
utilruntime.Must(barmancloudv1.AddToScheme(scheme))
utilruntime.Must(cnpgv1.AddToScheme(scheme))
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
}
// Start starts the sidecar informers and CNPG-i server
func Start(ctx context.Context) error {
setupLog := log.FromContext(ctx)
setupLog.Info("Starting barman cloud instance plugin")
namespace := viper.GetString("namespace")
boName := viper.GetString("barman-object-name")
clusterName := viper.GetString("cluster-name")
podName := viper.GetString("pod-name")
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Cache: cache.Options{
ByObject: map[client.Object]cache.ByObject{
&barmancloudv1.ObjectStore{}: {
Field: fields.OneTermEqualSelector("metadata.name", boName),
Namespaces: map[string]cache.Config{
namespace: {},
},
},
&cnpgv1.Cluster{}: {
Field: fields.OneTermEqualSelector("metadata.name", clusterName),
Namespaces: map[string]cache.Config{
namespace: {},
},
},
},
},
Client: client.Options{
Cache: &client.CacheOptions{
DisableFor: []client.Object{
&corev1.Secret{},
},
},
},
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
if err := mgr.Add(&CNPGI{
Client: mgr.GetClient(),
ClusterObjectKey: client.ObjectKey{
Namespace: namespace,
Name: clusterName,
},
BarmanObjectKey: client.ObjectKey{
Namespace: namespace,
Name: boName,
},
InstanceName: podName,
// TODO: improve
PGDataPath: viper.GetString("pgdata"),
PGWALPath: path.Join(viper.GetString("pgdata"), "pg_wal"),
SpoolDirectory: viper.GetString("spool-directory"),
PluginPath: viper.GetString("plugin-path"),
}); err != nil {
setupLog.Error(err, "unable to create CNPGI runnable")
return err
}
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
return err
}
return nil
}

View File

@ -13,16 +13,11 @@ import (
// CNPGI is the implementation of the PostgreSQL sidecar // CNPGI is the implementation of the PostgreSQL sidecar
type CNPGI struct { type CNPGI struct {
Client client.Client Client client.Client
WALConfigurationKey client.ObjectKey BarmanObjectKey client.ObjectKey
ClusterObjectKey client.ObjectKey ClusterObjectKey client.ObjectKey
PGDataPath string PGDataPath string
PGWALPath string PGWALPath string
SpoolDirectory string SpoolDirectory string
ServerCertPath string
ServerKeyPath string
ClientCertPath string
// mutually exclusive with pluginPath
ServerAddress string
// mutually exclusive with serverAddress // mutually exclusive with serverAddress
PluginPath string PluginPath string
InstanceName string InstanceName string
@ -32,7 +27,7 @@ type CNPGI struct {
func (c *CNPGI) Start(ctx context.Context) error { func (c *CNPGI) Start(ctx context.Context) error {
enrich := func(server *grpc.Server) error { enrich := func(server *grpc.Server) error {
wal.RegisterWALServer(server, WALServiceImplementation{ wal.RegisterWALServer(server, WALServiceImplementation{
BarmanObjectKey: c.WALConfigurationKey, BarmanObjectKey: c.BarmanObjectKey,
ClusterObjectKey: c.ClusterObjectKey, ClusterObjectKey: c.ClusterObjectKey,
InstanceName: c.InstanceName, InstanceName: c.InstanceName,
Client: c.Client, Client: c.Client,
@ -50,13 +45,9 @@ func (c *CNPGI) Start(ctx context.Context) error {
srv := http.Server{ srv := http.Server{
IdentityImpl: IdentityImplementation{ IdentityImpl: IdentityImplementation{
Client: c.Client, Client: c.Client,
WALConfigurationKey: c.WALConfigurationKey, BarmanObjectKey: c.BarmanObjectKey,
}, },
Enrichers: []http.ServerEnricher{enrich}, Enrichers: []http.ServerEnricher{enrich},
ServerCertPath: c.ServerCertPath,
ServerKeyPath: c.ServerKeyPath,
ClientCertPath: c.ClientCertPath,
ServerAddress: c.ServerAddress,
PluginPath: c.PluginPath, PluginPath: c.PluginPath,
} }

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path"
"strings" "strings"
"time" "time"
@ -22,6 +23,13 @@ import (
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1" barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
) )
const (
// CheckEmptyWalArchiveFile is the name of the file in the PGDATA that,
// if present, requires the WAL archiver to check that the backup object
// store is empty.
CheckEmptyWalArchiveFile = ".check-empty-wal-archive"
)
// WALServiceImplementation is the implementation of the WAL Service // WALServiceImplementation is the implementation of the WAL Service
type WALServiceImplementation struct { type WALServiceImplementation struct {
BarmanObjectKey client.ObjectKey BarmanObjectKey client.ObjectKey
@ -75,11 +83,20 @@ func (w WALServiceImplementation) Archive(
objectStore.Namespace, objectStore.Namespace,
&objectStore.Spec.Configuration, &objectStore.Spec.Configuration,
os.Environ()) os.Environ())
if err != nil {
if apierrors.IsForbidden(err) { if apierrors.IsForbidden(err) {
return nil, errors.New("backup credentials don't yet have access permissions. Will retry reconciliation loop") return nil, errors.New("backup credentials don't yet have access permissions. Will retry reconciliation loop")
} }
return nil, err
}
arch, err := archiver.New(ctx, envArchive, w.SpoolDirectory, w.PGDataPath, w.PGWALPath) arch, err := archiver.New(
ctx,
envArchive,
w.SpoolDirectory,
w.PGDataPath,
path.Join(w.PGDataPath, CheckEmptyWalArchiveFile),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -4,7 +4,7 @@ import "github.com/cloudnative-pg/cnpg-i/pkg/identity"
// PluginName is the name of the plugin from the instance manager // PluginName is the name of the plugin from the instance manager
// Point-of-view // Point-of-view
const PluginName = "instance.barman-cloud.cloudnative-pg.io" const PluginName = "barman-cloud.cloudnative-pg.io"
// Data is the metadata of this plugin. // Data is the metadata of this plugin.
var Data = identity.GetPluginMetadataResponse{ var Data = identity.GetPluginMetadataResponse{

View File

@ -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
}

View File

@ -0,0 +1,2 @@
// Package config contains the functions to parse the plugin configuration
package config

View File

@ -10,6 +10,8 @@ import (
"github.com/cloudnative-pg/machinery/pkg/log" "github.com/cloudnative-pg/machinery/pkg/log"
"github.com/spf13/viper" "github.com/spf13/viper"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config"
) )
// LifecycleImplementation is the implementation of the lifecycle handler // LifecycleImplementation is the implementation of the lifecycle handler
@ -52,16 +54,38 @@ func (impl LifecycleImplementation) LifecycleHook(
return nil, errors.New("no operation set") return nil, errors.New("no operation set")
} }
cluster, err := decoder.DecodeClusterJSON(request.GetClusterDefinition())
if err != nil {
return nil, err
}
pod, err := decoder.DecodePodJSON(request.GetObjectDefinition()) pod, err := decoder.DecodePodJSON(request.GetObjectDefinition())
if err != nil { if err != nil {
return nil, err return nil, err
} }
pluginConfiguration, err := config.NewFromCluster(cluster)
if err != nil {
return nil, err
}
mutatedPod := pod.DeepCopy() mutatedPod := pod.DeepCopy()
err = object.InjectPluginSidecar(mutatedPod, &corev1.Container{ err = object.InjectPluginSidecar(mutatedPod, &corev1.Container{
Name: "plugin-barman-cloud", Name: "plugin-barman-cloud",
Image: viper.GetString("sidecar-image"), Image: viper.GetString("sidecar-image"),
}, false) Env: []corev1.EnvVar{
{
Name: "BARMAN_OBJECT_NAME",
Value: pluginConfiguration.BarmanObjectName,
},
{
// TODO: should we really use this one?
// should we mount an emptyDir volume just for that?
Name: "SPOOL_DIRECTORY",
Value: "/controller/wal-restore-spool",
},
},
}, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -71,8 +95,7 @@ func (impl LifecycleImplementation) LifecycleHook(
return nil, err return nil, err
} }
// TODO: change to debug contextLogger.Debug("generated patch", "content", string(patch))
contextLogger.Info("generated patch", "content", string(patch))
return &lifecycle.OperatorLifecycleResponse{ return &lifecycle.OperatorLifecycleResponse{
JsonPatch: patch, JsonPatch: patch,
}, nil }, nil

View File

@ -14,22 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// Package manager contains the implementation of the ObjectStore controller manager package operator
package manager
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"flag"
// +kubebuilder:scaffold:imports // +kubebuilder:scaffold:imports
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
"github.com/cloudnative-pg/machinery/pkg/log"
"github.com/spf13/viper" "github.com/spf13/viper"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme" clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters" "sigs.k8s.io/controller-runtime/pkg/metrics/filters"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook"
@ -42,30 +41,21 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
) )
var ( var scheme = runtime.NewScheme()
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() { func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(barmancloudv1.AddToScheme(scheme)) utilruntime.Must(barmancloudv1.AddToScheme(scheme))
utilruntime.Must(cnpgv1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme // +kubebuilder:scaffold:scheme
} }
// Start starts the manager // Start starts the manager
func Start(ctx context.Context) error { func Start(ctx context.Context) error {
setupLog := log.FromContext(ctx)
var tlsOpts []func(*tls.Config) 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 // if the enable-http2 flag is false (the default), http/2 should be disabled
// due to its vulnerabilities. More specifically, disabling http/2 will // due to its vulnerabilities. More specifically, disabling http/2 will
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and // prevent from being vulnerable to the HTTP/2 Stream Cancellation and
@ -151,6 +141,18 @@ func Start(ctx context.Context) error {
return err 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") setupLog.Info("starting manager")
if err := mgr.Start(ctx); err != nil { if err := mgr.Start(ctx); err != nil {
setupLog.Error(err, "problem running manager") setupLog.Error(err, "problem running manager")

View File

@ -3,11 +3,25 @@ package operator
import ( import (
"context" "context"
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" "github.com/cloudnative-pg/cnpg-i/pkg/reconciler"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/equality"
apierrs "k8s.io/apimachinery/pkg/api/errors"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/specs"
) )
// ReconcilerImplementation implements the Reconciler capability // ReconcilerImplementation implements the Reconciler capability
type ReconcilerImplementation struct { type ReconcilerImplementation struct {
Client client.Client
reconciler.UnimplementedReconcilerHooksServer reconciler.UnimplementedReconcilerHooksServer
} }
@ -30,9 +44,55 @@ func (r ReconcilerImplementation) GetCapabilities(
// Pre implements the reconciler interface // Pre implements the reconciler interface
func (r ReconcilerImplementation) Pre( func (r ReconcilerImplementation) Pre(
_ context.Context, ctx context.Context,
_ *reconciler.ReconcilerHooksRequest, request *reconciler.ReconcilerHooksRequest,
) (*reconciler.ReconcilerHooksResult, error) { ) (*reconciler.ReconcilerHooksResult, error) {
contextLogger := log.FromContext(ctx)
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
}
contextLogger = contextLogger.WithValues("name", cluster.Name, "namespace", cluster.Namespace)
ctx = log.IntoContext(ctx, contextLogger)
pluginConfiguration, err := config.NewFromCluster(cluster)
if err != nil {
return nil, err
}
var barmanObject barmancloudv1.ObjectStore
if err := r.Client.Get(ctx, client.ObjectKey{
Namespace: cluster.Namespace,
Name: pluginConfiguration.BarmanObjectName,
}, &barmanObject); err != nil {
if apierrs.IsNotFound(err) {
contextLogger.Info("Not found barman object configuration, requeuing")
return &reconciler.ReconcilerHooksResult{
Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_REQUEUE,
}, nil
}
}
if err := r.ensureRole(ctx, cluster, &barmanObject); err != nil {
return nil, err
}
if err := r.ensureRoleBinding(ctx, cluster); err != nil {
return nil, err
}
return &reconciler.ReconcilerHooksResult{ return &reconciler.ReconcilerHooksResult{
Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE, Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE,
}, nil }, nil
@ -47,3 +107,83 @@ func (r ReconcilerImplementation) Post(
Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE, Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE,
}, nil }, nil
} }
func (r ReconcilerImplementation) ensureRole(
ctx context.Context,
cluster *cnpgv1.Cluster,
barmanObject *barmancloudv1.ObjectStore,
) error {
contextLogger := log.FromContext(ctx)
newRole := specs.BuildRole(cluster, barmanObject)
var role rbacv1.Role
if err := r.Client.Get(ctx, client.ObjectKey{
Namespace: newRole.Namespace,
Name: newRole.Name,
}, &role); err != nil {
if !apierrs.IsNotFound(err) {
return err
}
contextLogger.Info(
"Creating role",
"name", newRole.Name,
"namespace", newRole.Namespace,
)
if err := ctrl.SetControllerReference(
cluster,
newRole,
r.Client.Scheme(),
); err != nil {
return err
}
return r.Client.Create(ctx, newRole)
}
if equality.Semantic.DeepEqual(newRole.Rules, role.Rules) {
// There's no need to hit the API server again
return nil
}
contextLogger.Info(
"Patching role",
"name", newRole.Name,
"namespace", newRole.Namespace,
"rules", newRole.Rules,
)
return r.Client.Patch(ctx, newRole, client.MergeFrom(&role))
}
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: specs.GetRBACName(cluster.Name),
}, &role); err != nil {
if apierrs.IsNotFound(err) {
return r.createRoleBinding(ctx, cluster)
}
return err
}
// TODO: this assumes role bindings never change.
// Is that true? Should we relax this assumption?
return nil
}
func (r ReconcilerImplementation) createRoleBinding(
ctx context.Context,
cluster *cnpgv1.Cluster,
) error {
roleBinding := specs.BuildRoleBinding(cluster)
if err := ctrl.SetControllerReference(cluster, roleBinding, r.Client.Scheme()); err != nil {
return err
}
return r.Client.Create(ctx, roleBinding)
}

View File

@ -0,0 +1,88 @@
package specs
import (
"fmt"
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
)
// BuildRole builds the Role object for this cluster
func BuildRole(
cluster *cnpgv1.Cluster,
barmanObject *barmancloudv1.ObjectStore,
) *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{
barmanObject.Name,
},
},
{
APIGroups: []string{
"",
},
Resources: []string{
"secrets",
},
Verbs: []string{
"get",
"watch",
"list",
},
ResourceNames: collectSecretNames(barmanObject),
},
},
}
}
// BuildRoleBinding builds the role binding object for this cluster
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-cloud", clusterName)
}

View File

@ -0,0 +1,51 @@
package specs
import (
machineryapi "github.com/cloudnative-pg/machinery/pkg/api"
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
)
func collectSecretNames(object *barmancloudv1.ObjectStore) []string {
if object == nil {
return nil
}
var references []*machineryapi.SecretKeySelector
if object.Spec.Configuration.AWS != nil {
references = append(
references,
object.Spec.Configuration.AWS.AccessKeyIDReference,
object.Spec.Configuration.AWS.SecretAccessKeyReference,
object.Spec.Configuration.AWS.RegionReference,
object.Spec.Configuration.AWS.SessionToken,
)
}
if object.Spec.Configuration.Azure != nil {
references = append(
references,
object.Spec.Configuration.Azure.ConnectionString,
object.Spec.Configuration.Azure.StorageAccount,
object.Spec.Configuration.Azure.StorageKey,
object.Spec.Configuration.Azure.StorageSasToken,
)
}
if object.Spec.Configuration.Google != nil {
references = append(
references,
object.Spec.Configuration.Google.ApplicationCredentials,
)
}
result := make([]string, 0, len(references))
for _, reference := range references {
if reference == nil {
continue
}
result = append(result, reference.Name)
}
// TODO: stringset belongs to machinery :(
return result
}

View File

@ -0,0 +1,3 @@
// Package specs contains the specification of the kubernetes objects
// that are created by the plugin
package specs

View File

@ -1,21 +1,45 @@
package operator package operator
import ( import (
"context"
"github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/http" "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/lifecycle"
"github.com/cloudnative-pg/cnpg-i/pkg/reconciler" "github.com/cloudnative-pg/cnpg-i/pkg/reconciler"
"github.com/spf13/cobra"
"google.golang.org/grpc" "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 // of the operator plugin
func NewCommand() *cobra.Command { func (c *CNPGI) Start(ctx context.Context) error {
cmd := http.CreateMainCmd(IdentityImplementation{}, func(server *grpc.Server) error { enrich := func(server *grpc.Server) error {
reconciler.RegisterReconcilerHooksServer(server, ReconcilerImplementation{}) reconciler.RegisterReconcilerHooksServer(server, ReconcilerImplementation{
Client: c.Client,
})
lifecycle.RegisterOperatorLifecycleServer(server, LifecycleImplementation{}) lifecycle.RegisterOperatorLifecycleServer(server, LifecycleImplementation{})
return nil 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)
} }

View File

@ -34,6 +34,9 @@ type ObjectStoreReconciler struct {
Scheme *runtime.Scheme 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="",resources=secrets,verbs=create;list;get;watch;delete
// +kubebuilder:rbac:groups=barmancloud.cnpg.io,resources=objectstores,verbs=get;list;watch;create;update;patch;delete // +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/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=barmancloud.cnpg.io,resources=objectstores/finalizers,verbs=update // +kubebuilder:rbac:groups=barmancloud.cnpg.io,resources=objectstores/finalizers,verbs=update

View File

@ -29,7 +29,6 @@ spec:
key: SIDECAR_IMAGE key: SIDECAR_IMAGE
name: plugin-barman-cloud name: plugin-barman-cloud
args: args:
- operator
- --server-cert=/server/tls.crt - --server-cert=/server/tls.crt
- --server-key=/server/tls.key - --server-key=/server/tls.key
- --client-cert=/client/tls.crt - --client-cert=/client/tls.crt

View File

@ -11,7 +11,9 @@ resources:
- ../config/rbac - ../config/rbac
images: images:
- name: plugin-barman-cloud - name: plugin-barman-cloud
newName: kind.local/github.com/cloudnative-pg/plugin-barman-cloud/cmd/operator
newTag: 7e901b38eaf33b047dcf2eb044c9c8ca85535d8041a3144d25f7e1a4690ea071
secretGenerator: secretGenerator:
- literals: - literals:
- SIDECAR_IMAGE=plugin-sidecar - SIDECAR_IMAGE=kind.local/github.com/cloudnative-pg/plugin-barman-cloud/cmd/instance:ca1fd58413940a247bc52cdb44f4a6909192d781b1767dc7ee9625368ee9d7e2
name: plugin-barman-cloud name: plugin-barman-cloud

View File

@ -10,7 +10,7 @@ fi
current_context=$(kubectl config view --raw -o json | jq -r '."current-context"' | sed "s/kind-//") current_context=$(kubectl config view --raw -o json | jq -r '."current-context"' | sed "s/kind-//")
operator_image=$(KIND_CLUSTER_NAME="$current_context" KO_DOCKER_REPO=kind.local ko build -BP ./cmd/operator) operator_image=$(KIND_CLUSTER_NAME="$current_context" KO_DOCKER_REPO=kind.local ko build -BP ./cmd/operator)
instance_image=$(KIND_CLUSTER_NAME="$current_context" KO_DOCKER_REPO=kind.local ko build -BP ./cmd/instance) instance_image=$(KIND_CLUSTER_NAME="$current_context" KO_DOCKER_REPO=kind.local KO_DEFAULTBASEIMAGE="ghcr.io/cloudnative-pg/postgresql:17.0" ko build -BP ./cmd/instance)
( (
cd kubernetes; cd kubernetes;