From ac369cd7d266feb8e7d5d18e3a2d93b09d219931 Mon Sep 17 00:00:00 2001 From: Michele Sacchetti Date: Thu, 31 Mar 2022 17:47:24 +0200 Subject: [PATCH 1/2] GCP wrapper Add a GCP wrapper to work on Google SQL instances --- pkg/postgres/gcp.go | 97 ++++++++++++++++++++++++++++++++++++++++ pkg/postgres/postgres.go | 6 +++ 2 files changed, 103 insertions(+) create mode 100644 pkg/postgres/gcp.go diff --git a/pkg/postgres/gcp.go b/pkg/postgres/gcp.go new file mode 100644 index 00000000..5abd966d --- /dev/null +++ b/pkg/postgres/gcp.go @@ -0,0 +1,97 @@ +package postgres + +/* +To have operator work with GCP you have +1) use postgresql connection in secret +2) manually create a Master role e.g. "devops-operators" +3) use such role in database CR e.g. spec.masterRole: devops-operator + +DropRole method will check for db owner and will skip master role dropping +*/ +import ( + "fmt" + + "github.com/go-logr/logr" + "github.com/lib/pq" +) + +type gcppg struct { + pg +} + +func newGCPPG(postgres *pg) PG { + return &gcppg{ + *postgres, + } +} + + +func (c *gcppg) DropDatabase(database string, logger logr.Logger) error { + + _, err := c.db.Exec(fmt.Sprintf("REVOKE CONNECT ON DATABASE \"%s\" FROM public;", database)) + // Error code 3D000 is returned if database doesn't exist + if err != nil && err.(*pq.Error).Code != "3D000" { + return err + } + + _, err = c.db.Exec(fmt.Sprintf("select pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '%s' AND pid <> pg_backend_pid();", database)) + // Error code 3D000 is returned if database doesn't exist + if err != nil && err.(*pq.Error).Code != "3D000" { + return err + } + _, err = c.db.Exec(fmt.Sprintf(DROP_DATABASE, database)) + // Error code 3D000 is returned if database doesn't exist + if err != nil && err.(*pq.Error).Code != "3D000" { + return err + } + + logger.Info(fmt.Sprintf("Dropped database %s", database)) + + return nil +} + + +func (c *gcppg) CreateDB(dbname, role string) error { + + err := c.GrantRole(role, c.user) + if err != nil { + return err + } + err = c.pg.CreateDB(dbname, role) + if err != nil { + return err + } + return nil +} + + +func (c *gcppg) DropRole(role, newOwner, database string, logger logr.Logger) error { + + tmpDb, err := GetConnection(c.user, c.pass, c.host, database, c.args, logger) + q := fmt.Sprintf("SELECT pg_catalog.pg_get_userbyid(d.datdba) FROM pg_catalog.pg_database d WHERE d.datname = '%s';", database) + logger.Info("Checking master role: "+ q) + rows, err := tmpDb.Query(q) + if err != nil { + return err + } + var masterRole string + for rows.Next() { + rows.Scan(&masterRole) + } + + if( role != masterRole){ + + q = fmt.Sprintf(DROP_ROLE, role) + logger.Info("GCP Drop Role: "+ q) + _, err = tmpDb.Exec(q) + // Check if error exists and if different from "ROLE NOT FOUND" => 42704 + if err != nil && err.(*pq.Error).Code != "42704" { + return err + } + + defer tmpDb.Close() + } else { + logger.Info(fmt.Sprintf("GCP refusing DropRole on master role " + masterRole)) + } + return nil +} diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index bbe9778b..22f8f56e 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -53,10 +53,16 @@ func NewPG(host, user, password, uri_args, default_database, cloud_type string, switch cloud_type { case "AWS": + logger.Info("Using AWS wrapper") return newAWSPG(postgres), nil case "Azure": + logger.Info("Using Azure wrapper") return newAzurePG(postgres), nil + case "GCP": + logger.Info("Using GCP wrapper") + return newGCPPG(postgres), nil default: + logger.Info("Using default postgres implementation") return postgres, nil } } From 8b1f092c11fd2aa69d4499953013ecbb8aee464c Mon Sep 17 00:00:00 2001 From: Michele Sacchetti Date: Fri, 1 Apr 2022 17:07:03 +0200 Subject: [PATCH 2/2] PR fixes mod doc to readme and query to const --- Makefile | 4 ++++ README.md | 11 +++++++++++ pkg/postgres/database.go | 3 +++ pkg/postgres/gcp.go | 18 +++--------------- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 899c9744..fffe9451 100644 --- a/Makefile +++ b/Makefile @@ -11,3 +11,7 @@ unit-test: go tool cover -func coverage.out unit-test-coverage: unit-test go tool cover -html coverage.out +linux-build: + @GOBIN=/work/bin GO111MODULE=on GOOS=linux GOARC=x86_64 go build --mod=vendor -o operator github.com/movetokube/postgres-operator/cmd/manager +docker-build: + docker run -ti -v $(PWD):/work -w /work golang:1.13.15-stretch make linux-build diff --git a/README.md b/README.md index 11d1b1d3..f45a1fd6 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,17 @@ In order for this operator to work correctly with Azure managed PostgreSQL datab * `POSTGRES_CLOUD_PROVIDER` set to `Azure` * `POSTGRES_DEFAULT_DATABASE` set to your default database, i.e. `postgres` +### GCP + +In order for this operator to work correctly with GCP, you need to set `POSTGRES_CLOUD_PROVIDER` to `GCP` + +To have operator work with GCP properly you have to: +* use postgresql connection in secret +* manually create a Master role e.g. "devops-operators" +* use such role in database CR e.g. spec.masterRole: devops-operator + +DropRole method will check for db owner and will skip master role dropping + ## Installation This operator requires a Kubernetes Secret to be created in the same namespace as operator itself. diff --git a/pkg/postgres/database.go b/pkg/postgres/database.go index 8f3c4e82..a21afcdd 100644 --- a/pkg/postgres/database.go +++ b/pkg/postgres/database.go @@ -16,6 +16,9 @@ const ( GRANT_USAGE_SCHEMA = `GRANT USAGE ON SCHEMA "%s" TO "%s"` GRANT_ALL_TABLES = `GRANT %s ON ALL TABLES IN SCHEMA "%s" TO "%s"` DEFAULT_PRIVS_SCHEMA = `ALTER DEFAULT PRIVILEGES FOR ROLE "%s" IN SCHEMA "%s" GRANT %s ON TABLES TO "%s"` + REVOKE_CONNECT = `REVOKE CONNECT ON DATABASE "%s" FROM public` + TERMINATE_BACKEND = `SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = "%s" AND pid <> pg_backend_pid()` + GET_DB_OWNER = `SELECT pg_catalog.pg_get_userbyid(d.datdba) FROM pg_catalog.pg_database d WHERE d.datname = "%s"` ) func (c *pg) CreateDB(dbname, role string) error { diff --git a/pkg/postgres/gcp.go b/pkg/postgres/gcp.go index 5abd966d..9ebb1ec6 100644 --- a/pkg/postgres/gcp.go +++ b/pkg/postgres/gcp.go @@ -1,13 +1,5 @@ package postgres -/* -To have operator work with GCP you have -1) use postgresql connection in secret -2) manually create a Master role e.g. "devops-operators" -3) use such role in database CR e.g. spec.masterRole: devops-operator - -DropRole method will check for db owner and will skip master role dropping -*/ import ( "fmt" @@ -25,16 +17,15 @@ func newGCPPG(postgres *pg) PG { } } - func (c *gcppg) DropDatabase(database string, logger logr.Logger) error { - _, err := c.db.Exec(fmt.Sprintf("REVOKE CONNECT ON DATABASE \"%s\" FROM public;", database)) + _, err := c.db.Exec(fmt.Sprintf(REVOKE_CONNECT, database)) // Error code 3D000 is returned if database doesn't exist if err != nil && err.(*pq.Error).Code != "3D000" { return err } - _, err = c.db.Exec(fmt.Sprintf("select pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '%s' AND pid <> pg_backend_pid();", database)) + _, err = c.db.Exec(fmt.Sprintf(TERMINATE_BACKEND, database)) // Error code 3D000 is returned if database doesn't exist if err != nil && err.(*pq.Error).Code != "3D000" { return err @@ -50,7 +41,6 @@ func (c *gcppg) DropDatabase(database string, logger logr.Logger) error { return nil } - func (c *gcppg) CreateDB(dbname, role string) error { err := c.GrantRole(role, c.user) @@ -64,11 +54,10 @@ func (c *gcppg) CreateDB(dbname, role string) error { return nil } - func (c *gcppg) DropRole(role, newOwner, database string, logger logr.Logger) error { tmpDb, err := GetConnection(c.user, c.pass, c.host, database, c.args, logger) - q := fmt.Sprintf("SELECT pg_catalog.pg_get_userbyid(d.datdba) FROM pg_catalog.pg_database d WHERE d.datname = '%s';", database) + q := fmt.Sprintf(GET_DB_OWNER, database) logger.Info("Checking master role: "+ q) rows, err := tmpDb.Query(q) if err != nil { @@ -80,7 +69,6 @@ func (c *gcppg) DropRole(role, newOwner, database string, logger logr.Logger) er } if( role != masterRole){ - q = fmt.Sprintf(DROP_ROLE, role) logger.Info("GCP Drop Role: "+ q) _, err = tmpDb.Exec(q)