From 22974025e145e30e17e9412dc57dae61a9fdfad8 Mon Sep 17 00:00:00 2001 From: Michele Sacchetti Date: Wed, 6 Apr 2022 09:49:18 +0200 Subject: [PATCH] GCP wrapper (#83) * GCP wrapper Add a GCP wrapper to work on Google SQL instances * PR fixes mod doc to readme and query to const --- Makefile | 4 ++ README.md | 11 ++++++ pkg/postgres/database.go | 3 ++ pkg/postgres/gcp.go | 85 ++++++++++++++++++++++++++++++++++++++++ pkg/postgres/postgres.go | 6 +++ 5 files changed, 109 insertions(+) create mode 100644 pkg/postgres/gcp.go 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 1a125001..b499685a 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"` GRANT_CREATE_SCHEMA = `GRANT CREATE ON DATABASE "%s" TO "%s"` ) diff --git a/pkg/postgres/gcp.go b/pkg/postgres/gcp.go new file mode 100644 index 00000000..9ebb1ec6 --- /dev/null +++ b/pkg/postgres/gcp.go @@ -0,0 +1,85 @@ +package postgres + +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, 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(TERMINATE_BACKEND, 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(GET_DB_OWNER, 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 } }