mirror of
https://github.com/cloudnative-pg/plugin-barman-cloud.git
synced 2026-07-09 11:12:21 +02:00
Compare commits
9 Commits
511bb045cb
...
1ba856a35a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ba856a35a | ||
|
|
a6ddc7828e | ||
|
|
807e862422 | ||
|
|
a4b0f428b8 | ||
|
|
6c8fd63181 | ||
|
|
b03fd973f1 | ||
|
|
48575f5f8c | ||
|
|
3b6c25c2dc | ||
|
|
a81f6e3d73 |
16
go.mod
16
go.mod
@ -12,8 +12,8 @@ require (
|
|||||||
github.com/cloudnative-pg/cnpg-i v0.5.0
|
github.com/cloudnative-pg/cnpg-i v0.5.0
|
||||||
github.com/cloudnative-pg/cnpg-i-machinery v0.4.2
|
github.com/cloudnative-pg/cnpg-i-machinery v0.4.2
|
||||||
github.com/cloudnative-pg/machinery v0.4.0
|
github.com/cloudnative-pg/machinery v0.4.0
|
||||||
github.com/onsi/ginkgo/v2 v2.28.2
|
github.com/onsi/ginkgo/v2 v2.28.3
|
||||||
github.com/onsi/gomega v1.39.1
|
github.com/onsi/gomega v1.40.0
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/spf13/viper v1.21.0
|
github.com/spf13/viper v1.21.0
|
||||||
google.golang.org/grpc v1.80.0
|
google.golang.org/grpc v1.80.0
|
||||||
@ -67,7 +67,7 @@ require (
|
|||||||
github.com/google/cel-go v0.26.0 // indirect
|
github.com/google/cel-go v0.26.0 // indirect
|
||||||
github.com/google/gnostic-models v0.7.1 // indirect
|
github.com/google/gnostic-models v0.7.1 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect
|
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect
|
||||||
@ -114,15 +114,15 @@ require (
|
|||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
|
||||||
golang.org/x/mod v0.33.0 // indirect
|
golang.org/x/mod v0.35.0 // indirect
|
||||||
golang.org/x/net v0.52.0 // indirect
|
golang.org/x/net v0.53.0 // indirect
|
||||||
golang.org/x/oauth2 v0.35.0 // indirect
|
golang.org/x/oauth2 v0.35.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.43.0 // indirect
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
golang.org/x/term v0.41.0 // indirect
|
golang.org/x/term v0.42.0 // indirect
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.36.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.42.0 // indirect
|
golang.org/x/tools v0.44.0 // indirect
|
||||||
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect
|
||||||
|
|||||||
32
go.sum
32
go.sum
@ -119,8 +119,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
|||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
|
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg=
|
||||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||||
@ -165,10 +165,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
|||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||||
github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps=
|
github.com/onsi/ginkgo/v2 v2.28.3 h1:4JvMdwtFU0imd8fHx25OJXoDMRexnf8v5NHKYSTTji4=
|
||||||
github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
|
github.com/onsi/ginkgo/v2 v2.28.3/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
|
||||||
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
|
github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc=
|
||||||
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
|
github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@ -271,24 +271,24 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
|||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||||
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
|
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
|
||||||
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
|
||||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
|
|||||||
@ -28,8 +28,24 @@ const PluginName = "barman-cloud.cloudnative-pg.io"
|
|||||||
const (
|
const (
|
||||||
// ClusterLabelName is the label applied to RBAC resources created
|
// ClusterLabelName is the label applied to RBAC resources created
|
||||||
// by this plugin. Its value is the name of the owning Cluster.
|
// by this plugin. Its value is the name of the owning Cluster.
|
||||||
|
//
|
||||||
|
// Discovery contract: internal/controller/objectstore_controller.go
|
||||||
|
// selects Roles by this key when an ObjectStore is reconciled.
|
||||||
|
// Renaming or removing the label would break that controller; new
|
||||||
|
// recommended-label keys are added alongside it, never in place
|
||||||
|
// of it.
|
||||||
ClusterLabelName = "barmancloud.cnpg.io/cluster"
|
ClusterLabelName = "barmancloud.cnpg.io/cluster"
|
||||||
|
|
||||||
|
// AppLabelValue is the value applied to app.kubernetes.io/name on
|
||||||
|
// every plugin-managed object. It identifies the application as
|
||||||
|
// the Barman Cloud plugin (see issue #545).
|
||||||
|
AppLabelValue = "barman-cloud-plugin"
|
||||||
|
|
||||||
|
// ManagedByLabelValue is the value applied to app.kubernetes.io/managed-by
|
||||||
|
// on every plugin-managed object. It identifies this plugin as
|
||||||
|
// the controller responsible for the object.
|
||||||
|
ManagedByLabelValue = "plugin-barman-cloud"
|
||||||
|
|
||||||
// CheckEmptyWalArchiveFile is the name of the file in the PGDATA that,
|
// CheckEmptyWalArchiveFile is the name of the file in the PGDATA that,
|
||||||
// if present, requires the WAL archiver to check that the backup object
|
// if present, requires the WAL archiver to check that the backup object
|
||||||
// store is empty.
|
// store is empty.
|
||||||
|
|||||||
@ -21,6 +21,7 @@ package rbac
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
|
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
|
||||||
"github.com/cloudnative-pg/machinery/pkg/log"
|
"github.com/cloudnative-pg/machinery/pkg/log"
|
||||||
@ -31,7 +32,6 @@ import (
|
|||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
|
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
|
||||||
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
|
|
||||||
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/specs"
|
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/specs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,9 +62,7 @@ func EnsureRole(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return patchRole(ctx, c, roleKey, newRole.Rules, map[string]string{
|
return patchRole(ctx, c, roleKey, newRole.Rules, specs.BuildLabels(cluster))
|
||||||
metadata.ClusterLabelName: cluster.Name,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureRoleRules updates the rules of an existing Role to match
|
// EnsureRoleRules updates the rules of an existing Role to match
|
||||||
@ -90,6 +88,143 @@ func EnsureRoleRules(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnsureRoleBinding ensures the RoleBinding for the given Cluster
|
||||||
|
// is present and carries the recommended labels.
|
||||||
|
//
|
||||||
|
// This function is called from the Pre hook (gRPC). It creates the
|
||||||
|
// RoleBinding if it does not exist, then reconciles labels and
|
||||||
|
// Subjects:
|
||||||
|
// - Labels are written per-key. Keys the plugin manages overwrite
|
||||||
|
// existing values; unrelated keys (anything outside the desired
|
||||||
|
// set) are left alone.
|
||||||
|
// - Subjects are additive. The plugin guarantees its own Subject
|
||||||
|
// is bound, but never removes Subjects added by other actors —
|
||||||
|
// a Subject is a grant of access, and silently revoking access
|
||||||
|
// someone else granted is the wrong default.
|
||||||
|
//
|
||||||
|
// RoleRef is immutable in Kubernetes. If the existing RoleBinding
|
||||||
|
// points to a different Role, the plugin fails loudly so the
|
||||||
|
// operator notices and recreates the object.
|
||||||
|
func EnsureRoleBinding(ctx context.Context, c client.Client, cluster *cnpgv1.Cluster) error {
|
||||||
|
desiredRoleBinding := specs.BuildRoleBinding(cluster)
|
||||||
|
if err := specs.SetControllerReference(cluster, desiredRoleBinding); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
roleBinding, err := getOrCreateRoleBinding(ctx, c, desiredRoleBinding)
|
||||||
|
if err != nil || roleBinding == nil {
|
||||||
|
// Either an error, or we just created the object with the
|
||||||
|
// desired state — nothing to patch.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reconcileRoleBinding(ctx, c, roleBinding, desiredRoleBinding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOrCreateRoleBinding returns the existing RoleBinding when it
|
||||||
|
// is already present on the API server, or nil after a successful
|
||||||
|
// Create when the just-created object already carries the desired
|
||||||
|
// state (so the caller can skip the patch path).
|
||||||
|
//
|
||||||
|
// On a stale-informer-cache race during plugin pod startup, where
|
||||||
|
// Get returns NotFound but Create returns AlreadyExists, the
|
||||||
|
// function re-Gets to return the racing winner — the caller then
|
||||||
|
// falls through to reconciliation against that real object.
|
||||||
|
func getOrCreateRoleBinding(
|
||||||
|
ctx context.Context,
|
||||||
|
c client.Client,
|
||||||
|
desired *rbacv1.RoleBinding,
|
||||||
|
) (*rbacv1.RoleBinding, error) {
|
||||||
|
contextLogger := log.FromContext(ctx)
|
||||||
|
|
||||||
|
rb := &rbacv1.RoleBinding{}
|
||||||
|
err := c.Get(ctx, client.ObjectKeyFromObject(desired), rb)
|
||||||
|
if err == nil {
|
||||||
|
return rb, nil
|
||||||
|
}
|
||||||
|
if !apierrs.IsNotFound(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
createErr := c.Create(ctx, desired)
|
||||||
|
switch {
|
||||||
|
case createErr == nil:
|
||||||
|
contextLogger.Info("Created RoleBinding",
|
||||||
|
"name", desired.Name, "namespace", desired.Namespace)
|
||||||
|
// Just-created with the desired state — caller skips patch.
|
||||||
|
return nil, nil
|
||||||
|
case apierrs.IsAlreadyExists(createErr):
|
||||||
|
contextLogger.Debug(
|
||||||
|
"RoleBinding already exists, likely a stale informer cache; re-fetching",
|
||||||
|
"name", desired.Name, "namespace", desired.Namespace)
|
||||||
|
// Re-Get to return the racing winner so the caller can
|
||||||
|
// reconcile against the real existing object.
|
||||||
|
fetched := &rbacv1.RoleBinding{}
|
||||||
|
if err := c.Get(ctx, client.ObjectKeyFromObject(desired), fetched); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fetched, nil
|
||||||
|
default:
|
||||||
|
return nil, createErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconcileRoleBinding brings an existing RoleBinding's labels and
|
||||||
|
// Subjects into alignment with desired. It re-Gets the object on
|
||||||
|
// conflict-retry so each attempt observes fresh server state, and
|
||||||
|
// uses optimistic locking so a competing writer's patch is rejected
|
||||||
|
// with 409 instead of silently last-write-winning.
|
||||||
|
//
|
||||||
|
// On the first attempt the function uses the existing object passed
|
||||||
|
// in by the caller, avoiding a second Get on the steady-state path.
|
||||||
|
func reconcileRoleBinding(
|
||||||
|
ctx context.Context,
|
||||||
|
c client.Client,
|
||||||
|
existing, desired *rbacv1.RoleBinding,
|
||||||
|
) error {
|
||||||
|
contextLogger := log.FromContext(ctx)
|
||||||
|
first := true
|
||||||
|
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||||
|
var roleBinding *rbacv1.RoleBinding
|
||||||
|
if first {
|
||||||
|
roleBinding = existing
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
roleBinding = &rbacv1.RoleBinding{}
|
||||||
|
if err := c.Get(ctx, client.ObjectKeyFromObject(desired), roleBinding); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleRef is immutable in Kubernetes; we cannot patch it.
|
||||||
|
// Divergence at the canonical name is corruption regardless
|
||||||
|
// of who wrote the existing object — fail loudly so the
|
||||||
|
// operator notices and deletes the RoleBinding, and the
|
||||||
|
// next Pre call recreates it correctly.
|
||||||
|
if !equality.Semantic.DeepEqual(roleBinding.RoleRef, desired.RoleRef) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"RoleBinding %s/%s has divergent immutable RoleRef "+
|
||||||
|
"(existing=%+v, desired=%+v); delete the RoleBinding to allow recreation",
|
||||||
|
roleBinding.Namespace, roleBinding.Name,
|
||||||
|
roleBinding.RoleRef, desired.RoleRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !roleBindingNeedsUpdate(roleBinding, desired) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
contextLogger.Info("Patching role binding",
|
||||||
|
"name", roleBinding.Name, "namespace", roleBinding.Namespace)
|
||||||
|
|
||||||
|
oldRoleBinding := roleBinding.DeepCopy()
|
||||||
|
roleBinding.Labels = mergeLabels(roleBinding.Labels, desired.Labels)
|
||||||
|
roleBinding.Subjects = mergeSubjects(roleBinding.Subjects, desired.Subjects)
|
||||||
|
|
||||||
|
return c.Patch(ctx, roleBinding,
|
||||||
|
client.MergeFromWithOptions(oldRoleBinding, client.MergeFromWithOptimisticLock{}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ensureRoleExists creates the Role if it does not exist. Returns
|
// ensureRoleExists creates the Role if it does not exist. Returns
|
||||||
// nil on success and nil on AlreadyExists (another writer created
|
// nil on success and nil on AlreadyExists (another writer created
|
||||||
// it concurrently). The caller always follows up with patchRole.
|
// it concurrently). The caller always follows up with patchRole.
|
||||||
@ -155,22 +290,32 @@ func patchRole(
|
|||||||
|
|
||||||
oldRole := role.DeepCopy()
|
oldRole := role.DeepCopy()
|
||||||
role.Rules = desiredRules
|
role.Rules = desiredRules
|
||||||
|
role.Labels = mergeLabels(role.Labels, desiredLabels)
|
||||||
|
|
||||||
if desiredLabels != nil {
|
return c.Patch(ctx, &role,
|
||||||
if role.Labels == nil {
|
client.MergeFromWithOptions(oldRole, client.MergeFromWithOptimisticLock{}))
|
||||||
role.Labels = make(map[string]string, len(desiredLabels))
|
|
||||||
}
|
|
||||||
for k, v := range desiredLabels {
|
|
||||||
role.Labels[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Patch(ctx, &role, client.MergeFrom(oldRole))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// labelsNeedUpdate returns true if any key in desired is missing
|
// mergeLabels writes the desired labels onto existing per-key.
|
||||||
// or has a different value in existing.
|
// Keys in desired overwrite the existing value; keys not in desired
|
||||||
|
// (any unrelated label a user may have set) are left alone.
|
||||||
|
func mergeLabels(existing, desired map[string]string) map[string]string {
|
||||||
|
if len(desired) == 0 {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
if existing == nil {
|
||||||
|
existing = make(map[string]string, len(desired))
|
||||||
|
}
|
||||||
|
for k, v := range desired {
|
||||||
|
existing[k] = v
|
||||||
|
}
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
|
||||||
|
// labelsNeedUpdate returns true if a Patch is required to bring
|
||||||
|
// existing labels into the state mergeLabels would produce, i.e.
|
||||||
|
// any desired key is missing or has a different value in existing.
|
||||||
func labelsNeedUpdate(existing, desired map[string]string) bool {
|
func labelsNeedUpdate(existing, desired map[string]string) bool {
|
||||||
for k, v := range desired {
|
for k, v := range desired {
|
||||||
if existing[k] != v {
|
if existing[k] != v {
|
||||||
@ -179,3 +324,50 @@ func labelsNeedUpdate(existing, desired map[string]string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// containsSubject reports whether subjects contains an element that
|
||||||
|
// is semantically equal to subject.
|
||||||
|
func containsSubject(subjects []rbacv1.Subject, subject rbacv1.Subject) bool {
|
||||||
|
for _, s := range subjects {
|
||||||
|
if equality.Semantic.DeepEqual(s, subject) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeSubjects appends desired Subjects that are not already
|
||||||
|
// present in existing.
|
||||||
|
//
|
||||||
|
// This is intentionally asymmetric to mergeLabels: labels are
|
||||||
|
// metadata, so replacing stale plugin-set values is safe. A
|
||||||
|
// Subject is a grant of access, so removing a Subject silently
|
||||||
|
// revokes permissions an external operator chose to grant. The
|
||||||
|
// plugin only requires that ITS Subject is present, not that it
|
||||||
|
// is exclusive.
|
||||||
|
func mergeSubjects(existing, desired []rbacv1.Subject) []rbacv1.Subject {
|
||||||
|
for _, d := range desired {
|
||||||
|
if !containsSubject(existing, d) {
|
||||||
|
existing = append(existing, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
|
||||||
|
// roleBindingNeedsUpdate returns true if a Patch is required to
|
||||||
|
// bring existing into alignment with desired — any desired Subject
|
||||||
|
// missing (see mergeSubjects), or any desired label key missing
|
||||||
|
// or holding a stale value (see mergeLabels).
|
||||||
|
func roleBindingNeedsUpdate(existing, desired *rbacv1.RoleBinding) bool {
|
||||||
|
for _, s := range desired.Subjects {
|
||||||
|
if !containsSubject(existing.Subjects, s) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if labelsNeedUpdate(existing.Labels, desired.Labels) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
barmanapi "github.com/cloudnative-pg/barman-cloud/pkg/api"
|
barmanapi "github.com/cloudnative-pg/barman-cloud/pkg/api"
|
||||||
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
|
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
|
||||||
|
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
|
||||||
machineryapi "github.com/cloudnative-pg/machinery/pkg/api"
|
machineryapi "github.com/cloudnative-pg/machinery/pkg/api"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
@ -42,6 +43,42 @@ import (
|
|||||||
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/rbac"
|
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/rbac"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func expectRequiredLabels(labels map[string]string, clusterName string) {
|
||||||
|
ExpectWithOffset(1, labels).To(HaveKeyWithValue(metadata.ClusterLabelName, clusterName))
|
||||||
|
ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppLabelName, metadata.AppLabelValue))
|
||||||
|
ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppInstanceLabelName, clusterName))
|
||||||
|
ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppManagedByLabelName, metadata.ManagedByLabelValue))
|
||||||
|
ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppComponentLabelName, utils.DatabaseComponentName))
|
||||||
|
ExpectWithOffset(1, labels).To(HaveKeyWithValue(utils.KubernetesAppVersionLabelName, metadata.Data.Version))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPatchCountingClient returns a fake client plus a pointer to a
|
||||||
|
// counter incremented on every Patch call. Useful for asserting
|
||||||
|
// that a "no-op" reconcile path issues no Patch — more reliable
|
||||||
|
// than comparing ResourceVersion across reads, which depends on
|
||||||
|
// fake-client RV-bumping semantics that are not part of the
|
||||||
|
// controller-runtime contract.
|
||||||
|
func newPatchCountingClient(initObjs ...client.Object) (client.Client, *int) {
|
||||||
|
count := 0
|
||||||
|
c := fake.NewClientBuilder().
|
||||||
|
WithScheme(newScheme()).
|
||||||
|
WithObjects(initObjs...).
|
||||||
|
WithInterceptorFuncs(interceptor.Funcs{
|
||||||
|
Patch: func(
|
||||||
|
ctx context.Context,
|
||||||
|
client client.WithWatch,
|
||||||
|
obj client.Object,
|
||||||
|
patch client.Patch,
|
||||||
|
opts ...client.PatchOption,
|
||||||
|
) error {
|
||||||
|
count++
|
||||||
|
return client.Patch(ctx, obj, patch, opts...)
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
Build()
|
||||||
|
return c, &count
|
||||||
|
}
|
||||||
|
|
||||||
func newScheme() *runtime.Scheme {
|
func newScheme() *runtime.Scheme {
|
||||||
s := runtime.NewScheme()
|
s := runtime.NewScheme()
|
||||||
utilruntime.Must(rbacv1.AddToScheme(s))
|
utilruntime.Must(rbacv1.AddToScheme(s))
|
||||||
@ -124,33 +161,23 @@ var _ = Describe("EnsureRole", func() {
|
|||||||
Expect(role.OwnerReferences[0].Name).To(Equal("test-cluster"))
|
Expect(role.OwnerReferences[0].Name).To(Equal("test-cluster"))
|
||||||
Expect(role.OwnerReferences[0].Kind).To(Equal("Cluster"))
|
Expect(role.OwnerReferences[0].Kind).To(Equal("Cluster"))
|
||||||
|
|
||||||
Expect(role.Labels).To(HaveKeyWithValue(metadata.ClusterLabelName, "test-cluster"))
|
expectRequiredLabels(role.Labels, "test-cluster")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("when the Role exists with matching rules", func() {
|
Context("when the Role exists with matching rules", func() {
|
||||||
|
var patchCount *int
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
|
fakeClient, patchCount = newPatchCountingClient()
|
||||||
Expect(rbac.EnsureRole(ctx, fakeClient, cluster, objects)).To(Succeed())
|
Expect(rbac.EnsureRole(ctx, fakeClient, cluster, objects)).To(Succeed())
|
||||||
|
*patchCount = 0
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should not patch the Role", func() {
|
It("should not patch the Role", func() {
|
||||||
var before rbacv1.Role
|
|
||||||
Expect(fakeClient.Get(ctx, client.ObjectKey{
|
|
||||||
Namespace: "default",
|
|
||||||
Name: "test-cluster-barman-cloud",
|
|
||||||
}, &before)).To(Succeed())
|
|
||||||
|
|
||||||
err := rbac.EnsureRole(ctx, fakeClient, cluster, objects)
|
err := rbac.EnsureRole(ctx, fakeClient, cluster, objects)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(*patchCount).To(BeZero())
|
||||||
var after rbacv1.Role
|
|
||||||
Expect(fakeClient.Get(ctx, client.ObjectKey{
|
|
||||||
Namespace: "default",
|
|
||||||
Name: "test-cluster-barman-cloud",
|
|
||||||
}, &after)).To(Succeed())
|
|
||||||
|
|
||||||
Expect(after.ResourceVersion).To(Equal(before.ResourceVersion))
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -210,7 +237,7 @@ var _ = Describe("EnsureRole", func() {
|
|||||||
Name: "test-cluster-barman-cloud",
|
Name: "test-cluster-barman-cloud",
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"app.kubernetes.io/managed-by": "helm",
|
"custom-label": "custom-value",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -227,8 +254,40 @@ var _ = Describe("EnsureRole", func() {
|
|||||||
Name: "test-cluster-barman-cloud",
|
Name: "test-cluster-barman-cloud",
|
||||||
}, &role)).To(Succeed())
|
}, &role)).To(Succeed())
|
||||||
|
|
||||||
Expect(role.Labels).To(HaveKeyWithValue("app.kubernetes.io/managed-by", "helm"))
|
Expect(role.Labels).To(HaveKeyWithValue("custom-label", "custom-value"))
|
||||||
Expect(role.Labels).To(HaveKeyWithValue(metadata.ClusterLabelName, "test-cluster"))
|
expectRequiredLabels(role.Labels, "test-cluster")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the Role exists with a stale label value", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
|
||||||
|
existing := &rbacv1.Role{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{
|
||||||
|
// Stale value as if written by an older plugin
|
||||||
|
// version.
|
||||||
|
utils.KubernetesAppVersionLabelName: "0.0.0-stale",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Expect(fakeClient.Create(ctx, existing)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should overwrite the stale value with the current plugin's value", func() {
|
||||||
|
err := rbac.EnsureRole(ctx, fakeClient, cluster, objects)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
var role rbacv1.Role
|
||||||
|
Expect(fakeClient.Get(ctx, client.ObjectKey{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
}, &role)).To(Succeed())
|
||||||
|
|
||||||
|
Expect(role.Labels).To(HaveKeyWithValue(
|
||||||
|
utils.KubernetesAppVersionLabelName, metadata.Data.Version))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -257,12 +316,348 @@ var _ = Describe("EnsureRole", func() {
|
|||||||
Name: "test-cluster-barman-cloud",
|
Name: "test-cluster-barman-cloud",
|
||||||
}, &role)).To(Succeed())
|
}, &role)).To(Succeed())
|
||||||
|
|
||||||
Expect(role.Labels).To(HaveKeyWithValue(metadata.ClusterLabelName, "test-cluster"))
|
expectRequiredLabels(role.Labels, "test-cluster")
|
||||||
Expect(role.Rules).To(HaveLen(3))
|
Expect(role.Rules).To(HaveLen(3))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var _ = Describe("EnsureRoleBinding", func() {
|
||||||
|
var (
|
||||||
|
ctx context.Context
|
||||||
|
cluster *cnpgv1.Cluster
|
||||||
|
fakeClient client.Client
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
ctx = context.Background()
|
||||||
|
cluster = newCluster("test-cluster", "default")
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the RoleBinding does not exist", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should create the RoleBinding with owner reference, labels, and correct subjects", func() {
|
||||||
|
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
var rb rbacv1.RoleBinding
|
||||||
|
Expect(fakeClient.Get(ctx, client.ObjectKey{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
}, &rb)).To(Succeed())
|
||||||
|
|
||||||
|
Expect(rb.OwnerReferences).To(HaveLen(1))
|
||||||
|
Expect(rb.OwnerReferences[0].Name).To(Equal("test-cluster"))
|
||||||
|
Expect(rb.OwnerReferences[0].Kind).To(Equal("Cluster"))
|
||||||
|
|
||||||
|
expectRequiredLabels(rb.Labels, "test-cluster")
|
||||||
|
|
||||||
|
Expect(rb.Subjects).To(HaveLen(1))
|
||||||
|
Expect(rb.Subjects[0].Name).To(Equal("test-cluster"))
|
||||||
|
Expect(rb.Subjects[0].Kind).To(Equal("ServiceAccount"))
|
||||||
|
|
||||||
|
Expect(rb.RoleRef.Kind).To(Equal("Role"))
|
||||||
|
Expect(rb.RoleRef.Name).To(Equal("test-cluster-barman-cloud"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the RoleBinding exists with matching state", func() {
|
||||||
|
var patchCount *int
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
fakeClient, patchCount = newPatchCountingClient()
|
||||||
|
Expect(rbac.EnsureRoleBinding(ctx, fakeClient, cluster)).To(Succeed())
|
||||||
|
*patchCount = 0
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should not patch the RoleBinding", func() {
|
||||||
|
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(*patchCount).To(BeZero())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the RoleBinding exists with extra user-added subjects", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
|
||||||
|
existing := &rbacv1.RoleBinding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Subjects: []rbacv1.Subject{
|
||||||
|
{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: "user-debug-sa",
|
||||||
|
APIGroup: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RoleRef: rbacv1.RoleRef{
|
||||||
|
APIGroup: "rbac.authorization.k8s.io",
|
||||||
|
Kind: "Role",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Expect(fakeClient.Create(ctx, existing)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should add the plugin's subject without removing user-added ones", func() {
|
||||||
|
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
var rb rbacv1.RoleBinding
|
||||||
|
Expect(fakeClient.Get(ctx, client.ObjectKey{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
}, &rb)).To(Succeed())
|
||||||
|
|
||||||
|
// Additive policy: the user-added subject must remain.
|
||||||
|
Expect(rb.Subjects).To(ContainElement(rbacv1.Subject{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: "user-debug-sa",
|
||||||
|
APIGroup: "",
|
||||||
|
}))
|
||||||
|
// The plugin's required subject must be present.
|
||||||
|
Expect(rb.Subjects).To(ContainElement(rbacv1.Subject{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: "test-cluster",
|
||||||
|
Namespace: "default",
|
||||||
|
APIGroup: "",
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the RoleBinding exists with a stale label value", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
|
||||||
|
existing := &rbacv1.RoleBinding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{
|
||||||
|
utils.KubernetesAppVersionLabelName: "0.0.0-stale",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Subjects: []rbacv1.Subject{
|
||||||
|
{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: "test-cluster",
|
||||||
|
Namespace: "default",
|
||||||
|
APIGroup: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RoleRef: rbacv1.RoleRef{
|
||||||
|
APIGroup: "rbac.authorization.k8s.io",
|
||||||
|
Kind: "Role",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Expect(fakeClient.Create(ctx, existing)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should overwrite the stale value with the current plugin's value", func() {
|
||||||
|
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
var rb rbacv1.RoleBinding
|
||||||
|
Expect(fakeClient.Get(ctx, client.ObjectKey{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
}, &rb)).To(Succeed())
|
||||||
|
|
||||||
|
Expect(rb.Labels).To(HaveKeyWithValue(
|
||||||
|
utils.KubernetesAppVersionLabelName, metadata.Data.Version))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the RoleBinding has pre-existing unrelated labels", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
|
||||||
|
existing := &rbacv1.RoleBinding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"custom-label": "custom-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Subjects: []rbacv1.Subject{
|
||||||
|
{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: "test-cluster",
|
||||||
|
Namespace: "default",
|
||||||
|
APIGroup: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RoleRef: rbacv1.RoleRef{
|
||||||
|
APIGroup: "rbac.authorization.k8s.io",
|
||||||
|
Kind: "Role",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Expect(fakeClient.Create(ctx, existing)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should preserve unrelated labels while adding the required labels", func() {
|
||||||
|
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
var rb rbacv1.RoleBinding
|
||||||
|
Expect(fakeClient.Get(ctx, client.ObjectKey{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
}, &rb)).To(Succeed())
|
||||||
|
|
||||||
|
Expect(rb.Labels).To(HaveKeyWithValue("custom-label", "custom-value"))
|
||||||
|
expectRequiredLabels(rb.Labels, "test-cluster")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the RoleBinding has a divergent RoleRef", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
|
||||||
|
existing := &rbacv1.RoleBinding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Subjects: []rbacv1.Subject{
|
||||||
|
{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: "test-cluster",
|
||||||
|
Namespace: "default",
|
||||||
|
APIGroup: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RoleRef: rbacv1.RoleRef{
|
||||||
|
APIGroup: "rbac.authorization.k8s.io",
|
||||||
|
Kind: "Role",
|
||||||
|
Name: "wrong-role",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Expect(fakeClient.Create(ctx, existing)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should return a descriptive error since RoleRef is immutable", func() {
|
||||||
|
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring("RoleRef"))
|
||||||
|
Expect(err.Error()).To(ContainSubstring("wrong-role"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when an AlreadyExists race happens during a stale-cache create (plugin pod startup)", func() {
|
||||||
|
var preExisting *rbacv1.RoleBinding
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
preExisting = &rbacv1.RoleBinding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Subjects: []rbacv1.Subject{
|
||||||
|
{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: "test-cluster",
|
||||||
|
Namespace: "default",
|
||||||
|
APIGroup: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RoleRef: rbacv1.RoleRef{
|
||||||
|
APIGroup: "rbac.authorization.k8s.io",
|
||||||
|
Kind: "Role",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// First Get returns NotFound (simulates cold informer
|
||||||
|
// cache after plugin pod restart). Subsequent Gets
|
||||||
|
// fall through to real fake-client behavior.
|
||||||
|
gets := 0
|
||||||
|
fakeClient = fake.NewClientBuilder().
|
||||||
|
WithScheme(newScheme()).
|
||||||
|
WithObjects(preExisting).
|
||||||
|
WithInterceptorFuncs(interceptor.Funcs{
|
||||||
|
Get: func(
|
||||||
|
ctx context.Context,
|
||||||
|
c client.WithWatch,
|
||||||
|
key client.ObjectKey,
|
||||||
|
obj client.Object,
|
||||||
|
opts ...client.GetOption,
|
||||||
|
) error {
|
||||||
|
gets++
|
||||||
|
if gets == 1 {
|
||||||
|
return apierrs.NewNotFound(
|
||||||
|
rbacv1.Resource("rolebindings"), key.Name)
|
||||||
|
}
|
||||||
|
return c.Get(ctx, key, obj, opts...)
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
Build()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should tolerate the AlreadyExists and reconcile from the existing object", func() {
|
||||||
|
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
var rb rbacv1.RoleBinding
|
||||||
|
Expect(fakeClient.Get(ctx, client.ObjectKey{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
}, &rb)).To(Succeed())
|
||||||
|
|
||||||
|
Expect(rb.Subjects).To(ContainElement(rbacv1.Subject{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: "test-cluster",
|
||||||
|
Namespace: "default",
|
||||||
|
APIGroup: "",
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the RoleBinding exists without labels (upgrade scenario)", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).Build()
|
||||||
|
existing := &rbacv1.RoleBinding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Subjects: []rbacv1.Subject{
|
||||||
|
{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: "test-cluster",
|
||||||
|
Namespace: "default",
|
||||||
|
APIGroup: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RoleRef: rbacv1.RoleRef{
|
||||||
|
APIGroup: "rbac.authorization.k8s.io",
|
||||||
|
Kind: "Role",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Expect(fakeClient.Create(ctx, existing)).To(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should add the required labels", func() {
|
||||||
|
err := rbac.EnsureRoleBinding(ctx, fakeClient, cluster)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
var rb rbacv1.RoleBinding
|
||||||
|
Expect(fakeClient.Get(ctx, client.ObjectKey{
|
||||||
|
Namespace: "default",
|
||||||
|
Name: "test-cluster-barman-cloud",
|
||||||
|
}, &rb)).To(Succeed())
|
||||||
|
|
||||||
|
expectRequiredLabels(rb.Labels, "test-cluster")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
var _ = Describe("EnsureRoleRules", func() {
|
var _ = Describe("EnsureRoleRules", func() {
|
||||||
var (
|
var (
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -306,23 +701,21 @@ var _ = Describe("EnsureRoleRules", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("should not patch when rules already match", func() {
|
It("should not patch when rules already match", func() {
|
||||||
// Seed with the same objects so rules match
|
// Replace the seeded client with a counting one,
|
||||||
|
// then re-seed via EnsureRole so the desired rules
|
||||||
|
// are already in place when EnsureRoleRules runs.
|
||||||
|
var patchCount *int
|
||||||
|
fakeClient, patchCount = newPatchCountingClient()
|
||||||
cluster := newCluster("test-cluster", "default")
|
cluster := newCluster("test-cluster", "default")
|
||||||
Expect(rbac.EnsureRole(ctx, fakeClient, cluster, objects)).To(Succeed())
|
Expect(rbac.EnsureRole(ctx, fakeClient, cluster, objects)).To(Succeed())
|
||||||
|
*patchCount = 0
|
||||||
|
|
||||||
roleKey := client.ObjectKey{
|
roleKey := client.ObjectKey{
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Name: "test-cluster-barman-cloud",
|
Name: "test-cluster-barman-cloud",
|
||||||
}
|
}
|
||||||
|
|
||||||
var before rbacv1.Role
|
|
||||||
Expect(fakeClient.Get(ctx, roleKey, &before)).To(Succeed())
|
|
||||||
|
|
||||||
Expect(rbac.EnsureRoleRules(ctx, fakeClient, roleKey, objects)).To(Succeed())
|
Expect(rbac.EnsureRoleRules(ctx, fakeClient, roleKey, objects)).To(Succeed())
|
||||||
|
Expect(*patchCount).To(BeZero())
|
||||||
var after rbacv1.Role
|
|
||||||
Expect(fakeClient.Get(ctx, roleKey, &after)).To(Succeed())
|
|
||||||
Expect(after.ResourceVersion).To(Equal(before.ResourceVersion))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should not modify labels", func() {
|
It("should not modify labels", func() {
|
||||||
|
|||||||
@ -27,14 +27,12 @@ import (
|
|||||||
"github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/object"
|
"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"
|
||||||
"github.com/cloudnative-pg/machinery/pkg/log"
|
"github.com/cloudnative-pg/machinery/pkg/log"
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
|
||||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
|
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/config"
|
||||||
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/rbac"
|
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/rbac"
|
||||||
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/specs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReconcilerImplementation implements the Reconciler capability
|
// ReconcilerImplementation implements the Reconciler capability
|
||||||
@ -117,7 +115,7 @@ func (r ReconcilerImplementation) Pre(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.ensureRoleBinding(ctx, &cluster); err != nil {
|
if err := rbac.EnsureRoleBinding(ctx, r.Client, &cluster); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,34 +134,3 @@ func (r ReconcilerImplementation) Post(
|
|||||||
Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE,
|
Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r ReconcilerImplementation) ensureRoleBinding(
|
|
||||||
ctx context.Context,
|
|
||||||
cluster *cnpgv1.Cluster,
|
|
||||||
) error {
|
|
||||||
var roleBinding rbacv1.RoleBinding
|
|
||||||
if err := r.Client.Get(ctx, client.ObjectKey{
|
|
||||||
Namespace: cluster.Namespace,
|
|
||||||
Name: specs.GetRBACName(cluster.Name),
|
|
||||||
}, &roleBinding); 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 := specs.SetControllerReference(cluster, roleBinding); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return r.Client.Create(ctx, roleBinding)
|
|
||||||
}
|
|
||||||
|
|||||||
41
internal/cnpgi/operator/specs/labels.go
Normal file
41
internal/cnpgi/operator/specs/labels.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
Copyright © contributors to CloudNativePG, established as
|
||||||
|
CloudNativePG a Series of LF Projects, LLC.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package specs
|
||||||
|
|
||||||
|
import (
|
||||||
|
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
|
||||||
|
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
|
||||||
|
|
||||||
|
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildLabels returns the Kubernetes recommended labels applied to
|
||||||
|
// every object managed by this plugin for the given Cluster. See
|
||||||
|
// https://github.com/cloudnative-pg/plugin-barman-cloud/issues/545.
|
||||||
|
func BuildLabels(cluster *cnpgv1.Cluster) map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
metadata.ClusterLabelName: cluster.Name,
|
||||||
|
utils.KubernetesAppLabelName: metadata.AppLabelValue,
|
||||||
|
utils.KubernetesAppInstanceLabelName: cluster.Name,
|
||||||
|
utils.KubernetesAppVersionLabelName: metadata.Data.Version,
|
||||||
|
utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
|
||||||
|
utils.KubernetesAppManagedByLabelName: metadata.ManagedByLabelValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
68
internal/cnpgi/operator/specs/labels_test.go
Normal file
68
internal/cnpgi/operator/specs/labels_test.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
Copyright © contributors to CloudNativePG, established as
|
||||||
|
CloudNativePG a Series of LF Projects, LLC.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package specs
|
||||||
|
|
||||||
|
import (
|
||||||
|
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
|
||||||
|
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("BuildLabels", func() {
|
||||||
|
It("should return the recommended labels with plugin identity", func() {
|
||||||
|
cluster := &cnpgv1.Cluster{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "my-cluster",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
labels := BuildLabels(cluster)
|
||||||
|
|
||||||
|
Expect(labels).To(HaveKeyWithValue(metadata.ClusterLabelName, "my-cluster"))
|
||||||
|
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppLabelName, metadata.AppLabelValue))
|
||||||
|
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppInstanceLabelName, "my-cluster"))
|
||||||
|
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppVersionLabelName, metadata.Data.Version))
|
||||||
|
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppComponentLabelName, utils.DatabaseComponentName))
|
||||||
|
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppManagedByLabelName, metadata.ManagedByLabelValue))
|
||||||
|
Expect(labels).To(HaveLen(6))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should report the plugin version regardless of the cluster's Postgres image", func() {
|
||||||
|
cluster := &cnpgv1.Cluster{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "pg16-cluster",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Spec: cnpgv1.ClusterSpec{
|
||||||
|
ImageCatalogRef: &cnpgv1.ImageCatalogRef{
|
||||||
|
Major: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
labels := BuildLabels(cluster)
|
||||||
|
|
||||||
|
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppVersionLabelName, metadata.Data.Version))
|
||||||
|
Expect(labels).To(HaveKeyWithValue(utils.KubernetesAppInstanceLabelName, "pg16-cluster"))
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -29,7 +29,6 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
|
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
|
||||||
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BuildRole builds the Role object for this cluster
|
// BuildRole builds the Role object for this cluster
|
||||||
@ -41,9 +40,7 @@ func BuildRole(
|
|||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: cluster.Namespace,
|
Namespace: cluster.Namespace,
|
||||||
Name: GetRBACName(cluster.Name),
|
Name: GetRBACName(cluster.Name),
|
||||||
Labels: map[string]string{
|
Labels: BuildLabels(cluster),
|
||||||
metadata.ClusterLabelName: cluster.Name,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Rules: BuildRoleRules(barmanObjects),
|
Rules: BuildRoleRules(barmanObjects),
|
||||||
}
|
}
|
||||||
@ -131,6 +128,7 @@ func BuildRoleBinding(
|
|||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: cluster.Namespace,
|
Namespace: cluster.Namespace,
|
||||||
Name: GetRBACName(cluster.Name),
|
Name: GetRBACName(cluster.Name),
|
||||||
|
Labels: BuildLabels(cluster),
|
||||||
},
|
},
|
||||||
Subjects: []rbacv1.Subject{
|
Subjects: []rbacv1.Subject{
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user