mirror of
https://github.com/cloudnative-pg/plugin-barman-cloud.git
synced 2026-01-11 21:23:12 +01:00
Merge branch 'main' into dev/instance-backup
This commit is contained in:
commit
d11fabd2d3
@ -131,4 +131,4 @@ issues:
|
||||
- test
|
||||
exclude-files:
|
||||
- zz_generated.*
|
||||
- internal/controller/suite_test.go
|
||||
- internal/operator/controller/suite_test.go
|
||||
|
||||
2
Makefile
2
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.
|
||||
|
||||
@ -51,7 +51,7 @@ tasks:
|
||||
desc: Run go test
|
||||
env:
|
||||
# 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
|
||||
K8S_VERSION: 1.31.0
|
||||
# renovate: datasource=git-refs depName=controller-runtime packageName=https://github.com/kubernetes-sigs/controller-runtime versioning=semver
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/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"
|
||||
"github.com/cloudnative-pg/machinery/pkg/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
|
||||
"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() {
|
||||
setupLog.Info("Starting barman cloud instance plugin")
|
||||
namespace := mustGetEnv("NAMESPACE")
|
||||
boName := mustGetEnv("BARMAN_OBJECT_NAME")
|
||||
clusterName := mustGetEnv("CLUSTER_NAME")
|
||||
instanceName := mustGetEnv("INSTANCE_NAME")
|
||||
cobra.EnableTraverseRunHooks = true
|
||||
|
||||
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: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
logFlags := &log.Flags{}
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "instance",
|
||||
Short: "Starts the Barman Cloud CNPG-i sidecar plugin",
|
||||
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
|
||||
logFlags.ConfigureLogging()
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
requiredSettings := []string{
|
||||
"namespace",
|
||||
"barman-object-name",
|
||||
"cluster-name",
|
||||
"pod-name",
|
||||
"spool-directory",
|
||||
}
|
||||
|
||||
for _, k := range requiredSettings {
|
||||
if len(viper.GetString(k)) == 0 {
|
||||
return fmt.Errorf("missing required %s setting", k)
|
||||
}
|
||||
}
|
||||
|
||||
return instance.Start(cmd.Context())
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := mgr.Add(&instance.CNPGI{
|
||||
Client: mgr.GetClient(),
|
||||
ClusterObjectKey: client.ObjectKey{
|
||||
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)
|
||||
}
|
||||
logFlags.AddFlags(rootCmd.PersistentFlags())
|
||||
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
_ = viper.BindEnv("namespace", "NAMESPACE")
|
||||
_ = 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)
|
||||
}
|
||||
}
|
||||
|
||||
func mustGetEnv(envName string) string {
|
||||
value := os.Getenv(envName)
|
||||
if value == "" {
|
||||
setupLog.Error(
|
||||
errors.New("missing required env variable"),
|
||||
"while fetching env variables",
|
||||
"name",
|
||||
envName,
|
||||
)
|
||||
os.Exit(1)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@ -2,18 +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"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator"
|
||||
"github.com/cloudnative-pg/plugin-barman-cloud/internal/manager"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -21,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
|
||||
@ -29,62 +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 {
|
||||
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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -2,8 +2,18 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: manager-role
|
||||
name: plugin-barman-cloud
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- barmancloud.cnpg.io
|
||||
resources:
|
||||
@ -30,3 +40,15 @@ rules:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- rbac.authorization.k8s.io
|
||||
resources:
|
||||
- rolebindings
|
||||
- roles
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -7,6 +7,8 @@ spec:
|
||||
|
||||
plugins:
|
||||
- name: barman-cloud.cloudnative-pg.io
|
||||
parameters:
|
||||
barmanObjectName: minio-store
|
||||
|
||||
storage:
|
||||
size: 1Gi
|
||||
|
||||
23
docs/examples/minio-store.yaml
Normal file
23
docs/examples/minio-store.yaml
Normal 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"
|
||||
|
||||
20
docs/minio/minio-client.yaml
Normal file
20
docs/minio/minio-client.yaml
Normal 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
|
||||
1
docs/minio/minio-delete.sh
Normal file
1
docs/minio/minio-delete.sh
Normal file
@ -0,0 +1 @@
|
||||
kubectl exec -ti mc -- mc rm -r --force minio/backups
|
||||
42
docs/minio/minio-deployment.yaml
Normal file
42
docs/minio/minio-deployment.yaml
Normal 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
11
docs/minio/minio-pvc.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: minio
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
volumeMode: Filesystem
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
7
docs/minio/minio-secret.yaml
Normal file
7
docs/minio/minio-secret.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
data:
|
||||
ACCESS_KEY_ID: Y2hvb0plaXJvcm9vMm5vcXVvbWVpMnV1Y2Vpc2hldGg=
|
||||
ACCESS_SECRET_KEY: b25nZWlxdWVpdG9oTDBxdWVlTG9oa2l1cjJxdWFpbmc=
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: minio
|
||||
11
docs/minio/minio-service.yaml
Normal file
11
docs/minio/minio-service.yaml
Normal 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
4
go.mod
@ -6,11 +6,10 @@ require (
|
||||
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/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/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
|
||||
|
||||
4
go.sum
4
go.sum
@ -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/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-machinery v0.0.0-20241001135556-db88a95a39eb h1:fDZ4mOSwgEUKaXJI3a37Bw0bPa8bl3DqZ9nPu/6CiJ4=
|
||||
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 h1:C4CU5fBTYTiJBPDqcgHpXSc5IvRTy+5KTaFZzdKHfAQ=
|
||||
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/go.mod h1:bWp1Es5zlxElg4Z/c5f0RKOkDcyNvDHdYIvNcPQU4WM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
|
||||
@ -14,8 +14,8 @@ import (
|
||||
// IdentityImplementation implements IdentityServer
|
||||
type IdentityImplementation struct {
|
||||
identity.UnimplementedIdentityServer
|
||||
WALConfigurationKey client.ObjectKey
|
||||
Client client.Client
|
||||
BarmanObjectKey client.ObjectKey
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
// GetPluginMetadata implements IdentityServer
|
||||
@ -57,8 +57,8 @@ func (i IdentityImplementation) Probe(
|
||||
_ *identity.ProbeRequest,
|
||||
) (*identity.ProbeResponse, error) {
|
||||
var obj barmancloudv1.ObjectStore
|
||||
if err := i.Client.Get(ctx, i.WALConfigurationKey, &obj); err != nil {
|
||||
return nil, fmt.Errorf("while fetching object store %s: %w", i.WALConfigurationKey.Name, err)
|
||||
if err := i.Client.Get(ctx, i.BarmanObjectKey, &obj); err != nil {
|
||||
return nil, fmt.Errorf("while fetching object store %s: %w", i.BarmanObjectKey.Name, err)
|
||||
}
|
||||
|
||||
return &identity.ProbeResponse{
|
||||
|
||||
98
internal/cnpgi/instance/manager.go
Normal file
98
internal/cnpgi/instance/manager.go
Normal 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
|
||||
}
|
||||
@ -12,17 +12,12 @@ import (
|
||||
|
||||
// CNPGI is the implementation of the PostgreSQL sidecar
|
||||
type CNPGI struct {
|
||||
Client client.Client
|
||||
WALConfigurationKey client.ObjectKey
|
||||
ClusterObjectKey client.ObjectKey
|
||||
PGDataPath string
|
||||
PGWALPath string
|
||||
SpoolDirectory string
|
||||
ServerCertPath string
|
||||
ServerKeyPath string
|
||||
ClientCertPath string
|
||||
// mutually exclusive with pluginPath
|
||||
ServerAddress string
|
||||
Client client.Client
|
||||
BarmanObjectKey client.ObjectKey
|
||||
ClusterObjectKey client.ObjectKey
|
||||
PGDataPath string
|
||||
PGWALPath string
|
||||
SpoolDirectory string
|
||||
// mutually exclusive with serverAddress
|
||||
PluginPath string
|
||||
InstanceName string
|
||||
@ -32,7 +27,7 @@ type CNPGI struct {
|
||||
func (c *CNPGI) Start(ctx context.Context) error {
|
||||
enrich := func(server *grpc.Server) error {
|
||||
wal.RegisterWALServer(server, WALServiceImplementation{
|
||||
BarmanObjectKey: c.WALConfigurationKey,
|
||||
BarmanObjectKey: c.BarmanObjectKey,
|
||||
ClusterObjectKey: c.ClusterObjectKey,
|
||||
InstanceName: c.InstanceName,
|
||||
Client: c.Client,
|
||||
@ -49,15 +44,11 @@ func (c *CNPGI) Start(ctx context.Context) error {
|
||||
|
||||
srv := http.Server{
|
||||
IdentityImpl: IdentityImplementation{
|
||||
Client: c.Client,
|
||||
WALConfigurationKey: c.WALConfigurationKey,
|
||||
Client: c.Client,
|
||||
BarmanObjectKey: c.BarmanObjectKey,
|
||||
},
|
||||
Enrichers: []http.ServerEnricher{enrich},
|
||||
ServerCertPath: c.ServerCertPath,
|
||||
ServerKeyPath: c.ServerKeyPath,
|
||||
ClientCertPath: c.ClientCertPath,
|
||||
ServerAddress: c.ServerAddress,
|
||||
PluginPath: c.PluginPath,
|
||||
Enrichers: []http.ServerEnricher{enrich},
|
||||
PluginPath: c.PluginPath,
|
||||
}
|
||||
|
||||
return srv.Start(ctx)
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -22,6 +23,13 @@ import (
|
||||
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
|
||||
type WALServiceImplementation struct {
|
||||
BarmanObjectKey client.ObjectKey
|
||||
@ -75,11 +83,20 @@ func (w WALServiceImplementation) Archive(
|
||||
objectStore.Namespace,
|
||||
&objectStore.Spec.Configuration,
|
||||
os.Environ())
|
||||
if apierrors.IsForbidden(err) {
|
||||
return nil, errors.New("backup credentials don't yet have access permissions. Will retry reconciliation loop")
|
||||
if err != nil {
|
||||
if apierrors.IsForbidden(err) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import "github.com/cloudnative-pg/cnpg-i/pkg/identity"
|
||||
|
||||
// PluginName is the name of the plugin from the instance manager
|
||||
// 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.
|
||||
var Data = identity.GetPluginMetadataResponse{
|
||||
|
||||
71
internal/cnpgi/operator/config/config.go
Normal file
71
internal/cnpgi/operator/config/config.go
Normal 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
|
||||
}
|
||||
2
internal/cnpgi/operator/config/doc.go
Normal file
2
internal/cnpgi/operator/config/doc.go
Normal file
@ -0,0 +1,2 @@
|
||||
// Package config contains the functions to parse the plugin configuration
|
||||
package config
|
||||
@ -10,6 +10,8 @@ import (
|
||||
"github.com/cloudnative-pg/machinery/pkg/log"
|
||||
"github.com/spf13/viper"
|
||||
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
|
||||
@ -52,16 +54,38 @@ func (impl LifecycleImplementation) LifecycleHook(
|
||||
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())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pluginConfiguration, err := config.NewFromCluster(cluster)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mutatedPod := pod.DeepCopy()
|
||||
err = object.InjectPluginSidecar(mutatedPod, &corev1.Container{
|
||||
Name: "plugin-barman-cloud",
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -71,8 +95,7 @@ func (impl LifecycleImplementation) LifecycleHook(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: change to debug
|
||||
contextLogger.Info("generated patch", "content", string(patch))
|
||||
contextLogger.Debug("generated patch", "content", string(patch))
|
||||
return &lifecycle.OperatorLifecycleResponse{
|
||||
JsonPatch: patch,
|
||||
}, nil
|
||||
|
||||
@ -14,22 +14,21 @@ 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"
|
||||
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/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"
|
||||
@ -42,30 +41,21 @@ import (
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
var (
|
||||
scheme = runtime.NewScheme()
|
||||
setupLog = ctrl.Log.WithName("setup")
|
||||
)
|
||||
var scheme = runtime.NewScheme()
|
||||
|
||||
func init() {
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
|
||||
utilruntime.Must(barmancloudv1.AddToScheme(scheme))
|
||||
utilruntime.Must(cnpgv1.AddToScheme(scheme))
|
||||
// +kubebuilder:scaffold:scheme
|
||||
}
|
||||
|
||||
// Start starts the manager
|
||||
func Start(ctx context.Context) error {
|
||||
setupLog := log.FromContext(ctx)
|
||||
|
||||
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")
|
||||
@ -3,11 +3,25 @@ package operator
|
||||
import (
|
||||
"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"
|
||||
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
|
||||
type ReconcilerImplementation struct {
|
||||
Client client.Client
|
||||
reconciler.UnimplementedReconcilerHooksServer
|
||||
}
|
||||
|
||||
@ -30,9 +44,55 @@ 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) {
|
||||
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{
|
||||
Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE,
|
||||
}, nil
|
||||
@ -47,3 +107,83 @@ func (r ReconcilerImplementation) Post(
|
||||
Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE,
|
||||
}, 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)
|
||||
}
|
||||
|
||||
88
internal/cnpgi/operator/specs/role.go
Normal file
88
internal/cnpgi/operator/specs/role.go
Normal 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)
|
||||
}
|
||||
51
internal/cnpgi/operator/specs/secrets.go
Normal file
51
internal/cnpgi/operator/specs/secrets.go
Normal 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
|
||||
}
|
||||
3
internal/cnpgi/operator/specs/specs.go
Normal file
3
internal/cnpgi/operator/specs/specs.go
Normal file
@ -0,0 +1,3 @@
|
||||
// Package specs contains the specification of the kubernetes objects
|
||||
// that are created by the plugin
|
||||
package specs
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -34,6 +34,9 @@ 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="",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/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=barmancloud.cnpg.io,resources=objectstores/finalizers,verbs=update
|
||||
|
||||
@ -57,7 +57,7 @@ func TestControllers(t *testing.T) {
|
||||
var _ = BeforeSuite(func() {
|
||||
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
|
||||
|
||||
ctx, cancel = context.WithCancel(context.TODO())
|
||||
ctx, cancel = context.WithCancel(context.TODO())
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -11,7 +11,9 @@ resources:
|
||||
- ../config/rbac
|
||||
images:
|
||||
- name: plugin-barman-cloud
|
||||
newName: kind.local/github.com/cloudnative-pg/plugin-barman-cloud/cmd/operator
|
||||
newTag: 7e901b38eaf33b047dcf2eb044c9c8ca85535d8041a3144d25f7e1a4690ea071
|
||||
secretGenerator:
|
||||
- literals:
|
||||
- SIDECAR_IMAGE=plugin-sidecar
|
||||
- SIDECAR_IMAGE=kind.local/github.com/cloudnative-pg/plugin-barman-cloud/cmd/instance:ca1fd58413940a247bc52cdb44f4a6909192d781b1767dc7ee9625368ee9d7e2
|
||||
name: plugin-barman-cloud
|
||||
|
||||
@ -10,7 +10,7 @@ fi
|
||||
|
||||
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)
|
||||
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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user