1package godo 2 3import ( 4 "context" 5 "fmt" 6 "net/http" 7 "time" 8) 9 10const ( 11 databaseBasePath = "/v2/databases" 12 databaseSinglePath = databaseBasePath + "/%s" 13 databaseResizePath = databaseBasePath + "/%s/resize" 14 databaseMigratePath = databaseBasePath + "/%s/migrate" 15 databaseMaintenancePath = databaseBasePath + "/%s/maintenance" 16 databaseBackupsPath = databaseBasePath + "/%s/backups" 17 databaseUsersPath = databaseBasePath + "/%s/users" 18 databaseUserPath = databaseBasePath + "/%s/users/%s" 19 databaseDBPath = databaseBasePath + "/%s/dbs/%s" 20 databaseDBsPath = databaseBasePath + "/%s/dbs" 21 databasePoolPath = databaseBasePath + "/%s/pools/%s" 22 databasePoolsPath = databaseBasePath + "/%s/pools" 23 databaseReplicaPath = databaseBasePath + "/%s/replicas/%s" 24 databaseReplicasPath = databaseBasePath + "/%s/replicas" 25) 26 27// DatabasesService is an interface for interfacing with the databases endpoints 28// of the DigitalOcean API. 29// See: https://developers.digitalocean.com/documentation/v2#databases 30type DatabasesService interface { 31 List(context.Context, *ListOptions) ([]Database, *Response, error) 32 Get(context.Context, string) (*Database, *Response, error) 33 Create(context.Context, *DatabaseCreateRequest) (*Database, *Response, error) 34 Delete(context.Context, string) (*Response, error) 35 Resize(context.Context, string, *DatabaseResizeRequest) (*Response, error) 36 Migrate(context.Context, string, *DatabaseMigrateRequest) (*Response, error) 37 UpdateMaintenance(context.Context, string, *DatabaseUpdateMaintenanceRequest) (*Response, error) 38 ListBackups(context.Context, string, *ListOptions) ([]DatabaseBackup, *Response, error) 39 GetUser(context.Context, string, string) (*DatabaseUser, *Response, error) 40 ListUsers(context.Context, string, *ListOptions) ([]DatabaseUser, *Response, error) 41 CreateUser(context.Context, string, *DatabaseCreateUserRequest) (*DatabaseUser, *Response, error) 42 DeleteUser(context.Context, string, string) (*Response, error) 43 ListDBs(context.Context, string, *ListOptions) ([]DatabaseDB, *Response, error) 44 CreateDB(context.Context, string, *DatabaseCreateDBRequest) (*DatabaseDB, *Response, error) 45 GetDB(context.Context, string, string) (*DatabaseDB, *Response, error) 46 DeleteDB(context.Context, string, string) (*Response, error) 47 ListPools(context.Context, string, *ListOptions) ([]DatabasePool, *Response, error) 48 CreatePool(context.Context, string, *DatabaseCreatePoolRequest) (*DatabasePool, *Response, error) 49 GetPool(context.Context, string, string) (*DatabasePool, *Response, error) 50 DeletePool(context.Context, string, string) (*Response, error) 51 GetReplica(context.Context, string, string) (*DatabaseReplica, *Response, error) 52 ListReplicas(context.Context, string, *ListOptions) ([]DatabaseReplica, *Response, error) 53 CreateReplica(context.Context, string, *DatabaseCreateReplicaRequest) (*DatabaseReplica, *Response, error) 54 DeleteReplica(context.Context, string, string) (*Response, error) 55} 56 57// DatabasesServiceOp handles communication with the Databases related methods 58// of the DigitalOcean API. 59type DatabasesServiceOp struct { 60 client *Client 61} 62 63var _ DatabasesService = &DatabasesServiceOp{} 64 65// Database represents a DigitalOcean managed database product. These managed databases 66// are usually comprised of a cluster of database nodes, a primary and 0 or more replicas. 67// The EngineSlug is a string which indicates the type of database service. Some examples are 68// "pg", "mysql" or "redis". A Database also includes connection information and other 69// properties of the service like region, size and current status. 70type Database struct { 71 ID string `json:"id,omitempty"` 72 Name string `json:"name,omitempty"` 73 EngineSlug string `json:"engine,omitempty"` 74 VersionSlug string `json:"version,omitempty"` 75 Connection *DatabaseConnection `json:"connection,omitempty"` 76 Users []DatabaseUser `json:"users,omitempty"` 77 NumNodes int `json:"num_nodes,omitempty"` 78 SizeSlug string `json:"size,omitempty"` 79 DBNames []string `json:"db_names,omitempty"` 80 RegionSlug string `json:"region,omitempty"` 81 Status string `json:"status,omitempty"` 82 MaintenanceWindow *DatabaseMaintenanceWindow `json:"maintenance_window,omitempty"` 83 CreatedAt time.Time `json:"created_at,omitempty"` 84} 85 86// DatabaseConnection represents a database connection 87type DatabaseConnection struct { 88 URI string `json:"uri,omitempty"` 89 Database string `json:"database,omitempty"` 90 Host string `json:"host,omitempty"` 91 Port int `json:"port,omitempty"` 92 User string `json:"user,omitempty"` 93 Password string `json:"password,omitempty"` 94 SSL bool `json:"ssl,omitempty"` 95} 96 97// DatabaseUser represents a user in the database 98type DatabaseUser struct { 99 Name string `json:"name,omitempty"` 100 Role string `json:"role,omitempty"` 101 Password string `json:"password,omitempty"` 102} 103 104// DatabaseMaintenanceWindow represents the maintenance_window of a database 105// cluster 106type DatabaseMaintenanceWindow struct { 107 Day string `json:"day,omitempty"` 108 Hour string `json:"hour,omitempty"` 109 Pending bool `json:"pending,omitempty"` 110 Description []string `json:"description,omitempty"` 111} 112 113// DatabaseBackup represents a database backup. 114type DatabaseBackup struct { 115 CreatedAt time.Time `json:"created_at,omitempty"` 116 SizeGigabytes float64 `json:"size_gigabytes,omitempty"` 117} 118 119// DatabaseCreateRequest represents a request to create a database cluster 120type DatabaseCreateRequest struct { 121 Name string `json:"name,omitempty"` 122 EngineSlug string `json:"engine,omitempty"` 123 Version string `json:"version,omitempty"` 124 SizeSlug string `json:"size,omitempty"` 125 Region string `json:"region,omitempty"` 126 NumNodes int `json:"num_nodes,omitempty"` 127} 128 129// DatabaseResizeRequest can be used to initiate a database resize operation. 130type DatabaseResizeRequest struct { 131 SizeSlug string `json:"size,omitempty"` 132 NumNodes int `json:"num_nodes,omitempty"` 133} 134 135// DatabaseMigrateRequest can be used to initiate a database migrate operation. 136type DatabaseMigrateRequest struct { 137 Region string `json:"region,omitempty"` 138} 139 140// DatabaseUpdateMaintenanceRequest can be used to update the database's maintenance window. 141type DatabaseUpdateMaintenanceRequest struct { 142 Day string `json:"day,omitempty"` 143 Hour string `json:"hour,omitempty"` 144} 145 146// DatabaseDB represents an engine-specific database created within a database cluster. For SQL 147// databases like PostgreSQL or MySQL, a "DB" refers to a database created on the RDBMS. For instance, 148// a PostgreSQL database server can contain many database schemas, each with it's own settings, access 149// permissions and data. ListDBs will return all databases present on the server. 150type DatabaseDB struct { 151 Name string `json:"name"` 152} 153 154// DatabaseReplica represents a read-only replica of a particular database 155type DatabaseReplica struct { 156 Name string `json:"name"` 157 Connection *DatabaseConnection `json:"connection"` 158 Region string `json:"region"` 159 Status string `json:"status"` 160 CreatedAt time.Time `json:"created_at"` 161} 162 163// DatabasePool represents a database connection pool 164type DatabasePool struct { 165 User string `json:"user"` 166 Name string `json:"name"` 167 Size int `json:"size"` 168 Database string `json:"database"` 169 Mode string `json:"mode"` 170 Connection *DatabaseConnection `json:"connection"` 171} 172 173// DatabaseCreatePoolRequest is used to create a new database connection pool 174type DatabaseCreatePoolRequest struct { 175 Pool *DatabasePool `json:"pool"` 176} 177 178// DatabaseCreateUserRequest is used to create a new database user 179type DatabaseCreateUserRequest struct { 180 Name string `json:"name"` 181} 182 183// DatabaseCreateDBRequest is used to create a new engine-specific database within the cluster 184type DatabaseCreateDBRequest struct { 185 Name string `json:"name"` 186} 187 188// DatabaseCreateReplicaRequest is used to create a new read-only replica 189type DatabaseCreateReplicaRequest struct { 190 Name string `json:"name"` 191 Region string `json:"region"` 192 Size string `json:"size"` 193} 194 195type databaseUserRoot struct { 196 User *DatabaseUser `json:"user"` 197} 198 199type databaseUsersRoot struct { 200 Users []DatabaseUser `json:"users"` 201} 202 203type databaseDBRoot struct { 204 DB *DatabaseDB `json:"db"` 205} 206 207type databaseDBsRoot struct { 208 DBs []DatabaseDB `json:"dbs"` 209} 210 211type databasesRoot struct { 212 Databases []Database `json:"databases"` 213} 214 215type databaseRoot struct { 216 Database *Database `json:"database"` 217} 218 219type databaseBackupsRoot struct { 220 Backups []DatabaseBackup `json:"backups"` 221} 222 223type databasePoolRoot struct { 224 Pool *DatabasePool `json:"pool"` 225} 226 227type databasePoolsRoot struct { 228 Pools []DatabasePool `json:"pools"` 229} 230 231type databaseReplicaRoot struct { 232 Replica *DatabaseReplica `json:"replica"` 233} 234 235type databaseReplicasRoot struct { 236 Replicas []DatabaseReplica `json:"replicas"` 237} 238 239// List returns a list of the Databases visible with the caller's API token 240func (svc *DatabasesServiceOp) List(ctx context.Context, opts *ListOptions) ([]Database, *Response, error) { 241 path := databaseBasePath 242 path, err := addOptions(path, opts) 243 if err != nil { 244 return nil, nil, err 245 } 246 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 247 if err != nil { 248 return nil, nil, err 249 } 250 root := new(databasesRoot) 251 resp, err := svc.client.Do(ctx, req, root) 252 if err != nil { 253 return nil, resp, err 254 } 255 return root.Databases, resp, nil 256} 257 258// Get retrieves the details of a database cluster 259func (svc *DatabasesServiceOp) Get(ctx context.Context, databaseID string) (*Database, *Response, error) { 260 path := fmt.Sprintf(databaseSinglePath, databaseID) 261 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 262 if err != nil { 263 return nil, nil, err 264 } 265 root := new(databaseRoot) 266 resp, err := svc.client.Do(ctx, req, root) 267 if err != nil { 268 return nil, resp, err 269 } 270 return root.Database, resp, nil 271} 272 273// Create creates a database cluster 274func (svc *DatabasesServiceOp) Create(ctx context.Context, create *DatabaseCreateRequest) (*Database, *Response, error) { 275 path := databaseBasePath 276 req, err := svc.client.NewRequest(ctx, http.MethodPost, path, create) 277 if err != nil { 278 return nil, nil, err 279 } 280 root := new(databaseRoot) 281 resp, err := svc.client.Do(ctx, req, root) 282 if err != nil { 283 return nil, resp, err 284 } 285 return root.Database, resp, nil 286} 287 288// Delete deletes a database cluster. There is no way to recover a cluster once 289// it has been destroyed. 290func (svc *DatabasesServiceOp) Delete(ctx context.Context, databaseID string) (*Response, error) { 291 path := fmt.Sprintf("%s/%s", databaseBasePath, databaseID) 292 req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) 293 if err != nil { 294 return nil, err 295 } 296 resp, err := svc.client.Do(ctx, req, nil) 297 if err != nil { 298 return resp, err 299 } 300 return resp, nil 301} 302 303// Resize resizes a database cluster by number of nodes or size 304func (svc *DatabasesServiceOp) Resize(ctx context.Context, databaseID string, resize *DatabaseResizeRequest) (*Response, error) { 305 path := fmt.Sprintf(databaseResizePath, databaseID) 306 req, err := svc.client.NewRequest(ctx, http.MethodPut, path, resize) 307 if err != nil { 308 return nil, err 309 } 310 resp, err := svc.client.Do(ctx, req, nil) 311 if err != nil { 312 return resp, err 313 } 314 return resp, nil 315} 316 317// Migrate migrates a database cluster to a new region 318func (svc *DatabasesServiceOp) Migrate(ctx context.Context, databaseID string, migrate *DatabaseMigrateRequest) (*Response, error) { 319 path := fmt.Sprintf(databaseMigratePath, databaseID) 320 req, err := svc.client.NewRequest(ctx, http.MethodPut, path, migrate) 321 if err != nil { 322 return nil, err 323 } 324 resp, err := svc.client.Do(ctx, req, nil) 325 if err != nil { 326 return resp, err 327 } 328 return resp, nil 329} 330 331// UpdateMaintenance updates the maintenance window on a cluster 332func (svc *DatabasesServiceOp) UpdateMaintenance(ctx context.Context, databaseID string, maintenance *DatabaseUpdateMaintenanceRequest) (*Response, error) { 333 path := fmt.Sprintf(databaseMaintenancePath, databaseID) 334 req, err := svc.client.NewRequest(ctx, http.MethodPut, path, maintenance) 335 if err != nil { 336 return nil, err 337 } 338 resp, err := svc.client.Do(ctx, req, nil) 339 if err != nil { 340 return resp, err 341 } 342 return resp, nil 343} 344 345// ListBackups returns a list of the current backups of a database 346func (svc *DatabasesServiceOp) ListBackups(ctx context.Context, databaseID string, opts *ListOptions) ([]DatabaseBackup, *Response, error) { 347 path := fmt.Sprintf(databaseBackupsPath, databaseID) 348 path, err := addOptions(path, opts) 349 if err != nil { 350 return nil, nil, err 351 } 352 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 353 if err != nil { 354 return nil, nil, err 355 } 356 root := new(databaseBackupsRoot) 357 resp, err := svc.client.Do(ctx, req, root) 358 if err != nil { 359 return nil, resp, err 360 } 361 return root.Backups, resp, nil 362} 363 364// GetUser returns the database user identified by userID 365func (svc *DatabasesServiceOp) GetUser(ctx context.Context, databaseID, userID string) (*DatabaseUser, *Response, error) { 366 path := fmt.Sprintf(databaseUserPath, databaseID, userID) 367 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 368 if err != nil { 369 return nil, nil, err 370 } 371 root := new(databaseUserRoot) 372 resp, err := svc.client.Do(ctx, req, root) 373 if err != nil { 374 return nil, resp, err 375 } 376 return root.User, resp, nil 377} 378 379// ListUsers returns all database users for the database 380func (svc *DatabasesServiceOp) ListUsers(ctx context.Context, databaseID string, opts *ListOptions) ([]DatabaseUser, *Response, error) { 381 path := fmt.Sprintf(databaseUsersPath, databaseID) 382 path, err := addOptions(path, opts) 383 if err != nil { 384 return nil, nil, err 385 } 386 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 387 if err != nil { 388 return nil, nil, err 389 } 390 root := new(databaseUsersRoot) 391 resp, err := svc.client.Do(ctx, req, root) 392 if err != nil { 393 return nil, resp, err 394 } 395 return root.Users, resp, nil 396} 397 398// CreateUser will create a new database user 399func (svc *DatabasesServiceOp) CreateUser(ctx context.Context, databaseID string, createUser *DatabaseCreateUserRequest) (*DatabaseUser, *Response, error) { 400 path := fmt.Sprintf(databaseUsersPath, databaseID) 401 req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createUser) 402 if err != nil { 403 return nil, nil, err 404 } 405 root := new(databaseUserRoot) 406 resp, err := svc.client.Do(ctx, req, root) 407 if err != nil { 408 return nil, resp, err 409 } 410 return root.User, resp, nil 411} 412 413// DeleteUser will delete an existing database user 414func (svc *DatabasesServiceOp) DeleteUser(ctx context.Context, databaseID, userID string) (*Response, error) { 415 path := fmt.Sprintf(databaseUserPath, databaseID, userID) 416 req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) 417 if err != nil { 418 return nil, err 419 } 420 resp, err := svc.client.Do(ctx, req, nil) 421 if err != nil { 422 return resp, err 423 } 424 return resp, nil 425} 426 427// ListDBs returns all databases for a given database cluster 428func (svc *DatabasesServiceOp) ListDBs(ctx context.Context, databaseID string, opts *ListOptions) ([]DatabaseDB, *Response, error) { 429 path := fmt.Sprintf(databaseDBsPath, databaseID) 430 path, err := addOptions(path, opts) 431 if err != nil { 432 return nil, nil, err 433 } 434 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 435 if err != nil { 436 return nil, nil, err 437 } 438 root := new(databaseDBsRoot) 439 resp, err := svc.client.Do(ctx, req, root) 440 if err != nil { 441 return nil, resp, err 442 } 443 return root.DBs, resp, nil 444} 445 446// GetDB returns a single database by name 447func (svc *DatabasesServiceOp) GetDB(ctx context.Context, databaseID, name string) (*DatabaseDB, *Response, error) { 448 path := fmt.Sprintf(databaseDBPath, databaseID, name) 449 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 450 if err != nil { 451 return nil, nil, err 452 } 453 root := new(databaseDBRoot) 454 resp, err := svc.client.Do(ctx, req, root) 455 if err != nil { 456 return nil, resp, err 457 } 458 return root.DB, resp, nil 459} 460 461// CreateDB will create a new database 462func (svc *DatabasesServiceOp) CreateDB(ctx context.Context, databaseID string, createDB *DatabaseCreateDBRequest) (*DatabaseDB, *Response, error) { 463 path := fmt.Sprintf(databaseDBsPath, databaseID) 464 req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createDB) 465 if err != nil { 466 return nil, nil, err 467 } 468 root := new(databaseDBRoot) 469 resp, err := svc.client.Do(ctx, req, root) 470 if err != nil { 471 return nil, resp, err 472 } 473 return root.DB, resp, nil 474} 475 476// DeleteDB will delete an existing database 477func (svc *DatabasesServiceOp) DeleteDB(ctx context.Context, databaseID, name string) (*Response, error) { 478 path := fmt.Sprintf(databaseDBPath, databaseID, name) 479 req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) 480 if err != nil { 481 return nil, err 482 } 483 resp, err := svc.client.Do(ctx, req, nil) 484 if err != nil { 485 return resp, err 486 } 487 return resp, nil 488} 489 490// ListPools returns all connection pools for a given database cluster 491func (svc *DatabasesServiceOp) ListPools(ctx context.Context, databaseID string, opts *ListOptions) ([]DatabasePool, *Response, error) { 492 path := fmt.Sprintf(databasePoolsPath, databaseID) 493 path, err := addOptions(path, opts) 494 if err != nil { 495 return nil, nil, err 496 } 497 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 498 if err != nil { 499 return nil, nil, err 500 } 501 root := new(databasePoolsRoot) 502 resp, err := svc.client.Do(ctx, req, root) 503 if err != nil { 504 return nil, resp, err 505 } 506 return root.Pools, resp, nil 507} 508 509// GetPool returns a single database connection pool by name 510func (svc *DatabasesServiceOp) GetPool(ctx context.Context, databaseID, name string) (*DatabasePool, *Response, error) { 511 path := fmt.Sprintf(databasePoolPath, databaseID, name) 512 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 513 if err != nil { 514 return nil, nil, err 515 } 516 root := new(databasePoolRoot) 517 resp, err := svc.client.Do(ctx, req, root) 518 if err != nil { 519 return nil, resp, err 520 } 521 return root.Pool, resp, nil 522} 523 524// CreatePool will create a new database connection pool 525func (svc *DatabasesServiceOp) CreatePool(ctx context.Context, databaseID string, createPool *DatabaseCreatePoolRequest) (*DatabasePool, *Response, error) { 526 path := fmt.Sprintf(databasePoolsPath, databaseID) 527 req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createPool) 528 if err != nil { 529 return nil, nil, err 530 } 531 root := new(databasePoolRoot) 532 resp, err := svc.client.Do(ctx, req, root) 533 if err != nil { 534 return nil, resp, err 535 } 536 return root.Pool, resp, nil 537} 538 539// DeletePool will delete an existing database connection pool 540func (svc *DatabasesServiceOp) DeletePool(ctx context.Context, databaseID, name string) (*Response, error) { 541 path := fmt.Sprintf(databasePoolPath, databaseID, name) 542 req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) 543 if err != nil { 544 return nil, err 545 } 546 resp, err := svc.client.Do(ctx, req, nil) 547 if err != nil { 548 return resp, err 549 } 550 return resp, nil 551} 552 553// GetReplica returns a single database replica 554func (svc *DatabasesServiceOp) GetReplica(ctx context.Context, databaseID, name string) (*DatabaseReplica, *Response, error) { 555 path := fmt.Sprintf(databaseReplicaPath, databaseID, name) 556 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 557 if err != nil { 558 return nil, nil, err 559 } 560 root := new(databaseReplicaRoot) 561 resp, err := svc.client.Do(ctx, req, root) 562 if err != nil { 563 return nil, resp, err 564 } 565 return root.Replica, resp, nil 566} 567 568// ListReplicas returns all read-only replicas for a given database cluster 569func (svc *DatabasesServiceOp) ListReplicas(ctx context.Context, databaseID string, opts *ListOptions) ([]DatabaseReplica, *Response, error) { 570 path := fmt.Sprintf(databaseReplicasPath, databaseID) 571 path, err := addOptions(path, opts) 572 if err != nil { 573 return nil, nil, err 574 } 575 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 576 if err != nil { 577 return nil, nil, err 578 } 579 root := new(databaseReplicasRoot) 580 resp, err := svc.client.Do(ctx, req, root) 581 if err != nil { 582 return nil, resp, err 583 } 584 return root.Replicas, resp, nil 585} 586 587// CreateReplica will create a new database connection pool 588func (svc *DatabasesServiceOp) CreateReplica(ctx context.Context, databaseID string, createReplica *DatabaseCreateReplicaRequest) (*DatabaseReplica, *Response, error) { 589 path := fmt.Sprintf(databaseReplicasPath, databaseID) 590 req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createReplica) 591 if err != nil { 592 return nil, nil, err 593 } 594 root := new(databaseReplicaRoot) 595 resp, err := svc.client.Do(ctx, req, root) 596 if err != nil { 597 return nil, resp, err 598 } 599 return root.Replica, resp, nil 600} 601 602// DeleteReplica will delete an existing database replica 603func (svc *DatabasesServiceOp) DeleteReplica(ctx context.Context, databaseID, name string) (*Response, error) { 604 path := fmt.Sprintf(databaseReplicaPath, databaseID, name) 605 req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) 606 if err != nil { 607 return nil, err 608 } 609 resp, err := svc.client.Do(ctx, req, nil) 610 if err != nil { 611 return resp, err 612 } 613 return resp, nil 614} 615