1/* 2Copyright 2018 The Doctl Authors All rights reserved. 3Licensed under the Apache License, Version 2.0 (the "License"); 4you may not use this file except in compliance with the License. 5You may obtain a copy of the License at 6 http://www.apache.org/licenses/LICENSE-2.0 7Unless required by applicable law or agreed to in writing, software 8distributed under the License is distributed on an "AS IS" BASIS, 9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10See the License for the specific language governing permissions and 11limitations under the License. 12*/ 13 14package commands 15 16import ( 17 "errors" 18 "fmt" 19 "strings" 20 21 "github.com/digitalocean/doctl" 22 "github.com/digitalocean/doctl/commands/displayers" 23 "github.com/digitalocean/doctl/do" 24 "github.com/digitalocean/godo" 25 "github.com/spf13/cobra" 26) 27 28const ( 29 defaultDatabaseNodeSize = "db-s-1vcpu-1gb" 30 defaultDatabaseNodeCount = 1 31 defaultDatabaseRegion = "nyc1" 32 defaultDatabaseEngine = "pg" 33 databaseListDetails = ` 34 35This command requires the ID of a database cluster, which you can retrieve by calling: 36 37 doctl databases list` 38) 39 40// Databases creates the databases command 41func Databases() *Command { 42 cmd := &Command{ 43 Command: &cobra.Command{ 44 Use: "databases", 45 Aliases: []string{"db", "dbs", "d", "database"}, 46 Short: "Display commands that manage databases", 47 Long: "The commands under `doctl databases` are for managing your MySQL, Redis, and PostgreSQL database services.", 48 }, 49 } 50 51 clusterDetails := ` 52 53- The database ID, in UUID format 54- The name you gave the database cluster 55- The database engine (e.g. ` + "`" + `redis` + "`" + `, ` + "`" + `pg` + "`" + `, ` + "`" + `mysql` + "`" + `) 56- The engine version (e.g. ` + "`" + `11` + "`" + ` for PostgreSQL version 11) 57- The number of nodes in the database cluster 58- The region the database cluster resides in (e.g. ` + "`" + `sfo2` + "`" + `, ` + "`" + `nyc1` + "`" + `) 59- The current status of the database cluster (e.g. ` + "`" + `online` + "`" + `) 60- The size of the machine running the database instance (e.g. ` + "`" + `db-s-1vcpu-1gb` + "`" + `)` 61 62 CmdBuilder(cmd, RunDatabaseList, "list", "List your database clusters", `This command lists the database clusters associated with your account. The following details are provided:`+clusterDetails, Writer, aliasOpt("ls"), displayerType(&displayers.Databases{})) 63 CmdBuilder(cmd, RunDatabaseGet, "get <database-id>", "Get details for a database cluster", `This command retrieves the following details about the specified database cluster: `+clusterDetails+` 64- A connection string for the database cluster 65- The date and time when the database cluster was created`+databaseListDetails, Writer, aliasOpt("g"), displayerType(&displayers.Databases{})) 66 67 nodeSizeDetails := "The size of the nodes in the database cluster, e.g. `db-s-1vcpu-1gb`` for a 1 CPU, 1GB node" 68 nodeNumberDetails := "The number of nodes in the database cluster. Valid values are are 1-3. In addition to the primary node, up to two standby nodes may be added for high availability." 69 cmdDatabaseCreate := CmdBuilder(cmd, RunDatabaseCreate, "create <name>", "Create a database cluster", `This command creates a database cluster with the specified name. 70 71There are a number of flags that customize the configuration, all of which are optional. Without any flags set, a single-node, single-CPU PostgreSQL database cluster will be created.`, Writer, 72 aliasOpt("c")) 73 AddIntFlag(cmdDatabaseCreate, doctl.ArgDatabaseNumNodes, "", defaultDatabaseNodeCount, nodeNumberDetails) 74 AddStringFlag(cmdDatabaseCreate, doctl.ArgRegionSlug, "", defaultDatabaseRegion, "The region where the database cluster will be created, e.g. `nyc1` or `sfo2`") 75 AddStringFlag(cmdDatabaseCreate, doctl.ArgSizeSlug, "", defaultDatabaseNodeSize, nodeSizeDetails) 76 AddStringFlag(cmdDatabaseCreate, doctl.ArgDatabaseEngine, "", defaultDatabaseEngine, "The database engine to be used for the cluster. Possible values are: `pg` for PostgreSQL, `mysql`, and `redis`.") 77 AddStringFlag(cmdDatabaseCreate, doctl.ArgVersion, "", "", "The database engine version, e.g. 11 for PostgreSQL version 11") 78 AddStringFlag(cmdDatabaseCreate, doctl.ArgPrivateNetworkUUID, "", "", "The UUID of a VPC to create the database cluster in; the default VPC for the region will be used if excluded") 79 80 cmdDatabaseDelete := CmdBuilder(cmd, RunDatabaseDelete, "delete <database-id>", "Delete a database cluster", `This command deletes the database cluster with the given ID. 81 82To retrieve a list of your database clusters and their IDs, call `+"`"+`doctl databases list`+"`"+`.`, Writer, 83 aliasOpt("rm")) 84 AddBoolFlag(cmdDatabaseDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Delete the database cluster without a confirmation prompt") 85 86 CmdBuilder(cmd, RunDatabaseConnectionGet, "connection <database-id>", "Retrieve connection details for a database cluster", `This command retrieves the following connection details for a database cluster: 87 88- The connection string for the database cluster 89- The default database name 90- The fully-qualified domain name of the publicly-connectable host 91- The port on which the database is listening for connections 92- The default username 93- The randomly-generated password for the default username 94- A boolean indicating if the connection should be made over SSL 95 96While these connection details will work, you may wish to use different connection details, such as the private hostname, a custom username, or a different database.`, Writer, 97 aliasOpt("conn"), displayerType(&displayers.DatabaseConnection{})) 98 99 CmdBuilder(cmd, RunDatabaseBackupsList, "backups <database-id>", "List database cluster backups", `This command retrieves a list of backups created for the specified database cluster. 100 101The list contains the size in GB, and the date and time the backup was taken.`, Writer, 102 aliasOpt("bu"), displayerType(&displayers.DatabaseBackups{})) 103 104 cmdDatabaseResize := CmdBuilder(cmd, RunDatabaseResize, "resize <database-id>", "Resize a database cluster", `This command resizes the specified database cluster. 105 106You must specify the size of the machines you wish to use as nodes as well as how many nodes you would like. For example: 107 108 doctl databases resize ca9f591d-9999-5555-a0ef-1c02d1d1e352 --num-nodes 2 --size db-s-16vcpu-64gb`, Writer, 109 aliasOpt("rs")) 110 AddIntFlag(cmdDatabaseResize, doctl.ArgDatabaseNumNodes, "", 0, nodeNumberDetails, requiredOpt()) 111 AddStringFlag(cmdDatabaseResize, doctl.ArgSizeSlug, "", "", nodeSizeDetails, requiredOpt()) 112 113 cmdDatabaseMigrate := CmdBuilder(cmd, RunDatabaseMigrate, "migrate <database-id>", "Migrate a database cluster to a new region", `This command migrates the specified database cluster to a new region`, Writer, 114 aliasOpt("m")) 115 AddStringFlag(cmdDatabaseMigrate, doctl.ArgRegionSlug, "", "", "The region to which the database cluster should be migrated, e.g. `sfo2` or `nyc3`.", requiredOpt()) 116 AddStringFlag(cmdDatabaseMigrate, doctl.ArgPrivateNetworkUUID, "", "", "The UUID of a VPC to create the database cluster in; the default VPC for the region will be used if excluded") 117 118 cmd.AddCommand(databaseReplica()) 119 cmd.AddCommand(databaseMaintenanceWindow()) 120 cmd.AddCommand(databaseUser()) 121 cmd.AddCommand(databaseDB()) 122 cmd.AddCommand(databasePool()) 123 cmd.AddCommand(sqlMode()) 124 cmd.AddCommand(databaseFirewalls()) 125 126 return cmd 127} 128 129// Clusters 130 131// RunDatabaseList returns a list of database clusters. 132func RunDatabaseList(c *CmdConfig) error { 133 dbs, err := c.Databases().List() 134 if err != nil { 135 return err 136 } 137 138 return displayDatabases(c, true, dbs...) 139} 140 141// RunDatabaseGet returns an individual database cluster 142func RunDatabaseGet(c *CmdConfig) error { 143 if len(c.Args) == 0 { 144 return doctl.NewMissingArgsErr(c.NS) 145 } 146 147 id := c.Args[0] 148 db, err := c.Databases().Get(id) 149 if err != nil { 150 return err 151 } 152 153 return displayDatabases(c, false, *db) 154} 155 156// RunDatabaseCreate creates a database cluster 157func RunDatabaseCreate(c *CmdConfig) error { 158 if len(c.Args) == 0 { 159 return doctl.NewMissingArgsErr(c.NS) 160 } 161 162 r, err := buildDatabaseCreateRequestFromArgs(c) 163 if err != nil { 164 return err 165 } 166 167 db, err := c.Databases().Create(r) 168 if err != nil { 169 return err 170 } 171 172 return displayDatabases(c, false, *db) 173} 174 175func buildDatabaseCreateRequestFromArgs(c *CmdConfig) (*godo.DatabaseCreateRequest, error) { 176 r := &godo.DatabaseCreateRequest{Name: c.Args[0]} 177 178 region, err := c.Doit.GetString(c.NS, doctl.ArgRegionSlug) 179 if err != nil { 180 return nil, err 181 } 182 r.Region = region 183 184 numNodes, err := c.Doit.GetInt(c.NS, doctl.ArgDatabaseNumNodes) 185 if err != nil { 186 return nil, err 187 } 188 r.NumNodes = numNodes 189 190 size, err := c.Doit.GetString(c.NS, doctl.ArgSizeSlug) 191 if err != nil { 192 return nil, err 193 } 194 r.SizeSlug = size 195 196 engine, err := c.Doit.GetString(c.NS, doctl.ArgDatabaseEngine) 197 if err != nil { 198 return nil, err 199 } 200 r.EngineSlug = engine 201 202 version, err := c.Doit.GetString(c.NS, doctl.ArgVersion) 203 if err != nil { 204 return nil, err 205 } 206 r.Version = version 207 208 privateNetworkUUID, err := c.Doit.GetString(c.NS, doctl.ArgPrivateNetworkUUID) 209 if err != nil { 210 return nil, err 211 } 212 r.PrivateNetworkUUID = privateNetworkUUID 213 214 return r, nil 215} 216 217// RunDatabaseDelete deletes a database cluster 218func RunDatabaseDelete(c *CmdConfig) error { 219 if len(c.Args) == 0 { 220 return doctl.NewMissingArgsErr(c.NS) 221 } 222 223 force, err := c.Doit.GetBool(c.NS, doctl.ArgForce) 224 if err != nil { 225 return err 226 } 227 228 if force || AskForConfirmDelete("database cluster", 1) == nil { 229 id := c.Args[0] 230 return c.Databases().Delete(id) 231 } 232 233 return errOperationAborted 234} 235 236func displayDatabases(c *CmdConfig, short bool, dbs ...do.Database) error { 237 item := &displayers.Databases{ 238 Databases: do.Databases(dbs), 239 Short: short, 240 } 241 return c.Display(item) 242} 243 244// RunDatabaseConnectionGet gets database connection info 245func RunDatabaseConnectionGet(c *CmdConfig) error { 246 if len(c.Args) == 0 { 247 return doctl.NewMissingArgsErr(c.NS) 248 } 249 250 id := c.Args[0] 251 connInfo, err := c.Databases().GetConnection(id) 252 if err != nil { 253 return err 254 } 255 256 return displayDatabaseConnection(c, *connInfo) 257} 258 259func displayDatabaseConnection(c *CmdConfig, conn do.DatabaseConnection) error { 260 item := &displayers.DatabaseConnection{DatabaseConnection: conn} 261 return c.Display(item) 262} 263 264// RunDatabaseBackupsList lists all the backups for a database cluster 265func RunDatabaseBackupsList(c *CmdConfig) error { 266 if len(c.Args) == 0 { 267 return doctl.NewMissingArgsErr(c.NS) 268 } 269 270 id := c.Args[0] 271 backups, err := c.Databases().ListBackups(id) 272 if err != nil { 273 return err 274 } 275 276 return displayDatabaseBackups(c, backups) 277} 278 279func displayDatabaseBackups(c *CmdConfig, bu do.DatabaseBackups) error { 280 item := &displayers.DatabaseBackups{DatabaseBackups: bu} 281 return c.Display(item) 282} 283 284// RunDatabaseResize resizes a database cluster 285func RunDatabaseResize(c *CmdConfig) error { 286 if len(c.Args) == 0 { 287 return doctl.NewMissingArgsErr(c.NS) 288 } 289 290 id := c.Args[0] 291 292 r, err := buildDatabaseResizeRequestFromArgs(c) 293 if err != nil { 294 return err 295 } 296 297 return c.Databases().Resize(id, r) 298} 299 300func buildDatabaseResizeRequestFromArgs(c *CmdConfig) (*godo.DatabaseResizeRequest, error) { 301 r := &godo.DatabaseResizeRequest{} 302 303 numNodes, err := c.Doit.GetInt(c.NS, doctl.ArgDatabaseNumNodes) 304 if err != nil { 305 return nil, err 306 } 307 r.NumNodes = numNodes 308 309 size, err := c.Doit.GetString(c.NS, doctl.ArgSizeSlug) 310 if err != nil { 311 return nil, err 312 } 313 r.SizeSlug = size 314 315 return r, nil 316} 317 318// RunDatabaseMigrate migrates a database cluster to a new region 319func RunDatabaseMigrate(c *CmdConfig) error { 320 if len(c.Args) == 0 { 321 return doctl.NewMissingArgsErr(c.NS) 322 } 323 324 id := c.Args[0] 325 326 r, err := buildDatabaseMigrateRequestFromArgs(c) 327 if err != nil { 328 return err 329 } 330 331 return c.Databases().Migrate(id, r) 332} 333 334func buildDatabaseMigrateRequestFromArgs(c *CmdConfig) (*godo.DatabaseMigrateRequest, error) { 335 r := &godo.DatabaseMigrateRequest{} 336 337 region, err := c.Doit.GetString(c.NS, doctl.ArgRegionSlug) 338 if err != nil { 339 return nil, err 340 } 341 r.Region = region 342 343 privateNetworkUUID, err := c.Doit.GetString(c.NS, doctl.ArgPrivateNetworkUUID) 344 if err != nil { 345 return nil, err 346 } 347 r.PrivateNetworkUUID = privateNetworkUUID 348 349 return r, nil 350} 351 352func databaseMaintenanceWindow() *Command { 353 cmd := &Command{ 354 Command: &cobra.Command{ 355 Use: "maintenance-window", 356 Aliases: []string{"maintenance", "mw", "main"}, 357 Short: "Display commands for scheduling automatic maintenance on your database cluster", 358 Long: `The ` + "`" + `doctl databases maintenance-window` + "`" + ` commands allow you to schedule, and check the schedule of, maintenance windows for your databases. 359 360Maintenance windows are hour-long blocks of time during which DigitalOcean performs automatic maintenance on databases every week. During this time, health checks, security updates, version upgrades, and more are performed.`, 361 }, 362 } 363 364 CmdBuilder(cmd, RunDatabaseMaintenanceGet, "get <database-id>", 365 "Retrieve details about a database cluster's maintenance windows", `This command retrieves the following information on currently-scheduled maintenance windows for the specified database cluster: 366 367- The day of the week the maintenance window occurs 368- The hour in UTC when maintenance updates will be applied, in 24 hour format (e.g. "16:00") 369- A boolean representing whether maintence updates are currently pending 370 371To see a list of your databases and their IDs, run `+"`"+`doctl databases list`+"`"+`.`, Writer, aliasOpt("g"), 372 displayerType(&displayers.DatabaseMaintenanceWindow{})) 373 374 cmdDatabaseCreate := CmdBuilder(cmd, RunDatabaseMaintenanceUpdate, 375 "update <database-id>", "Update the maintenance window for a database cluster", `This command allows you to update the maintenance window for the specified database cluster. 376 377Maintenance windows are hour-long blocks of time during which DigitalOcean performs automatic maintenance on databases every week. During this time, health checks, security updates, version upgrades, and more are performed. 378 379To change the maintenance window for your database cluster, specify a day of the week and an hour of that day during which you would prefer such maintenance would occur. 380 381 doctl databases maintenance-window ca9f591d-f38h-5555-a0ef-1c02d1d1e35 update --day tuesday --hour 16:00 382 383To see a list of your databases and their IDs, run `+"`"+`doctl databases list`+"`"+`.`, Writer, aliasOpt("u")) 384 AddStringFlag(cmdDatabaseCreate, doctl.ArgDatabaseMaintenanceDay, "", "", 385 "The day of the week the maintenance window occurs (e.g. 'tuesday')", requiredOpt()) 386 AddStringFlag(cmdDatabaseCreate, doctl.ArgDatabaseMaintenanceHour, "", "", 387 "The hour in UTC when maintenance updates will be applied, in 24 hour format (e.g. '16:00')", requiredOpt()) 388 389 return cmd 390} 391 392// Database Maintenance Window 393 394// RunDatabaseMaintenanceGet retrieves the maintenance window info for a database cluster 395func RunDatabaseMaintenanceGet(c *CmdConfig) error { 396 if len(c.Args) == 0 { 397 return doctl.NewMissingArgsErr(c.NS) 398 } 399 400 id := c.Args[0] 401 402 window, err := c.Databases().GetMaintenance(id) 403 if err != nil { 404 return err 405 } 406 407 return displayDatabaseMaintenanceWindow(c, *window) 408} 409 410func displayDatabaseMaintenanceWindow(c *CmdConfig, mw do.DatabaseMaintenanceWindow) error { 411 item := &displayers.DatabaseMaintenanceWindow{DatabaseMaintenanceWindow: mw} 412 return c.Display(item) 413} 414 415// RunDatabaseMaintenanceUpdate updates the maintenance window info for a database cluster 416func RunDatabaseMaintenanceUpdate(c *CmdConfig) error { 417 if len(c.Args) == 0 { 418 return doctl.NewMissingArgsErr(c.NS) 419 } 420 421 id := c.Args[0] 422 r, err := buildDatabaseUpdateMaintenanceRequestFromArgs(c) 423 if err != nil { 424 return err 425 } 426 427 return c.Databases().UpdateMaintenance(id, r) 428} 429 430func buildDatabaseUpdateMaintenanceRequestFromArgs(c *CmdConfig) (*godo.DatabaseUpdateMaintenanceRequest, error) { 431 r := &godo.DatabaseUpdateMaintenanceRequest{} 432 433 day, err := c.Doit.GetString(c.NS, doctl.ArgDatabaseMaintenanceDay) 434 if err != nil { 435 return nil, err 436 } 437 r.Day = strings.ToLower(day) 438 439 hour, err := c.Doit.GetString(c.NS, doctl.ArgDatabaseMaintenanceHour) 440 if err != nil { 441 return nil, err 442 } 443 r.Hour = hour 444 445 return r, nil 446} 447 448func databaseUser() *Command { 449 cmd := &Command{ 450 Command: &cobra.Command{ 451 Use: "user", 452 Aliases: []string{"u"}, 453 Short: "Display commands for managing database users", 454 Long: `The commands under ` + "`" + `doctl databases user` + "`" + ` allow you to view details for, and create, database users. 455 456Database user accounts are scoped to one database cluster, to which they have full admin access, and are given an automatically-generated password.`, 457 }, 458 } 459 460 userDetailsDesc := ` 461 462- The username for the user 463- The password for the user 464- The user's role. The value will be either "primary" or "normal". 465 466Primary user accounts are created by DigitalOcean at database cluster creation time and can't be deleted. Normal user accounts are created by you. Both have administrative privileges on the database cluster. 467 468To retrieve a list of your databases and their IDs, call ` + "`" + `doctl databases list` + "`" + `.` 469 CmdBuilder(cmd, RunDatabaseUserList, "list <database-id>", "Retrieve list of database users", 470 `This command retrieves a list of users for the specified database with the following details:`+userDetailsDesc, Writer, aliasOpt("ls"), displayerType(&displayers.DatabaseUsers{})) 471 CmdBuilder(cmd, RunDatabaseUserGet, "get <database-id> <user-name>", 472 "Retrieve details about a database user", `This command retrieves the following details about the specified user:`+userDetailsDesc+` 473 474To retrieve a list of database users for a database, call `+"`"+`doctl databases user list <database-id>`+"`"+`.`, Writer, aliasOpt("g"), 475 displayerType(&displayers.DatabaseUsers{})) 476 cmdDatabaseUserCreate := CmdBuilder(cmd, RunDatabaseUserCreate, "create <database-id> <user-name>", 477 "Create a database user", `This command creates a user with the username you specify, who will be granted access to the database cluster you specify. 478 479The user will be created with the role set to `+"`"+`normal`+"`"+`, and given an automatically-generated password. 480 481To retrieve a list of your databases and their IDs, call `+"`"+`doctl databases list`+"`"+`.`, Writer, aliasOpt("c")) 482 483 AddStringFlag(cmdDatabaseUserCreate, doctl.ArgDatabaseUserMySQLAuthPlugin, "", "", 484 "set auth mode for MySQL users") 485 486 CmdBuilder(cmd, RunDatabaseUserResetAuth, "reset <database-id> <user-name> <new-auth-mode>", 487 "Resets a user's auth", "This command resets the auth password or the MySQL auth plugin for a given user. It will return the new user credentials. When resetting MySQL auth, valid values for `<new-auth-mode>` are `caching_sha2_password` and `mysql_native_password`.", Writer, aliasOpt("rs")) 488 489 cmdDatabaseUserDelete := CmdBuilder(cmd, RunDatabaseUserDelete, 490 "delete <database-id> <user-id>", "Delete a database user", `This command deletes the user with the username you specify, whose account was given access to the database cluster you specify. 491 492To retrieve a list of your databases and their IDs, call `+"`"+`doctl databases list`+"`"+`.`, Writer, aliasOpt("rm")) 493 AddBoolFlag(cmdDatabaseUserDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Delete the user without a confirmation prompt") 494 495 return cmd 496} 497 498// Database Users 499 500// RunDatabaseUserList retrieves a list of users for specific database cluster 501func RunDatabaseUserList(c *CmdConfig) error { 502 if len(c.Args) == 0 { 503 return doctl.NewMissingArgsErr(c.NS) 504 } 505 506 id := c.Args[0] 507 508 users, err := c.Databases().ListUsers(id) 509 if err != nil { 510 return err 511 } 512 513 return displayDatabaseUsers(c, users...) 514} 515 516// RunDatabaseUserGet retrieves a database user for a specific database cluster 517func RunDatabaseUserGet(c *CmdConfig) error { 518 if len(c.Args) < 2 { 519 return doctl.NewMissingArgsErr(c.NS) 520 } 521 522 databaseID := c.Args[0] 523 userID := c.Args[1] 524 525 user, err := c.Databases().GetUser(databaseID, userID) 526 if err != nil { 527 return err 528 } 529 530 return displayDatabaseUsers(c, *user) 531} 532 533// RunDatabaseUserCreate creates a database user for a database cluster 534func RunDatabaseUserCreate(c *CmdConfig) error { 535 if len(c.Args) < 2 { 536 return doctl.NewMissingArgsErr(c.NS) 537 } 538 539 var ( 540 databaseID = c.Args[0] 541 userName = c.Args[1] 542 ) 543 544 req := &godo.DatabaseCreateUserRequest{Name: userName} 545 546 authMode, err := c.Doit.GetString(c.NS, doctl.ArgDatabaseUserMySQLAuthPlugin) 547 if err != nil { 548 return err 549 } 550 551 if authMode != "" { 552 req.MySQLSettings = &godo.DatabaseMySQLUserSettings{ 553 AuthPlugin: authMode, 554 } 555 } 556 557 user, err := c.Databases().CreateUser(databaseID, req) 558 if err != nil { 559 return err 560 } 561 562 return displayDatabaseUsers(c, *user) 563} 564 565func RunDatabaseUserResetAuth(c *CmdConfig) error { 566 if len(c.Args) < 2 { 567 return doctl.NewMissingArgsErr(c.NS) 568 } 569 570 var ( 571 databaseID = c.Args[0] 572 userName = c.Args[1] 573 ) 574 575 database, err := c.Databases().Get(databaseID) 576 577 if err != nil { 578 return err 579 } 580 581 var req *godo.DatabaseResetUserAuthRequest 582 if strings.ToLower(database.EngineSlug) == "mysql" { 583 if len(c.Args) < 3 { 584 return doctl.NewMissingArgsErr(c.NS) 585 } 586 authMode := c.Args[2] 587 req = &godo.DatabaseResetUserAuthRequest{ 588 MySQLSettings: &godo.DatabaseMySQLUserSettings{ 589 AuthPlugin: authMode, 590 }, 591 } 592 } else { 593 req = &godo.DatabaseResetUserAuthRequest{} 594 } 595 596 user, err := c.Databases().ResetUserAuth(databaseID, userName, req) 597 if err != nil { 598 return err 599 } 600 601 return displayDatabaseUsers(c, *user) 602} 603 604// RunDatabaseUserDelete deletes a database user 605func RunDatabaseUserDelete(c *CmdConfig) error { 606 if len(c.Args) < 2 { 607 return doctl.NewMissingArgsErr(c.NS) 608 } 609 610 force, err := c.Doit.GetBool(c.NS, doctl.ArgForce) 611 if err != nil { 612 return err 613 } 614 615 if force || AskForConfirmDelete("database user", 1) == nil { 616 databaseID := c.Args[0] 617 userID := c.Args[1] 618 return c.Databases().DeleteUser(databaseID, userID) 619 } 620 621 return errOperationAborted 622} 623 624func displayDatabaseUsers(c *CmdConfig, users ...do.DatabaseUser) error { 625 item := &displayers.DatabaseUsers{DatabaseUsers: users} 626 return c.Display(item) 627} 628 629func databasePool() *Command { 630 cmd := &Command{ 631 Command: &cobra.Command{ 632 Use: "pool", 633 Aliases: []string{"p"}, 634 Short: "Display commands for managing connection pools", 635 Long: `The subcommands under ` + "`" + `doctl databases pool` + "`" + ` are for managing connection pools for your database cluster. 636 637A connection pool may be useful if your database: 638 639- Typically handles a large number of idle connections, 640- Has wide variability in the possible number of connections at any given time, 641- Drops connections due to max connection limits, or 642- Experiences performance issues due to high CPU usage. 643 644Connection pools can be created and deleted with these commands, or you can simply retrieve information about them.`, 645 }, 646 } 647 648 connectionPoolDetails := ` 649 650- The username of the database user account that the connection pool uses 651- The name of the connection pool 652- The size of the connection pool, i.e. the number of connections that will be allocated 653- The database within the cluster for which the connection pool is used 654- The pool mode for the connection pool, which can be 'session', 'transaction', or 'statement' 655- A connection string for the connection pool` 656 getPoolDetails := ` 657 658You can get a list of existing connection pools by calling: 659 660 doctl databases pool list 661 662You can get a list of existing database clusters and their IDs by calling: 663 664 doctl databases list` 665 CmdBuilder(cmd, RunDatabasePoolList, "list <database-id>", "List connection pools for a database cluster", `This command lists the existing connection pools for the specified database. The following information will be returned:`+connectionPoolDetails, 666 Writer, aliasOpt("ls"), displayerType(&displayers.DatabasePools{})) 667 CmdBuilder(cmd, RunDatabasePoolGet, "get <database-id> <pool-name>", 668 "Retrieve information about a database connection pool", `This command retrieves the following information about the specified connection pool for the specified database cluster:`+connectionPoolDetails+getPoolDetails, Writer, aliasOpt("g"), 669 displayerType(&displayers.DatabasePools{})) 670 cmdDatabasePoolCreate := CmdBuilder(cmd, RunDatabasePoolCreate, 671 "create <database-id> <pool-name>", "Create a connection pool for a database", `This command creates a connection pool for the specified database cluster and gives it the specified name. 672 673You must also use flags to specify the target database, pool size, and database user's username that will be used for the pool. An example call would be: 674 675 pool create ca9f591d-fb58-5555-a0ef-1c02d1d1e352 mypool --db defaultdb --size 10 --user doadmin 676 677The pool size is the minimum number of connections the pool can handle. The maximum pool size varies based on the size of the cluster. 678 679There’s no perfect formula to determine how large your pool should be, but there are a few good guidelines to keep in mind: 680 681- A large pool will stress your database at similar levels as that number of clients would alone. 682- A pool that’s much smaller than the number of clients communicating with the database can act as a bottleneck, reducing the rate when your database receives and responds to transactions. 683 684We recommend starting with a pool size of about half your available connections and adjusting later based on performance. If you see slow query responses, check the CPU usage on the database’s Overview tab. We recommend decreasing your pool size if CPU usage is high, and increasing your pool size if it’s low.`+getPoolDetails, Writer, 685 aliasOpt("c")) 686 AddStringFlag(cmdDatabasePoolCreate, doctl.ArgDatabasePoolMode, "", 687 "transaction", "The pool mode for the connection pool, e.g. `session`, `transaction`, and `statement`") 688 AddIntFlag(cmdDatabasePoolCreate, doctl.ArgSizeSlug, "", 0, "pool size", 689 requiredOpt()) 690 AddStringFlag(cmdDatabasePoolCreate, doctl.ArgDatabasePoolUserName, "", "", 691 "The username for the database user", requiredOpt()) 692 AddStringFlag(cmdDatabasePoolCreate, doctl.ArgDatabasePoolDBName, "", "", 693 "The name of the specific database within the database cluster", requiredOpt()) 694 695 cmdDatabasePoolDelete := CmdBuilder(cmd, RunDatabasePoolDelete, 696 "delete <database-id> <pool-name>", "Delete a connection pool for a database", `This command deletes the specified connection pool for the specified database cluster.`+getPoolDetails, Writer, 697 aliasOpt("rm")) 698 AddBoolFlag(cmdDatabasePoolDelete, doctl.ArgForce, doctl.ArgShortForce, 699 false, "Delete connection pool without confirmation prompt") 700 701 return cmd 702} 703 704// Database Pools 705 706// RunDatabasePoolList retrieves a list of pools for specific database cluster 707func RunDatabasePoolList(c *CmdConfig) error { 708 if len(c.Args) == 0 { 709 return doctl.NewMissingArgsErr(c.NS) 710 } 711 712 id := c.Args[0] 713 714 pools, err := c.Databases().ListPools(id) 715 if err != nil { 716 return err 717 } 718 719 return displayDatabasePools(c, pools...) 720} 721 722// RunDatabasePoolGet retrieves a database pool for a specific database cluster 723func RunDatabasePoolGet(c *CmdConfig) error { 724 if len(c.Args) < 2 { 725 return doctl.NewMissingArgsErr(c.NS) 726 } 727 728 databaseID := c.Args[0] 729 poolID := c.Args[1] 730 731 pool, err := c.Databases().GetPool(databaseID, poolID) 732 if err != nil { 733 return err 734 } 735 736 return displayDatabasePools(c, *pool) 737} 738 739// RunDatabasePoolCreate creates a database pool for a database cluster 740func RunDatabasePoolCreate(c *CmdConfig) error { 741 if len(c.Args) < 2 { 742 return doctl.NewMissingArgsErr(c.NS) 743 } 744 745 databaseID := c.Args[0] 746 r, err := buildDatabaseCreatePoolRequestFromArgs(c) 747 if err != nil { 748 return err 749 } 750 751 pool, err := c.Databases().CreatePool(databaseID, r) 752 if err != nil { 753 return err 754 } 755 756 return displayDatabasePools(c, *pool) 757} 758 759func buildDatabaseCreatePoolRequestFromArgs(c *CmdConfig) (*godo.DatabaseCreatePoolRequest, error) { 760 req := &godo.DatabaseCreatePoolRequest{Name: c.Args[1]} 761 762 mode, err := c.Doit.GetString(c.NS, doctl.ArgDatabasePoolMode) 763 if err != nil { 764 return nil, err 765 } 766 req.Mode = mode 767 768 size, err := c.Doit.GetInt(c.NS, doctl.ArgDatabasePoolSize) 769 if err != nil { 770 return nil, err 771 } 772 req.Size = size 773 774 db, err := c.Doit.GetString(c.NS, doctl.ArgDatabasePoolDBName) 775 if err != nil { 776 return nil, err 777 } 778 req.Database = db 779 780 user, err := c.Doit.GetString(c.NS, doctl.ArgDatabasePoolUserName) 781 if err != nil { 782 return nil, err 783 } 784 req.User = user 785 786 return req, nil 787} 788 789// RunDatabasePoolDelete deletes a database pool 790func RunDatabasePoolDelete(c *CmdConfig) error { 791 if len(c.Args) < 2 { 792 return doctl.NewMissingArgsErr(c.NS) 793 } 794 795 force, err := c.Doit.GetBool(c.NS, doctl.ArgForce) 796 if err != nil { 797 return err 798 } 799 800 if force || AskForConfirmDelete("database pool", 1) == nil { 801 databaseID := c.Args[0] 802 poolID := c.Args[1] 803 return c.Databases().DeletePool(databaseID, poolID) 804 } 805 806 return errOperationAborted 807} 808 809func displayDatabasePools(c *CmdConfig, pools ...do.DatabasePool) error { 810 item := &displayers.DatabasePools{DatabasePools: pools} 811 return c.Display(item) 812} 813 814func databaseDB() *Command { 815 getClusterList := ` 816 817You can get a list of existing database clusters and their IDs by calling: 818 819 doctl databases list` 820 getDBList := ` 821 822You can get a list of existing databases that are hosted within a cluster by calling: 823 824 doctl databases db list {cluster-id}` 825 cmd := &Command{ 826 Command: &cobra.Command{ 827 Use: "db", 828 Short: "Display commands for managing individual databases within a cluster", 829 Long: `The subcommands under ` + "`" + `doctl databases db` + "`" + ` are for managing specific databases that are served by a database cluster. 830 831You can use these commands to create and delete databases within a cluster, or simply get information about them.` + getClusterList, 832 }, 833 } 834 835 CmdBuilder(cmd, RunDatabaseDBList, "list <database-id>", "Retrieve a list of databases within a cluster", "This command retrieves the names of all databases being hosted in the specified database cluster."+getClusterList, Writer, 836 aliasOpt("ls"), displayerType(&displayers.DatabaseDBs{})) 837 CmdBuilder(cmd, RunDatabaseDBGet, "get <database-id> <db-name>", "Retrieve the name of a database within a cluster", "This command retrieves the name of the specified database hosted in the specified database cluster."+getClusterList+getDBList, 838 Writer, aliasOpt("g"), displayerType(&displayers.DatabaseDBs{})) 839 CmdBuilder(cmd, RunDatabaseDBCreate, "create <database-id> <db-name>", 840 "Create a database within a cluster", "This command creates a database with the specified name in the specified database cluster."+getClusterList, Writer, aliasOpt("c")) 841 842 cmdDatabaseDBDelete := CmdBuilder(cmd, RunDatabaseDBDelete, 843 "delete <database-id> <db-name>", "Delete the specified database from the cluster", "This command deletes the specified database from the specified database cluster."+getClusterList+getDBList, Writer, aliasOpt("rm")) 844 AddBoolFlag(cmdDatabaseDBDelete, doctl.ArgForce, doctl.ArgShortForce, 845 false, "Delete the database without a confirmation prompt") 846 847 return cmd 848} 849 850// Database DBs 851 852// RunDatabaseDBList retrieves a list of databases for specific database cluster 853func RunDatabaseDBList(c *CmdConfig) error { 854 if len(c.Args) == 0 { 855 return doctl.NewMissingArgsErr(c.NS) 856 } 857 858 id := c.Args[0] 859 860 dbs, err := c.Databases().ListDBs(id) 861 if err != nil { 862 return err 863 } 864 865 return displayDatabaseDBs(c, dbs...) 866} 867 868// RunDatabaseDBGet retrieves a database for a specific database cluster 869func RunDatabaseDBGet(c *CmdConfig) error { 870 if len(c.Args) < 2 { 871 return doctl.NewMissingArgsErr(c.NS) 872 } 873 874 databaseID := c.Args[0] 875 dbID := c.Args[1] 876 877 db, err := c.Databases().GetDB(databaseID, dbID) 878 if err != nil { 879 return err 880 } 881 882 return displayDatabaseDBs(c, *db) 883} 884 885// RunDatabaseDBCreate creates a database for a database cluster 886func RunDatabaseDBCreate(c *CmdConfig) error { 887 if len(c.Args) < 2 { 888 return doctl.NewMissingArgsErr(c.NS) 889 } 890 891 databaseID := c.Args[0] 892 req := &godo.DatabaseCreateDBRequest{Name: c.Args[1]} 893 894 db, err := c.Databases().CreateDB(databaseID, req) 895 if err != nil { 896 return err 897 } 898 899 return displayDatabaseDBs(c, *db) 900} 901 902// RunDatabaseDBDelete deletes a database 903func RunDatabaseDBDelete(c *CmdConfig) error { 904 if len(c.Args) < 2 { 905 return doctl.NewMissingArgsErr(c.NS) 906 } 907 908 force, err := c.Doit.GetBool(c.NS, doctl.ArgForce) 909 if err != nil { 910 return err 911 } 912 913 if force || AskForConfirmDelete("database", 1) == nil { 914 databaseID := c.Args[0] 915 dbID := c.Args[1] 916 return c.Databases().DeleteDB(databaseID, dbID) 917 } 918 919 return errOperationAborted 920} 921 922func displayDatabaseDBs(c *CmdConfig, dbs ...do.DatabaseDB) error { 923 item := &displayers.DatabaseDBs{DatabaseDBs: dbs} 924 return c.Display(item) 925} 926 927func databaseReplica() *Command { 928 cmd := &Command{ 929 Command: &cobra.Command{ 930 Use: "replica", 931 Aliases: []string{"rep", "r"}, 932 Short: "Display commands to manage read-only database replicas", 933 Long: `The subcommands under ` + "`" + `doctl databases replica` + "`" + ` enable the management of read-only replicas associated with a database cluster. 934 935In addition to primary nodes in a database cluster, you can create up to 2 read-only replica nodes (also referred to as "standby nodes") to maintain high availability.`, 936 }, 937 } 938 howToGetReplica := ` 939 940This command requires that you pass in the replica's name, which you can retrieve by querying a database ID: 941 942 doctl databases replica list ca9f591d-5555-5555-a0ef-1c02d1d1e352` 943 replicaDetails := ` 944 945- The name of the replica 946- The region where the database cluster is located (e.g. ` + "`" + `nyc3` + "`" + `, ` + "`" + `sfo2` + "`" + `) 947- The status of the replica (possible values are ` + "`" + `forking` + "`" + ` and ` + "`" + `active` + "`" + `) 948` 949 CmdBuilder(cmd, RunDatabaseReplicaList, "list <database-id>", "Retrieve list of read-only database replicas", `Lists the following details for read-only replicas for the specified database cluster.`+replicaDetails+databaseListDetails, 950 Writer, aliasOpt("ls"), 951 displayerType(&displayers.DatabaseReplicas{})) 952 CmdBuilder(cmd, RunDatabaseReplicaGet, "get <database-id> <replica-name>", "Retrieve information about a read-only database replica", 953 `Gets the following details for the specified read-only replica for the specified database cluster: 954 955- The name of the replica 956- Information required to connect to the read-only replica 957- The region where the database cluster is located (e.g. `+"`"+`nyc3`+"`"+`, `+"`"+`sfo2`+"`"+`) 958- The status of the replica (possible values are `+"`"+`creating`+"`"+`, `+"`"+`forking`+"`"+`, or `+"`"+`active`+"`"+`) 959- A time value given in ISO8601 combined date and time format that represents when the read-only replica was created.`+howToGetReplica+databaseListDetails, 960 Writer, aliasOpt("g"), 961 displayerType(&displayers.DatabaseReplicas{})) 962 963 cmdDatabaseReplicaCreate := CmdBuilder(cmd, RunDatabaseReplicaCreate, 964 "create <database-id> <replica-name>", "Create a read-only database replica", `This command creates a read-only database replica for the specified database cluster, giving it the specified name.`+databaseListDetails, 965 Writer, aliasOpt("c")) 966 AddStringFlag(cmdDatabaseReplicaCreate, doctl.ArgRegionSlug, "", 967 defaultDatabaseRegion, "Specifies the region (e.g. nyc3, sfo2) in which to create the replica") 968 AddStringFlag(cmdDatabaseReplicaCreate, doctl.ArgSizeSlug, "", 969 defaultDatabaseNodeSize, "Specifies the machine size for the replica (e.g. db-s-1vcpu-1gb). Must be the same or equal to the original.") 970 AddStringFlag(cmdDatabaseReplicaCreate, doctl.ArgPrivateNetworkUUID, "", 971 "", "The UUID of a VPC to create the replica in; the default VPC for the region will be used if excluded") 972 973 cmdDatabaseReplicaDelete := CmdBuilder(cmd, RunDatabaseReplicaDelete, 974 "delete <database-id> <replica-name>", "Delete a read-only database replica", 975 `Delete the specified read-only replica for the specified database cluster.`+howToGetReplica+databaseListDetails, 976 Writer, aliasOpt("rm")) 977 AddBoolFlag(cmdDatabaseReplicaDelete, doctl.ArgForce, doctl.ArgShortForce, 978 false, "Deletes the replica without a confirmation prompt") 979 980 CmdBuilder(cmd, RunDatabaseReplicaConnectionGet, 981 "connection <database-id> <replica-name>", 982 "Retrieve information for connecting to a read-only database replica", 983 `This command retrieves information for connecting to the specified read-only database replica in the specified database cluster`+howToGetReplica+databaseListDetails, Writer, aliasOpt("conn")) 984 985 return cmd 986} 987 988// Database Replicas 989 990// RunDatabaseReplicaList retrieves a list of replicas for specific database cluster 991func RunDatabaseReplicaList(c *CmdConfig) error { 992 if len(c.Args) == 0 { 993 return doctl.NewMissingArgsErr(c.NS) 994 } 995 996 id := c.Args[0] 997 998 replicas, err := c.Databases().ListReplicas(id) 999 if err != nil { 1000 return err 1001 } 1002 1003 return displayDatabaseReplicas(c, true, replicas...) 1004} 1005 1006// RunDatabaseReplicaGet retrieves a read-only replica for a specific database cluster 1007func RunDatabaseReplicaGet(c *CmdConfig) error { 1008 if len(c.Args) < 2 { 1009 return doctl.NewMissingArgsErr(c.NS) 1010 } 1011 1012 databaseID := c.Args[0] 1013 replicaID := c.Args[1] 1014 1015 replica, err := c.Databases().GetReplica(databaseID, replicaID) 1016 if err != nil { 1017 return err 1018 } 1019 1020 return displayDatabaseReplicas(c, false, *replica) 1021} 1022 1023// RunDatabaseReplicaCreate creates a read-only replica for a database cluster 1024func RunDatabaseReplicaCreate(c *CmdConfig) error { 1025 if len(c.Args) < 2 { 1026 return doctl.NewMissingArgsErr(c.NS) 1027 } 1028 1029 databaseID := c.Args[0] 1030 r, err := buildDatabaseCreateReplicaRequestFromArgs(c) 1031 if err != nil { 1032 return err 1033 } 1034 1035 replica, err := c.Databases().CreateReplica(databaseID, r) 1036 if err != nil { 1037 return err 1038 } 1039 1040 return displayDatabaseReplicas(c, false, *replica) 1041} 1042 1043func buildDatabaseCreateReplicaRequestFromArgs(c *CmdConfig) (*godo.DatabaseCreateReplicaRequest, error) { 1044 r := &godo.DatabaseCreateReplicaRequest{Name: c.Args[1]} 1045 1046 size, err := c.Doit.GetString(c.NS, doctl.ArgSizeSlug) 1047 if err != nil { 1048 return nil, err 1049 } 1050 r.Size = size 1051 1052 region, err := c.Doit.GetString(c.NS, doctl.ArgRegionSlug) 1053 if err != nil { 1054 return nil, err 1055 } 1056 r.Region = region 1057 1058 privateNetworkUUID, err := c.Doit.GetString(c.NS, doctl.ArgPrivateNetworkUUID) 1059 if err != nil { 1060 return nil, err 1061 } 1062 r.PrivateNetworkUUID = privateNetworkUUID 1063 1064 return r, nil 1065} 1066 1067// RunDatabaseReplicaDelete deletes a read-only replica 1068func RunDatabaseReplicaDelete(c *CmdConfig) error { 1069 if len(c.Args) < 2 { 1070 return doctl.NewMissingArgsErr(c.NS) 1071 } 1072 1073 force, err := c.Doit.GetBool(c.NS, doctl.ArgForce) 1074 if err != nil { 1075 return err 1076 } 1077 1078 if force || AskForConfirmDelete("database replica", 1) == nil { 1079 databaseID := c.Args[0] 1080 replicaID := c.Args[1] 1081 return c.Databases().DeleteReplica(databaseID, replicaID) 1082 } 1083 1084 return errOperationAborted 1085} 1086 1087func displayDatabaseReplicas(c *CmdConfig, short bool, replicas ...do.DatabaseReplica) error { 1088 item := &displayers.DatabaseReplicas{ 1089 DatabaseReplicas: replicas, 1090 Short: short, 1091 } 1092 return c.Display(item) 1093} 1094 1095// RunDatabaseReplicaConnectionGet gets read-only replica connection info 1096func RunDatabaseReplicaConnectionGet(c *CmdConfig) error { 1097 if len(c.Args) == 0 { 1098 return doctl.NewMissingArgsErr(c.NS) 1099 } 1100 1101 databaseID := c.Args[0] 1102 replicaID := c.Args[1] 1103 connInfo, err := c.Databases().GetReplicaConnection(databaseID, replicaID) 1104 if err != nil { 1105 return err 1106 } 1107 1108 return displayDatabaseReplicaConnection(c, *connInfo) 1109} 1110 1111func displayDatabaseReplicaConnection(c *CmdConfig, conn do.DatabaseConnection) error { 1112 item := &displayers.DatabaseConnection{DatabaseConnection: conn} 1113 return c.Display(item) 1114} 1115 1116func sqlMode() *Command { 1117 cmd := &Command{ 1118 Command: &cobra.Command{ 1119 Use: "sql-mode", 1120 Aliases: []string{"sm"}, 1121 Short: "Display commands to configure a MySQL database cluster's SQL modes", 1122 Long: "The subcommands of `doctl databases sql-mode` are used to view and configure a MySQL database cluster's global SQL modes.", 1123 }, 1124 } 1125 1126 getSqlModeDesc := "This command displays the the configured SQL modes for the specified MySQL database cluster." 1127 CmdBuilder(cmd, RunDatabaseGetSQLModes, "get <database-id>", 1128 "Get a MySQL database cluster's SQL modes", getSqlModeDesc, Writer, 1129 displayerType(&displayers.DatabaseSQLModes{}), aliasOpt("g")) 1130 setSqlModeDesc := `This command configures the SQL modes for the specified MySQL database cluster. The SQL modes should be provided as a space separated list. 1131 1132This will replace the existing SQL mode configuration completely. Include all of the current values when adding a new one. 1133` 1134 CmdBuilder(cmd, RunDatabaseSetSQLModes, "set <database-id> <sql-mode-1> ... <sql-mode-n>", 1135 "Set a MySQL database cluster's SQL modes", setSqlModeDesc, Writer, aliasOpt("s")) 1136 1137 return cmd 1138} 1139 1140// RunDatabaseGetSQLModes gets the sql modes set on the database 1141func RunDatabaseGetSQLModes(c *CmdConfig) error { 1142 err := ensureOneArg(c) 1143 if err != nil { 1144 return err 1145 } 1146 1147 databaseID := c.Args[0] 1148 sqlModes, err := c.Databases().GetSQLMode(databaseID) 1149 if err != nil { 1150 return err 1151 } 1152 return displaySQLModes(c, sqlModes) 1153} 1154 1155func displaySQLModes(c *CmdConfig, sqlModes []string) error { 1156 return c.Display(&displayers.DatabaseSQLModes{ 1157 DatabaseSQLModes: sqlModes, 1158 }) 1159} 1160 1161// RunDatabaseSetSQLModes sets the sql modes on the database 1162func RunDatabaseSetSQLModes(c *CmdConfig) error { 1163 if len(c.Args) < 2 { 1164 return doctl.NewMissingArgsErr(c.NS) 1165 } 1166 1167 databaseID := c.Args[0] 1168 sqlModes := c.Args[1:] 1169 1170 return c.Databases().SetSQLMode(databaseID, sqlModes...) 1171} 1172 1173func databaseFirewalls() *Command { 1174 cmd := &Command{ 1175 Command: &cobra.Command{ 1176 Use: "firewalls", 1177 Aliases: []string{"fw"}, 1178 Short: `Display commands to manage firewall rules (called` + "`" + `trusted sources` + "`" + ` in the control panel) for database clusters`, 1179 Long: `The subcommands under ` + "`" + `doctl databases firewalls` + "`" + ` enable the management of firewalls for database clusters`, 1180 }, 1181 } 1182 1183 firewallRuleDetails := ` 1184This command lists the following details for each firewall rule in a given database: 1185 1186 - The UUID of the firewall rule. 1187 - The Cluster UUID for the database cluster to which the rule is applied. 1188 - The Type of resource that the firewall rule allows to access the database cluster. The possible values are: "droplet", "k8s", "ip_addr", "tag", or "app". 1189 - The Value, which is either the ID of the specific resource, the name of a tag applied to a group of resources, or the IP address that the firewall rule allows to access the database cluster. 1190 - The Time value given in ISO8601 combined date and time format that represents when the firewall rule was created. 1191 ` 1192 databaseFirewallRuleDetails := ` 1193 1194This command requires the ID of a database cluster, which you can retrieve by calling: 1195 1196 doctl databases list` 1197 1198 databaseFirewallRulesTxt := "A comma-separated list of firewall rules of format type:value, e.g.: `type:value`" 1199 1200 databaseFirewallUpdateDetails := ` 1201Use this command to replace the firewall rules of a given database. This command requires the ID of a database cluster, which you can retrieve by calling: 1202 1203 doctl databases list 1204 1205This command also requires a --rule flag. You can pass in multiple --rule flags. Each rule passed in to the --rule flag must be of format type:value 1206 - "type" is the type of resource that the firewall rule allows to access the database cluster. The possible values for type are: "droplet", "k8s", "ip_addr", "tag", or "app" 1207 - "value" is either the ID of the specific resource, the name of a tag applied to a group of resources, or the IP address that the firewall rule allows to access the database cluster 1208 1209For example: 1210 1211 doctl databases firewalls replace d1234-1c12-1234-b123-12345c4789 --rule tag:backend --rule ip_addr:0.0.0.0 1212 1213 or 1214 1215 databases firewalls replace d1234-1c12-1234-b123-12345c4789 --rule tag:backend,ip_addr:0.0.0.0 1216 1217This would replace the firewall rules for database of id d1234-1c12-1234-b123-12345c4789 with the two rules passed above (tag:backend, ip_addr:0.0.0.0) 1218 ` 1219 1220 databaseFirewallAddDetails := 1221 ` 1222Use this command to append a single rule to the existing firewall rules of a given database. This command requires the ID of a database cluster, which you can retrieve by calling: 1223 1224 doctl databases list 1225 1226This command also requires a --rule flag. Each rule passed in to the --rule flag must be of format type:value 1227 - "type" is the type of resource that the firewall rule allows to access the database cluster. The possible values for type are: "droplet", "k8s", "ip_addr", "tag", or "app" 1228 - "value" is either the ID of the specific resource, the name of a tag applied to a group of resources, or the IP address that the firewall rule allows to access the database cluster 1229 1230For example: 1231 1232 doctl databases firewalls append d1234-1c12-1234-b123-12345c4789 --rule tag:backend 1233 1234This would append the firewall rule "tag:backend" for database of id d1234-1c12-1234-b123-12345c4789` 1235 1236 databaseFirewallRemoveDetails := 1237 ` 1238Use this command to remove an existing, single rule from the list of firewall rules for a given database. This command requires the ID of a database cluster, which you can retrieve by calling: 1239 1240 doctl databases list 1241 1242This command also requires a --uuid flag. You must pass in the UUID of the firewall rule you'd like to remove. You can retrieve the firewall rule's UUIDs by calling: 1243 1244 doctl database firewalls list <db-id> 1245 1246For example: 1247 1248 doctl databases firewalls remove d1234-1c12-1234-b123-12345c4789 --uuid 12345d-1234-123d-123x-123eee456e 1249 1250This would remove the firewall rule of uuid 12345d-1234-123d-123x-123eee456e for database of id d1234-1c12-1234-b123-12345c4789 1251 ` 1252 1253 CmdBuilder(cmd, RunDatabaseFirewallRulesList, "list <database-id>", "Retrieve a list of firewall rules for a given database", firewallRuleDetails+databaseFirewallRuleDetails, 1254 Writer, aliasOpt("ls")) 1255 1256 cmdDatabaseFirewallUpdate := CmdBuilder(cmd, RunDatabaseFirewallRulesUpdate, "replace <db-id> --rules type:value [--rule type:value]", "Replaces the firewall rules for a given database. The rules passed in to the --rules flag will replace the firewall rules previously assigned to the database,", databaseFirewallUpdateDetails, 1257 Writer, aliasOpt("r")) 1258 AddStringSliceFlag(cmdDatabaseFirewallUpdate, doctl.ArgDatabaseFirewallRule, "", []string{}, databaseFirewallRulesTxt, requiredOpt()) 1259 1260 cmdDatabaseFirewallCreate := CmdBuilder(cmd, RunDatabaseFirewallRulesAppend, "append <db-id> --rule type:value", "Add a database firewall rule to a given database", databaseFirewallAddDetails, 1261 Writer, aliasOpt("a")) 1262 AddStringFlag(cmdDatabaseFirewallCreate, doctl.ArgDatabaseFirewallRule, "", "", "", requiredOpt()) 1263 1264 cmdDatabaseFirewallRemove := CmdBuilder(cmd, RunDatabaseFirewallRulesRemove, "remove <firerule-uuid>", "Remove a firewall rule for a given database", databaseFirewallRemoveDetails, 1265 Writer, aliasOpt("rm")) 1266 AddStringFlag(cmdDatabaseFirewallRemove, doctl.ArgDatabaseFirewallRuleUUID, "", "", "", requiredOpt()) 1267 1268 return cmd 1269 1270} 1271 1272// displayDatabaseFirewallRules calls Get Firewall Rules to list all current rules. 1273func displayDatabaseFirewallRules(c *CmdConfig, short bool, id string) error { 1274 firewallRules, err := c.Databases().GetFirewallRules(id) 1275 if err != nil { 1276 return err 1277 } 1278 1279 item := &displayers.DatabaseFirewallRules{ 1280 DatabaseFirewallRules: firewallRules, 1281 } 1282 1283 return c.Display(item) 1284} 1285 1286// All firewall rules require the databaseID 1287func firewallRulesArgumentCheck(c *CmdConfig) error { 1288 if len(c.Args) == 0 { 1289 return doctl.NewMissingArgsErr(c.NS) 1290 } 1291 if len(c.Args) > 1 { 1292 return doctl.NewTooManyArgsErr(c.NS) 1293 } 1294 return nil 1295} 1296 1297// RunDatabaseFirewallRulesList retrieves a list of firewalls for specific database cluster 1298func RunDatabaseFirewallRulesList(c *CmdConfig) error { 1299 err := firewallRulesArgumentCheck(c) 1300 if err != nil { 1301 return err 1302 } 1303 1304 id := c.Args[0] 1305 1306 return displayDatabaseFirewallRules(c, true, id) 1307} 1308 1309// RunDatabaseFirewallRulesUpdate replaces previous rules with the rules passed in to --rules 1310func RunDatabaseFirewallRulesUpdate(c *CmdConfig) error { 1311 err := firewallRulesArgumentCheck(c) 1312 if err != nil { 1313 return err 1314 } 1315 1316 id := c.Args[0] 1317 r, err := buildDatabaseUpdateFirewallRulesRequestFromArgs(c) 1318 if err != nil { 1319 return err 1320 } 1321 1322 err = c.Databases().UpdateFirewallRules(id, r) 1323 if err != nil { 1324 return err 1325 } 1326 1327 return displayDatabaseFirewallRules(c, true, id) 1328 1329} 1330 1331// buildDatabaseUpdateFirewallRulesRequestFromArgs will ingest the --rules arguments into a DatabaseUpdateFirewallRulesRequest object. 1332func buildDatabaseUpdateFirewallRulesRequestFromArgs(c *CmdConfig) (*godo.DatabaseUpdateFirewallRulesRequest, error) { 1333 r := &godo.DatabaseUpdateFirewallRulesRequest{} 1334 1335 firewallRules, err := c.Doit.GetStringSlice(c.NS, doctl.ArgDatabaseFirewallRule) 1336 if err != nil { 1337 return nil, err 1338 } 1339 1340 if len(firewallRules) == 0 { 1341 return nil, errors.New("Must pass in a key:value pair for the --rule flag") 1342 } 1343 1344 firewallRulesList, err := extractFirewallRules(firewallRules) 1345 if err != nil { 1346 return nil, err 1347 } 1348 r.Rules = firewallRulesList 1349 1350 return r, nil 1351 1352} 1353 1354// extractFirewallRules will ingest the --rules arguments into a list of DatabaseFirewallRule objects. 1355func extractFirewallRules(rulesStringList []string) (rules []*godo.DatabaseFirewallRule, err error) { 1356 for _, rule := range rulesStringList { 1357 pair := strings.SplitN(rule, ":", 2) 1358 if len(pair) != 2 { 1359 return nil, fmt.Errorf("Unexpected input value [%v], must be a key:value pair", pair) 1360 } 1361 1362 firewallRule := new(godo.DatabaseFirewallRule) 1363 firewallRule.Type = pair[0] 1364 firewallRule.Value = pair[1] 1365 1366 rules = append(rules, firewallRule) 1367 } 1368 1369 return rules, nil 1370 1371} 1372 1373// RunDatabaseFirewallRulesAppend creates a firewall rule for a database cluster. 1374// 1375// Any new rules will be appended to the existing rules. If you want to replace 1376// rules, use RunDatabaseFirewallRulesUpdate. 1377func RunDatabaseFirewallRulesAppend(c *CmdConfig) error { 1378 err := firewallRulesArgumentCheck(c) 1379 if err != nil { 1380 return err 1381 } 1382 1383 databaseID := c.Args[0] 1384 firewallRuleArg, err := c.Doit.GetString(c.NS, doctl.ArgDatabaseFirewallRule) 1385 if err != nil { 1386 return err 1387 } 1388 1389 pair := strings.SplitN(firewallRuleArg, ":", 2) 1390 if len(pair) != 2 { 1391 return fmt.Errorf("Unexpected input value [%v], must be a key:value pair", pair) 1392 } 1393 1394 // Slice will house old rules and new rule 1395 allRules := []*godo.DatabaseFirewallRule{} 1396 1397 // Adding new rule to slice. 1398 allRules = append(allRules, &godo.DatabaseFirewallRule{ 1399 Type: pair[0], 1400 Value: pair[1], 1401 ClusterUUID: databaseID, 1402 }) 1403 1404 // Retrieve any existing firewall rules so that we don't destroy existing 1405 // rules in the create request. 1406 oldRules, err := c.Databases().GetFirewallRules(databaseID) 1407 if err != nil { 1408 return err 1409 } 1410 1411 // Add old rules to allRules slice. 1412 for _, rule := range oldRules { 1413 1414 firewallRule := new(godo.DatabaseFirewallRule) 1415 firewallRule.Type = rule.Type 1416 firewallRule.Value = rule.Value 1417 firewallRule.ClusterUUID = rule.ClusterUUID 1418 firewallRule.UUID = rule.UUID 1419 1420 allRules = append(allRules, firewallRule) 1421 } 1422 1423 // Run update firewall rules with old rules + new rule 1424 if err := c.Databases().UpdateFirewallRules(databaseID, &godo.DatabaseUpdateFirewallRulesRequest{ 1425 Rules: allRules, 1426 }); err != nil { 1427 return err 1428 } 1429 1430 return displayDatabaseFirewallRules(c, true, databaseID) 1431} 1432 1433// RunDatabaseFirewallRulesRemove removes a firewall rule for a database cluster via Firewall rule UUID 1434func RunDatabaseFirewallRulesRemove(c *CmdConfig) error { 1435 err := firewallRulesArgumentCheck(c) 1436 if err != nil { 1437 return err 1438 } 1439 1440 databaseID := c.Args[0] 1441 1442 firewallRuleUUIDArg, err := c.Doit.GetString(c.NS, doctl.ArgDatabaseFirewallRuleUUID) 1443 if err != nil { 1444 return err 1445 } 1446 1447 // Retrieve any existing firewall rules so that we don't destroy existing 1448 // rules in the create request. 1449 rules, err := c.Databases().GetFirewallRules(databaseID) 1450 if err != nil { 1451 return err 1452 } 1453 1454 // Create a slice of database firewall rules containing only the new rule. 1455 firewallRules := []*godo.DatabaseFirewallRule{} 1456 1457 // only append rules that do not match the firewall rule with uuid to be removed. 1458 for _, rule := range rules { 1459 if rule.UUID != firewallRuleUUIDArg { 1460 firewallRules = append(firewallRules, &godo.DatabaseFirewallRule{ 1461 UUID: rule.UUID, 1462 ClusterUUID: rule.ClusterUUID, 1463 Type: rule.Type, 1464 Value: rule.Value, 1465 }) 1466 } 1467 } 1468 1469 if err := c.Databases().UpdateFirewallRules(databaseID, &godo.DatabaseUpdateFirewallRulesRequest{ 1470 Rules: firewallRules, 1471 }); err != nil { 1472 return err 1473 } 1474 1475 return displayDatabaseFirewallRules(c, true, databaseID) 1476} 1477