From 52e8505a88c3b8768c56ef90b98a6e6bec8db4bc Mon Sep 17 00:00:00 2001 From: tgrondier <5384645+tgrondier@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:43:44 +0100 Subject: [PATCH] dbaas: database management for mysql, pg (#661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Allows users to use the CLI to directly automate database creation for selected services Also includes a few check before creating users for DBAAS service to verify if the service is ready to accept user creation ## Checklist (For exoscale contributors) * [x] Changelog updated (under *Unreleased* block) * [x] Testing ## Testing ``` ➜ ~/exo/cli git:(tgrondier/sc-114061/dbaas-support-for-mysql-posgres-database) go run . dbaas create pg hobbyist-2 test-pg 25-01-09 12:18 ✔ Creating Database Service "test-pg"... 0s ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ DATABASE SERVICE │ │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ Zone │ de-muc-1 │ │ Name │ test-pg │ │ Type │ pg │ │ Plan │ hobbyist-2 │ │ Disk Size │ 8.0 GiB │ │ State │ rebuilding │ │ Creation Date │ 2025-01-09 11:18:44 +0000 UTC │ │ Update Date │ 2025-01-09 11:18:44 +0000 UTC │ │ Nodes │ 1 │ │ Node CPUs │ 2 │ │ Node Memory │ 2.0 GiB │ │ Termination Protected │ true │ │ Maintenance │ saturday (02:51:27) │ │ Version │ 16 │ │ Backup Schedule │ 10:51 │ │ URI │ postgres://avnadmin:xxxxx@test-pg-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699/defaultdb?sslmode=require │ │ IP Filter │ │ │ Components │ │ │ │ pgbouncer test-pg-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21700 route:dynamic usage:primary │ │ │ pg test-pg-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699 route:dynamic usage:primary │ │ │ │ │ Users │ n/a │ │ Databases │ │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ ➜ ~/exo/cli git:(tgrondier/sc-114061/dbaas-support-for-mysql-posgres-database) go run . dbaas create mysql hobbyist-2 test 25-01-09 12:18 ✔ Creating Database Service "test"... 1s ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ DATABASE SERVICE │ │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ Zone │ de-muc-1 │ │ Name │ test │ │ Type │ mysql │ │ Plan │ hobbyist-2 │ │ Disk Size │ 8.0 GiB │ │ State │ rebuilding │ │ Creation Date │ 2025-01-09 11:18:51 +0000 UTC │ │ Update Date │ 2025-01-09 11:18:51 +0000 UTC │ │ Nodes │ 1 │ │ Node CPUs │ 2 │ │ Node Memory │ 2.0 GiB │ │ Termination Protected │ true │ │ Maintenance │ sunday (02:45:14) │ │ Version │ 8 │ │ Backup Schedule │ 15:17 │ │ URI │ mysql://avnadmin:xxxxx@test-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699/defaultdb?ssl-mode=REQUIRED │ │ IP Filter │ │ │ Components │ │ │ │ mysqlx test-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21703 route:dynamic usage:primary │ │ │ mysql test-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699 route:dynamic usage:primary │ │ │ │ │ Users │ n/a │ │ Databases │ │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ ➜ ~/exo/cli git:(tgrondier/sc-114061/dbaas-support-for-mysql-posgres-database) exo dbaas delete test -f 25-01-09 12:18 ➜ ~/exo/cli git:(tgrondier/sc-114061/dbaas-support-for-mysql-posgres-database) go run . dbaas database create test-pg blz 25-01-09 12:18 error: service "test-pg" is not ready for database creation exit status 1 ➜ ~/exo/cli git:(tgrondier/sc-114061/dbaas-support-for-mysql-posgres-database) go run . dbaas database create test blz 25-01-09 12:19 error: service "test" is not ready for database creation exit status 1 ➜ ~/exo/cli git:(tgrondier/sc-114061/dbaas-support-for-mysql-posgres-database) go run . dbaas database create test-pg blz 25-01-09 12:19 ✔ Creating DBaaS database "blz" 0s ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ DATABASE SERVICE │ │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ Zone │ de-muc-1 │ │ Name │ test-pg │ │ Type │ pg │ │ Plan │ hobbyist-2 │ │ Disk Size │ 8.0 GiB │ │ State │ running │ │ Creation Date │ 2025-01-09 11:18:44 +0000 UTC │ │ Update Date │ 2025-01-09 11:21:03 +0000 UTC │ │ Nodes │ 1 │ │ Node CPUs │ 2 │ │ Node Memory │ 2.0 GiB │ │ Termination Protected │ true │ │ Maintenance │ saturday (02:51:27) │ │ Version │ 16.6 │ │ Backup Schedule │ 10:51 │ │ URI │ postgres://avnadmin:xxxxx@test-pg-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699/defaultdb?sslmode=require │ │ IP Filter │ │ │ Components │ │ │ │ pgbouncer test-pg-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21700 route:dynamic usage:primary │ │ │ pg test-pg-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699 route:dynamic usage:primary │ │ │ │ │ Users │ avnadmin (primary) │ │ Databases │ blz │ │ │ defaultdb │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ ➜ ~/exo/cli git:(tgrondier/sc-114061/dbaas-support-for-mysql-posgres-database) go run . dbaas database create test-pg blz 25-01-09 12:21 error: CreateDBAASPGDatabase: http response: Conflict: Service database 'blz' already exists exit status 1 ➜ ~/exo/cli git:(tgrondier/sc-114061/dbaas-support-for-mysql-posgres-database) go run . dbaas database delete test-pg blz 25-01-09 12:21 [+] Are you sure you want to delete database "blz" [yN]: y ✔ Deleting DBaaS database "blz" 0s ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ DATABASE SERVICE │ │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ Zone │ de-muc-1 │ │ Name │ test-pg │ │ Type │ pg │ │ Plan │ hobbyist-2 │ │ Disk Size │ 8.0 GiB │ │ State │ running │ │ Creation Date │ 2025-01-09 11:18:44 +0000 UTC │ │ Update Date │ 2025-01-09 11:21:17 +0000 UTC │ │ Nodes │ 1 │ │ Node CPUs │ 2 │ │ Node Memory │ 2.0 GiB │ │ Termination Protected │ true │ │ Maintenance │ saturday (02:51:27) │ │ Version │ 16.6 │ │ Backup Schedule │ 10:51 │ │ URI │ postgres://avnadmin:xxxxx@test-pg-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699/defaultdb?sslmode=require │ │ IP Filter │ │ │ Components │ │ │ │ pgbouncer test-pg-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21700 route:dynamic usage:primary │ │ │ pg test-pg-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699 route:dynamic usage:primary │ │ │ │ │ Users │ avnadmin (primary) │ │ Databases │ defaultdb │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ ➜ ~/exo/cli git:(tgrondier/sc-114061/dbaas-support-for-mysql-posgres-database) go run . dbaas database create test blz 25-01-09 12:21 ✔ Creating DBaaS database "blz" 0s ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ DATABASE SERVICE │ │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ Zone │ de-muc-1 │ │ Name │ test │ │ Type │ mysql │ │ Plan │ hobbyist-2 │ │ Disk Size │ 8.0 GiB │ │ State │ running │ │ Creation Date │ 2025-01-09 11:18:51 +0000 UTC │ │ Update Date │ 2025-01-09 11:21:22 +0000 UTC │ │ Nodes │ 1 │ │ Node CPUs │ 2 │ │ Node Memory │ 2.0 GiB │ │ Termination Protected │ true │ │ Maintenance │ sunday (02:45:14) │ │ Version │ 8.0.30 │ │ Backup Schedule │ 15:17 │ │ URI │ mysql://avnadmin:xxxxx@test-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699/defaultdb?ssl-mode=REQUIRED │ │ IP Filter │ │ │ Components │ │ │ │ mysqlx test-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21703 route:dynamic usage:primary │ │ │ mysql test-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699 route:dynamic usage:primary │ │ │ │ │ Users │ avnadmin (primary) │ │ Databases │ blz │ │ │ defaultdb │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ ➜ ~/exo/cli git:(tgrondier/sc-114061/dbaas-support-for-mysql-posgres-database) go run . dbaas database delete test-pg blz -f 25-01-09 12:21 error: database "blz" not found for service "test-pg" exit status 1 ➜ ~/exo/cli git:(tgrondier/sc-114061/dbaas-support-for-mysql-posgres-database) go run . dbaas database delete test blz -f 25-01-09 12:21 ✔ Deleting DBaaS database "blz" 0s ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ DATABASE SERVICE │ │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ │ Zone │ de-muc-1 │ │ Name │ test │ │ Type │ mysql │ │ Plan │ hobbyist-2 │ │ Disk Size │ 8.0 GiB │ │ State │ running │ │ Creation Date │ 2025-01-09 11:18:51 +0000 UTC │ │ Update Date │ 2025-01-09 11:21:32 +0000 UTC │ │ Nodes │ 1 │ │ Node CPUs │ 2 │ │ Node Memory │ 2.0 GiB │ │ Termination Protected │ true │ │ Maintenance │ sunday (02:45:14) │ │ Version │ 8.0.30 │ │ Backup Schedule │ 15:17 │ │ URI │ mysql://avnadmin:xxxxx@test-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699/defaultdb?ssl-mode=REQUIRED │ │ IP Filter │ │ │ Components │ │ │ │ mysqlx test-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21703 route:dynamic usage:primary │ │ │ mysql test-exoscale-6b703a50-10bb-400c-bd87-2cd1ad11d7f9.h.aivencloud.com:21699 route:dynamic usage:primary │ │ │ │ │ Users │ avnadmin (primary) │ │ Databases │ defaultdb │ ┼───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼ ``` --- CHANGELOG.md | 1 + cmd/dbaas_database.go | 14 ++++++ cmd/dbaas_database_create.go | 72 +++++++++++++++++++++++++++++ cmd/dbaas_database_create_mysql.go | 55 ++++++++++++++++++++++ cmd/dbaas_database_create_pg.go | 61 ++++++++++++++++++++++++ cmd/dbaas_database_delete.go | 59 +++++++++++++++++++++++ cmd/dbaas_database_delete_mysql.go | 64 +++++++++++++++++++++++++ cmd/dbaas_database_delete_pg.go | 64 +++++++++++++++++++++++++ cmd/dbaas_show_mysql.go | 25 +++++++++- cmd/dbaas_show_pg.go | 25 ++++++++++ cmd/dbaas_user_create_kafka.go | 9 ++++ cmd/dbaas_user_create_mysql.go | 9 ++++ cmd/dbaas_user_create_opensearch.go | 9 ++++ cmd/dbaas_user_create_pg.go | 9 ++++ cmd/dbaas_user_delete_kafka.go | 1 + cmd/dbaas_user_delete_opensearch.go | 1 + cmd/dbaas_user_delete_pg.go | 1 + 17 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 cmd/dbaas_database.go create mode 100644 cmd/dbaas_database_create.go create mode 100644 cmd/dbaas_database_create_mysql.go create mode 100644 cmd/dbaas_database_create_pg.go create mode 100644 cmd/dbaas_database_delete.go create mode 100644 cmd/dbaas_database_delete_mysql.go create mode 100644 cmd/dbaas_database_delete_pg.go diff --git a/CHANGELOG.md b/CHANGELOG.md index cf72f49c..65929910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - storage: Adding recursive feature to the storage command #653 - Update help for instance protection #658 +- dbaas: database management for mysql, pg #661 ### Bug fixes diff --git a/cmd/dbaas_database.go b/cmd/dbaas_database.go new file mode 100644 index 00000000..638f472c --- /dev/null +++ b/cmd/dbaas_database.go @@ -0,0 +1,14 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var dbaasDatabaseCmd = &cobra.Command{ + Use: "database", + Short: "Manage DBaaS databases", +} + +func init() { + dbaasCmd.AddCommand(dbaasDatabaseCmd) +} diff --git a/cmd/dbaas_database_create.go b/cmd/dbaas_database_create.go new file mode 100644 index 00000000..eb0d09f2 --- /dev/null +++ b/cmd/dbaas_database_create.go @@ -0,0 +1,72 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +type dbaasDatabaseCreateCmd struct { + cliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"create"` + + Name string `cli-arg:"#"` + Database string `cli-arg:"#"` + + HelpPg bool `cli-usage:"show usage for flags specific to the pg type"` + Zone string `cli-short:"z" cli-usage:"Database Service zone"` + + // "pg" type specific flags + PgLcCollate string `cli-usage:"Default string sort order (LC_COLLATE) for PostgreSQL database" cli-hidden:""` + PgLcCtype string `cli-usage:"Default character classification (LC_CTYPE) for PostgreSQL database" cli-hidden:""` +} + +func (c *dbaasDatabaseCreateCmd) cmdAliases() []string { return nil } + +func (c *dbaasDatabaseCreateCmd) cmdShort() string { return "Create DBAAS database" } + +func (c *dbaasDatabaseCreateCmd) cmdLong() string { + return `This command creates a DBAAS database for the specified service.` +} + +func (c *dbaasDatabaseCreateCmd) cmdPreRun(cmd *cobra.Command, args []string) error { + switch { + + case cmd.Flags().Changed("help-mysql"): + cmdShowHelpFlags(cmd.Flags(), "mysql-") + os.Exit(0) + case cmd.Flags().Changed("help-pg"): + cmdShowHelpFlags(cmd.Flags(), "pg-") + os.Exit(0) + } + + cmdSetZoneFlagFromDefault(cmd) + return cliCommandDefaultPreRun(c, cmd, args) +} + +func (c *dbaasDatabaseCreateCmd) cmdRun(cmd *cobra.Command, args []string) error { + + ctx := gContext + db, err := dbaasGetV3(ctx, c.Name, c.Zone) + if err != nil { + return err + } + + switch db.Type { + case "mysql": + return c.createMysql(cmd, args) + case "pg": + return c.createPg(cmd, args) + default: + return fmt.Errorf("creating database unsupported for service of type %q", db.Type) + } + +} + +func init() { + cobra.CheckErr(registerCLICommand(dbaasDatabaseCmd, &dbaasDatabaseCreateCmd{ + cliCommandSettings: defaultCLICmdSettings(), + })) +} diff --git a/cmd/dbaas_database_create_mysql.go b/cmd/dbaas_database_create_mysql.go new file mode 100644 index 00000000..3aed4d03 --- /dev/null +++ b/cmd/dbaas_database_create_mysql.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "fmt" + + "github.com/exoscale/cli/pkg/account" + "github.com/exoscale/cli/pkg/globalstate" + exoapi "github.com/exoscale/egoscale/v2/api" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +func (c dbaasDatabaseCreateCmd) createMysql(cmd *cobra.Command, _ []string) error { + + ctx := gContext + + client, err := switchClientZoneV3(ctx, globalstate.EgoscaleV3Client, v3.ZoneName(c.Zone)) + if err != nil { + return err + } + + s, err := client.GetDBAASServiceMysql(ctx, c.Name) + if err != nil { + return err + } + + if len(s.Databases) == 0 { + return fmt.Errorf("service %q is not ready for database creation", c.Name) + } + + req := v3.CreateDBAASMysqlDatabaseRequest{ + DatabaseName: v3.DBAASDatabaseName(c.Database), + } + + op, err := client.CreateDBAASMysqlDatabase(ctx, c.Name, req) + if err != nil { + return err + } + + decorateAsyncOperation(fmt.Sprintf("Creating DBaaS database %q", c.Database), func() { + op, err = client.Wait(ctx, op, v3.OperationStateSuccess) + }) + if err != nil { + return err + } + + if !globalstate.Quiet { + return c.outputFunc((&dbaasServiceShowCmd{ + Name: c.Name, + Zone: c.Zone, + }).showDatabaseServiceMysql(exoapi.WithEndpoint(gContext, exoapi.NewReqEndpoint(account.CurrentAccount.Environment, c.Zone)))) + } + + return err +} diff --git a/cmd/dbaas_database_create_pg.go b/cmd/dbaas_database_create_pg.go new file mode 100644 index 00000000..45b9b2fe --- /dev/null +++ b/cmd/dbaas_database_create_pg.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "fmt" + + "github.com/exoscale/cli/pkg/account" + "github.com/exoscale/cli/pkg/globalstate" + exoapi "github.com/exoscale/egoscale/v2/api" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +func (c dbaasDatabaseCreateCmd) createPg(cmd *cobra.Command, _ []string) error { + ctx := gContext + + client, err := switchClientZoneV3(ctx, globalstate.EgoscaleV3Client, v3.ZoneName(c.Zone)) + if err != nil { + return err + } + s, err := client.GetDBAASServicePG(ctx, c.Name) + if err != nil { + return err + } + + if len(s.Databases) == 0 { + return fmt.Errorf("service %q is not ready for database creation", c.Name) + } + + req := v3.CreateDBAASPGDatabaseRequest{ + DatabaseName: v3.DBAASDatabaseName(c.Database), + } + + if c.PgLcCollate != "" { + req.LCCollate = c.PgLcCollate + } + + if c.PgLcCtype != "" { + req.LCCtype = c.PgLcCtype + } + + op, err := client.CreateDBAASPGDatabase(ctx, c.Name, req) + if err != nil { + return err + } + + decorateAsyncOperation(fmt.Sprintf("Creating DBaaS database %q", c.Database), func() { + op, err = client.Wait(ctx, op, v3.OperationStateSuccess) + }) + if err != nil { + return err + } + + if !globalstate.Quiet { + return c.outputFunc((&dbaasServiceShowCmd{ + Name: c.Name, + Zone: c.Zone, + }).showDatabaseServicePG(exoapi.WithEndpoint(gContext, exoapi.NewReqEndpoint(account.CurrentAccount.Environment, c.Zone)))) + } + + return err +} diff --git a/cmd/dbaas_database_delete.go b/cmd/dbaas_database_delete.go new file mode 100644 index 00000000..f8f18e84 --- /dev/null +++ b/cmd/dbaas_database_delete.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +type dbaasDatabaseDeleteCmd struct { + cliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"delete"` + + Name string `cli-arg:"#"` + Database string `cli-arg:"#"` + + Zone string `cli-short:"z" cli-usage:"Database Service zone"` + + Force bool `cli-short:"f" cli-usage:"don't prompt for confirmation"` +} + +func (c *dbaasDatabaseDeleteCmd) cmdAliases() []string { return nil } + +func (c *dbaasDatabaseDeleteCmd) cmdShort() string { return "Delete DBAAS database" } + +func (c *dbaasDatabaseDeleteCmd) cmdLong() string { + return `This command deletes a DBAAS database for the specified service.` +} + +func (c *dbaasDatabaseDeleteCmd) cmdPreRun(cmd *cobra.Command, args []string) error { + + cmdSetZoneFlagFromDefault(cmd) + return cliCommandDefaultPreRun(c, cmd, args) +} + +func (c *dbaasDatabaseDeleteCmd) cmdRun(cmd *cobra.Command, args []string) error { + + ctx := gContext + db, err := dbaasGetV3(ctx, c.Name, c.Zone) + if err != nil { + return err + } + + switch db.Type { + case "mysql": + return c.deleteMysql(cmd, args) + case "pg": + return c.deletePg(cmd, args) + default: + return fmt.Errorf("creating database unsupported for service of type %q", db.Type) + } + +} + +func init() { + cobra.CheckErr(registerCLICommand(dbaasDatabaseCmd, &dbaasDatabaseDeleteCmd{ + cliCommandSettings: defaultCLICmdSettings(), + })) +} diff --git a/cmd/dbaas_database_delete_mysql.go b/cmd/dbaas_database_delete_mysql.go new file mode 100644 index 00000000..6a030973 --- /dev/null +++ b/cmd/dbaas_database_delete_mysql.go @@ -0,0 +1,64 @@ +package cmd + +import ( + "fmt" + + "github.com/exoscale/cli/pkg/account" + "github.com/exoscale/cli/pkg/globalstate" + exoapi "github.com/exoscale/egoscale/v2/api" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +func (c dbaasDatabaseDeleteCmd) deleteMysql(cmd *cobra.Command, _ []string) error { + ctx := gContext + + client, err := switchClientZoneV3(ctx, globalstate.EgoscaleV3Client, v3.ZoneName(c.Zone)) + if err != nil { + return err + } + + s, err := client.GetDBAASServiceMysql(ctx, c.Name) + if err != nil { + return err + } + + dbFound := false + for _, db := range s.Databases { + if db == v3.DBAASMysqlDatabaseName(c.Database) { + dbFound = true + break + } + } + + if !dbFound { + return fmt.Errorf("database %q not found for service %q", c.Database, c.Name) + } + if !c.Force { + if !askQuestion(fmt.Sprintf( + "Are you sure you want to delete database %q", c.Database)) { + return nil + } + } + + op, err := client.DeleteDBAASMysqlDatabase(ctx, c.Name, c.Database) + if err != nil { + return err + } + + decorateAsyncOperation(fmt.Sprintf("Deleting DBaaS database %q", c.Database), func() { + op, err = client.Wait(ctx, op, v3.OperationStateSuccess) + }) + if err != nil { + return err + } + + if !globalstate.Quiet { + return c.outputFunc((&dbaasServiceShowCmd{ + Name: c.Name, + Zone: c.Zone, + }).showDatabaseServiceMysql(exoapi.WithEndpoint(gContext, exoapi.NewReqEndpoint(account.CurrentAccount.Environment, c.Zone)))) + } + + return err +} diff --git a/cmd/dbaas_database_delete_pg.go b/cmd/dbaas_database_delete_pg.go new file mode 100644 index 00000000..a4941b58 --- /dev/null +++ b/cmd/dbaas_database_delete_pg.go @@ -0,0 +1,64 @@ +package cmd + +import ( + "fmt" + + "github.com/exoscale/cli/pkg/account" + "github.com/exoscale/cli/pkg/globalstate" + exoapi "github.com/exoscale/egoscale/v2/api" + v3 "github.com/exoscale/egoscale/v3" + "github.com/spf13/cobra" +) + +func (c dbaasDatabaseDeleteCmd) deletePg(cmd *cobra.Command, _ []string) error { + ctx := gContext + + client, err := switchClientZoneV3(ctx, globalstate.EgoscaleV3Client, v3.ZoneName(c.Zone)) + if err != nil { + return err + } + + s, err := client.GetDBAASServicePG(ctx, c.Name) + if err != nil { + return err + } + + dbFound := false + for _, db := range s.Databases { + if db == v3.DBAASDatabaseName(c.Database) { + dbFound = true + break + } + } + + if !dbFound { + return fmt.Errorf("database %q not found for service %q", c.Database, c.Name) + } + if !c.Force { + if !askQuestion(fmt.Sprintf( + "Are you sure you want to delete database %q", c.Database)) { + return nil + } + } + + op, err := client.DeleteDBAASPGDatabase(ctx, c.Name, c.Database) + if err != nil { + return err + } + + decorateAsyncOperation(fmt.Sprintf("Deleting DBaaS database %q", c.Database), func() { + op, err = client.Wait(ctx, op, v3.OperationStateSuccess) + }) + if err != nil { + return err + } + + if !globalstate.Quiet { + return c.outputFunc((&dbaasServiceShowCmd{ + Name: c.Name, + Zone: c.Zone, + }).showDatabaseServicePG(exoapi.WithEndpoint(gContext, exoapi.NewReqEndpoint(account.CurrentAccount.Environment, c.Zone)))) + } + + return err +} diff --git a/cmd/dbaas_show_mysql.go b/cmd/dbaas_show_mysql.go index 85e34f09..c043aad1 100644 --- a/cmd/dbaas_show_mysql.go +++ b/cmd/dbaas_show_mysql.go @@ -37,6 +37,7 @@ type dbServiceMysqlUserShowOutput struct { type dbServiceMysqlShowOutput struct { BackupSchedule string `json:"backup_schedule"` Components []dbServiceMysqlComponentShowOutput `json:"components"` + Databases []string `json:"databases"` IPFilter []string `json:"ip_filter"` URI string `json:"uri"` URIParams map[string]interface{} `json:"uri_params"` @@ -49,7 +50,6 @@ func formatDatabaseServiceMysqlTable(t *table.Table, o *dbServiceMysqlShowOutput t.Append([]string{"Backup Schedule", o.BackupSchedule}) t.Append([]string{"URI", redactDatabaseServiceURI(o.URI)}) t.Append([]string{"IP Filter", strings.Join(o.IPFilter, ", ")}) - t.Append([]string{"Components", func() string { buf := bytes.NewBuffer(nil) ct := table.NewEmbeddedTable(buf) @@ -81,6 +81,19 @@ func formatDatabaseServiceMysqlTable(t *table.Table, o *dbServiceMysqlShowOutput } return "n/a" }()}) + + t.Append([]string{"Databases", func() string { + if len(o.Databases) > 0 { + return strings.Join( + func() []string { + dbs := make([]string, len(o.Databases)) + copy(dbs, o.Databases) + return dbs + }(), + "\n") + } + return "n/a" + }()}) } func (c *dbaasServiceShowCmd) showDatabaseServiceMysql(ctx context.Context) (output.Outputter, error) { @@ -235,6 +248,16 @@ func (c *dbaasServiceShowCmd) showDatabaseServiceMysql(ctx context.Context) (out return }(), + Databases: func() (v []string) { + if databaseService.Databases != nil { + v = make([]string, len(*databaseService.Databases)) + for i, d := range *databaseService.Databases { + v[i] = string(d) + } + } + return + }(), + IPFilter: func() (v []string) { if databaseService.IpFilter != nil { v = *databaseService.IpFilter diff --git a/cmd/dbaas_show_pg.go b/cmd/dbaas_show_pg.go index b3d4a47e..c1d148b4 100644 --- a/cmd/dbaas_show_pg.go +++ b/cmd/dbaas_show_pg.go @@ -47,6 +47,7 @@ type dbServicePGUserShowOutput struct { type dbServicePGShowOutput struct { BackupSchedule string `json:"backup_schedule"` Components []dbServicePGComponentShowOutput `json:"components"` + Databases []string `json:"databases"` ConnectionPools []dbServicePGConnectionPool `json:"connection_pools"` IPFilter []string `json:"ip_filter"` URI string `json:"uri"` @@ -111,6 +112,20 @@ func formatDatabaseServicePGTable(t *table.Table, o *dbServicePGShowOutput) { } return "n/a" }()}) + + t.Append([]string{"Databases", func() string { + if len(o.Databases) > 0 { + return strings.Join( + func() []string { + dbs := make([]string, len(o.Databases)) + copy(dbs, o.Databases) + return dbs + }(), + "\n") + } + return "n/a" + }()}) + } func (c *dbaasServiceShowCmd) showDatabaseServicePG(ctx context.Context) (output.Outputter, error) { @@ -269,6 +284,16 @@ func (c *dbaasServiceShowCmd) showDatabaseServicePG(ctx context.Context) (output return }(), + Databases: func() (v []string) { + if databaseService.Databases != nil { + v = make([]string, len(*databaseService.Databases)) + for i, d := range *databaseService.Databases { + v[i] = string(d) + } + } + return + }(), + ConnectionPools: func() (v []dbServicePGConnectionPool) { if databaseService.ConnectionPools != nil { for _, pool := range *databaseService.ConnectionPools { diff --git a/cmd/dbaas_user_create_kafka.go b/cmd/dbaas_user_create_kafka.go index e0693268..3d00a1b8 100644 --- a/cmd/dbaas_user_create_kafka.go +++ b/cmd/dbaas_user_create_kafka.go @@ -17,6 +17,15 @@ func (c *dbaasUserCreateCmd) createKafka(cmd *cobra.Command, _ []string) error { return err } + s, err := client.GetDBAASServicePG(ctx, c.Name) + if err != nil { + return err + } + + if len(s.Users) == 0 { + return fmt.Errorf("service %q is not ready for user creation", c.Name) + } + req := v3.CreateDBAASKafkaUserRequest{Username: v3.DBAASUserUsername(c.Username)} op, err := client.CreateDBAASKafkaUser(ctx, c.Name, req) diff --git a/cmd/dbaas_user_create_mysql.go b/cmd/dbaas_user_create_mysql.go index f88f776c..af2609fa 100644 --- a/cmd/dbaas_user_create_mysql.go +++ b/cmd/dbaas_user_create_mysql.go @@ -17,6 +17,15 @@ func (c *dbaasUserCreateCmd) createMysql(cmd *cobra.Command, _ []string) error { return err } + s, err := client.GetDBAASServiceMysql(ctx, c.Name) + if err != nil { + return err + } + + if len(s.Users) == 0 { + return fmt.Errorf("service %q is not ready for user creation", c.Name) + } + req := v3.CreateDBAASMysqlUserRequest{Username: v3.DBAASUserUsername(c.Username)} if c.MysqlAuthenticationMethod != "" { req.Authentication = v3.EnumMysqlAuthenticationPlugin(c.MysqlAuthenticationMethod) diff --git a/cmd/dbaas_user_create_opensearch.go b/cmd/dbaas_user_create_opensearch.go index d7157e6f..22926e7b 100644 --- a/cmd/dbaas_user_create_opensearch.go +++ b/cmd/dbaas_user_create_opensearch.go @@ -17,6 +17,15 @@ func (c *dbaasUserCreateCmd) createOpensearch(cmd *cobra.Command, _ []string) er return err } + s, err := client.GetDBAASServiceOpensearch(ctx, c.Name) + if err != nil { + return err + } + + if len(s.Users) == 0 { + return fmt.Errorf("service %q is not ready for user creation", c.Name) + } + req := v3.CreateDBAASOpensearchUserRequest{Username: v3.DBAASUserUsername(c.Username)} op, err := client.CreateDBAASOpensearchUser(ctx, c.Name, req) diff --git a/cmd/dbaas_user_create_pg.go b/cmd/dbaas_user_create_pg.go index 446a8b94..18a88286 100644 --- a/cmd/dbaas_user_create_pg.go +++ b/cmd/dbaas_user_create_pg.go @@ -17,6 +17,15 @@ func (c *dbaasUserCreateCmd) createPg(cmd *cobra.Command, _ []string) error { return err } + s, err := client.GetDBAASServicePG(ctx, c.Name) + if err != nil { + return err + } + + if len(s.Users) == 0 { + return fmt.Errorf("service %q is not ready for user creation", c.Name) + } + req := v3.CreateDBAASPostgresUserRequest{Username: v3.DBAASUserUsername(c.Username), AllowReplication: &c.PostgresAllowReplication} op, err := client.CreateDBAASPostgresUser(ctx, c.Name, req) diff --git a/cmd/dbaas_user_delete_kafka.go b/cmd/dbaas_user_delete_kafka.go index 1e5e82ad..120cc56b 100644 --- a/cmd/dbaas_user_delete_kafka.go +++ b/cmd/dbaas_user_delete_kafka.go @@ -26,6 +26,7 @@ func (c *dbaasUserDeleteCmd) deleteKafka(cmd *cobra.Command, _ []string) error { userFound := false for _, u := range s.Users { if u.Username == c.Username { + userFound = true break } } diff --git a/cmd/dbaas_user_delete_opensearch.go b/cmd/dbaas_user_delete_opensearch.go index 3990be30..64ce249b 100644 --- a/cmd/dbaas_user_delete_opensearch.go +++ b/cmd/dbaas_user_delete_opensearch.go @@ -26,6 +26,7 @@ func (c *dbaasUserDeleteCmd) deleteOpensearch(cmd *cobra.Command, _ []string) er userFound := false for _, u := range s.Users { if u.Username == c.Username { + userFound = true break } } diff --git a/cmd/dbaas_user_delete_pg.go b/cmd/dbaas_user_delete_pg.go index 53dc4f81..0cd3c9a9 100644 --- a/cmd/dbaas_user_delete_pg.go +++ b/cmd/dbaas_user_delete_pg.go @@ -26,6 +26,7 @@ func (c *dbaasUserDeleteCmd) deletePg(cmd *cobra.Command, _ []string) error { userFound := false for _, u := range s.Users { if u.Username == c.Username { + userFound = true break } }