diff --git a/CLAUDE.md b/CLAUDE.md index df341525..c5dd80a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -137,13 +137,13 @@ Further conventions: ### Service Refs -A command that identifies a service takes a **ref** — its ID, a read replica set ID, or its name — and hands it to the `ref` filter on the API's service list through the helpers in `service_ref_helper.go`, which also return the whole service. Never try to tell an ID from a name in the CLI. +A command that identifies a service takes a **ref** — its ID, a read replica set ID, or its name — and resolves it through the API, which also returns the whole resource. Never try to tell an ID from a name in the CLI. `tiger service` commands resolve through the `ref` filter on the API's service list (`resolveService`). `tiger branch` and `tiger db` commands use the API's resolve endpoint, whose `type` parameter decides what a ref can name: only branches for `tiger branch` (`common.ResolveBranch`, called by `resolveBranch`), and anything they can connect to — a service, a read replica set, or a branch — for `tiger db` (`common.ResolveConnectionTarget`, called by `resolveConnectionTarget` in `db.go`). The service and branch ref helpers sit side by side in `ref_helper.go`. - A command that changes the service it resolves uses `resolveServiceForWrite`, which carries the read-only gate; everything else uses `resolveService`. - Help text shows `[name-or-id]` and says the service "can be given by ID or name". Shell completion offers names, falling back to IDs when what is typed matches an ID instead. - A name is accepted only as an argument. A configured default must be an ID, and one that resolves by name is refused. The `--service-id` flag completion only offers IDs. - Destructive commands accept a name, but their confirmation prompt takes only the ID. -- MCP tools stay IDs-only — an intentional divergence, documented at `setServiceIDSchemaProperties`. +- MCP tools stay IDs-only — an intentional divergence, documented at `setServiceIDSchemaProperties`. A tool that goes through the resolve endpoint (the branch tools, `db_query`, `db_schema`) refuses a match by name. ## Output @@ -154,12 +154,13 @@ A command that identifies a service takes a **ref** — its ID, a read replica s - **stdout** gets the command's primary output: the data payload (table, JSON, YAML, env vars), and in the plain-text path the result text itself. - **stderr** gets everything else: errors and warnings; interactive UI (confirmation prompts, password prompts, spinners); and progress/status messages that accompany structured output, so a piped stdout stays clean. `tiger service create` is the model — every status line is `cmd.PrintErrf`, and only the final service payload goes to stdout. -Helper functions inside `internal/cmd` take the `*cobra.Command` and print through it. A helper whose whole job is rendering into a writer (the `output*Table` functions) takes an `io.Writer` instead, since `tablewriter` needs one anyway. Code in other packages (`internal/common`, `internal/version`) must not import cobra — it takes an `io.Writer` (e.g. `common.WaitForServiceArgs.Output`) and the caller in `internal/cmd` passes `cmd.ErrOrStderr()` or `cmd.OutOrStdout()`. +Helper functions inside `internal/cmd` take the `*cobra.Command` and print through it. A helper whose whole job is rendering into a writer (the `output*Table` functions) takes an `io.Writer` instead, since `tablewriter` needs one anyway — as its first parameter, matching `util.SerializeToJSON` and `util.SerializeToYAML`. Code in other packages (`internal/common`, `internal/version`) must not import cobra — it takes an `io.Writer` (e.g. `common.WaitForServiceArgs.Output`) and the caller in `internal/cmd` passes `cmd.ErrOrStderr()` or `cmd.OutOrStdout()`. ### Formatting - Commands with structured output take `-o`/`--output` with `json`, `yaml`, and `table` (the default); some commands support extras (`env`, `bare`). Register the flag with the validating flag types in `flag_helper.go` (`new(outputFlag)` and friends, with no bound variable) and read the value from `cfg.Output` — `output` is a config value. - Serialize with `util.SerializeToJSON` and `util.SerializeToYAML`. **Don't add `yaml:` struct tags to output types** — `SerializeToYAML` encodes to JSON first and converts, so only `json:` tags matter and the two formats stay consistent (including for generated types that only carry `json:` tags). +- **Structured output is unconditional.** When a command succeeds with a structured format (`json`, `yaml`, `env`), stdout carries a payload in that format and nothing else, whatever the result, so it can always be piped into `jq` and the like. An empty list is `[]`, never a message and never empty output. A message for an empty or fallback result ("No services found.") belongs only in the output function's table case, printed to stderr before the table function would run. `tiger service list` is the model. - Colored output uses lipgloss styles, restricted to the eight base ANSI colors (`lipgloss.Blue`, `lipgloss.Green`, …) rather than their bright variants or 256-color and true-color values, and without bold; de-emphasize text with `Faint` rather than a grey. That way output follows the user's terminal theme (some themes render bright colors and bold as greys). Render styles unconditionally: the output writers strip colors when the stream isn't a terminal, and the lifecycle disables color on them when `cfg.Color` is off, so commands need no per-command wiring. The `tiger skills install` picker is the model. ### Tone @@ -195,7 +196,7 @@ Interactive menus and spinners use BubbleTea v2 (`charm.land/bubbletea/v2`). Alw - Cleanup that can fail (closing a reader, removing a temporary directory) is called explicitly on the success path, with its error returned, and also deferred as best-effort cleanup for the error paths, where a more important error is already being returned. This relies on the cleanup being safe to call twice, so use it only for idempotent operations. `writeSkill` in `skills_install.go` is the model. - Return errors from `RunE`; never call `os.Exit` in a command. For specific exit codes, wrap with `common.ExitWithCode`, and map API errors to exit codes with `common.ExitWithErrorFromStatusCode`. The exit codes are defined in `internal/common/errors.go`, and `main.go` honors a `common.ExitCodeError` anywhere in the error chain. - After checking an API response's status code, nil-check the parsed body (`resp.JSON200`, etc.) before using it — a nil body indicates a bug and should return `errors.New("empty response from API")`. -- Operations that connect to a service should call `common.CheckServiceReady` first and translate `common.ErrPaused`/`common.ErrNotReady` into actionable messages. +- Operations that connect to a database should check `ConnectionTarget.CheckReady` first and translate `common.ErrPaused`/`common.ErrNotReady` into actionable messages. ## HTTP Requests @@ -260,12 +261,12 @@ The CLI can be used to get around the MCP server's safeguards: `tiger config set ## Read-Only Mode -`cfg.ReadOnly` is a `config.ReadOnlyMode` — `all`, `prod`, or `off` (`prod` protects only services tagged `PROD`). `config.Load` normalizes every value through `parseReadOnlyMode`, which also accepts the legacy boolean spellings, so nothing downstream sees an unnormalized value. Every write/destructive surface on both planes gates through one of the two checks in `internal/common/read_only.go` — on the CLI side that's the `RunE` of `service create`, `fork`, `start`, `stop`, `rename`, `resize`, `update-password`, `delete`, `backup region add`, `backup region remove`, and `db create role`; on the MCP side, the handlers of the write tools listed in `readOnlyGatedTools`: +`cfg.ReadOnly` is a `config.ReadOnlyMode` — `all`, `prod`, or `off` (`prod` protects only services tagged `PROD`). `config.Load` normalizes every value through `parseReadOnlyMode`, which also accepts the legacy boolean spellings, so nothing downstream sees an unnormalized value. Every write/destructive surface on both planes gates through one of the two checks in `internal/common/read_only.go` — on the CLI side that's the `RunE` of `service create`, `fork`, `start`, `stop`, `rename`, `resize`, `update-password`, `delete`, `backup region add`, `backup region remove`, `db create role`, and every `branch` command that writes (`create` — also reachable as `service branch` — `delete`, `start`, `stop`, `resize`, `rename`, `update-password`); on the MCP side, the handlers of the write tools listed in `readOnlyGatedTools`: - `common.CheckReadOnly(cfg, tag)` — for a caller that has the target's environment tag: read off a service it already holds via `common.ServiceEnvironmentTag(service)` (what `resolveServiceForWrite` does on the CLI side), or the tag about to be requested (`--environment` on `service create`/`fork`, and the `environment` parameter of the matching MCP tools, both defaulting to `DEV`). - `common.CheckReadOnlyByServiceID(ctx, cfg, client, projectID, serviceID)` — the same verdict from an ID, fetching the service to read its tag. The fetch happens only under `prod` (`all` refuses and `off` allows without one), and a failed fetch is a refusal. Only the MCP write tools that don't already hold the service need it. -Gotchas when adding a gated surface: a replica set is judged on its *own* tag, never its primary's; `prod` refuses *creating* a `PROD` service too (otherwise it would create services it then can't stop or delete), so `create`/`fork` gate on the tag they're about to request; the MCP server skips registering write tools only under `all` — under `prod` they stay registered and refuse per call, with a `prod` variant of the server instructions explaining that; and where the verdict is wanted as a boolean, write `CheckReadOnly(…) != nil` rather than a wrapper, so grepping `CheckReadOnly` finds every gate. `CheckReadOnly` requires a tag so it can't silently ignore `prod`; the one shortcut allowed is refusing the blanket case before a fetch the command was making anyway (`if cfg.ReadOnly.BlocksAll() { return common.ErrReadOnly }`), then still gating once the service arrives — on the CLI side `resolveServiceForWrite` does both, so a command gets that order by calling it. +Gotchas when adding a gated surface: a replica set is judged on its *own* tag, never its primary's; a branch has no tag and is always judged as DEV (`common.BranchEnvironmentTag`), never by its parent's, so `prod` leaves branches of a PROD service writable; `prod` refuses *creating* a `PROD` service too (otherwise it would create services it then can't stop or delete), so `create`/`fork` gate on the tag they're about to request; the MCP server skips registering write tools only under `all` — under `prod` they stay registered and refuse per call, with a `prod` variant of the server instructions explaining that; and where the verdict is wanted as a boolean, write `CheckReadOnly(…) != nil` rather than a wrapper, so grepping `CheckReadOnly` finds every gate. `CheckReadOnly` requires a tag so it can't silently ignore `prod`; the one shortcut allowed is refusing the blanket case before a fetch the command was making anyway (`if cfg.ReadOnly.BlocksAll() { return common.ErrReadOnly }`), then still gating once the service arrives — on the CLI side `resolveServiceForWrite` does both, so a command gets that order by calling it. ## CLI/MCP Synchronization diff --git a/internal/api/client.go b/internal/api/client.go index 5013141d..8567fc19 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -302,6 +302,16 @@ type ClientInterface interface { // Corresponds with PATCH /projects/{project_id}/allow-lists/{allow_list_id} (the `UpdateAllowList` operationId). UpdateAllowList(ctx context.Context, projectID ProjectID, allowListID AllowListID, body UpdateAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetProjectBranches List All Branches in a Project + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves a list of all branches within a specific project, regardless + // of parent service. + // + // Corresponds with GET /projects/{project_id}/branches (the `GetProjectBranches` operationId). + GetProjectBranches(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetExporters List All Exporters // // **Preview - this endpoint is experimental and may change without notice.** @@ -406,6 +416,13 @@ type ClientInterface interface { // Corresponds with PATCH /projects/{project_id}/exporters/{exporter_id} (the `UpdateExporter` operationId). UpdateExporter(ctx context.Context, projectID ProjectID, exporterID ExporterID, body UpdateExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ResolveRef Resolve a Service, Branch, or Read Replica Set + // + // Resolves `ref` to the service, branch, or read replica set it names. + // + // Corresponds with GET /projects/{project_id}/resolve (the `ResolveRef` operationId). + ResolveRef(ctx context.Context, projectID ProjectID, params *ResolveRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetServices List All Services // // Retrieves a list of all services within a specific project. @@ -616,6 +633,149 @@ type ClientInterface interface { // Corresponds with GET /projects/{project_id}/services/{service_id}/backups (the `GetBackups` operationId). GetBackups(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetBranches List All Branches + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves a list of all branches created from a service within a project. + // + // Corresponds with GET /projects/{project_id}/services/{service_id}/branches (the `GetBranches` operationId). + GetBranches(ctx context.Context, projectID ProjectID, serviceID ServiceID, params *GetBranchesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateBranchWithBody Create a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Creates a new branch from the service identified by `service_id`. A + // branch shares its parent's data at the moment of creation and gets + // its own, independently-sized compute. This is an asynchronous + // operation. + // + // Takes any type of body and a specified content type. + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches (the `CreateBranch` operationId). + CreateBranchWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateBranch Create a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Creates a new branch from the service identified by `service_id`. A + // branch shares its parent's data at the moment of creation and gets + // its own, independently-sized compute. This is an asynchronous + // operation. + // + // Takes a body of the `application/json` content type. + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches (the `CreateBranch` operationId). + CreateBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteBranch Delete a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Deletes a specific branch. This is an asynchronous operation. + // + // Corresponds with DELETE /projects/{project_id}/services/{service_id}/branches/{branch_id} (the `DeleteBranch` operationId). + DeleteBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetBranch Get a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves the details of a specific branch by its ID. + // + // Corresponds with GET /projects/{project_id}/services/{service_id}/branches/{branch_id} (the `GetBranch` operationId). + GetBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*http.Response, error) + + // RenameBranchWithBody Rename a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Updates the name of a specific branch. + // + // Takes any type of body and a specified content type. + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/rename (the `RenameBranch` operationId). + RenameBranchWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + // RenameBranch Rename a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Updates the name of a specific branch. + // + // Takes a body of the `application/json` content type. + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/rename (the `RenameBranch` operationId). + RenameBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body RenameBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ResizeBranchWithBody Resize a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Changes the compute allocation for a specific branch. + // Storage size is shared with the parent and is not resized through + // this operation. + // + // Takes any type of body and a specified content type. + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/resize (the `ResizeBranch` operationId). + ResizeBranchWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ResizeBranch Resize a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Changes the compute allocation for a specific branch. + // Storage size is shared with the parent and is not resized through + // this operation. + // + // Takes a body of the `application/json` content type. + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/resize (the `ResizeBranch` operationId). + ResizeBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body ResizeBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // StartBranch Start a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Starts a stopped branch. This is an asynchronous operation. + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/start (the `StartBranch` operationId). + StartBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*http.Response, error) + + // StopBranch Stop a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Stops a running branch. This is an asynchronous operation. + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/stop (the `StopBranch` operationId). + StopBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateBranchPasswordWithBody Update Branch Password + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Sets a new master password for the branch. + // + // Takes any type of body and a specified content type. + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/updatePassword (the `UpdateBranchPassword` operationId). + UpdateBranchPasswordWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateBranchPassword Update Branch Password + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Sets a new master password for the branch. + // + // Takes a body of the `application/json` content type. + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/updatePassword (the `UpdateBranchPassword` operationId). + UpdateBranchPassword(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body UpdateBranchPasswordJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // DetachServiceFromAllowListWithBody Detach Service from IP Allow List // // **Preview - this endpoint is experimental and may change without notice.** @@ -1456,6 +1616,26 @@ func (c *Client) UpdateAllowList(ctx context.Context, projectID ProjectID, allow return c.Client.Do(req) } +// GetProjectBranches List All Branches in a Project +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Retrieves a list of all branches within a specific project, regardless +// of parent service. +// +// Corresponds with GET /projects/{project_id}/branches (the `GetProjectBranches` operationId). +func (c *Client) GetProjectBranches(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetProjectBranchesRequest(c.Server, projectID) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + // GetExporters List All Exporters // // **Preview - this endpoint is experimental and may change without notice.** @@ -1630,6 +1810,23 @@ func (c *Client) UpdateExporter(ctx context.Context, projectID ProjectID, export return c.Client.Do(req) } +// ResolveRef Resolve a Service, Branch, or Read Replica Set +// +// Resolves `ref` to the service, branch, or read replica set it names. +// +// Corresponds with GET /projects/{project_id}/resolve (the `ResolveRef` operationId). +func (c *Client) ResolveRef(ctx context.Context, projectID ProjectID, params *ResolveRefParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewResolveRefRequest(c.Server, projectID, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + // GetServices List All Services // // Retrieves a list of all services within a specific project. @@ -2030,18 +2227,15 @@ func (c *Client) GetBackups(ctx context.Context, projectID ProjectID, serviceID return c.Client.Do(req) } -// DetachServiceFromAllowListWithBody Detach Service from IP Allow List +// GetBranches List All Branches // // **Preview - this endpoint is experimental and may change without notice.** // -// Removes the IP restriction an IP allow list placed on a service. The -// IP allow list itself is not deleted. -// -// Takes any type of body and a specified content type. +// Retrieves a list of all branches created from a service within a project. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). -func (c *Client) DetachServiceFromAllowListWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDetachServiceFromAllowListRequestWithBody(c.Server, projectID, serviceID, contentType, body) +// Corresponds with GET /projects/{project_id}/services/{service_id}/branches (the `GetBranches` operationId). +func (c *Client) GetBranches(ctx context.Context, projectID ProjectID, serviceID ServiceID, params *GetBranchesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetBranchesRequest(c.Server, projectID, serviceID, params) if err != nil { return nil, err } @@ -2052,18 +2246,20 @@ func (c *Client) DetachServiceFromAllowListWithBody(ctx context.Context, project return c.Client.Do(req) } -// DetachServiceFromAllowList Detach Service from IP Allow List +// CreateBranchWithBody Create a Branch // // **Preview - this endpoint is experimental and may change without notice.** // -// Removes the IP restriction an IP allow list placed on a service. The -// IP allow list itself is not deleted. +// Creates a new branch from the service identified by `service_id`. A +// branch shares its parent's data at the moment of creation and gets +// its own, independently-sized compute. This is an asynchronous +// operation. // -// Takes a body of the `application/json` content type. +// Takes any type of body and a specified content type. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). -func (c *Client) DetachServiceFromAllowList(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDetachServiceFromAllowListRequest(c.Server, projectID, serviceID, body) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches (the `CreateBranch` operationId). +func (c *Client) CreateBranchWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateBranchRequestWithBody(c.Server, projectID, serviceID, contentType, body) if err != nil { return nil, err } @@ -2074,20 +2270,20 @@ func (c *Client) DetachServiceFromAllowList(ctx context.Context, projectID Proje return c.Client.Do(req) } -// DetachServiceFromExporterWithBody Detach Service from Exporter +// CreateBranch Create a Branch // // **Preview - this endpoint is experimental and may change without notice.** // -// Stops sending a service's logs or metrics to an exporter. The exporter -// is not deleted and keeps serving any other service attached to it. -// -// Data already delivered to the destination is untouched. +// Creates a new branch from the service identified by `service_id`. A +// branch shares its parent's data at the moment of creation and gets +// its own, independently-sized compute. This is an asynchronous +// operation. // -// Takes any type of body and a specified content type. +// Takes a body of the `application/json` content type. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). -func (c *Client) DetachServiceFromExporterWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDetachServiceFromExporterRequestWithBody(c.Server, projectID, serviceID, contentType, body) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches (the `CreateBranch` operationId). +func (c *Client) CreateBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateBranchRequest(c.Server, projectID, serviceID, body) if err != nil { return nil, err } @@ -2098,20 +2294,15 @@ func (c *Client) DetachServiceFromExporterWithBody(ctx context.Context, projectI return c.Client.Do(req) } -// DetachServiceFromExporter Detach Service from Exporter +// DeleteBranch Delete a Branch // // **Preview - this endpoint is experimental and may change without notice.** // -// Stops sending a service's logs or metrics to an exporter. The exporter -// is not deleted and keeps serving any other service attached to it. -// -// Data already delivered to the destination is untouched. -// -// Takes a body of the `application/json` content type. +// Deletes a specific branch. This is an asynchronous operation. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). -func (c *Client) DetachServiceFromExporter(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDetachServiceFromExporterRequest(c.Server, projectID, serviceID, body) +// Corresponds with DELETE /projects/{project_id}/services/{service_id}/branches/{branch_id} (the `DeleteBranch` operationId). +func (c *Client) DeleteBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteBranchRequest(c.Server, projectID, serviceID, branchID) if err != nil { return nil, err } @@ -2122,15 +2313,15 @@ func (c *Client) DetachServiceFromExporter(ctx context.Context, projectID Projec return c.Client.Do(req) } -// DetachServiceFromVPCWithBody Detach Service from VPC +// GetBranch Get a Branch // -// Disassociates a service from its VPC. +// **Preview - this endpoint is experimental and may change without notice.** // -// Takes any type of body and a specified content type. +// Retrieves the details of a specific branch by its ID. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). -func (c *Client) DetachServiceFromVPCWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDetachServiceFromVPCRequestWithBody(c.Server, projectID, serviceID, contentType, body) +// Corresponds with GET /projects/{project_id}/services/{service_id}/branches/{branch_id} (the `GetBranch` operationId). +func (c *Client) GetBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetBranchRequest(c.Server, projectID, serviceID, branchID) if err != nil { return nil, err } @@ -2141,15 +2332,17 @@ func (c *Client) DetachServiceFromVPCWithBody(ctx context.Context, projectID Pro return c.Client.Do(req) } -// DetachServiceFromVPC Detach Service from VPC +// RenameBranchWithBody Rename a Branch // -// Disassociates a service from its VPC. +// **Preview - this endpoint is experimental and may change without notice.** // -// Takes a body of the `application/json` content type. +// Updates the name of a specific branch. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). -func (c *Client) DetachServiceFromVPC(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDetachServiceFromVPCRequest(c.Server, projectID, serviceID, body) +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/rename (the `RenameBranch` operationId). +func (c *Client) RenameBranchWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRenameBranchRequestWithBody(c.Server, projectID, serviceID, branchID, contentType, body) if err != nil { return nil, err } @@ -2160,13 +2353,17 @@ func (c *Client) DetachServiceFromVPC(ctx context.Context, projectID ProjectID, return c.Client.Do(req) } -// DisablePooler Disable Connection Pooler for a Service +// RenameBranch Rename a Branch // -// Deactivates the connection pooler for a specific service within a project. +// **Preview - this endpoint is experimental and may change without notice.** // -// Corresponds with POST /projects/{project_id}/services/{service_id}/disablePooler (the `DisablePooler` operationId). -func (c *Client) DisablePooler(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDisablePoolerRequest(c.Server, projectID, serviceID) +// Updates the name of a specific branch. +// +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/rename (the `RenameBranch` operationId). +func (c *Client) RenameBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body RenameBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRenameBranchRequest(c.Server, projectID, serviceID, branchID, body) if err != nil { return nil, err } @@ -2177,19 +2374,19 @@ func (c *Client) DisablePooler(ctx context.Context, projectID ProjectID, service return c.Client.Do(req) } -// EnableDataTiering Enable Data Tiering for a Service +// ResizeBranchWithBody Resize a Branch // -// Enables tiered storage on the service. Tiering policies move older, -// rarely-accessed data from the high-performance storage tier to a -// low-cost object storage tier. Tiered data stays queryable but becomes -// read-only. +// **Preview - this endpoint is experimental and may change without notice.** // -// There is no way to disable tiering through this API. Contact Tiger -// Data support if you need to turn it off. +// Changes the compute allocation for a specific branch. +// Storage size is shared with the parent and is not resized through +// this operation. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/enableDataTiering (the `EnableDataTiering` operationId). -func (c *Client) EnableDataTiering(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewEnableDataTieringRequest(c.Server, projectID, serviceID) +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/resize (the `ResizeBranch` operationId). +func (c *Client) ResizeBranchWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewResizeBranchRequestWithBody(c.Server, projectID, serviceID, branchID, contentType, body) if err != nil { return nil, err } @@ -2200,13 +2397,19 @@ func (c *Client) EnableDataTiering(ctx context.Context, projectID ProjectID, ser return c.Client.Do(req) } -// EnablePooler Enable Connection Pooler for a Service +// ResizeBranch Resize a Branch // -// Activates the connection pooler for a specific service within a project. +// **Preview - this endpoint is experimental and may change without notice.** // -// Corresponds with POST /projects/{project_id}/services/{service_id}/enablePooler (the `EnablePooler` operationId). -func (c *Client) EnablePooler(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewEnablePoolerRequest(c.Server, projectID, serviceID) +// Changes the compute allocation for a specific branch. +// Storage size is shared with the parent and is not resized through +// this operation. +// +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/resize (the `ResizeBranch` operationId). +func (c *Client) ResizeBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body ResizeBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewResizeBranchRequest(c.Server, projectID, serviceID, branchID, body) if err != nil { return nil, err } @@ -2217,15 +2420,15 @@ func (c *Client) EnablePooler(ctx context.Context, projectID ProjectID, serviceI return c.Client.Do(req) } -// ForkServiceWithBody Fork a Service +// StartBranch Start a Branch // -// Creates a new, independent service within a project by taking a snapshot of an existing one. +// **Preview - this endpoint is experimental and may change without notice.** // -// Takes any type of body and a specified content type. +// Starts a stopped branch. This is an asynchronous operation. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). -func (c *Client) ForkServiceWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewForkServiceRequestWithBody(c.Server, projectID, serviceID, contentType, body) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/start (the `StartBranch` operationId). +func (c *Client) StartBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewStartBranchRequest(c.Server, projectID, serviceID, branchID) if err != nil { return nil, err } @@ -2236,15 +2439,15 @@ func (c *Client) ForkServiceWithBody(ctx context.Context, projectID ProjectID, s return c.Client.Do(req) } -// ForkService Fork a Service +// StopBranch Stop a Branch // -// Creates a new, independent service within a project by taking a snapshot of an existing one. +// **Preview - this endpoint is experimental and may change without notice.** // -// Takes a body of the `application/json` content type. +// Stops a running branch. This is an asynchronous operation. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). -func (c *Client) ForkService(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ForkServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewForkServiceRequest(c.Server, projectID, serviceID, body) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/stop (the `StopBranch` operationId). +func (c *Client) StopBranch(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewStopBranchRequest(c.Server, projectID, serviceID, branchID) if err != nil { return nil, err } @@ -2255,18 +2458,17 @@ func (c *Client) ForkService(ctx context.Context, projectID ProjectID, serviceID return c.Client.Do(req) } -// GetServiceLogs Get service logs +// UpdateBranchPasswordWithBody Update Branch Password // -// Fetch logs for a specific service. Returns up to 500 log entries in reverse -// chronological order. Supports cursor-based pagination via the `cursor` parameter -// and the `last_cursor` field in the response. +// **Preview - this endpoint is experimental and may change without notice.** // -// Use the returned `last_cursor` value as the `cursor` parameter on subsequent -// requests to page through all available logs. +// Sets a new master password for the branch. // -// Corresponds with GET /projects/{project_id}/services/{service_id}/logs (the `GetServiceLogs` operationId). -func (c *Client) GetServiceLogs(ctx context.Context, projectID ProjectID, serviceID ServiceID, params *GetServiceLogsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetServiceLogsRequest(c.Server, projectID, serviceID, params) +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/updatePassword (the `UpdateBranchPassword` operationId). +func (c *Client) UpdateBranchPasswordWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateBranchPasswordRequestWithBody(c.Server, projectID, serviceID, branchID, contentType, body) if err != nil { return nil, err } @@ -2277,13 +2479,17 @@ func (c *Client) GetServiceLogs(ctx context.Context, projectID ProjectID, servic return c.Client.Do(req) } -// GetServiceMetricsAvailableSeries List available metric series +// UpdateBranchPassword Update Branch Password // -// Returns the names of all metric series available for a service. +// **Preview - this endpoint is experimental and may change without notice.** // -// Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/available-series (the `GetServiceMetricsAvailableSeries` operationId). -func (c *Client) GetServiceMetricsAvailableSeries(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetServiceMetricsAvailableSeriesRequest(c.Server, projectID, serviceID) +// Sets a new master password for the branch. +// +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/updatePassword (the `UpdateBranchPassword` operationId). +func (c *Client) UpdateBranchPassword(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body UpdateBranchPasswordJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateBranchPasswordRequest(c.Server, projectID, serviceID, branchID, body) if err != nil { return nil, err } @@ -2294,17 +2500,18 @@ func (c *Client) GetServiceMetricsAvailableSeries(ctx context.Context, projectID return c.Client.Do(req) } -// GetServiceMetricDetails Get details about a metric +// DetachServiceFromAllowListWithBody Detach Service from IP Allow List // -// Returns descriptive metadata for a named metric: what it measures, -// its type, default aggregation, and every label it can be filtered or -// grouped by — its own labels plus the region/role/ordinal labels most -// metrics also carry. Use getServiceMetricsAvailableSeries to discover -// valid metric names. +// **Preview - this endpoint is experimental and may change without notice.** // -// Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/details/{metric_name} (the `GetServiceMetricDetails` operationId). -func (c *Client) GetServiceMetricDetails(ctx context.Context, projectID ProjectID, serviceID ServiceID, metricName string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetServiceMetricDetailsRequest(c.Server, projectID, serviceID, metricName) +// Removes the IP restriction an IP allow list placed on a service. The +// IP allow list itself is not deleted. +// +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). +func (c *Client) DetachServiceFromAllowListWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDetachServiceFromAllowListRequestWithBody(c.Server, projectID, serviceID, contentType, body) if err != nil { return nil, err } @@ -2315,21 +2522,18 @@ func (c *Client) GetServiceMetricDetails(ctx context.Context, projectID ProjectI return c.Client.Do(req) } -// GetServiceMetricsSeriesWithBody Get a metric series +// DetachServiceFromAllowList Detach Service from IP Allow List // -// Returns time-series data points for a named metric within a time window. -// Use getServiceMetricsAvailableSeries to discover valid metric names. +// **Preview - this endpoint is experimental and may change without notice.** // -// Parameters are sent as a JSON body so callers can pass an unbounded -// filter set without query-string length pressure. The response is a list -// of MetricSeries, each carrying its label set as a structured map -// alongside its per-bucket data points. +// Removes the IP restriction an IP allow list placed on a service. The +// IP allow list itself is not deleted. // -// Takes any type of body and a specified content type. +// Takes a body of the `application/json` content type. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). -func (c *Client) GetServiceMetricsSeriesWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetServiceMetricsSeriesRequestWithBody(c.Server, projectID, serviceID, contentType, body) +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). +func (c *Client) DetachServiceFromAllowList(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDetachServiceFromAllowListRequest(c.Server, projectID, serviceID, body) if err != nil { return nil, err } @@ -2340,21 +2544,20 @@ func (c *Client) GetServiceMetricsSeriesWithBody(ctx context.Context, projectID return c.Client.Do(req) } -// GetServiceMetricsSeries Get a metric series +// DetachServiceFromExporterWithBody Detach Service from Exporter // -// Returns time-series data points for a named metric within a time window. -// Use getServiceMetricsAvailableSeries to discover valid metric names. +// **Preview - this endpoint is experimental and may change without notice.** // -// Parameters are sent as a JSON body so callers can pass an unbounded -// filter set without query-string length pressure. The response is a list -// of MetricSeries, each carrying its label set as a structured map -// alongside its per-bucket data points. +// Stops sending a service's logs or metrics to an exporter. The exporter +// is not deleted and keeps serving any other service attached to it. // -// Takes a body of the `application/json` content type. +// Data already delivered to the destination is untouched. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). -func (c *Client) GetServiceMetricsSeries(ctx context.Context, projectID ProjectID, serviceID ServiceID, body GetServiceMetricsSeriesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetServiceMetricsSeriesRequest(c.Server, projectID, serviceID, body) +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). +func (c *Client) DetachServiceFromExporterWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDetachServiceFromExporterRequestWithBody(c.Server, projectID, serviceID, contentType, body) if err != nil { return nil, err } @@ -2365,15 +2568,20 @@ func (c *Client) GetServiceMetricsSeries(ctx context.Context, projectID ProjectI return c.Client.Do(req) } -// RenameServiceWithBody Rename a Service +// DetachServiceFromExporter Detach Service from Exporter // -// Updates the name of a specific service. +// **Preview - this endpoint is experimental and may change without notice.** // -// Takes any type of body and a specified content type. +// Stops sending a service's logs or metrics to an exporter. The exporter +// is not deleted and keeps serving any other service attached to it. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). -func (c *Client) RenameServiceWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRenameServiceRequestWithBody(c.Server, projectID, serviceID, contentType, body) +// Data already delivered to the destination is untouched. +// +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). +func (c *Client) DetachServiceFromExporter(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDetachServiceFromExporterRequest(c.Server, projectID, serviceID, body) if err != nil { return nil, err } @@ -2384,15 +2592,15 @@ func (c *Client) RenameServiceWithBody(ctx context.Context, projectID ProjectID, return c.Client.Do(req) } -// RenameService Rename a Service +// DetachServiceFromVPCWithBody Detach Service from VPC // -// Updates the name of a specific service. +// Disassociates a service from its VPC. // -// Takes a body of the `application/json` content type. +// Takes any type of body and a specified content type. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). -func (c *Client) RenameService(ctx context.Context, projectID ProjectID, serviceID ServiceID, body RenameServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRenameServiceRequest(c.Server, projectID, serviceID, body) +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). +func (c *Client) DetachServiceFromVPCWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDetachServiceFromVPCRequestWithBody(c.Server, projectID, serviceID, contentType, body) if err != nil { return nil, err } @@ -2403,13 +2611,15 @@ func (c *Client) RenameService(ctx context.Context, projectID ProjectID, service return c.Client.Do(req) } -// GetReplicaSets Get Read Replica Sets +// DetachServiceFromVPC Detach Service from VPC // -// Retrieves a list of all read replica sets associated with a primary service within a project. +// Disassociates a service from its VPC. // -// Corresponds with GET /projects/{project_id}/services/{service_id}/replicaSets (the `GetReplicaSets` operationId). -func (c *Client) GetReplicaSets(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetReplicaSetsRequest(c.Server, projectID, serviceID) +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). +func (c *Client) DetachServiceFromVPC(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDetachServiceFromVPCRequest(c.Server, projectID, serviceID, body) if err != nil { return nil, err } @@ -2420,15 +2630,13 @@ func (c *Client) GetReplicaSets(ctx context.Context, projectID ProjectID, servic return c.Client.Do(req) } -// CreateReplicaSetWithBody Create a Read Replica Set -// -// Creates a new read replica set for a service. This is an asynchronous operation. +// DisablePooler Disable Connection Pooler for a Service // -// Takes any type of body and a specified content type. +// Deactivates the connection pooler for a specific service within a project. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). -func (c *Client) CreateReplicaSetWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateReplicaSetRequestWithBody(c.Server, projectID, serviceID, contentType, body) +// Corresponds with POST /projects/{project_id}/services/{service_id}/disablePooler (the `DisablePooler` operationId). +func (c *Client) DisablePooler(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDisablePoolerRequest(c.Server, projectID, serviceID) if err != nil { return nil, err } @@ -2439,15 +2647,19 @@ func (c *Client) CreateReplicaSetWithBody(ctx context.Context, projectID Project return c.Client.Do(req) } -// CreateReplicaSet Create a Read Replica Set +// EnableDataTiering Enable Data Tiering for a Service // -// Creates a new read replica set for a service. This is an asynchronous operation. +// Enables tiered storage on the service. Tiering policies move older, +// rarely-accessed data from the high-performance storage tier to a +// low-cost object storage tier. Tiered data stays queryable but becomes +// read-only. // -// Takes a body of the `application/json` content type. +// There is no way to disable tiering through this API. Contact Tiger +// Data support if you need to turn it off. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). -func (c *Client) CreateReplicaSet(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateReplicaSetRequest(c.Server, projectID, serviceID, body) +// Corresponds with POST /projects/{project_id}/services/{service_id}/enableDataTiering (the `EnableDataTiering` operationId). +func (c *Client) EnableDataTiering(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEnableDataTieringRequest(c.Server, projectID, serviceID) if err != nil { return nil, err } @@ -2458,13 +2670,13 @@ func (c *Client) CreateReplicaSet(ctx context.Context, projectID ProjectID, serv return c.Client.Do(req) } -// DeleteReplicaSet Delete a Read Replica Set +// EnablePooler Enable Connection Pooler for a Service // -// Deletes a specific read replica set. This is an asynchronous operation. +// Activates the connection pooler for a specific service within a project. // -// Corresponds with DELETE /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id} (the `DeleteReplicaSet` operationId). -func (c *Client) DeleteReplicaSet(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteReplicaSetRequest(c.Server, projectID, serviceID, replicaSetID) +// Corresponds with POST /projects/{project_id}/services/{service_id}/enablePooler (the `EnablePooler` operationId). +func (c *Client) EnablePooler(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEnablePoolerRequest(c.Server, projectID, serviceID) if err != nil { return nil, err } @@ -2475,13 +2687,15 @@ func (c *Client) DeleteReplicaSet(ctx context.Context, projectID ProjectID, serv return c.Client.Do(req) } -// DisableReplicaPooler Disable Connection Pooler for a Read Replica +// ForkServiceWithBody Fork a Service // -// Deactivates the connection pooler for a specific read replica set. +// Creates a new, independent service within a project by taking a snapshot of an existing one. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/disablePooler (the `DisableReplicaPooler` operationId). -func (c *Client) DisableReplicaPooler(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDisableReplicaPoolerRequest(c.Server, projectID, serviceID, replicaSetID) +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). +func (c *Client) ForkServiceWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewForkServiceRequestWithBody(c.Server, projectID, serviceID, contentType, body) if err != nil { return nil, err } @@ -2492,13 +2706,15 @@ func (c *Client) DisableReplicaPooler(ctx context.Context, projectID ProjectID, return c.Client.Do(req) } -// EnableReplicaPooler Enable Connection Pooler for a Read Replica +// ForkService Fork a Service // -// Activates the connection pooler for a specific read replica set. +// Creates a new, independent service within a project by taking a snapshot of an existing one. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/enablePooler (the `EnableReplicaPooler` operationId). -func (c *Client) EnableReplicaPooler(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewEnableReplicaPoolerRequest(c.Server, projectID, serviceID, replicaSetID) +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). +func (c *Client) ForkService(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ForkServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewForkServiceRequest(c.Server, projectID, serviceID, body) if err != nil { return nil, err } @@ -2509,15 +2725,18 @@ func (c *Client) EnableReplicaPooler(ctx context.Context, projectID ProjectID, s return c.Client.Do(req) } -// ResizeReplicaSetWithBody Resize a Read Replica Set +// GetServiceLogs Get service logs // -// Changes the resource allocation for a specific read replica set. +// Fetch logs for a specific service. Returns up to 500 log entries in reverse +// chronological order. Supports cursor-based pagination via the `cursor` parameter +// and the `last_cursor` field in the response. // -// Takes any type of body and a specified content type. +// Use the returned `last_cursor` value as the `cursor` parameter on subsequent +// requests to page through all available logs. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). -func (c *Client) ResizeReplicaSetWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewResizeReplicaSetRequestWithBody(c.Server, projectID, serviceID, replicaSetID, contentType, body) +// Corresponds with GET /projects/{project_id}/services/{service_id}/logs (the `GetServiceLogs` operationId). +func (c *Client) GetServiceLogs(ctx context.Context, projectID ProjectID, serviceID ServiceID, params *GetServiceLogsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetServiceLogsRequest(c.Server, projectID, serviceID, params) if err != nil { return nil, err } @@ -2528,15 +2747,13 @@ func (c *Client) ResizeReplicaSetWithBody(ctx context.Context, projectID Project return c.Client.Do(req) } -// ResizeReplicaSet Resize a Read Replica Set -// -// Changes the resource allocation for a specific read replica set. +// GetServiceMetricsAvailableSeries List available metric series // -// Takes a body of the `application/json` content type. +// Returns the names of all metric series available for a service. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). -func (c *Client) ResizeReplicaSet(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body ResizeReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewResizeReplicaSetRequest(c.Server, projectID, serviceID, replicaSetID, body) +// Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/available-series (the `GetServiceMetricsAvailableSeries` operationId). +func (c *Client) GetServiceMetricsAvailableSeries(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetServiceMetricsAvailableSeriesRequest(c.Server, projectID, serviceID) if err != nil { return nil, err } @@ -2547,15 +2764,17 @@ func (c *Client) ResizeReplicaSet(ctx context.Context, projectID ProjectID, serv return c.Client.Do(req) } -// SetReplicaEnvironmentWithBody Set Environment for a Read Replica -// -// Sets the environment type for the read replica set. +// GetServiceMetricDetails Get details about a metric // -// Takes any type of body and a specified content type. +// Returns descriptive metadata for a named metric: what it measures, +// its type, default aggregation, and every label it can be filtered or +// grouped by — its own labels plus the region/role/ordinal labels most +// metrics also carry. Use getServiceMetricsAvailableSeries to discover +// valid metric names. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). -func (c *Client) SetReplicaEnvironmentWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetReplicaEnvironmentRequestWithBody(c.Server, projectID, serviceID, replicaSetID, contentType, body) +// Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/details/{metric_name} (the `GetServiceMetricDetails` operationId). +func (c *Client) GetServiceMetricDetails(ctx context.Context, projectID ProjectID, serviceID ServiceID, metricName string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetServiceMetricDetailsRequest(c.Server, projectID, serviceID, metricName) if err != nil { return nil, err } @@ -2566,15 +2785,21 @@ func (c *Client) SetReplicaEnvironmentWithBody(ctx context.Context, projectID Pr return c.Client.Do(req) } -// SetReplicaEnvironment Set Environment for a Read Replica +// GetServiceMetricsSeriesWithBody Get a metric series // -// Sets the environment type for the read replica set. +// Returns time-series data points for a named metric within a time window. +// Use getServiceMetricsAvailableSeries to discover valid metric names. // -// Takes a body of the `application/json` content type. +// Parameters are sent as a JSON body so callers can pass an unbounded +// filter set without query-string length pressure. The response is a list +// of MetricSeries, each carrying its label set as a structured map +// alongside its per-bucket data points. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). -func (c *Client) SetReplicaEnvironment(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body SetReplicaEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetReplicaEnvironmentRequest(c.Server, projectID, serviceID, replicaSetID, body) +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). +func (c *Client) GetServiceMetricsSeriesWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetServiceMetricsSeriesRequestWithBody(c.Server, projectID, serviceID, contentType, body) if err != nil { return nil, err } @@ -2585,15 +2810,21 @@ func (c *Client) SetReplicaEnvironment(ctx context.Context, projectID ProjectID, return c.Client.Do(req) } -// ResizeServiceWithBody Resize a Service +// GetServiceMetricsSeries Get a metric series // -// Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. +// Returns time-series data points for a named metric within a time window. +// Use getServiceMetricsAvailableSeries to discover valid metric names. // -// Takes any type of body and a specified content type. +// Parameters are sent as a JSON body so callers can pass an unbounded +// filter set without query-string length pressure. The response is a list +// of MetricSeries, each carrying its label set as a structured map +// alongside its per-bucket data points. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). -func (c *Client) ResizeServiceWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewResizeServiceRequestWithBody(c.Server, projectID, serviceID, contentType, body) +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). +func (c *Client) GetServiceMetricsSeries(ctx context.Context, projectID ProjectID, serviceID ServiceID, body GetServiceMetricsSeriesJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetServiceMetricsSeriesRequest(c.Server, projectID, serviceID, body) if err != nil { return nil, err } @@ -2604,15 +2835,15 @@ func (c *Client) ResizeServiceWithBody(ctx context.Context, projectID ProjectID, return c.Client.Do(req) } -// ResizeService Resize a Service +// RenameServiceWithBody Rename a Service // -// Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. +// Updates the name of a specific service. // -// Takes a body of the `application/json` content type. +// Takes any type of body and a specified content type. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). -func (c *Client) ResizeService(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ResizeServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewResizeServiceRequest(c.Server, projectID, serviceID, body) +// Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). +func (c *Client) RenameServiceWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRenameServiceRequestWithBody(c.Server, projectID, serviceID, contentType, body) if err != nil { return nil, err } @@ -2623,15 +2854,15 @@ func (c *Client) ResizeService(ctx context.Context, projectID ProjectID, service return c.Client.Do(req) } -// SetEnvironmentWithBody Set Environment for a Service +// RenameService Rename a Service // -// Sets the environment type for the service. +// Updates the name of a specific service. // -// Takes any type of body and a specified content type. +// Takes a body of the `application/json` content type. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). -func (c *Client) SetEnvironmentWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetEnvironmentRequestWithBody(c.Server, projectID, serviceID, contentType, body) +// Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). +func (c *Client) RenameService(ctx context.Context, projectID ProjectID, serviceID ServiceID, body RenameServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRenameServiceRequest(c.Server, projectID, serviceID, body) if err != nil { return nil, err } @@ -2642,15 +2873,13 @@ func (c *Client) SetEnvironmentWithBody(ctx context.Context, projectID ProjectID return c.Client.Do(req) } -// SetEnvironment Set Environment for a Service -// -// Sets the environment type for the service. +// GetReplicaSets Get Read Replica Sets // -// Takes a body of the `application/json` content type. +// Retrieves a list of all read replica sets associated with a primary service within a project. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). -func (c *Client) SetEnvironment(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewSetEnvironmentRequest(c.Server, projectID, serviceID, body) +// Corresponds with GET /projects/{project_id}/services/{service_id}/replicaSets (the `GetReplicaSets` operationId). +func (c *Client) GetReplicaSets(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetReplicaSetsRequest(c.Server, projectID, serviceID) if err != nil { return nil, err } @@ -2661,7 +2890,248 @@ func (c *Client) SetEnvironment(ctx context.Context, projectID ProjectID, servic return c.Client.Do(req) } -// SetHAReplicaWithBody Change HA configuration for a Service +// CreateReplicaSetWithBody Create a Read Replica Set +// +// Creates a new read replica set for a service. This is an asynchronous operation. +// +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). +func (c *Client) CreateReplicaSetWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateReplicaSetRequestWithBody(c.Server, projectID, serviceID, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// CreateReplicaSet Create a Read Replica Set +// +// Creates a new read replica set for a service. This is an asynchronous operation. +// +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). +func (c *Client) CreateReplicaSet(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateReplicaSetRequest(c.Server, projectID, serviceID, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// DeleteReplicaSet Delete a Read Replica Set +// +// Deletes a specific read replica set. This is an asynchronous operation. +// +// Corresponds with DELETE /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id} (the `DeleteReplicaSet` operationId). +func (c *Client) DeleteReplicaSet(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteReplicaSetRequest(c.Server, projectID, serviceID, replicaSetID) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// DisableReplicaPooler Disable Connection Pooler for a Read Replica +// +// Deactivates the connection pooler for a specific read replica set. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/disablePooler (the `DisableReplicaPooler` operationId). +func (c *Client) DisableReplicaPooler(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDisableReplicaPoolerRequest(c.Server, projectID, serviceID, replicaSetID) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// EnableReplicaPooler Enable Connection Pooler for a Read Replica +// +// Activates the connection pooler for a specific read replica set. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/enablePooler (the `EnableReplicaPooler` operationId). +func (c *Client) EnableReplicaPooler(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEnableReplicaPoolerRequest(c.Server, projectID, serviceID, replicaSetID) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// ResizeReplicaSetWithBody Resize a Read Replica Set +// +// Changes the resource allocation for a specific read replica set. +// +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). +func (c *Client) ResizeReplicaSetWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewResizeReplicaSetRequestWithBody(c.Server, projectID, serviceID, replicaSetID, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// ResizeReplicaSet Resize a Read Replica Set +// +// Changes the resource allocation for a specific read replica set. +// +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). +func (c *Client) ResizeReplicaSet(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body ResizeReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewResizeReplicaSetRequest(c.Server, projectID, serviceID, replicaSetID, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// SetReplicaEnvironmentWithBody Set Environment for a Read Replica +// +// Sets the environment type for the read replica set. +// +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). +func (c *Client) SetReplicaEnvironmentWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSetReplicaEnvironmentRequestWithBody(c.Server, projectID, serviceID, replicaSetID, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// SetReplicaEnvironment Set Environment for a Read Replica +// +// Sets the environment type for the read replica set. +// +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). +func (c *Client) SetReplicaEnvironment(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body SetReplicaEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSetReplicaEnvironmentRequest(c.Server, projectID, serviceID, replicaSetID, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// ResizeServiceWithBody Resize a Service +// +// Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. +// +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). +func (c *Client) ResizeServiceWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewResizeServiceRequestWithBody(c.Server, projectID, serviceID, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// ResizeService Resize a Service +// +// Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. +// +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). +func (c *Client) ResizeService(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ResizeServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewResizeServiceRequest(c.Server, projectID, serviceID, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// SetEnvironmentWithBody Set Environment for a Service +// +// Sets the environment type for the service. +// +// Takes any type of body and a specified content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). +func (c *Client) SetEnvironmentWithBody(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSetEnvironmentRequestWithBody(c.Server, projectID, serviceID, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// SetEnvironment Set Environment for a Service +// +// Sets the environment type for the service. +// +// Takes a body of the `application/json` content type. +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). +func (c *Client) SetEnvironment(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSetEnvironmentRequest(c.Server, projectID, serviceID, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// SetHAReplicaWithBody Change HA configuration for a Service // // Changes the HA configuration for a specific service. This is an asynchronous operation. // @@ -3418,8 +3888,8 @@ func NewUpdateAllowListRequestWithBody(server string, projectID ProjectID, allow return req, nil } -// NewGetExportersRequest constructs an http.Request for the GetExporters method -func NewGetExportersRequest(server string, projectID ProjectID) (*http.Request, error) { +// NewGetProjectBranchesRequest constructs an http.Request for the GetProjectBranches method +func NewGetProjectBranchesRequest(server string, projectID ProjectID) (*http.Request, error) { var err error var pathParam0 string @@ -3434,7 +3904,7 @@ func NewGetExportersRequest(server string, projectID ProjectID) (*http.Request, return nil, err } - operationPath := fmt.Sprintf("/projects/%s/exporters", pathParam0) + operationPath := fmt.Sprintf("/projects/%s/branches", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -3452,14 +3922,48 @@ func NewGetExportersRequest(server string, projectID ProjectID) (*http.Request, return req, nil } -// NewCreateExporterRequest calls the generic CreateExporter builder with application/json body -func NewCreateExporterRequest(server string, projectID ProjectID, body CreateExporterJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) +// NewGetExportersRequest constructs an http.Request for the GetExporters method +func NewGetExportersRequest(server string, projectID ProjectID) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "project_id", projectID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } - bodyReader = bytes.NewReader(buf) + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/projects/%s/exporters", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateExporterRequest calls the generic CreateExporter builder with application/json body +func NewCreateExporterRequest(server string, projectID ProjectID, body CreateExporterJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) return NewCreateExporterRequestWithBody(server, projectID, "application/json", bodyReader) } @@ -3635,6 +4139,75 @@ func NewUpdateExporterRequestWithBody(server string, projectID ProjectID, export return req, nil } +// NewResolveRefRequest constructs an http.Request for the ResolveRef method +func NewResolveRefRequest(server string, projectID ProjectID, params *ResolveRefParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "project_id", projectID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/projects/%s/resolve", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + // queryValues collects non-styled parameters (passthrough, JSON) + // that are safe to round-trip through url.Values.Encode(). + queryValues := queryURL.Query() + // rawQueryFragments collects pre-encoded query fragments from + // styled parameters, preserving literal commas as delimiters + // per the OpenAPI spec (e.g. "color=blue,black,brown"). + var rawQueryFragments []string + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "ref", params.Ref, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + if params.Type != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "type", *params.Type, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if encoded := queryValues.Encode(); encoded != "" { + rawQueryFragments = append(rawQueryFragments, encoded) + } + queryURL.RawQuery = strings.Join(rawQueryFragments, "&") + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewGetServicesRequest constructs an http.Request for the GetServices method func NewGetServicesRequest(server string, projectID ProjectID, params *GetServicesParams) (*http.Request, error) { var err error @@ -4266,19 +4839,8 @@ func NewGetBackupsRequest(server string, projectID ProjectID, serviceID ServiceI return req, nil } -// NewDetachServiceFromAllowListRequest calls the generic DetachServiceFromAllowList builder with application/json body -func NewDetachServiceFromAllowListRequest(server string, projectID ProjectID, serviceID ServiceID, body DetachServiceFromAllowListJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewDetachServiceFromAllowListRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) -} - -// NewDetachServiceFromAllowListRequestWithBody constructs an http.Request for the DetachServiceFromAllowList method, with any body, and a specified content type -func NewDetachServiceFromAllowListRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { +// NewGetBranchesRequest constructs an http.Request for the GetBranches method +func NewGetBranchesRequest(server string, projectID ProjectID, serviceID ServiceID, params *GetBranchesParams) (*http.Request, error) { var err error var pathParam0 string @@ -4300,7 +4862,7 @@ func NewDetachServiceFromAllowListRequestWithBody(server string, projectID Proje return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/detachFromAllowList", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/branches", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4310,29 +4872,54 @@ func NewDetachServiceFromAllowListRequestWithBody(server string, projectID Proje return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + if params != nil { + // queryValues collects non-styled parameters (passthrough, JSON) + // that are safe to round-trip through url.Values.Encode(). + queryValues := queryURL.Query() + // rawQueryFragments collects pre-encoded query fragments from + // styled parameters, preserving literal commas as delimiters + // per the OpenAPI spec (e.g. "color=blue,black,brown"). + var rawQueryFragments []string + + if params.Ref != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "ref", *params.Ref, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if encoded := queryValues.Encode(); encoded != "" { + rawQueryFragments = append(rawQueryFragments, encoded) + } + queryURL.RawQuery = strings.Join(rawQueryFragments, "&") + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewDetachServiceFromExporterRequest calls the generic DetachServiceFromExporter builder with application/json body -func NewDetachServiceFromExporterRequest(server string, projectID ProjectID, serviceID ServiceID, body DetachServiceFromExporterJSONRequestBody) (*http.Request, error) { +// NewCreateBranchRequest calls the generic CreateBranch builder with application/json body +func NewCreateBranchRequest(server string, projectID ProjectID, serviceID ServiceID, body CreateBranchJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewDetachServiceFromExporterRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) + return NewCreateBranchRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) } -// NewDetachServiceFromExporterRequestWithBody constructs an http.Request for the DetachServiceFromExporter method, with any body, and a specified content type -func NewDetachServiceFromExporterRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { +// NewCreateBranchRequestWithBody constructs an http.Request for the CreateBranch method, with any body, and a specified content type +func NewCreateBranchRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -4354,7 +4941,7 @@ func NewDetachServiceFromExporterRequestWithBody(server string, projectID Projec return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/detachFromExporter", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/branches", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4374,19 +4961,8 @@ func NewDetachServiceFromExporterRequestWithBody(server string, projectID Projec return req, nil } -// NewDetachServiceFromVPCRequest calls the generic DetachServiceFromVPC builder with application/json body -func NewDetachServiceFromVPCRequest(server string, projectID ProjectID, serviceID ServiceID, body DetachServiceFromVPCJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewDetachServiceFromVPCRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) -} - -// NewDetachServiceFromVPCRequestWithBody constructs an http.Request for the DetachServiceFromVPC method, with any body, and a specified content type -func NewDetachServiceFromVPCRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { +// NewDeleteBranchRequest constructs an http.Request for the DeleteBranch method +func NewDeleteBranchRequest(server string, projectID ProjectID, serviceID ServiceID, branchID BranchID) (*http.Request, error) { var err error var pathParam0 string @@ -4403,12 +4979,19 @@ func NewDetachServiceFromVPCRequestWithBody(server string, projectID ProjectID, return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "branch_id", branchID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/detachFromVPC", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/branches/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4418,18 +5001,16 @@ func NewDetachServiceFromVPCRequestWithBody(server string, projectID ProjectID, return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewDisablePoolerRequest constructs an http.Request for the DisablePooler method -func NewDisablePoolerRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { +// NewGetBranchRequest constructs an http.Request for the GetBranch method +func NewGetBranchRequest(server string, projectID ProjectID, serviceID ServiceID, branchID BranchID) (*http.Request, error) { var err error var pathParam0 string @@ -4446,12 +5027,19 @@ func NewDisablePoolerRequest(server string, projectID ProjectID, serviceID Servi return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "branch_id", branchID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/disablePooler", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/branches/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4461,7 +5049,7 @@ func NewDisablePoolerRequest(server string, projectID ProjectID, serviceID Servi return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) if err != nil { return nil, err } @@ -4469,8 +5057,19 @@ func NewDisablePoolerRequest(server string, projectID ProjectID, serviceID Servi return req, nil } -// NewEnableDataTieringRequest constructs an http.Request for the EnableDataTiering method -func NewEnableDataTieringRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { +// NewRenameBranchRequest calls the generic RenameBranch builder with application/json body +func NewRenameBranchRequest(server string, projectID ProjectID, serviceID ServiceID, branchID BranchID, body RenameBranchJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewRenameBranchRequestWithBody(server, projectID, serviceID, branchID, "application/json", bodyReader) +} + +// NewRenameBranchRequestWithBody constructs an http.Request for the RenameBranch method, with any body, and a specified content type +func NewRenameBranchRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -4487,12 +5086,19 @@ func NewEnableDataTieringRequest(server string, projectID ProjectID, serviceID S return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "branch_id", branchID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/enableDataTiering", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/branches/%s/rename", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4502,16 +5108,29 @@ func NewEnableDataTieringRequest(server string, projectID ProjectID, serviceID S return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewEnablePoolerRequest constructs an http.Request for the EnablePooler method -func NewEnablePoolerRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { +// NewResizeBranchRequest calls the generic ResizeBranch builder with application/json body +func NewResizeBranchRequest(server string, projectID ProjectID, serviceID ServiceID, branchID BranchID, body ResizeBranchJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewResizeBranchRequestWithBody(server, projectID, serviceID, branchID, "application/json", bodyReader) +} + +// NewResizeBranchRequestWithBody constructs an http.Request for the ResizeBranch method, with any body, and a specified content type +func NewResizeBranchRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -4528,12 +5147,19 @@ func NewEnablePoolerRequest(server string, projectID ProjectID, serviceID Servic return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "branch_id", branchID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/enablePooler", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/branches/%s/resize", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4543,27 +5169,18 @@ func NewEnablePoolerRequest(server string, projectID ProjectID, serviceID Servic return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) if err != nil { return nil, err } - return req, nil -} + req.Header.Add("Content-Type", contentType) -// NewForkServiceRequest calls the generic ForkService builder with application/json body -func NewForkServiceRequest(server string, projectID ProjectID, serviceID ServiceID, body ForkServiceJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewForkServiceRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) + return req, nil } -// NewForkServiceRequestWithBody constructs an http.Request for the ForkService method, with any body, and a specified content type -func NewForkServiceRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { +// NewStartBranchRequest constructs an http.Request for the StartBranch method +func NewStartBranchRequest(server string, projectID ProjectID, serviceID ServiceID, branchID BranchID) (*http.Request, error) { var err error var pathParam0 string @@ -4580,12 +5197,19 @@ func NewForkServiceRequestWithBody(server string, projectID ProjectID, serviceID return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "branch_id", branchID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/forkService", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/branches/%s/start", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4595,18 +5219,16 @@ func NewForkServiceRequestWithBody(server string, projectID ProjectID, serviceID return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewGetServiceLogsRequest constructs an http.Request for the GetServiceLogs method -func NewGetServiceLogsRequest(server string, projectID ProjectID, serviceID ServiceID, params *GetServiceLogsParams) (*http.Request, error) { +// NewStopBranchRequest constructs an http.Request for the StopBranch method +func NewStopBranchRequest(server string, projectID ProjectID, serviceID ServiceID, branchID BranchID) (*http.Request, error) { var err error var pathParam0 string @@ -4623,12 +5245,19 @@ func NewGetServiceLogsRequest(server string, projectID ProjectID, serviceID Serv return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "branch_id", branchID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/logs", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/branches/%s/stop", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4638,94 +5267,7 @@ func NewGetServiceLogsRequest(server string, projectID ProjectID, serviceID Serv return nil, err } - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Node != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "node", *params.Node, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if params.Cursor != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "cursor", *params.Cursor, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if params.Until != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "until", *params.Until, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: "date-time"}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if params.Since != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "since", *params.Since, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: "date-time"}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if params.Search != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "search", *params.Search, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "array", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if params.Severities != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "severities", *params.Severities, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "array", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) if err != nil { return nil, err } @@ -4733,8 +5275,19 @@ func NewGetServiceLogsRequest(server string, projectID ProjectID, serviceID Serv return req, nil } -// NewGetServiceMetricsAvailableSeriesRequest constructs an http.Request for the GetServiceMetricsAvailableSeries method -func NewGetServiceMetricsAvailableSeriesRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { +// NewUpdateBranchPasswordRequest calls the generic UpdateBranchPassword builder with application/json body +func NewUpdateBranchPasswordRequest(server string, projectID ProjectID, serviceID ServiceID, branchID BranchID, body UpdateBranchPasswordJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateBranchPasswordRequestWithBody(server, projectID, serviceID, branchID, "application/json", bodyReader) +} + +// NewUpdateBranchPasswordRequestWithBody constructs an http.Request for the UpdateBranchPassword method, with any body, and a specified content type +func NewUpdateBranchPasswordRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -4751,12 +5304,19 @@ func NewGetServiceMetricsAvailableSeriesRequest(server string, projectID Project return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "branch_id", branchID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/metrics/available-series", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/branches/%s/updatePassword", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4766,16 +5326,29 @@ func NewGetServiceMetricsAvailableSeriesRequest(server string, projectID Project return nil, err } - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewGetServiceMetricDetailsRequest constructs an http.Request for the GetServiceMetricDetails method -func NewGetServiceMetricDetailsRequest(server string, projectID ProjectID, serviceID ServiceID, metricName string) (*http.Request, error) { +// NewDetachServiceFromAllowListRequest calls the generic DetachServiceFromAllowList builder with application/json body +func NewDetachServiceFromAllowListRequest(server string, projectID ProjectID, serviceID ServiceID, body DetachServiceFromAllowListJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewDetachServiceFromAllowListRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) +} + +// NewDetachServiceFromAllowListRequestWithBody constructs an http.Request for the DetachServiceFromAllowList method, with any body, and a specified content type +func NewDetachServiceFromAllowListRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -4792,19 +5365,12 @@ func NewGetServiceMetricDetailsRequest(server string, projectID ProjectID, servi return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithOptions("simple", false, "metric_name", metricName, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/metrics/details/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/projects/%s/services/%s/detachFromAllowList", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4814,27 +5380,29 @@ func NewGetServiceMetricDetailsRequest(server string, projectID ProjectID, servi return nil, err } - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewGetServiceMetricsSeriesRequest calls the generic GetServiceMetricsSeries builder with application/json body -func NewGetServiceMetricsSeriesRequest(server string, projectID ProjectID, serviceID ServiceID, body GetServiceMetricsSeriesJSONRequestBody) (*http.Request, error) { +// NewDetachServiceFromExporterRequest calls the generic DetachServiceFromExporter builder with application/json body +func NewDetachServiceFromExporterRequest(server string, projectID ProjectID, serviceID ServiceID, body DetachServiceFromExporterJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewGetServiceMetricsSeriesRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) + return NewDetachServiceFromExporterRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) } -// NewGetServiceMetricsSeriesRequestWithBody constructs an http.Request for the GetServiceMetricsSeries method, with any body, and a specified content type -func NewGetServiceMetricsSeriesRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { +// NewDetachServiceFromExporterRequestWithBody constructs an http.Request for the DetachServiceFromExporter method, with any body, and a specified content type +func NewDetachServiceFromExporterRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -4856,7 +5424,7 @@ func NewGetServiceMetricsSeriesRequestWithBody(server string, projectID ProjectI return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/metrics/series", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/detachFromExporter", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4876,19 +5444,19 @@ func NewGetServiceMetricsSeriesRequestWithBody(server string, projectID ProjectI return req, nil } -// NewRenameServiceRequest calls the generic RenameService builder with application/json body -func NewRenameServiceRequest(server string, projectID ProjectID, serviceID ServiceID, body RenameServiceJSONRequestBody) (*http.Request, error) { +// NewDetachServiceFromVPCRequest calls the generic DetachServiceFromVPC builder with application/json body +func NewDetachServiceFromVPCRequest(server string, projectID ProjectID, serviceID ServiceID, body DetachServiceFromVPCJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewRenameServiceRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) + return NewDetachServiceFromVPCRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) } -// NewRenameServiceRequestWithBody constructs an http.Request for the RenameService method, with any body, and a specified content type -func NewRenameServiceRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { +// NewDetachServiceFromVPCRequestWithBody constructs an http.Request for the DetachServiceFromVPC method, with any body, and a specified content type +func NewDetachServiceFromVPCRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -4910,7 +5478,7 @@ func NewRenameServiceRequestWithBody(server string, projectID ProjectID, service return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/rename", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/detachFromVPC", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4930,8 +5498,8 @@ func NewRenameServiceRequestWithBody(server string, projectID ProjectID, service return req, nil } -// NewGetReplicaSetsRequest constructs an http.Request for the GetReplicaSets method -func NewGetReplicaSetsRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { +// NewDisablePoolerRequest constructs an http.Request for the DisablePooler method +func NewDisablePoolerRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { var err error var pathParam0 string @@ -4953,7 +5521,7 @@ func NewGetReplicaSetsRequest(server string, projectID ProjectID, serviceID Serv return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/disablePooler", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -4963,7 +5531,7 @@ func NewGetReplicaSetsRequest(server string, projectID ProjectID, serviceID Serv return nil, err } - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) if err != nil { return nil, err } @@ -4971,19 +5539,8 @@ func NewGetReplicaSetsRequest(server string, projectID ProjectID, serviceID Serv return req, nil } -// NewCreateReplicaSetRequest calls the generic CreateReplicaSet builder with application/json body -func NewCreateReplicaSetRequest(server string, projectID ProjectID, serviceID ServiceID, body CreateReplicaSetJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewCreateReplicaSetRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) -} - -// NewCreateReplicaSetRequestWithBody constructs an http.Request for the CreateReplicaSet method, with any body, and a specified content type -func NewCreateReplicaSetRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { +// NewEnableDataTieringRequest constructs an http.Request for the EnableDataTiering method +func NewEnableDataTieringRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { var err error var pathParam0 string @@ -5005,7 +5562,7 @@ func NewCreateReplicaSetRequestWithBody(server string, projectID ProjectID, serv return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/enableDataTiering", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5015,18 +5572,16 @@ func NewCreateReplicaSetRequestWithBody(server string, projectID ProjectID, serv return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewDeleteReplicaSetRequest constructs an http.Request for the DeleteReplicaSet method -func NewDeleteReplicaSetRequest(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID) (*http.Request, error) { +// NewEnablePoolerRequest constructs an http.Request for the EnablePooler method +func NewEnablePoolerRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { var err error var pathParam0 string @@ -5043,19 +5598,12 @@ func NewDeleteReplicaSetRequest(server string, projectID ProjectID, serviceID Se return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithOptions("simple", false, "replica_set_id", replicaSetID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/projects/%s/services/%s/enablePooler", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5065,7 +5613,7 @@ func NewDeleteReplicaSetRequest(server string, projectID ProjectID, serviceID Se return nil, err } - req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) if err != nil { return nil, err } @@ -5073,8 +5621,19 @@ func NewDeleteReplicaSetRequest(server string, projectID ProjectID, serviceID Se return req, nil } -// NewDisableReplicaPoolerRequest constructs an http.Request for the DisableReplicaPooler method -func NewDisableReplicaPoolerRequest(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID) (*http.Request, error) { +// NewForkServiceRequest calls the generic ForkService builder with application/json body +func NewForkServiceRequest(server string, projectID ProjectID, serviceID ServiceID, body ForkServiceJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewForkServiceRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) +} + +// NewForkServiceRequestWithBody constructs an http.Request for the ForkService method, with any body, and a specified content type +func NewForkServiceRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -5091,19 +5650,12 @@ func NewDisableReplicaPoolerRequest(server string, projectID ProjectID, serviceI return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithOptions("simple", false, "replica_set_id", replicaSetID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets/%s/disablePooler", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/projects/%s/services/%s/forkService", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5113,16 +5665,18 @@ func NewDisableReplicaPoolerRequest(server string, projectID ProjectID, serviceI return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewEnableReplicaPoolerRequest constructs an http.Request for the EnableReplicaPooler method -func NewEnableReplicaPoolerRequest(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID) (*http.Request, error) { +// NewGetServiceLogsRequest constructs an http.Request for the GetServiceLogs method +func NewGetServiceLogsRequest(server string, projectID ProjectID, serviceID ServiceID, params *GetServiceLogsParams) (*http.Request, error) { var err error var pathParam0 string @@ -5139,19 +5693,12 @@ func NewEnableReplicaPoolerRequest(server string, projectID ProjectID, serviceID return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithOptions("simple", false, "replica_set_id", replicaSetID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets/%s/enablePooler", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/projects/%s/services/%s/logs", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5161,27 +5708,103 @@ func NewEnableReplicaPoolerRequest(server string, projectID ProjectID, serviceID return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) - if err != nil { - return nil, err - } + if params != nil { + // queryValues collects non-styled parameters (passthrough, JSON) + // that are safe to round-trip through url.Values.Encode(). + queryValues := queryURL.Query() + // rawQueryFragments collects pre-encoded query fragments from + // styled parameters, preserving literal commas as delimiters + // per the OpenAPI spec (e.g. "color=blue,black,brown"). + var rawQueryFragments []string - return req, nil -} + if params.Node != nil { -// NewResizeReplicaSetRequest calls the generic ResizeReplicaSet builder with application/json body -func NewResizeReplicaSetRequest(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body ResizeReplicaSetJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "node", *params.Node, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.Cursor != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "cursor", *params.Cursor, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.Until != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "until", *params.Until, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: "date-time"}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.Since != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "since", *params.Since, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: "date-time"}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.Search != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "search", *params.Search, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "array", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.Severities != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "severities", *params.Severities, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "array", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if encoded := queryValues.Encode(); encoded != "" { + rawQueryFragments = append(rawQueryFragments, encoded) + } + queryURL.RawQuery = strings.Join(rawQueryFragments, "&") + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) if err != nil { return nil, err } - bodyReader = bytes.NewReader(buf) - return NewResizeReplicaSetRequestWithBody(server, projectID, serviceID, replicaSetID, "application/json", bodyReader) + + return req, nil } -// NewResizeReplicaSetRequestWithBody constructs an http.Request for the ResizeReplicaSet method, with any body, and a specified content type -func NewResizeReplicaSetRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader) (*http.Request, error) { +// NewGetServiceMetricsAvailableSeriesRequest constructs an http.Request for the GetServiceMetricsAvailableSeries method +func NewGetServiceMetricsAvailableSeriesRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { var err error var pathParam0 string @@ -5198,19 +5821,12 @@ func NewResizeReplicaSetRequestWithBody(server string, projectID ProjectID, serv return nil, err } - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithOptions("simple", false, "replica_set_id", replicaSetID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets/%s/resize", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/projects/%s/services/%s/metrics/available-series", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5220,29 +5836,16 @@ func NewResizeReplicaSetRequestWithBody(server string, projectID ProjectID, serv return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewSetReplicaEnvironmentRequest calls the generic SetReplicaEnvironment builder with application/json body -func NewSetReplicaEnvironmentRequest(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body SetReplicaEnvironmentJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewSetReplicaEnvironmentRequestWithBody(server, projectID, serviceID, replicaSetID, "application/json", bodyReader) -} - -// NewSetReplicaEnvironmentRequestWithBody constructs an http.Request for the SetReplicaEnvironment method, with any body, and a specified content type -func NewSetReplicaEnvironmentRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader) (*http.Request, error) { +// NewGetServiceMetricDetailsRequest constructs an http.Request for the GetServiceMetricDetails method +func NewGetServiceMetricDetailsRequest(server string, projectID ProjectID, serviceID ServiceID, metricName string) (*http.Request, error) { var err error var pathParam0 string @@ -5261,7 +5864,7 @@ func NewSetReplicaEnvironmentRequestWithBody(server string, projectID ProjectID, var pathParam2 string - pathParam2, err = runtime.StyleParamWithOptions("simple", false, "replica_set_id", replicaSetID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "metric_name", metricName, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } @@ -5271,7 +5874,7 @@ func NewSetReplicaEnvironmentRequestWithBody(server string, projectID ProjectID, return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets/%s/setEnvironment", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/projects/%s/services/%s/metrics/details/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5281,29 +5884,27 @@ func NewSetReplicaEnvironmentRequestWithBody(server string, projectID ProjectID, return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewResizeServiceRequest calls the generic ResizeService builder with application/json body -func NewResizeServiceRequest(server string, projectID ProjectID, serviceID ServiceID, body ResizeServiceJSONRequestBody) (*http.Request, error) { +// NewGetServiceMetricsSeriesRequest calls the generic GetServiceMetricsSeries builder with application/json body +func NewGetServiceMetricsSeriesRequest(server string, projectID ProjectID, serviceID ServiceID, body GetServiceMetricsSeriesJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewResizeServiceRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) + return NewGetServiceMetricsSeriesRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) } -// NewResizeServiceRequestWithBody constructs an http.Request for the ResizeService method, with any body, and a specified content type -func NewResizeServiceRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { +// NewGetServiceMetricsSeriesRequestWithBody constructs an http.Request for the GetServiceMetricsSeries method, with any body, and a specified content type +func NewGetServiceMetricsSeriesRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -5325,7 +5926,7 @@ func NewResizeServiceRequestWithBody(server string, projectID ProjectID, service return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/resize", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/metrics/series", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5345,19 +5946,19 @@ func NewResizeServiceRequestWithBody(server string, projectID ProjectID, service return req, nil } -// NewSetEnvironmentRequest calls the generic SetEnvironment builder with application/json body -func NewSetEnvironmentRequest(server string, projectID ProjectID, serviceID ServiceID, body SetEnvironmentJSONRequestBody) (*http.Request, error) { +// NewRenameServiceRequest calls the generic RenameService builder with application/json body +func NewRenameServiceRequest(server string, projectID ProjectID, serviceID ServiceID, body RenameServiceJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewSetEnvironmentRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) + return NewRenameServiceRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) } -// NewSetEnvironmentRequestWithBody constructs an http.Request for the SetEnvironment method, with any body, and a specified content type -func NewSetEnvironmentRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { +// NewRenameServiceRequestWithBody constructs an http.Request for the RenameService method, with any body, and a specified content type +func NewRenameServiceRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -5379,7 +5980,7 @@ func NewSetEnvironmentRequestWithBody(server string, projectID ProjectID, servic return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/setEnvironment", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/rename", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5399,19 +6000,8 @@ func NewSetEnvironmentRequestWithBody(server string, projectID ProjectID, servic return req, nil } -// NewSetHAReplicaRequest calls the generic SetHAReplica builder with application/json body -func NewSetHAReplicaRequest(server string, projectID ProjectID, serviceID ServiceID, body SetHAReplicaJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewSetHAReplicaRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) -} - -// NewSetHAReplicaRequestWithBody constructs an http.Request for the SetHAReplica method, with any body, and a specified content type -func NewSetHAReplicaRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { +// NewGetReplicaSetsRequest constructs an http.Request for the GetReplicaSets method +func NewGetReplicaSetsRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { var err error var pathParam0 string @@ -5433,7 +6023,7 @@ func NewSetHAReplicaRequestWithBody(server string, projectID ProjectID, serviceI return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/setHA", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5443,18 +6033,27 @@ func NewSetHAReplicaRequestWithBody(server string, projectID ProjectID, serviceI return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewStartServiceRequest constructs an http.Request for the StartService method -func NewStartServiceRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { +// NewCreateReplicaSetRequest calls the generic CreateReplicaSet builder with application/json body +func NewCreateReplicaSetRequest(server string, projectID ProjectID, serviceID ServiceID, body CreateReplicaSetJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateReplicaSetRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) +} + +// NewCreateReplicaSetRequestWithBody constructs an http.Request for the CreateReplicaSet method, with any body, and a specified content type +func NewCreateReplicaSetRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -5476,7 +6075,7 @@ func NewStartServiceRequest(server string, projectID ProjectID, serviceID Servic return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/start", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5486,16 +6085,18 @@ func NewStartServiceRequest(server string, projectID ProjectID, serviceID Servic return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewStopServiceRequest constructs an http.Request for the StopService method -func NewStopServiceRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { +// NewDeleteReplicaSetRequest constructs an http.Request for the DeleteReplicaSet method +func NewDeleteReplicaSetRequest(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID) (*http.Request, error) { var err error var pathParam0 string @@ -5512,12 +6113,19 @@ func NewStopServiceRequest(server string, projectID ProjectID, serviceID Service return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "replica_set_id", replicaSetID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/stop", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets/%s", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5527,7 +6135,7 @@ func NewStopServiceRequest(server string, projectID ProjectID, serviceID Service return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) if err != nil { return nil, err } @@ -5535,19 +6143,8 @@ func NewStopServiceRequest(server string, projectID ProjectID, serviceID Service return req, nil } -// NewUpdatePasswordRequest calls the generic UpdatePassword builder with application/json body -func NewUpdatePasswordRequest(server string, projectID ProjectID, serviceID ServiceID, body UpdatePasswordJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewUpdatePasswordRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) -} - -// NewUpdatePasswordRequestWithBody constructs an http.Request for the UpdatePassword method, with any body, and a specified content type -func NewUpdatePasswordRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { +// NewDisableReplicaPoolerRequest constructs an http.Request for the DisableReplicaPooler method +func NewDisableReplicaPoolerRequest(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID) (*http.Request, error) { var err error var pathParam0 string @@ -5564,12 +6161,19 @@ func NewUpdatePasswordRequestWithBody(server string, projectID ProjectID, servic return nil, err } + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "replica_set_id", replicaSetID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/services/%s/updatePassword", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets/%s/disablePooler", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5579,18 +6183,16 @@ func NewUpdatePasswordRequestWithBody(server string, projectID ProjectID, servic return nil, err } - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewGetVPCsRequest constructs an http.Request for the GetVPCs method -func NewGetVPCsRequest(server string, projectID ProjectID) (*http.Request, error) { +// NewEnableReplicaPoolerRequest constructs an http.Request for the EnableReplicaPooler method +func NewEnableReplicaPoolerRequest(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID) (*http.Request, error) { var err error var pathParam0 string @@ -5600,42 +6202,56 @@ func NewGetVPCsRequest(server string, projectID ProjectID) (*http.Request, error return nil, err } - serverURL, err := url.Parse(server) + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "service_id", serviceID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/vpcs", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } + var pathParam2 string - queryURL, err := serverURL.Parse(operationPath) + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "replica_set_id", replicaSetID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + serverURL, err := url.Parse(server) if err != nil { return nil, err } - return req, nil -} + operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets/%s/enablePooler", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } -// NewCreateVPCRequest calls the generic CreateVPC builder with application/json body -func NewCreateVPCRequest(server string, projectID ProjectID, body CreateVPCJSONRequestBody) (*http.Request, error) { + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewResizeReplicaSetRequest calls the generic ResizeReplicaSet builder with application/json body +func NewResizeReplicaSetRequest(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body ResizeReplicaSetJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewCreateVPCRequestWithBody(server, projectID, "application/json", bodyReader) + return NewResizeReplicaSetRequestWithBody(server, projectID, serviceID, replicaSetID, "application/json", bodyReader) } -// NewCreateVPCRequestWithBody constructs an http.Request for the CreateVPC method, with any body, and a specified content type -func NewCreateVPCRequestWithBody(server string, projectID ProjectID, contentType string, body io.Reader) (*http.Request, error) { +// NewResizeReplicaSetRequestWithBody constructs an http.Request for the ResizeReplicaSet method, with any body, and a specified content type +func NewResizeReplicaSetRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -5645,12 +6261,26 @@ func NewCreateVPCRequestWithBody(server string, projectID ProjectID, contentType return nil, err } + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "service_id", serviceID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "replica_set_id", replicaSetID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/projects/%s/vpcs", pathParam0) + operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets/%s/resize", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5670,8 +6300,19 @@ func NewCreateVPCRequestWithBody(server string, projectID ProjectID, contentType return req, nil } -// NewDeleteVPCRequest constructs an http.Request for the DeleteVPC method -func NewDeleteVPCRequest(server string, projectID ProjectID, vpcID VPCId) (*http.Request, error) { +// NewSetReplicaEnvironmentRequest calls the generic SetReplicaEnvironment builder with application/json body +func NewSetReplicaEnvironmentRequest(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body SetReplicaEnvironmentJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewSetReplicaEnvironmentRequestWithBody(server, projectID, serviceID, replicaSetID, "application/json", bodyReader) +} + +// NewSetReplicaEnvironmentRequestWithBody constructs an http.Request for the SetReplicaEnvironment method, with any body, and a specified content type +func NewSetReplicaEnvironmentRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -5683,7 +6324,14 @@ func NewDeleteVPCRequest(server string, projectID ProjectID, vpcID VPCId) (*http var pathParam1 string - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "service_id", serviceID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "replica_set_id", replicaSetID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } @@ -5693,7 +6341,7 @@ func NewDeleteVPCRequest(server string, projectID ProjectID, vpcID VPCId) (*http return nil, err } - operationPath := fmt.Sprintf("/projects/%s/vpcs/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/replicaSets/%s/setEnvironment", pathParam0, pathParam1, pathParam2) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5703,16 +6351,29 @@ func NewDeleteVPCRequest(server string, projectID ProjectID, vpcID VPCId) (*http return nil, err } - req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewGetVPCRequest constructs an http.Request for the GetVPC method -func NewGetVPCRequest(server string, projectID ProjectID, vpcID VPCId) (*http.Request, error) { +// NewResizeServiceRequest calls the generic ResizeService builder with application/json body +func NewResizeServiceRequest(server string, projectID ProjectID, serviceID ServiceID, body ResizeServiceJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewResizeServiceRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) +} + +// NewResizeServiceRequestWithBody constructs an http.Request for the ResizeService method, with any body, and a specified content type +func NewResizeServiceRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -5724,7 +6385,7 @@ func NewGetVPCRequest(server string, projectID ProjectID, vpcID VPCId) (*http.Re var pathParam1 string - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "service_id", serviceID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } @@ -5734,7 +6395,7 @@ func NewGetVPCRequest(server string, projectID ProjectID, vpcID VPCId) (*http.Re return nil, err } - operationPath := fmt.Sprintf("/projects/%s/vpcs/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/resize", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5744,16 +6405,29 @@ func NewGetVPCRequest(server string, projectID ProjectID, vpcID VPCId) (*http.Re return nil, err } - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewGetVPCPeeringsRequest constructs an http.Request for the GetVPCPeerings method -func NewGetVPCPeeringsRequest(server string, projectID ProjectID, vpcID VPCId) (*http.Request, error) { +// NewSetEnvironmentRequest calls the generic SetEnvironment builder with application/json body +func NewSetEnvironmentRequest(server string, projectID ProjectID, serviceID ServiceID, body SetEnvironmentJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewSetEnvironmentRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) +} + +// NewSetEnvironmentRequestWithBody constructs an http.Request for the SetEnvironment method, with any body, and a specified content type +func NewSetEnvironmentRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -5765,7 +6439,7 @@ func NewGetVPCPeeringsRequest(server string, projectID ProjectID, vpcID VPCId) ( var pathParam1 string - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "service_id", serviceID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } @@ -5775,7 +6449,7 @@ func NewGetVPCPeeringsRequest(server string, projectID ProjectID, vpcID VPCId) ( return nil, err } - operationPath := fmt.Sprintf("/projects/%s/vpcs/%s/peerings", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/setEnvironment", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5785,27 +6459,29 @@ func NewGetVPCPeeringsRequest(server string, projectID ProjectID, vpcID VPCId) ( return nil, err } - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) if err != nil { return nil, err } + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewCreateVPCPeeringRequest calls the generic CreateVPCPeering builder with application/json body -func NewCreateVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, body CreateVPCPeeringJSONRequestBody) (*http.Request, error) { +// NewSetHAReplicaRequest calls the generic SetHAReplica builder with application/json body +func NewSetHAReplicaRequest(server string, projectID ProjectID, serviceID ServiceID, body SetHAReplicaJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewCreateVPCPeeringRequestWithBody(server, projectID, vpcID, "application/json", bodyReader) + return NewSetHAReplicaRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) } -// NewCreateVPCPeeringRequestWithBody constructs an http.Request for the CreateVPCPeering method, with any body, and a specified content type -func NewCreateVPCPeeringRequestWithBody(server string, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader) (*http.Request, error) { +// NewSetHAReplicaRequestWithBody constructs an http.Request for the SetHAReplica method, with any body, and a specified content type +func NewSetHAReplicaRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -5817,7 +6493,7 @@ func NewCreateVPCPeeringRequestWithBody(server string, projectID ProjectID, vpcI var pathParam1 string - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "service_id", serviceID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } @@ -5827,7 +6503,7 @@ func NewCreateVPCPeeringRequestWithBody(server string, projectID ProjectID, vpcI return nil, err } - operationPath := fmt.Sprintf("/projects/%s/vpcs/%s/peerings", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/setHA", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5847,8 +6523,8 @@ func NewCreateVPCPeeringRequestWithBody(server string, projectID ProjectID, vpcI return req, nil } -// NewDeleteVPCPeeringRequest constructs an http.Request for the DeleteVPCPeering method -func NewDeleteVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, peeringID PeeringID) (*http.Request, error) { +// NewStartServiceRequest constructs an http.Request for the StartService method +func NewStartServiceRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { var err error var pathParam0 string @@ -5860,14 +6536,7 @@ func NewDeleteVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, var pathParam1 string - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithOptions("simple", false, "peering_id", peeringID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "service_id", serviceID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } @@ -5877,7 +6546,7 @@ func NewDeleteVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, return nil, err } - operationPath := fmt.Sprintf("/projects/%s/vpcs/%s/peerings/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/projects/%s/services/%s/start", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5887,7 +6556,7 @@ func NewDeleteVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, return nil, err } - req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) if err != nil { return nil, err } @@ -5895,8 +6564,8 @@ func NewDeleteVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, return req, nil } -// NewGetVPCPeeringRequest constructs an http.Request for the GetVPCPeering method -func NewGetVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, peeringID PeeringID) (*http.Request, error) { +// NewStopServiceRequest constructs an http.Request for the StopService method +func NewStopServiceRequest(server string, projectID ProjectID, serviceID ServiceID) (*http.Request, error) { var err error var pathParam0 string @@ -5908,14 +6577,7 @@ func NewGetVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, pe var pathParam1 string - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - var pathParam2 string - - pathParam2, err = runtime.StyleParamWithOptions("simple", false, "peering_id", peeringID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "service_id", serviceID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } @@ -5925,7 +6587,7 @@ func NewGetVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, pe return nil, err } - operationPath := fmt.Sprintf("/projects/%s/vpcs/%s/peerings/%s", pathParam0, pathParam1, pathParam2) + operationPath := fmt.Sprintf("/projects/%s/services/%s/stop", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5935,7 +6597,7 @@ func NewGetVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, pe return nil, err } - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) if err != nil { return nil, err } @@ -5943,19 +6605,19 @@ func NewGetVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, pe return req, nil } -// NewRenameVPCRequest calls the generic RenameVPC builder with application/json body -func NewRenameVPCRequest(server string, projectID ProjectID, vpcID VPCId, body RenameVPCJSONRequestBody) (*http.Request, error) { +// NewUpdatePasswordRequest calls the generic UpdatePassword builder with application/json body +func NewUpdatePasswordRequest(server string, projectID ProjectID, serviceID ServiceID, body UpdatePasswordJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewRenameVPCRequestWithBody(server, projectID, vpcID, "application/json", bodyReader) + return NewUpdatePasswordRequestWithBody(server, projectID, serviceID, "application/json", bodyReader) } -// NewRenameVPCRequestWithBody constructs an http.Request for the RenameVPC method, with any body, and a specified content type -func NewRenameVPCRequestWithBody(server string, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader) (*http.Request, error) { +// NewUpdatePasswordRequestWithBody constructs an http.Request for the UpdatePassword method, with any body, and a specified content type +func NewUpdatePasswordRequestWithBody(server string, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -5967,7 +6629,7 @@ func NewRenameVPCRequestWithBody(server string, projectID ProjectID, vpcID VPCId var pathParam1 string - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "service_id", serviceID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) if err != nil { return nil, err } @@ -5977,7 +6639,7 @@ func NewRenameVPCRequestWithBody(server string, projectID ProjectID, vpcID VPCId return nil, err } - operationPath := fmt.Sprintf("/projects/%s/vpcs/%s/rename", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/projects/%s/services/%s/updatePassword", pathParam0, pathParam1) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -5997,1126 +6659,2167 @@ func NewRenameVPCRequestWithBody(server string, projectID ProjectID, vpcID VPCId return req, nil } -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { - return err - } +// NewGetVPCsRequest constructs an http.Request for the GetVPCs method +func NewGetVPCsRequest(server string, projectID ProjectID) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "project_id", projectID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { - return err - } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } - return nil -} -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} + operationPath := fmt.Sprintf("/projects/%s/vpcs", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) + queryURL, err := serverURL.Parse(operationPath) if err != nil { return nil, err } - return &ClientWithResponses{client}, nil + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil } -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil +// NewCreateVPCRequest calls the generic CreateVPC builder with application/json body +func NewCreateVPCRequest(server string, projectID ProjectID, body CreateVPCJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err } + bodyReader = bytes.NewReader(buf) + return NewCreateVPCRequestWithBody(server, projectID, "application/json", bodyReader) } -// ClientWithResponsesInterface is the interface specification for the client with responses above. -type ClientWithResponsesInterface interface { +// NewCreateVPCRequestWithBody constructs an http.Request for the CreateVPC method, with any body, and a specified content type +func NewCreateVPCRequestWithBody(server string, projectID ProjectID, contentType string, body io.Reader) (*http.Request, error) { + var err error - // IdentifyUserWithBodyWithResponse Identify a user - // - // Identifies a user with optional properties for analytics tracking. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /analytics/identify (the `IdentifyUser` operationId). - IdentifyUserWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*IdentifyUserResponse, error) + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "project_id", projectID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/projects/%s/vpcs", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteVPCRequest constructs an http.Request for the DeleteVPC method +func NewDeleteVPCRequest(server string, projectID ProjectID, vpcID VPCId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "project_id", projectID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/projects/%s/vpcs/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetVPCRequest constructs an http.Request for the GetVPC method +func NewGetVPCRequest(server string, projectID ProjectID, vpcID VPCId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "project_id", projectID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/projects/%s/vpcs/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetVPCPeeringsRequest constructs an http.Request for the GetVPCPeerings method +func NewGetVPCPeeringsRequest(server string, projectID ProjectID, vpcID VPCId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "project_id", projectID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/projects/%s/vpcs/%s/peerings", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateVPCPeeringRequest calls the generic CreateVPCPeering builder with application/json body +func NewCreateVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, body CreateVPCPeeringJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateVPCPeeringRequestWithBody(server, projectID, vpcID, "application/json", bodyReader) +} + +// NewCreateVPCPeeringRequestWithBody constructs an http.Request for the CreateVPCPeering method, with any body, and a specified content type +func NewCreateVPCPeeringRequestWithBody(server string, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "project_id", projectID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/projects/%s/vpcs/%s/peerings", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteVPCPeeringRequest constructs an http.Request for the DeleteVPCPeering method +func NewDeleteVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, peeringID PeeringID) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "project_id", projectID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "peering_id", peeringID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/projects/%s/vpcs/%s/peerings/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetVPCPeeringRequest constructs an http.Request for the GetVPCPeering method +func NewGetVPCPeeringRequest(server string, projectID ProjectID, vpcID VPCId, peeringID PeeringID) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "project_id", projectID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithOptions("simple", false, "peering_id", peeringID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/projects/%s/vpcs/%s/peerings/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewRenameVPCRequest calls the generic RenameVPC builder with application/json body +func NewRenameVPCRequest(server string, projectID ProjectID, vpcID VPCId, body RenameVPCJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewRenameVPCRequestWithBody(server, projectID, vpcID, "application/json", bodyReader) +} + +// NewRenameVPCRequestWithBody constructs an http.Request for the RenameVPC method, with any body, and a specified content type +func NewRenameVPCRequestWithBody(server string, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "project_id", projectID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithOptions("simple", false, "vpc_id", vpcID, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/projects/%s/vpcs/%s/rename", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + + // IdentifyUserWithBodyWithResponse Identify a user + // + // Identifies a user with optional properties for analytics tracking. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /analytics/identify (the `IdentifyUser` operationId). + IdentifyUserWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*IdentifyUserResponse, error) + + // IdentifyUserWithResponse Identify a user + // + // Identifies a user with optional properties for analytics tracking. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /analytics/identify (the `IdentifyUser` operationId). + IdentifyUserWithResponse(ctx context.Context, body IdentifyUserJSONRequestBody, reqEditors ...RequestEditorFn) (*IdentifyUserResponse, error) + + // TrackEventWithBodyWithResponse Track an analytics event + // + // Tracks an analytics event with optional properties. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /analytics/track (the `TrackEvent` operationId). + TrackEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TrackEventResponse, error) + + // TrackEventWithResponse Track an analytics event + // + // Tracks an analytics event with optional properties. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /analytics/track (the `TrackEvent` operationId). + TrackEventWithResponse(ctx context.Context, body TrackEventJSONRequestBody, reqEditors ...RequestEditorFn) (*TrackEventResponse, error) + + // GetAuthInfoWithResponse Get Authentication Info + // + // Returns the authenticated caller's identity. The response shape depends on the credential type: `apiKey` for PAT + // callers, `oauth` for OAuth user callers. Exactly one of `api_key` or `oauth` is populated; + // `type` discriminates. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /auth/info (the `GetAuthInfo` operationId). + GetAuthInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetAuthInfoResponse, error) + + // LogoutWithBodyWithResponse Log out and revoke refresh token + // + // Revokes the caller's refresh token server-side. For OAuth Bearer + // callers, include the refresh token in the request body — it is held + // client-side and cannot be derived from the access token. Callers + // without a refresh token to revoke may post an + // empty body to clear server-side session state best-effort. + // + // Returns 204 No Content on success. The caller is still responsible + // for dropping any locally-cached credentials. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /auth/logout (the `Logout` operationId). + LogoutWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LogoutResponse, error) + + // LogoutWithResponse Log out and revoke refresh token + // + // Revokes the caller's refresh token server-side. For OAuth Bearer + // callers, include the refresh token in the request body — it is held + // client-side and cannot be derived from the access token. Callers + // without a refresh token to revoke may post an + // empty body to clear server-side session state best-effort. + // + // Returns 204 No Content on success. The caller is still responsible + // for dropping any locally-cached credentials. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /auth/logout (the `Logout` operationId). + LogoutWithResponse(ctx context.Context, body LogoutJSONRequestBody, reqEditors ...RequestEditorFn) (*LogoutResponse, error) + + // SubmitFeedbackWithBodyWithResponse Submit feedback + // + // Sends feedback, a bug report, or a support request to Tiger Data. + // + // The submitter's email address is resolved from the caller's + // credentials, and the client version and operating system from the + // `User-Agent` header, so neither is supplied in the body. + // + // Returns 204 No Content on success. This endpoint opens no support + // case and returns no ticket to track: use it for unsolicited feedback, + // and raise anything needing a tracked response through support. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /feedback (the `SubmitFeedback` operationId). + SubmitFeedbackWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SubmitFeedbackResponse, error) + + // SubmitFeedbackWithResponse Submit feedback + // + // Sends feedback, a bug report, or a support request to Tiger Data. + // + // The submitter's email address is resolved from the caller's + // credentials, and the client version and operating system from the + // `User-Agent` header, so neither is supplied in the body. + // + // Returns 204 No Content on success. This endpoint opens no support + // case and returns no ticket to track: use it for unsolicited feedback, + // and raise anything needing a tracked response through support. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /feedback (the `SubmitFeedback` operationId). + SubmitFeedbackWithResponse(ctx context.Context, body SubmitFeedbackJSONRequestBody, reqEditors ...RequestEditorFn) (*SubmitFeedbackResponse, error) + + // GetProjectsWithResponse List projects accessible to the authenticated caller + // + // Returns the projects the caller can access. PAT callers see only the + // project their PAT is scoped to; OAuth user callers see all projects + // they belong to. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects (the `GetProjects` operationId). + GetProjectsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetProjectsResponse, error) + + // GetAllowListsWithResponse List All IP Allow Lists + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves every IP allow list in a project. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/allow-lists (the `GetAllowLists` operationId). + GetAllowListsWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetAllowListsResponse, error) + + // CreateAllowListWithBodyWithResponse Create an IP Allow List + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Creates an IP allow list in a project. An IP allow list carries no + // effect until it is attached to a service. + // + // The number of CIDR blocks per list and the number of lists per + // project are plan-dependent. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/allow-lists (the `CreateAllowList` operationId). + CreateAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateAllowListResponse, error) + + // CreateAllowListWithResponse Create an IP Allow List + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Creates an IP allow list in a project. An IP allow list carries no + // effect until it is attached to a service. + // + // The number of CIDR blocks per list and the number of lists per + // project are plan-dependent. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/allow-lists (the `CreateAllowList` operationId). + CreateAllowListWithResponse(ctx context.Context, projectID ProjectID, body CreateAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateAllowListResponse, error) + + // DeleteAllowListWithResponse Delete an IP Allow List + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Deletes an IP allow list. Refused if the list is still attached to + // any service. Detach it first. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with DELETE /projects/{project_id}/allow-lists/{allow_list_id} (the `DeleteAllowList` operationId). + DeleteAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, reqEditors ...RequestEditorFn) (*DeleteAllowListResponse, error) + + // GetAllowListWithResponse Get an IP Allow List + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves a single IP allow list. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/allow-lists/{allow_list_id} (the `GetAllowList` operationId). + GetAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, reqEditors ...RequestEditorFn) (*GetAllowListResponse, error) + + // UpdateAllowListWithBodyWithResponse Update an IP Allow List + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Updates an IP allow list in place. Send `description` and/or + // `cidr_blocks` to change them; omit either one to leave it + // unchanged. + // + // Every service the IP allow list is attached to is updated with the + // new CIDR blocks. To remove every CIDR block, delete the IP allow + // list instead of sending an empty `cidr_blocks`. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with PATCH /projects/{project_id}/allow-lists/{allow_list_id} (the `UpdateAllowList` operationId). + UpdateAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateAllowListResponse, error) + + // UpdateAllowListWithResponse Update an IP Allow List + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Updates an IP allow list in place. Send `description` and/or + // `cidr_blocks` to change them; omit either one to leave it + // unchanged. + // + // Every service the IP allow list is attached to is updated with the + // new CIDR blocks. To remove every CIDR block, delete the IP allow + // list instead of sending an empty `cidr_blocks`. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with PATCH /projects/{project_id}/allow-lists/{allow_list_id} (the `UpdateAllowList` operationId). + UpdateAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, body UpdateAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateAllowListResponse, error) + + // GetProjectBranchesWithResponse List All Branches in a Project + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves a list of all branches within a specific project, regardless + // of parent service. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/branches (the `GetProjectBranches` operationId). + GetProjectBranchesWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetProjectBranchesResponse, error) + + // GetExportersWithResponse List All Exporters + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves every exporter in a project, across every region. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/exporters (the `GetExporters` operationId). + GetExportersWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetExportersResponse, error) + + // CreateExporterWithBodyWithResponse Create an Exporter + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Creates an exporter in a project and region. + // + // An exporter carries no data until it is attached to a service. + // + // Exporters aren't available on every plan. If your project's plan + // doesn't include them, this request is refused here rather than later, + // when you try to attach one. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/exporters (the `CreateExporter` operationId). + CreateExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateExporterResponse, error) + + // CreateExporterWithResponse Create an Exporter + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Creates an exporter in a project and region. + // + // An exporter carries no data until it is attached to a service. + // + // Exporters aren't available on every plan. If your project's plan + // doesn't include them, this request is refused here rather than later, + // when you try to attach one. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/exporters (the `CreateExporter` operationId). + CreateExporterWithResponse(ctx context.Context, projectID ProjectID, body CreateExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateExporterResponse, error) + + // DeleteExporterWithResponse Delete an Exporter + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Deletes an exporter and stops the data it was sending. Any service + // still attached is detached automatically. You don't need to detach it + // first, and nothing errors if you don't. + // + // Data already delivered to the destination is untouched, and nothing on + // the services themselves changes. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with DELETE /projects/{project_id}/exporters/{exporter_id} (the `DeleteExporter` operationId). + DeleteExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, reqEditors ...RequestEditorFn) (*DeleteExporterResponse, error) + + // GetExporterWithResponse Get an Exporter + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves a single exporter. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/exporters/{exporter_id} (the `GetExporter` operationId). + GetExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, reqEditors ...RequestEditorFn) (*GetExporterResponse, error) + + // UpdateExporterWithBodyWithResponse Update an Exporter + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Updates an exporter in place. Send `config` to replace the whole + // configuration; omit it to leave the configuration unchanged. + // + // Every service the exporter is attached to picks up the change; there + // is no need to detach and reattach. + // + // `type` must be included and must match the exporter's existing type; + // it cannot be changed. To send data somewhere else, create a new + // exporter and move the services over. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with PATCH /projects/{project_id}/exporters/{exporter_id} (the `UpdateExporter` operationId). + UpdateExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateExporterResponse, error) + + // UpdateExporterWithResponse Update an Exporter + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Updates an exporter in place. Send `config` to replace the whole + // configuration; omit it to leave the configuration unchanged. + // + // Every service the exporter is attached to picks up the change; there + // is no need to detach and reattach. + // + // `type` must be included and must match the exporter's existing type; + // it cannot be changed. To send data somewhere else, create a new + // exporter and move the services over. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with PATCH /projects/{project_id}/exporters/{exporter_id} (the `UpdateExporter` operationId). + UpdateExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, body UpdateExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateExporterResponse, error) + + // ResolveRefWithResponse Resolve a Service, Branch, or Read Replica Set + // + // Resolves `ref` to the service, branch, or read replica set it names. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/resolve (the `ResolveRef` operationId). + ResolveRefWithResponse(ctx context.Context, projectID ProjectID, params *ResolveRefParams, reqEditors ...RequestEditorFn) (*ResolveRefResponse, error) + + // GetServicesWithResponse List All Services + // + // Retrieves a list of all services within a specific project. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/services (the `GetServices` operationId). + GetServicesWithResponse(ctx context.Context, projectID ProjectID, params *GetServicesParams, reqEditors ...RequestEditorFn) (*GetServicesResponse, error) + + // CreateServiceWithBodyWithResponse Create a Service + // + // Creates a new database service within a project. This is an asynchronous operation. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services (the `CreateService` operationId). + CreateServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateServiceResponse, error) + + // CreateServiceWithResponse Create a Service + // + // Creates a new database service within a project. This is an asynchronous operation. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services (the `CreateService` operationId). + CreateServiceWithResponse(ctx context.Context, projectID ProjectID, body CreateServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateServiceResponse, error) + + // DeleteServiceWithResponse Delete a Service + // + // Deletes a specific service. This is an asynchronous operation. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with DELETE /projects/{project_id}/services/{service_id} (the `DeleteService` operationId). + DeleteServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*DeleteServiceResponse, error) - // IdentifyUserWithResponse Identify a user + // GetServiceWithResponse Get a Service // - // Identifies a user with optional properties for analytics tracking. + // Retrieves the details of a specific service by its ID. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/services/{service_id} (the `GetService` operationId). + GetServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetServiceResponse, error) + + // AttachServiceToAllowListWithBodyWithResponse Attach Service to IP Allow List + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Restricts a service's connections to the IP ranges in an IP allow list. + // A service has at most one IP allow list; attaching a second one + // without detaching the first is refused. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToAllowList (the `AttachServiceToAllowList` operationId). + AttachServiceToAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToAllowListResponse, error) + + // AttachServiceToAllowListWithResponse Attach Service to IP Allow List + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Restricts a service's connections to the IP ranges in an IP allow list. + // A service has at most one IP allow list; attaching a second one + // without detaching the first is refused. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /analytics/identify (the `IdentifyUser` operationId). - IdentifyUserWithResponse(ctx context.Context, body IdentifyUserJSONRequestBody, reqEditors ...RequestEditorFn) (*IdentifyUserResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToAllowList (the `AttachServiceToAllowList` operationId). + AttachServiceToAllowListWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToAllowListResponse, error) - // TrackEventWithBodyWithResponse Track an analytics event + // AttachServiceToExporterWithBodyWithResponse Attach Service to Exporter // - // Tracks an analytics event with optional properties. + // **Preview - this endpoint is experimental and may change without notice.** + // + // Starts sending a service's logs or metrics to an exporter. + // + // A service sends to at most one log exporter and one metric exporter at + // a time. Attaching a second exporter of the same kind is refused; + // detach the current one first. + // + // The first time a log exporter is attached to a service, the service + // restarts once. Attaching a different log exporter later does not + // restart anything, and neither does attaching a metric exporter. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /analytics/track (the `TrackEvent` operationId). - TrackEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TrackEventResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToExporter (the `AttachServiceToExporter` operationId). + AttachServiceToExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToExporterResponse, error) - // TrackEventWithResponse Track an analytics event + // AttachServiceToExporterWithResponse Attach Service to Exporter // - // Tracks an analytics event with optional properties. + // **Preview - this endpoint is experimental and may change without notice.** + // + // Starts sending a service's logs or metrics to an exporter. + // + // A service sends to at most one log exporter and one metric exporter at + // a time. Attaching a second exporter of the same kind is refused; + // detach the current one first. + // + // The first time a log exporter is attached to a service, the service + // restarts once. Attaching a different log exporter later does not + // restart anything, and neither does attaching a metric exporter. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /analytics/track (the `TrackEvent` operationId). - TrackEventWithResponse(ctx context.Context, body TrackEventJSONRequestBody, reqEditors ...RequestEditorFn) (*TrackEventResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToExporter (the `AttachServiceToExporter` operationId). + AttachServiceToExporterWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToExporterResponse, error) - // GetAuthInfoWithResponse Get Authentication Info + // AttachServiceToVPCWithBodyWithResponse Attach Service to VPC // - // Returns the authenticated caller's identity. The response shape depends on the credential type: `apiKey` for PAT - // callers, `oauth` for OAuth user callers. Exactly one of `api_key` or `oauth` is populated; - // `type` discriminates. + // Associates a service with a VPC. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToVPC (the `AttachServiceToVPC` operationId). + AttachServiceToVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToVPCResponse, error) + + // AttachServiceToVPCWithResponse Attach Service to VPC + // + // Associates a service with a VPC. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToVPC (the `AttachServiceToVPC` operationId). + AttachServiceToVPCWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToVPCResponse, error) + + // GetBackupRegionsWithResponse List All Backup Regions + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves the additional regions this service's backups are copied to + // (cross-region backups). The region the service runs in is not listed. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with GET /auth/info (the `GetAuthInfo` operationId). - GetAuthInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetAuthInfoResponse, error) + // Corresponds with GET /projects/{project_id}/services/{service_id}/backup-regions (the `GetBackupRegions` operationId). + GetBackupRegionsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupRegionsResponse, error) - // LogoutWithBodyWithResponse Log out and revoke refresh token + // CreateBackupRegionWithBodyWithResponse Add a Backup Region // - // Revokes the caller's refresh token server-side. For OAuth Bearer - // callers, include the refresh token in the request body — it is held - // client-side and cannot be derived from the access token. Callers - // without a refresh token to revoke may post an - // empty body to clear server-side session state best-effort. + // **Preview - this endpoint is experimental and may change without notice.** // - // Returns 204 No Content on success. The caller is still responsible - // for dropping any locally-cached credentials. + // Starts copying this service's backups to another region. The region is + // added immediately; existing backups are copied to it in the background. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /auth/logout (the `Logout` operationId). - LogoutWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LogoutResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/backup-regions (the `CreateBackupRegion` operationId). + CreateBackupRegionWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBackupRegionResponse, error) - // LogoutWithResponse Log out and revoke refresh token + // CreateBackupRegionWithResponse Add a Backup Region // - // Revokes the caller's refresh token server-side. For OAuth Bearer - // callers, include the refresh token in the request body — it is held - // client-side and cannot be derived from the access token. Callers - // without a refresh token to revoke may post an - // empty body to clear server-side session state best-effort. + // **Preview - this endpoint is experimental and may change without notice.** // - // Returns 204 No Content on success. The caller is still responsible - // for dropping any locally-cached credentials. + // Starts copying this service's backups to another region. The region is + // added immediately; existing backups are copied to it in the background. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /auth/logout (the `Logout` operationId). - LogoutWithResponse(ctx context.Context, body LogoutJSONRequestBody, reqEditors ...RequestEditorFn) (*LogoutResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/backup-regions (the `CreateBackupRegion` operationId). + CreateBackupRegionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateBackupRegionJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBackupRegionResponse, error) - // SubmitFeedbackWithBodyWithResponse Submit feedback + // DeleteBackupRegionWithResponse Remove a Backup Region // - // Sends feedback, a bug report, or a support request to Tiger Data. + // **Preview - this endpoint is experimental and may change without notice.** // - // The submitter's email address is resolved from the caller's - // credentials, and the client version and operating system from the - // `User-Agent` header, so neither is supplied in the body. + // Stops copying this service's backups to a region. Copies already stored + // there are deleted in the background and cannot be recovered. // - // Returns 204 No Content on success. This endpoint opens no support - // case and returns no ticket to track: use it for unsolicited feedback, - // and raise anything needing a tracked response through support. + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with DELETE /projects/{project_id}/services/{service_id}/backup-regions/{region_code} (the `DeleteBackupRegion` operationId). + DeleteBackupRegionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, regionCode BackupRegionCode, reqEditors ...RequestEditorFn) (*DeleteBackupRegionResponse, error) + + // GetBackupRetentionWithResponse Get the Backup Retention Policy + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves how long this service keeps its backups. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/services/{service_id}/backup-retention (the `GetBackupRetention` operationId). + GetBackupRetentionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupRetentionResponse, error) + + // SetBackupRetentionWithBodyWithResponse Set the Backup Retention Policy + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Sets how long this service keeps its backups. Shortening retention + // deletes the backups that no longer fit. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with PUT /projects/{project_id}/services/{service_id}/backup-retention (the `SetBackupRetention` operationId). + SetBackupRetentionWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetBackupRetentionResponse, error) + + // SetBackupRetentionWithResponse Set the Backup Retention Policy + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Sets how long this service keeps its backups. Shortening retention + // deletes the backups that no longer fit. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with PUT /projects/{project_id}/services/{service_id}/backup-retention (the `SetBackupRetention` operationId). + SetBackupRetentionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetBackupRetentionJSONRequestBody, reqEditors ...RequestEditorFn) (*SetBackupRetentionResponse, error) + + // GetBackupsWithResponse List All Backups + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves a list of the full and incremental backups taken for a + // service. Backups run automatically on a schedule; there is no operation + // to create or delete one. To restore data, create a recovery fork with + // `fork_strategy: PITR`. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/services/{service_id}/backups (the `GetBackups` operationId). + GetBackupsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupsResponse, error) + + // GetBranchesWithResponse List All Branches + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves a list of all branches created from a service within a project. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/services/{service_id}/branches (the `GetBranches` operationId). + GetBranchesWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, params *GetBranchesParams, reqEditors ...RequestEditorFn) (*GetBranchesResponse, error) + + // CreateBranchWithBodyWithResponse Create a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Creates a new branch from the service identified by `service_id`. A + // branch shares its parent's data at the moment of creation and gets + // its own, independently-sized compute. This is an asynchronous + // operation. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches (the `CreateBranch` operationId). + CreateBranchWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + + // CreateBranchWithResponse Create a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Creates a new branch from the service identified by `service_id`. A + // branch shares its parent's data at the moment of creation and gets + // its own, independently-sized compute. This is an asynchronous + // operation. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches (the `CreateBranch` operationId). + CreateBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) + + // DeleteBranchWithResponse Delete a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Deletes a specific branch. This is an asynchronous operation. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with DELETE /projects/{project_id}/services/{service_id}/branches/{branch_id} (the `DeleteBranch` operationId). + DeleteBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) + + // GetBranchWithResponse Get a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Retrieves the details of a specific branch by its ID. + // + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/services/{service_id}/branches/{branch_id} (the `GetBranch` operationId). + GetBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) + + // RenameBranchWithBodyWithResponse Rename a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Updates the name of a specific branch. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/rename (the `RenameBranch` operationId). + RenameBranchWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RenameBranchResponse, error) + + // RenameBranchWithResponse Rename a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Updates the name of a specific branch. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/rename (the `RenameBranch` operationId). + RenameBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body RenameBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*RenameBranchResponse, error) + + // ResizeBranchWithBodyWithResponse Resize a Branch + // + // **Preview - this endpoint is experimental and may change without notice.** + // + // Changes the compute allocation for a specific branch. + // Storage size is shared with the parent and is not resized through + // this operation. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /feedback (the `SubmitFeedback` operationId). - SubmitFeedbackWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SubmitFeedbackResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/resize (the `ResizeBranch` operationId). + ResizeBranchWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ResizeBranchResponse, error) - // SubmitFeedbackWithResponse Submit feedback + // ResizeBranchWithResponse Resize a Branch // - // Sends feedback, a bug report, or a support request to Tiger Data. - // - // The submitter's email address is resolved from the caller's - // credentials, and the client version and operating system from the - // `User-Agent` header, so neither is supplied in the body. + // **Preview - this endpoint is experimental and may change without notice.** // - // Returns 204 No Content on success. This endpoint opens no support - // case and returns no ticket to track: use it for unsolicited feedback, - // and raise anything needing a tracked response through support. + // Changes the compute allocation for a specific branch. + // Storage size is shared with the parent and is not resized through + // this operation. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /feedback (the `SubmitFeedback` operationId). - SubmitFeedbackWithResponse(ctx context.Context, body SubmitFeedbackJSONRequestBody, reqEditors ...RequestEditorFn) (*SubmitFeedbackResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/resize (the `ResizeBranch` operationId). + ResizeBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body ResizeBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*ResizeBranchResponse, error) - // GetProjectsWithResponse List projects accessible to the authenticated caller + // StartBranchWithResponse Start a Branch // - // Returns the projects the caller can access. PAT callers see only the - // project their PAT is scoped to; OAuth user callers see all projects - // they belong to. + // **Preview - this endpoint is experimental and may change without notice.** + // + // Starts a stopped branch. This is an asynchronous operation. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with GET /projects (the `GetProjects` operationId). - GetProjectsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetProjectsResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/start (the `StartBranch` operationId). + StartBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*StartBranchResponse, error) - // GetAllowListsWithResponse List All IP Allow Lists + // StopBranchWithResponse Stop a Branch // // **Preview - this endpoint is experimental and may change without notice.** // - // Retrieves every IP allow list in a project. + // Stops a running branch. This is an asynchronous operation. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with GET /projects/{project_id}/allow-lists (the `GetAllowLists` operationId). - GetAllowListsWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetAllowListsResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/stop (the `StopBranch` operationId). + StopBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*StopBranchResponse, error) - // CreateAllowListWithBodyWithResponse Create an IP Allow List + // UpdateBranchPasswordWithBodyWithResponse Update Branch Password // // **Preview - this endpoint is experimental and may change without notice.** // - // Creates an IP allow list in a project. An IP allow list carries no - // effect until it is attached to a service. - // - // The number of CIDR blocks per list and the number of lists per - // project are plan-dependent. + // Sets a new master password for the branch. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/allow-lists (the `CreateAllowList` operationId). - CreateAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateAllowListResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/updatePassword (the `UpdateBranchPassword` operationId). + UpdateBranchPasswordWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateBranchPasswordResponse, error) - // CreateAllowListWithResponse Create an IP Allow List + // UpdateBranchPasswordWithResponse Update Branch Password // // **Preview - this endpoint is experimental and may change without notice.** // - // Creates an IP allow list in a project. An IP allow list carries no - // effect until it is attached to a service. - // - // The number of CIDR blocks per list and the number of lists per - // project are plan-dependent. + // Sets a new master password for the branch. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/allow-lists (the `CreateAllowList` operationId). - CreateAllowListWithResponse(ctx context.Context, projectID ProjectID, body CreateAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateAllowListResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/updatePassword (the `UpdateBranchPassword` operationId). + UpdateBranchPasswordWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body UpdateBranchPasswordJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateBranchPasswordResponse, error) - // DeleteAllowListWithResponse Delete an IP Allow List + // DetachServiceFromAllowListWithBodyWithResponse Detach Service from IP Allow List // // **Preview - this endpoint is experimental and may change without notice.** // - // Deletes an IP allow list. Refused if the list is still attached to - // any service. Detach it first. + // Removes the IP restriction an IP allow list placed on a service. The + // IP allow list itself is not deleted. // - // Returns a wrapper object for the known response body format(s). + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with DELETE /projects/{project_id}/allow-lists/{allow_list_id} (the `DeleteAllowList` operationId). - DeleteAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, reqEditors ...RequestEditorFn) (*DeleteAllowListResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). + DetachServiceFromAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromAllowListResponse, error) - // GetAllowListWithResponse Get an IP Allow List + // DetachServiceFromAllowListWithResponse Detach Service from IP Allow List // // **Preview - this endpoint is experimental and may change without notice.** // - // Retrieves a single IP allow list. + // Removes the IP restriction an IP allow list placed on a service. The + // IP allow list itself is not deleted. // - // Returns a wrapper object for the known response body format(s). + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with GET /projects/{project_id}/allow-lists/{allow_list_id} (the `GetAllowList` operationId). - GetAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, reqEditors ...RequestEditorFn) (*GetAllowListResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). + DetachServiceFromAllowListWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromAllowListResponse, error) - // UpdateAllowListWithBodyWithResponse Update an IP Allow List + // DetachServiceFromExporterWithBodyWithResponse Detach Service from Exporter // // **Preview - this endpoint is experimental and may change without notice.** // - // Updates an IP allow list in place. Send `description` and/or - // `cidr_blocks` to change them; omit either one to leave it - // unchanged. + // Stops sending a service's logs or metrics to an exporter. The exporter + // is not deleted and keeps serving any other service attached to it. // - // Every service the IP allow list is attached to is updated with the - // new CIDR blocks. To remove every CIDR block, delete the IP allow - // list instead of sending an empty `cidr_blocks`. + // Data already delivered to the destination is untouched. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with PATCH /projects/{project_id}/allow-lists/{allow_list_id} (the `UpdateAllowList` operationId). - UpdateAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateAllowListResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). + DetachServiceFromExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromExporterResponse, error) - // UpdateAllowListWithResponse Update an IP Allow List + // DetachServiceFromExporterWithResponse Detach Service from Exporter // // **Preview - this endpoint is experimental and may change without notice.** // - // Updates an IP allow list in place. Send `description` and/or - // `cidr_blocks` to change them; omit either one to leave it - // unchanged. + // Stops sending a service's logs or metrics to an exporter. The exporter + // is not deleted and keeps serving any other service attached to it. // - // Every service the IP allow list is attached to is updated with the - // new CIDR blocks. To remove every CIDR block, delete the IP allow - // list instead of sending an empty `cidr_blocks`. + // Data already delivered to the destination is untouched. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with PATCH /projects/{project_id}/allow-lists/{allow_list_id} (the `UpdateAllowList` operationId). - UpdateAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, body UpdateAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateAllowListResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). + DetachServiceFromExporterWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromExporterResponse, error) - // GetExportersWithResponse List All Exporters + // DetachServiceFromVPCWithBodyWithResponse Detach Service from VPC // - // **Preview - this endpoint is experimental and may change without notice.** + // Disassociates a service from its VPC. // - // Retrieves every exporter in a project, across every region. + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). + DetachServiceFromVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromVPCResponse, error) + + // DetachServiceFromVPCWithResponse Detach Service from VPC + // + // Disassociates a service from its VPC. + // + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). + DetachServiceFromVPCWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromVPCResponse, error) + + // DisablePoolerWithResponse Disable Connection Pooler for a Service + // + // Deactivates the connection pooler for a specific service within a project. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with GET /projects/{project_id}/exporters (the `GetExporters` operationId). - GetExportersWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetExportersResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/disablePooler (the `DisablePooler` operationId). + DisablePoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*DisablePoolerResponse, error) - // CreateExporterWithBodyWithResponse Create an Exporter + // EnableDataTieringWithResponse Enable Data Tiering for a Service // - // **Preview - this endpoint is experimental and may change without notice.** + // Enables tiered storage on the service. Tiering policies move older, + // rarely-accessed data from the high-performance storage tier to a + // low-cost object storage tier. Tiered data stays queryable but becomes + // read-only. // - // Creates an exporter in a project and region. + // There is no way to disable tiering through this API. Contact Tiger + // Data support if you need to turn it off. // - // An exporter carries no data until it is attached to a service. + // Returns a wrapper object for the known response body format(s). // - // Exporters aren't available on every plan. If your project's plan - // doesn't include them, this request is refused here rather than later, - // when you try to attach one. + // Corresponds with POST /projects/{project_id}/services/{service_id}/enableDataTiering (the `EnableDataTiering` operationId). + EnableDataTieringWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*EnableDataTieringResponse, error) + + // EnablePoolerWithResponse Enable Connection Pooler for a Service // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // Activates the connection pooler for a specific service within a project. // - // Corresponds with POST /projects/{project_id}/exporters (the `CreateExporter` operationId). - CreateExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateExporterResponse, error) + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/enablePooler (the `EnablePooler` operationId). + EnablePoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*EnablePoolerResponse, error) - // CreateExporterWithResponse Create an Exporter + // ForkServiceWithBodyWithResponse Fork a Service // - // **Preview - this endpoint is experimental and may change without notice.** + // Creates a new, independent service within a project by taking a snapshot of an existing one. // - // Creates an exporter in a project and region. + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // An exporter carries no data until it is attached to a service. + // Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). + ForkServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ForkServiceResponse, error) + + // ForkServiceWithResponse Fork a Service // - // Exporters aren't available on every plan. If your project's plan - // doesn't include them, this request is refused here rather than later, - // when you try to attach one. + // Creates a new, independent service within a project by taking a snapshot of an existing one. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/exporters (the `CreateExporter` operationId). - CreateExporterWithResponse(ctx context.Context, projectID ProjectID, body CreateExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateExporterResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). + ForkServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ForkServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*ForkServiceResponse, error) - // DeleteExporterWithResponse Delete an Exporter - // - // **Preview - this endpoint is experimental and may change without notice.** + // GetServiceLogsWithResponse Get service logs // - // Deletes an exporter and stops the data it was sending. Any service - // still attached is detached automatically. You don't need to detach it - // first, and nothing errors if you don't. + // Fetch logs for a specific service. Returns up to 500 log entries in reverse + // chronological order. Supports cursor-based pagination via the `cursor` parameter + // and the `last_cursor` field in the response. // - // Data already delivered to the destination is untouched, and nothing on - // the services themselves changes. + // Use the returned `last_cursor` value as the `cursor` parameter on subsequent + // requests to page through all available logs. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with DELETE /projects/{project_id}/exporters/{exporter_id} (the `DeleteExporter` operationId). - DeleteExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, reqEditors ...RequestEditorFn) (*DeleteExporterResponse, error) + // Corresponds with GET /projects/{project_id}/services/{service_id}/logs (the `GetServiceLogs` operationId). + GetServiceLogsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, params *GetServiceLogsParams, reqEditors ...RequestEditorFn) (*GetServiceLogsResponse, error) - // GetExporterWithResponse Get an Exporter - // - // **Preview - this endpoint is experimental and may change without notice.** + // GetServiceMetricsAvailableSeriesWithResponse List available metric series // - // Retrieves a single exporter. + // Returns the names of all metric series available for a service. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with GET /projects/{project_id}/exporters/{exporter_id} (the `GetExporter` operationId). - GetExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, reqEditors ...RequestEditorFn) (*GetExporterResponse, error) + // Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/available-series (the `GetServiceMetricsAvailableSeries` operationId). + GetServiceMetricsAvailableSeriesWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetServiceMetricsAvailableSeriesResponse, error) - // UpdateExporterWithBodyWithResponse Update an Exporter + // GetServiceMetricDetailsWithResponse Get details about a metric // - // **Preview - this endpoint is experimental and may change without notice.** + // Returns descriptive metadata for a named metric: what it measures, + // its type, default aggregation, and every label it can be filtered or + // grouped by — its own labels plus the region/role/ordinal labels most + // metrics also carry. Use getServiceMetricsAvailableSeries to discover + // valid metric names. // - // Updates an exporter in place. Send `config` to replace the whole - // configuration; omit it to leave the configuration unchanged. + // Returns a wrapper object for the known response body format(s). // - // Every service the exporter is attached to picks up the change; there - // is no need to detach and reattach. + // Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/details/{metric_name} (the `GetServiceMetricDetails` operationId). + GetServiceMetricDetailsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, metricName string, reqEditors ...RequestEditorFn) (*GetServiceMetricDetailsResponse, error) + + // GetServiceMetricsSeriesWithBodyWithResponse Get a metric series // - // `type` must be included and must match the exporter's existing type; - // it cannot be changed. To send data somewhere else, create a new - // exporter and move the services over. + // Returns time-series data points for a named metric within a time window. + // Use getServiceMetricsAvailableSeries to discover valid metric names. + // + // Parameters are sent as a JSON body so callers can pass an unbounded + // filter set without query-string length pressure. The response is a list + // of MetricSeries, each carrying its label set as a structured map + // alongside its per-bucket data points. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with PATCH /projects/{project_id}/exporters/{exporter_id} (the `UpdateExporter` operationId). - UpdateExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateExporterResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). + GetServiceMetricsSeriesWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*GetServiceMetricsSeriesResponse, error) - // UpdateExporterWithResponse Update an Exporter + // GetServiceMetricsSeriesWithResponse Get a metric series // - // **Preview - this endpoint is experimental and may change without notice.** + // Returns time-series data points for a named metric within a time window. + // Use getServiceMetricsAvailableSeries to discover valid metric names. // - // Updates an exporter in place. Send `config` to replace the whole - // configuration; omit it to leave the configuration unchanged. + // Parameters are sent as a JSON body so callers can pass an unbounded + // filter set without query-string length pressure. The response is a list + // of MetricSeries, each carrying its label set as a structured map + // alongside its per-bucket data points. // - // Every service the exporter is attached to picks up the change; there - // is no need to detach and reattach. + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // `type` must be included and must match the exporter's existing type; - // it cannot be changed. To send data somewhere else, create a new - // exporter and move the services over. + // Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). + GetServiceMetricsSeriesWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body GetServiceMetricsSeriesJSONRequestBody, reqEditors ...RequestEditorFn) (*GetServiceMetricsSeriesResponse, error) + + // RenameServiceWithBodyWithResponse Rename a Service + // + // Updates the name of a specific service. + // + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). + RenameServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RenameServiceResponse, error) + + // RenameServiceWithResponse Rename a Service + // + // Updates the name of a specific service. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with PATCH /projects/{project_id}/exporters/{exporter_id} (the `UpdateExporter` operationId). - UpdateExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, body UpdateExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateExporterResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). + RenameServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body RenameServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*RenameServiceResponse, error) - // GetServicesWithResponse List All Services + // GetReplicaSetsWithResponse Get Read Replica Sets // - // Retrieves a list of all services within a specific project. + // Retrieves a list of all read replica sets associated with a primary service within a project. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with GET /projects/{project_id}/services (the `GetServices` operationId). - GetServicesWithResponse(ctx context.Context, projectID ProjectID, params *GetServicesParams, reqEditors ...RequestEditorFn) (*GetServicesResponse, error) + // Corresponds with GET /projects/{project_id}/services/{service_id}/replicaSets (the `GetReplicaSets` operationId). + GetReplicaSetsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetReplicaSetsResponse, error) - // CreateServiceWithBodyWithResponse Create a Service + // CreateReplicaSetWithBodyWithResponse Create a Read Replica Set // - // Creates a new database service within a project. This is an asynchronous operation. + // Creates a new read replica set for a service. This is an asynchronous operation. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services (the `CreateService` operationId). - CreateServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateServiceResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). + CreateReplicaSetWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateReplicaSetResponse, error) - // CreateServiceWithResponse Create a Service + // CreateReplicaSetWithResponse Create a Read Replica Set // - // Creates a new database service within a project. This is an asynchronous operation. + // Creates a new read replica set for a service. This is an asynchronous operation. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services (the `CreateService` operationId). - CreateServiceWithResponse(ctx context.Context, projectID ProjectID, body CreateServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateServiceResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). + CreateReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateReplicaSetResponse, error) - // DeleteServiceWithResponse Delete a Service + // DeleteReplicaSetWithResponse Delete a Read Replica Set // - // Deletes a specific service. This is an asynchronous operation. + // Deletes a specific read replica set. This is an asynchronous operation. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with DELETE /projects/{project_id}/services/{service_id} (the `DeleteService` operationId). - DeleteServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*DeleteServiceResponse, error) + // Corresponds with DELETE /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id} (the `DeleteReplicaSet` operationId). + DeleteReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*DeleteReplicaSetResponse, error) - // GetServiceWithResponse Get a Service + // DisableReplicaPoolerWithResponse Disable Connection Pooler for a Read Replica // - // Retrieves the details of a specific service by its ID. + // Deactivates the connection pooler for a specific read replica set. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with GET /projects/{project_id}/services/{service_id} (the `GetService` operationId). - GetServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetServiceResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/disablePooler (the `DisableReplicaPooler` operationId). + DisableReplicaPoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*DisableReplicaPoolerResponse, error) - // AttachServiceToAllowListWithBodyWithResponse Attach Service to IP Allow List - // - // **Preview - this endpoint is experimental and may change without notice.** + // EnableReplicaPoolerWithResponse Enable Connection Pooler for a Read Replica // - // Restricts a service's connections to the IP ranges in an IP allow list. - // A service has at most one IP allow list; attaching a second one - // without detaching the first is refused. + // Activates the connection pooler for a specific read replica set. // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // Returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToAllowList (the `AttachServiceToAllowList` operationId). - AttachServiceToAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToAllowListResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/enablePooler (the `EnableReplicaPooler` operationId). + EnableReplicaPoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*EnableReplicaPoolerResponse, error) - // AttachServiceToAllowListWithResponse Attach Service to IP Allow List - // - // **Preview - this endpoint is experimental and may change without notice.** + // ResizeReplicaSetWithBodyWithResponse Resize a Read Replica Set // - // Restricts a service's connections to the IP ranges in an IP allow list. - // A service has at most one IP allow list; attaching a second one - // without detaching the first is refused. + // Changes the resource allocation for a specific read replica set. // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToAllowList (the `AttachServiceToAllowList` operationId). - AttachServiceToAllowListWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToAllowListResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). + ResizeReplicaSetWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ResizeReplicaSetResponse, error) - // AttachServiceToExporterWithBodyWithResponse Attach Service to Exporter + // ResizeReplicaSetWithResponse Resize a Read Replica Set // - // **Preview - this endpoint is experimental and may change without notice.** + // Changes the resource allocation for a specific read replica set. // - // Starts sending a service's logs or metrics to an exporter. + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // A service sends to at most one log exporter and one metric exporter at - // a time. Attaching a second exporter of the same kind is refused; - // detach the current one first. + // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). + ResizeReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body ResizeReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*ResizeReplicaSetResponse, error) + + // SetReplicaEnvironmentWithBodyWithResponse Set Environment for a Read Replica // - // The first time a log exporter is attached to a service, the service - // restarts once. Attaching a different log exporter later does not - // restart anything, and neither does attaching a metric exporter. + // Sets the environment type for the read replica set. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToExporter (the `AttachServiceToExporter` operationId). - AttachServiceToExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToExporterResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). + SetReplicaEnvironmentWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetReplicaEnvironmentResponse, error) - // AttachServiceToExporterWithResponse Attach Service to Exporter - // - // **Preview - this endpoint is experimental and may change without notice.** - // - // Starts sending a service's logs or metrics to an exporter. - // - // A service sends to at most one log exporter and one metric exporter at - // a time. Attaching a second exporter of the same kind is refused; - // detach the current one first. + // SetReplicaEnvironmentWithResponse Set Environment for a Read Replica // - // The first time a log exporter is attached to a service, the service - // restarts once. Attaching a different log exporter later does not - // restart anything, and neither does attaching a metric exporter. + // Sets the environment type for the read replica set. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToExporter (the `AttachServiceToExporter` operationId). - AttachServiceToExporterWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToExporterResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). + SetReplicaEnvironmentWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body SetReplicaEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*SetReplicaEnvironmentResponse, error) - // AttachServiceToVPCWithBodyWithResponse Attach Service to VPC + // ResizeServiceWithBodyWithResponse Resize a Service // - // Associates a service with a VPC. + // Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToVPC (the `AttachServiceToVPC` operationId). - AttachServiceToVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToVPCResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). + ResizeServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ResizeServiceResponse, error) - // AttachServiceToVPCWithResponse Attach Service to VPC + // ResizeServiceWithResponse Resize a Service // - // Associates a service with a VPC. + // Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/attachToVPC (the `AttachServiceToVPC` operationId). - AttachServiceToVPCWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToVPCResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). + ResizeServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ResizeServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*ResizeServiceResponse, error) - // GetBackupRegionsWithResponse List All Backup Regions - // - // **Preview - this endpoint is experimental and may change without notice.** + // SetEnvironmentWithBodyWithResponse Set Environment for a Service // - // Retrieves the additional regions this service's backups are copied to - // (cross-region backups). The region the service runs in is not listed. + // Sets the environment type for the service. // - // Returns a wrapper object for the known response body format(s). + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with GET /projects/{project_id}/services/{service_id}/backup-regions (the `GetBackupRegions` operationId). - GetBackupRegionsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupRegionsResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). + SetEnvironmentWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetEnvironmentResponse, error) - // CreateBackupRegionWithBodyWithResponse Add a Backup Region + // SetEnvironmentWithResponse Set Environment for a Service // - // **Preview - this endpoint is experimental and may change without notice.** + // Sets the environment type for the service. // - // Starts copying this service's backups to another region. The region is - // added immediately; existing backups are copied to it in the background. + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // + // Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). + SetEnvironmentWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*SetEnvironmentResponse, error) + + // SetHAReplicaWithBodyWithResponse Change HA configuration for a Service + // + // Changes the HA configuration for a specific service. This is an asynchronous operation. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/backup-regions (the `CreateBackupRegion` operationId). - CreateBackupRegionWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBackupRegionResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/setHA (the `SetHAReplica` operationId). + SetHAReplicaWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetHAReplicaResponse, error) - // CreateBackupRegionWithResponse Add a Backup Region - // - // **Preview - this endpoint is experimental and may change without notice.** + // SetHAReplicaWithResponse Change HA configuration for a Service // - // Starts copying this service's backups to another region. The region is - // added immediately; existing backups are copied to it in the background. + // Changes the HA configuration for a specific service. This is an asynchronous operation. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/backup-regions (the `CreateBackupRegion` operationId). - CreateBackupRegionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateBackupRegionJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBackupRegionResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/setHA (the `SetHAReplica` operationId). + SetHAReplicaWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetHAReplicaJSONRequestBody, reqEditors ...RequestEditorFn) (*SetHAReplicaResponse, error) - // DeleteBackupRegionWithResponse Remove a Backup Region - // - // **Preview - this endpoint is experimental and may change without notice.** + // StartServiceWithResponse Start a Service // - // Stops copying this service's backups to a region. Copies already stored - // there are deleted in the background and cannot be recovered. + // Starts a stopped service within a project. This is an asynchronous operation. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with DELETE /projects/{project_id}/services/{service_id}/backup-regions/{region_code} (the `DeleteBackupRegion` operationId). - DeleteBackupRegionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, regionCode BackupRegionCode, reqEditors ...RequestEditorFn) (*DeleteBackupRegionResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/start (the `StartService` operationId). + StartServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*StartServiceResponse, error) - // GetBackupRetentionWithResponse Get the Backup Retention Policy - // - // **Preview - this endpoint is experimental and may change without notice.** + // StopServiceWithResponse Stop a Service // - // Retrieves how long this service keeps its backups. + // Stops a running service within a project. This is an asynchronous operation. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with GET /projects/{project_id}/services/{service_id}/backup-retention (the `GetBackupRetention` operationId). - GetBackupRetentionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupRetentionResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/stop (the `StopService` operationId). + StopServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*StopServiceResponse, error) - // SetBackupRetentionWithBodyWithResponse Set the Backup Retention Policy - // - // **Preview - this endpoint is experimental and may change without notice.** + // UpdatePasswordWithBodyWithResponse Update Service Password // - // Sets how long this service keeps its backups. Shortening retention - // deletes the backups that no longer fit. + // Sets a new master password for the service within a project. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with PUT /projects/{project_id}/services/{service_id}/backup-retention (the `SetBackupRetention` operationId). - SetBackupRetentionWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetBackupRetentionResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/updatePassword (the `UpdatePassword` operationId). + UpdatePasswordWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdatePasswordResponse, error) - // SetBackupRetentionWithResponse Set the Backup Retention Policy - // - // **Preview - this endpoint is experimental and may change without notice.** + // UpdatePasswordWithResponse Update Service Password // - // Sets how long this service keeps its backups. Shortening retention - // deletes the backups that no longer fit. + // Sets a new master password for the service within a project. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with PUT /projects/{project_id}/services/{service_id}/backup-retention (the `SetBackupRetention` operationId). - SetBackupRetentionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetBackupRetentionJSONRequestBody, reqEditors ...RequestEditorFn) (*SetBackupRetentionResponse, error) + // Corresponds with POST /projects/{project_id}/services/{service_id}/updatePassword (the `UpdatePassword` operationId). + UpdatePasswordWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body UpdatePasswordJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdatePasswordResponse, error) - // GetBackupsWithResponse List All Backups - // - // **Preview - this endpoint is experimental and may change without notice.** + // GetVPCsWithResponse List All VPCs // - // Retrieves a list of the full and incremental backups taken for a - // service. Backups run automatically on a schedule; there is no operation - // to create or delete one. To restore data, create a recovery fork with - // `fork_strategy: PITR`. + // Retrieves a list of all Virtual Private Clouds (VPCs). // // Returns a wrapper object for the known response body format(s). // - // Corresponds with GET /projects/{project_id}/services/{service_id}/backups (the `GetBackups` operationId). - GetBackupsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupsResponse, error) + // Corresponds with GET /projects/{project_id}/vpcs (the `GetVPCs` operationId). + GetVPCsWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetVPCsResponse, error) - // DetachServiceFromAllowListWithBodyWithResponse Detach Service from IP Allow List - // - // **Preview - this endpoint is experimental and may change without notice.** + // CreateVPCWithBodyWithResponse Create a VPC // - // Removes the IP restriction an IP allow list placed on a service. The - // IP allow list itself is not deleted. + // Creates a new Virtual Private Cloud (VPC). This is an asynchronous operation. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). - DetachServiceFromAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromAllowListResponse, error) + // Corresponds with POST /projects/{project_id}/vpcs (the `CreateVPC` operationId). + CreateVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVPCResponse, error) - // DetachServiceFromAllowListWithResponse Detach Service from IP Allow List - // - // **Preview - this endpoint is experimental and may change without notice.** + // CreateVPCWithResponse Create a VPC // - // Removes the IP restriction an IP allow list placed on a service. The - // IP allow list itself is not deleted. + // Creates a new Virtual Private Cloud (VPC). This is an asynchronous operation. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). - DetachServiceFromAllowListWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromAllowListResponse, error) + // Corresponds with POST /projects/{project_id}/vpcs (the `CreateVPC` operationId). + CreateVPCWithResponse(ctx context.Context, projectID ProjectID, body CreateVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVPCResponse, error) - // DetachServiceFromExporterWithBodyWithResponse Detach Service from Exporter - // - // **Preview - this endpoint is experimental and may change without notice.** - // - // Stops sending a service's logs or metrics to an exporter. The exporter - // is not deleted and keeps serving any other service attached to it. + // DeleteVPCWithResponse Delete a VPC // - // Data already delivered to the destination is untouched. + // Deletes a specific VPC. This is an asynchronous operation. // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // Returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). - DetachServiceFromExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromExporterResponse, error) + // Corresponds with DELETE /projects/{project_id}/vpcs/{vpc_id} (the `DeleteVPC` operationId). + DeleteVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*DeleteVPCResponse, error) - // DetachServiceFromExporterWithResponse Detach Service from Exporter + // GetVPCWithResponse Get a VPC // - // **Preview - this endpoint is experimental and may change without notice.** + // Retrieves the details of a specific VPC by its ID. // - // Stops sending a service's logs or metrics to an exporter. The exporter - // is not deleted and keeps serving any other service attached to it. + // Returns a wrapper object for the known response body format(s). // - // Data already delivered to the destination is untouched. + // Corresponds with GET /projects/{project_id}/vpcs/{vpc_id} (the `GetVPC` operationId). + GetVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*GetVPCResponse, error) + + // GetVPCPeeringsWithResponse List VPC Peerings // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). + // Retrieves a list of all VPC peering connections for a given VPC. // - // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). - DetachServiceFromExporterWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromExporterResponse, error) + // Returns a wrapper object for the known response body format(s). + // + // Corresponds with GET /projects/{project_id}/vpcs/{vpc_id}/peerings (the `GetVPCPeerings` operationId). + GetVPCPeeringsWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*GetVPCPeeringsResponse, error) - // DetachServiceFromVPCWithBodyWithResponse Detach Service from VPC + // CreateVPCPeeringWithBodyWithResponse Create a VPC Peering // - // Disassociates a service from its VPC. + // Creates a new VPC peering connection. This is an asynchronous operation. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). - DetachServiceFromVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromVPCResponse, error) + // Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/peerings (the `CreateVPCPeering` operationId). + CreateVPCPeeringWithBodyWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVPCPeeringResponse, error) - // DetachServiceFromVPCWithResponse Detach Service from VPC + // CreateVPCPeeringWithResponse Create a VPC Peering // - // Disassociates a service from its VPC. + // Creates a new VPC peering connection. This is an asynchronous operation. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). - DetachServiceFromVPCWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromVPCResponse, error) + // Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/peerings (the `CreateVPCPeering` operationId). + CreateVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, body CreateVPCPeeringJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVPCPeeringResponse, error) - // DisablePoolerWithResponse Disable Connection Pooler for a Service + // DeleteVPCPeeringWithResponse Delete a VPC Peering // - // Deactivates the connection pooler for a specific service within a project. + // Deletes a specific VPC peering connection. This is an asynchronous operation. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/disablePooler (the `DisablePooler` operationId). - DisablePoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*DisablePoolerResponse, error) + // Corresponds with DELETE /projects/{project_id}/vpcs/{vpc_id}/peerings/{peering_id} (the `DeleteVPCPeering` operationId). + DeleteVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, peeringID PeeringID, reqEditors ...RequestEditorFn) (*DeleteVPCPeeringResponse, error) - // EnableDataTieringWithResponse Enable Data Tiering for a Service - // - // Enables tiered storage on the service. Tiering policies move older, - // rarely-accessed data from the high-performance storage tier to a - // low-cost object storage tier. Tiered data stays queryable but becomes - // read-only. + // GetVPCPeeringWithResponse Get a VPC Peering // - // There is no way to disable tiering through this API. Contact Tiger - // Data support if you need to turn it off. + // Retrieves the details of a specific VPC peering connection. // // Returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/enableDataTiering (the `EnableDataTiering` operationId). - EnableDataTieringWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*EnableDataTieringResponse, error) + // Corresponds with GET /projects/{project_id}/vpcs/{vpc_id}/peerings/{peering_id} (the `GetVPCPeering` operationId). + GetVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, peeringID PeeringID, reqEditors ...RequestEditorFn) (*GetVPCPeeringResponse, error) - // EnablePoolerWithResponse Enable Connection Pooler for a Service + // RenameVPCWithBodyWithResponse Rename a VPC // - // Activates the connection pooler for a specific service within a project. + // Updates the name of a specific VPC. // - // Returns a wrapper object for the known response body format(s). + // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/enablePooler (the `EnablePooler` operationId). - EnablePoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*EnablePoolerResponse, error) + // Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/rename (the `RenameVPC` operationId). + RenameVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RenameVPCResponse, error) - // ForkServiceWithBodyWithResponse Fork a Service + // RenameVPCWithResponse Rename a VPC // - // Creates a new, independent service within a project by taking a snapshot of an existing one. + // Updates the name of a specific VPC. // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). + // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // - // Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). - ForkServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ForkServiceResponse, error) + // Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/rename (the `RenameVPC` operationId). + RenameVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, body RenameVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*RenameVPCResponse, error) +} + +type IdentifyUserResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *AnalyticsResponse + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} + +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r IdentifyUserResponse) GetJSON200() *AnalyticsResponse { + return r.JSON200 +} + +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r IdentifyUserResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} + +// GetBody returns the raw response body bytes +func (r IdentifyUserResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r IdentifyUserResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r IdentifyUserResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r IdentifyUserResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type TrackEventResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *AnalyticsResponse + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} + +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r TrackEventResponse) GetJSON200() *AnalyticsResponse { + return r.JSON200 +} + +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r TrackEventResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} + +// GetBody returns the raw response body bytes +func (r TrackEventResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r TrackEventResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r TrackEventResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r TrackEventResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type GetAuthInfoResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *AuthInfo + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} + +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r GetAuthInfoResponse) GetJSON200() *AuthInfo { + return r.JSON200 +} + +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r GetAuthInfoResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} + +// GetBody returns the raw response body bytes +func (r GetAuthInfoResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r GetAuthInfoResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetAuthInfoResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetAuthInfoResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type LogoutResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} + +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r LogoutResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} + +// GetBody returns the raw response body bytes +func (r LogoutResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r LogoutResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} - // ForkServiceWithResponse Fork a Service - // - // Creates a new, independent service within a project by taking a snapshot of an existing one. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). - ForkServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ForkServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*ForkServiceResponse, error) +// StatusCode returns HTTPResponse.StatusCode +func (r LogoutResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} - // GetServiceLogsWithResponse Get service logs - // - // Fetch logs for a specific service. Returns up to 500 log entries in reverse - // chronological order. Supports cursor-based pagination via the `cursor` parameter - // and the `last_cursor` field in the response. - // - // Use the returned `last_cursor` value as the `cursor` parameter on subsequent - // requests to page through all available logs. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with GET /projects/{project_id}/services/{service_id}/logs (the `GetServiceLogs` operationId). - GetServiceLogsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, params *GetServiceLogsParams, reqEditors ...RequestEditorFn) (*GetServiceLogsResponse, error) +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r LogoutResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} - // GetServiceMetricsAvailableSeriesWithResponse List available metric series - // - // Returns the names of all metric series available for a service. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/available-series (the `GetServiceMetricsAvailableSeries` operationId). - GetServiceMetricsAvailableSeriesWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetServiceMetricsAvailableSeriesResponse, error) +type SubmitFeedbackResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} - // GetServiceMetricDetailsWithResponse Get details about a metric - // - // Returns descriptive metadata for a named metric: what it measures, - // its type, default aggregation, and every label it can be filtered or - // grouped by — its own labels plus the region/role/ordinal labels most - // metrics also carry. Use getServiceMetricsAvailableSeries to discover - // valid metric names. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/details/{metric_name} (the `GetServiceMetricDetails` operationId). - GetServiceMetricDetailsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, metricName string, reqEditors ...RequestEditorFn) (*GetServiceMetricDetailsResponse, error) +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r SubmitFeedbackResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} - // GetServiceMetricsSeriesWithBodyWithResponse Get a metric series - // - // Returns time-series data points for a named metric within a time window. - // Use getServiceMetricsAvailableSeries to discover valid metric names. - // - // Parameters are sent as a JSON body so callers can pass an unbounded - // filter set without query-string length pressure. The response is a list - // of MetricSeries, each carrying its label set as a structured map - // alongside its per-bucket data points. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). - GetServiceMetricsSeriesWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*GetServiceMetricsSeriesResponse, error) +// GetBody returns the raw response body bytes +func (r SubmitFeedbackResponse) GetBody() []byte { + return r.Body +} - // GetServiceMetricsSeriesWithResponse Get a metric series - // - // Returns time-series data points for a named metric within a time window. - // Use getServiceMetricsAvailableSeries to discover valid metric names. - // - // Parameters are sent as a JSON body so callers can pass an unbounded - // filter set without query-string length pressure. The response is a list - // of MetricSeries, each carrying its label set as a structured map - // alongside its per-bucket data points. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). - GetServiceMetricsSeriesWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body GetServiceMetricsSeriesJSONRequestBody, reqEditors ...RequestEditorFn) (*GetServiceMetricsSeriesResponse, error) +// Status returns HTTPResponse.Status +func (r SubmitFeedbackResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} - // RenameServiceWithBodyWithResponse Rename a Service - // - // Updates the name of a specific service. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). - RenameServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RenameServiceResponse, error) +// StatusCode returns HTTPResponse.StatusCode +func (r SubmitFeedbackResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} - // RenameServiceWithResponse Rename a Service - // - // Updates the name of a specific service. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). - RenameServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body RenameServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*RenameServiceResponse, error) +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r SubmitFeedbackResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} - // GetReplicaSetsWithResponse Get Read Replica Sets - // - // Retrieves a list of all read replica sets associated with a primary service within a project. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with GET /projects/{project_id}/services/{service_id}/replicaSets (the `GetReplicaSets` operationId). - GetReplicaSetsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetReplicaSetsResponse, error) +type GetProjectsResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *[]Project + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} - // CreateReplicaSetWithBodyWithResponse Create a Read Replica Set - // - // Creates a new read replica set for a service. This is an asynchronous operation. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). - CreateReplicaSetWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateReplicaSetResponse, error) +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r GetProjectsResponse) GetJSON200() *[]Project { + return r.JSON200 +} - // CreateReplicaSetWithResponse Create a Read Replica Set - // - // Creates a new read replica set for a service. This is an asynchronous operation. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). - CreateReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateReplicaSetResponse, error) +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r GetProjectsResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} - // DeleteReplicaSetWithResponse Delete a Read Replica Set - // - // Deletes a specific read replica set. This is an asynchronous operation. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with DELETE /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id} (the `DeleteReplicaSet` operationId). - DeleteReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*DeleteReplicaSetResponse, error) +// GetBody returns the raw response body bytes +func (r GetProjectsResponse) GetBody() []byte { + return r.Body +} - // DisableReplicaPoolerWithResponse Disable Connection Pooler for a Read Replica - // - // Deactivates the connection pooler for a specific read replica set. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/disablePooler (the `DisableReplicaPooler` operationId). - DisableReplicaPoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*DisableReplicaPoolerResponse, error) +// Status returns HTTPResponse.Status +func (r GetProjectsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} - // EnableReplicaPoolerWithResponse Enable Connection Pooler for a Read Replica - // - // Activates the connection pooler for a specific read replica set. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/enablePooler (the `EnableReplicaPooler` operationId). - EnableReplicaPoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*EnableReplicaPoolerResponse, error) +// StatusCode returns HTTPResponse.StatusCode +func (r GetProjectsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} - // ResizeReplicaSetWithBodyWithResponse Resize a Read Replica Set - // - // Changes the resource allocation for a specific read replica set. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). - ResizeReplicaSetWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ResizeReplicaSetResponse, error) +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetProjectsResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} - // ResizeReplicaSetWithResponse Resize a Read Replica Set - // - // Changes the resource allocation for a specific read replica set. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). - ResizeReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body ResizeReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*ResizeReplicaSetResponse, error) +type GetAllowListsResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *[]AllowList + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} - // SetReplicaEnvironmentWithBodyWithResponse Set Environment for a Read Replica - // - // Sets the environment type for the read replica set. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). - SetReplicaEnvironmentWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetReplicaEnvironmentResponse, error) +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r GetAllowListsResponse) GetJSON200() *[]AllowList { + return r.JSON200 +} - // SetReplicaEnvironmentWithResponse Set Environment for a Read Replica - // - // Sets the environment type for the read replica set. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). - SetReplicaEnvironmentWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body SetReplicaEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*SetReplicaEnvironmentResponse, error) +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r GetAllowListsResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} - // ResizeServiceWithBodyWithResponse Resize a Service - // - // Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). - ResizeServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ResizeServiceResponse, error) +// GetBody returns the raw response body bytes +func (r GetAllowListsResponse) GetBody() []byte { + return r.Body +} - // ResizeServiceWithResponse Resize a Service - // - // Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). - ResizeServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ResizeServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*ResizeServiceResponse, error) +// Status returns HTTPResponse.Status +func (r GetAllowListsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} - // SetEnvironmentWithBodyWithResponse Set Environment for a Service - // - // Sets the environment type for the service. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). - SetEnvironmentWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetEnvironmentResponse, error) +// StatusCode returns HTTPResponse.StatusCode +func (r GetAllowListsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} - // SetEnvironmentWithResponse Set Environment for a Service - // - // Sets the environment type for the service. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). - SetEnvironmentWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*SetEnvironmentResponse, error) +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetAllowListsResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} - // SetHAReplicaWithBodyWithResponse Change HA configuration for a Service - // - // Changes the HA configuration for a specific service. This is an asynchronous operation. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/setHA (the `SetHAReplica` operationId). - SetHAReplicaWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetHAReplicaResponse, error) +type CreateAllowListResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON201 the response for an HTTP 201 `application/json` response + JSON201 *AllowList + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} - // SetHAReplicaWithResponse Change HA configuration for a Service - // - // Changes the HA configuration for a specific service. This is an asynchronous operation. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/setHA (the `SetHAReplica` operationId). - SetHAReplicaWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetHAReplicaJSONRequestBody, reqEditors ...RequestEditorFn) (*SetHAReplicaResponse, error) +// GetJSON201 returns the response for an HTTP 201 `application/json` response +func (r CreateAllowListResponse) GetJSON201() *AllowList { + return r.JSON201 +} - // StartServiceWithResponse Start a Service - // - // Starts a stopped service within a project. This is an asynchronous operation. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/start (the `StartService` operationId). - StartServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*StartServiceResponse, error) +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r CreateAllowListResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} - // StopServiceWithResponse Stop a Service - // - // Stops a running service within a project. This is an asynchronous operation. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/stop (the `StopService` operationId). - StopServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*StopServiceResponse, error) +// GetBody returns the raw response body bytes +func (r CreateAllowListResponse) GetBody() []byte { + return r.Body +} - // UpdatePasswordWithBodyWithResponse Update Service Password - // - // Sets a new master password for the service within a project. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/updatePassword (the `UpdatePassword` operationId). - UpdatePasswordWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdatePasswordResponse, error) +// Status returns HTTPResponse.Status +func (r CreateAllowListResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} - // UpdatePasswordWithResponse Update Service Password - // - // Sets a new master password for the service within a project. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/services/{service_id}/updatePassword (the `UpdatePassword` operationId). - UpdatePasswordWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body UpdatePasswordJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdatePasswordResponse, error) +// StatusCode returns HTTPResponse.StatusCode +func (r CreateAllowListResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} - // GetVPCsWithResponse List All VPCs - // - // Retrieves a list of all Virtual Private Clouds (VPCs). - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with GET /projects/{project_id}/vpcs (the `GetVPCs` operationId). - GetVPCsWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetVPCsResponse, error) +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r CreateAllowListResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} - // CreateVPCWithBodyWithResponse Create a VPC - // - // Creates a new Virtual Private Cloud (VPC). This is an asynchronous operation. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/vpcs (the `CreateVPC` operationId). - CreateVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVPCResponse, error) +type DeleteAllowListResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} - // CreateVPCWithResponse Create a VPC - // - // Creates a new Virtual Private Cloud (VPC). This is an asynchronous operation. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/vpcs (the `CreateVPC` operationId). - CreateVPCWithResponse(ctx context.Context, projectID ProjectID, body CreateVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVPCResponse, error) +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r DeleteAllowListResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} - // DeleteVPCWithResponse Delete a VPC - // - // Deletes a specific VPC. This is an asynchronous operation. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with DELETE /projects/{project_id}/vpcs/{vpc_id} (the `DeleteVPC` operationId). - DeleteVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*DeleteVPCResponse, error) +// GetBody returns the raw response body bytes +func (r DeleteAllowListResponse) GetBody() []byte { + return r.Body +} - // GetVPCWithResponse Get a VPC - // - // Retrieves the details of a specific VPC by its ID. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with GET /projects/{project_id}/vpcs/{vpc_id} (the `GetVPC` operationId). - GetVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*GetVPCResponse, error) +// Status returns HTTPResponse.Status +func (r DeleteAllowListResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} - // GetVPCPeeringsWithResponse List VPC Peerings - // - // Retrieves a list of all VPC peering connections for a given VPC. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with GET /projects/{project_id}/vpcs/{vpc_id}/peerings (the `GetVPCPeerings` operationId). - GetVPCPeeringsWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*GetVPCPeeringsResponse, error) +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteAllowListResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} - // CreateVPCPeeringWithBodyWithResponse Create a VPC Peering - // - // Creates a new VPC peering connection. This is an asynchronous operation. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/peerings (the `CreateVPCPeering` operationId). - CreateVPCPeeringWithBodyWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVPCPeeringResponse, error) +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r DeleteAllowListResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type GetAllowListResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *AllowList + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} - // CreateVPCPeeringWithResponse Create a VPC Peering - // - // Creates a new VPC peering connection. This is an asynchronous operation. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/peerings (the `CreateVPCPeering` operationId). - CreateVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, body CreateVPCPeeringJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVPCPeeringResponse, error) +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r GetAllowListResponse) GetJSON200() *AllowList { + return r.JSON200 +} - // DeleteVPCPeeringWithResponse Delete a VPC Peering - // - // Deletes a specific VPC peering connection. This is an asynchronous operation. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with DELETE /projects/{project_id}/vpcs/{vpc_id}/peerings/{peering_id} (the `DeleteVPCPeering` operationId). - DeleteVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, peeringID PeeringID, reqEditors ...RequestEditorFn) (*DeleteVPCPeeringResponse, error) +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r GetAllowListResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} - // GetVPCPeeringWithResponse Get a VPC Peering - // - // Retrieves the details of a specific VPC peering connection. - // - // Returns a wrapper object for the known response body format(s). - // - // Corresponds with GET /projects/{project_id}/vpcs/{vpc_id}/peerings/{peering_id} (the `GetVPCPeering` operationId). - GetVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, peeringID PeeringID, reqEditors ...RequestEditorFn) (*GetVPCPeeringResponse, error) +// GetBody returns the raw response body bytes +func (r GetAllowListResponse) GetBody() []byte { + return r.Body +} - // RenameVPCWithBodyWithResponse Rename a VPC - // - // Updates the name of a specific VPC. - // - // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/rename (the `RenameVPC` operationId). - RenameVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RenameVPCResponse, error) +// Status returns HTTPResponse.Status +func (r GetAllowListResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} - // RenameVPCWithResponse Rename a VPC - // - // Updates the name of a specific VPC. - // - // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). - // - // Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/rename (the `RenameVPC` operationId). - RenameVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, body RenameVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*RenameVPCResponse, error) +// StatusCode returns HTTPResponse.StatusCode +func (r GetAllowListResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 } -type IdentifyUserResponse struct { +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetAllowListResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type UpdateAllowListResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *AnalyticsResponse + JSON200 *AllowList // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r IdentifyUserResponse) GetJSON200() *AnalyticsResponse { +func (r UpdateAllowListResponse) GetJSON200() *AllowList { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r IdentifyUserResponse) GetJSON4XX() *ClientError { +func (r UpdateAllowListResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r IdentifyUserResponse) GetBody() []byte { +func (r UpdateAllowListResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r IdentifyUserResponse) Status() string { +func (r UpdateAllowListResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7124,7 +8827,7 @@ func (r IdentifyUserResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r IdentifyUserResponse) StatusCode() int { +func (r UpdateAllowListResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7132,39 +8835,39 @@ func (r IdentifyUserResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r IdentifyUserResponse) ContentType() string { +func (r UpdateAllowListResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type TrackEventResponse struct { +type GetProjectBranchesResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *AnalyticsResponse + JSON200 *[]Branch // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r TrackEventResponse) GetJSON200() *AnalyticsResponse { +func (r GetProjectBranchesResponse) GetJSON200() *[]Branch { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r TrackEventResponse) GetJSON4XX() *ClientError { +func (r GetProjectBranchesResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r TrackEventResponse) GetBody() []byte { +func (r GetProjectBranchesResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r TrackEventResponse) Status() string { +func (r GetProjectBranchesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7172,7 +8875,7 @@ func (r TrackEventResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r TrackEventResponse) StatusCode() int { +func (r GetProjectBranchesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7180,39 +8883,39 @@ func (r TrackEventResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r TrackEventResponse) ContentType() string { +func (r GetProjectBranchesResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetAuthInfoResponse struct { +type GetExportersResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *AuthInfo + JSON200 *[]Exporter // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetAuthInfoResponse) GetJSON200() *AuthInfo { +func (r GetExportersResponse) GetJSON200() *[]Exporter { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetAuthInfoResponse) GetJSON4XX() *ClientError { +func (r GetExportersResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetAuthInfoResponse) GetBody() []byte { +func (r GetExportersResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetAuthInfoResponse) Status() string { +func (r GetExportersResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7220,7 +8923,7 @@ func (r GetAuthInfoResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetAuthInfoResponse) StatusCode() int { +func (r GetExportersResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7228,14 +8931,62 @@ func (r GetAuthInfoResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetAuthInfoResponse) ContentType() string { +func (r GetExportersResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type LogoutResponse struct { +type CreateExporterResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON201 the response for an HTTP 201 `application/json` response + JSON201 *Exporter + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} + +// GetJSON201 returns the response for an HTTP 201 `application/json` response +func (r CreateExporterResponse) GetJSON201() *Exporter { + return r.JSON201 +} + +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r CreateExporterResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} + +// GetBody returns the raw response body bytes +func (r CreateExporterResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r CreateExporterResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateExporterResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r CreateExporterResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type DeleteExporterResponse struct { Body []byte HTTPResponse *http.Response // JSON4XX the response for an HTTP 4XX `application/json` response @@ -7243,17 +8994,17 @@ type LogoutResponse struct { } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r LogoutResponse) GetJSON4XX() *ClientError { +func (r DeleteExporterResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r LogoutResponse) GetBody() []byte { +func (r DeleteExporterResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r LogoutResponse) Status() string { +func (r DeleteExporterResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7261,7 +9012,7 @@ func (r LogoutResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r LogoutResponse) StatusCode() int { +func (r DeleteExporterResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7269,32 +9020,39 @@ func (r LogoutResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r LogoutResponse) ContentType() string { +func (r DeleteExporterResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type SubmitFeedbackResponse struct { +type GetExporterResponse struct { Body []byte HTTPResponse *http.Response + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *Exporter // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r GetExporterResponse) GetJSON200() *Exporter { + return r.JSON200 +} + // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r SubmitFeedbackResponse) GetJSON4XX() *ClientError { +func (r GetExporterResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r SubmitFeedbackResponse) GetBody() []byte { +func (r GetExporterResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r SubmitFeedbackResponse) Status() string { +func (r GetExporterResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7302,7 +9060,7 @@ func (r SubmitFeedbackResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r SubmitFeedbackResponse) StatusCode() int { +func (r GetExporterResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7310,39 +9068,39 @@ func (r SubmitFeedbackResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r SubmitFeedbackResponse) ContentType() string { +func (r GetExporterResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetProjectsResponse struct { +type UpdateExporterResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *[]Project + JSON200 *Exporter // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetProjectsResponse) GetJSON200() *[]Project { +func (r UpdateExporterResponse) GetJSON200() *Exporter { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetProjectsResponse) GetJSON4XX() *ClientError { +func (r UpdateExporterResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetProjectsResponse) GetBody() []byte { +func (r UpdateExporterResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetProjectsResponse) Status() string { +func (r UpdateExporterResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7350,7 +9108,7 @@ func (r GetProjectsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetProjectsResponse) StatusCode() int { +func (r UpdateExporterResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7358,39 +9116,39 @@ func (r GetProjectsResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetProjectsResponse) ContentType() string { +func (r UpdateExporterResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetAllowListsResponse struct { +type ResolveRefResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *[]AllowList + JSON200 *ResolveResult // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetAllowListsResponse) GetJSON200() *[]AllowList { +func (r ResolveRefResponse) GetJSON200() *ResolveResult { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetAllowListsResponse) GetJSON4XX() *ClientError { +func (r ResolveRefResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetAllowListsResponse) GetBody() []byte { +func (r ResolveRefResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetAllowListsResponse) Status() string { +func (r ResolveRefResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7398,7 +9156,7 @@ func (r GetAllowListsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetAllowListsResponse) StatusCode() int { +func (r ResolveRefResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7406,39 +9164,39 @@ func (r GetAllowListsResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetAllowListsResponse) ContentType() string { +func (r ResolveRefResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type CreateAllowListResponse struct { +type GetServicesResponse struct { Body []byte HTTPResponse *http.Response - // JSON201 the response for an HTTP 201 `application/json` response - JSON201 *AllowList + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *[]Service // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON201 returns the response for an HTTP 201 `application/json` response -func (r CreateAllowListResponse) GetJSON201() *AllowList { - return r.JSON201 +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r GetServicesResponse) GetJSON200() *[]Service { + return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r CreateAllowListResponse) GetJSON4XX() *ClientError { +func (r GetServicesResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r CreateAllowListResponse) GetBody() []byte { +func (r GetServicesResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r CreateAllowListResponse) Status() string { +func (r GetServicesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7446,7 +9204,7 @@ func (r CreateAllowListResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateAllowListResponse) StatusCode() int { +func (r GetServicesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7454,32 +9212,39 @@ func (r CreateAllowListResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CreateAllowListResponse) ContentType() string { +func (r GetServicesResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type DeleteAllowListResponse struct { +type CreateServiceResponse struct { Body []byte HTTPResponse *http.Response + // JSON202 the response for an HTTP 202 `application/json` response + JSON202 *Service // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } +// GetJSON202 returns the response for an HTTP 202 `application/json` response +func (r CreateServiceResponse) GetJSON202() *Service { + return r.JSON202 +} + // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r DeleteAllowListResponse) GetJSON4XX() *ClientError { +func (r CreateServiceResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r DeleteAllowListResponse) GetBody() []byte { +func (r CreateServiceResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r DeleteAllowListResponse) Status() string { +func (r CreateServiceResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7487,7 +9252,7 @@ func (r DeleteAllowListResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteAllowListResponse) StatusCode() int { +func (r CreateServiceResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7495,39 +9260,32 @@ func (r DeleteAllowListResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeleteAllowListResponse) ContentType() string { +func (r CreateServiceResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetAllowListResponse struct { +type DeleteServiceResponse struct { Body []byte HTTPResponse *http.Response - // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *AllowList // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetAllowListResponse) GetJSON200() *AllowList { - return r.JSON200 -} - // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetAllowListResponse) GetJSON4XX() *ClientError { +func (r DeleteServiceResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetAllowListResponse) GetBody() []byte { +func (r DeleteServiceResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetAllowListResponse) Status() string { +func (r DeleteServiceResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7535,7 +9293,7 @@ func (r GetAllowListResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetAllowListResponse) StatusCode() int { +func (r DeleteServiceResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7543,39 +9301,39 @@ func (r GetAllowListResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetAllowListResponse) ContentType() string { +func (r DeleteServiceResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type UpdateAllowListResponse struct { +type GetServiceResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *AllowList + JSON200 *Service // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r UpdateAllowListResponse) GetJSON200() *AllowList { +func (r GetServiceResponse) GetJSON200() *Service { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r UpdateAllowListResponse) GetJSON4XX() *ClientError { +func (r GetServiceResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r UpdateAllowListResponse) GetBody() []byte { +func (r GetServiceResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r UpdateAllowListResponse) Status() string { +func (r GetServiceResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7583,7 +9341,7 @@ func (r UpdateAllowListResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UpdateAllowListResponse) StatusCode() int { +func (r GetServiceResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7591,39 +9349,39 @@ func (r UpdateAllowListResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r UpdateAllowListResponse) ContentType() string { +func (r GetServiceResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetExportersResponse struct { +type AttachServiceToAllowListResponse struct { Body []byte HTTPResponse *http.Response - // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *[]Exporter + // JSON202 the response for an HTTP 202 `application/json` response + JSON202 *Service // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetExportersResponse) GetJSON200() *[]Exporter { - return r.JSON200 +// GetJSON202 returns the response for an HTTP 202 `application/json` response +func (r AttachServiceToAllowListResponse) GetJSON202() *Service { + return r.JSON202 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetExportersResponse) GetJSON4XX() *ClientError { +func (r AttachServiceToAllowListResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetExportersResponse) GetBody() []byte { +func (r AttachServiceToAllowListResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetExportersResponse) Status() string { +func (r AttachServiceToAllowListResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7631,7 +9389,7 @@ func (r GetExportersResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetExportersResponse) StatusCode() int { +func (r AttachServiceToAllowListResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7639,39 +9397,39 @@ func (r GetExportersResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetExportersResponse) ContentType() string { +func (r AttachServiceToAllowListResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type CreateExporterResponse struct { +type AttachServiceToExporterResponse struct { Body []byte HTTPResponse *http.Response - // JSON201 the response for an HTTP 201 `application/json` response - JSON201 *Exporter + // JSON202 the response for an HTTP 202 `application/json` response + JSON202 *Service // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON201 returns the response for an HTTP 201 `application/json` response -func (r CreateExporterResponse) GetJSON201() *Exporter { - return r.JSON201 +// GetJSON202 returns the response for an HTTP 202 `application/json` response +func (r AttachServiceToExporterResponse) GetJSON202() *Service { + return r.JSON202 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r CreateExporterResponse) GetJSON4XX() *ClientError { +func (r AttachServiceToExporterResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r CreateExporterResponse) GetBody() []byte { +func (r AttachServiceToExporterResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r CreateExporterResponse) Status() string { +func (r AttachServiceToExporterResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7679,7 +9437,7 @@ func (r CreateExporterResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateExporterResponse) StatusCode() int { +func (r AttachServiceToExporterResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7687,32 +9445,39 @@ func (r CreateExporterResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CreateExporterResponse) ContentType() string { +func (r AttachServiceToExporterResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type DeleteExporterResponse struct { +type AttachServiceToVPCResponse struct { Body []byte HTTPResponse *http.Response + // JSON202 the response for an HTTP 202 `application/json` response + JSON202 *SuccessMessage // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } +// GetJSON202 returns the response for an HTTP 202 `application/json` response +func (r AttachServiceToVPCResponse) GetJSON202() *SuccessMessage { + return r.JSON202 +} + // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r DeleteExporterResponse) GetJSON4XX() *ClientError { +func (r AttachServiceToVPCResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r DeleteExporterResponse) GetBody() []byte { +func (r AttachServiceToVPCResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r DeleteExporterResponse) Status() string { +func (r AttachServiceToVPCResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7720,7 +9485,7 @@ func (r DeleteExporterResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteExporterResponse) StatusCode() int { +func (r AttachServiceToVPCResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7728,39 +9493,39 @@ func (r DeleteExporterResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeleteExporterResponse) ContentType() string { +func (r AttachServiceToVPCResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetExporterResponse struct { +type GetBackupRegionsResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *Exporter + JSON200 *[]BackupRegion // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetExporterResponse) GetJSON200() *Exporter { +func (r GetBackupRegionsResponse) GetJSON200() *[]BackupRegion { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetExporterResponse) GetJSON4XX() *ClientError { +func (r GetBackupRegionsResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetExporterResponse) GetBody() []byte { +func (r GetBackupRegionsResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetExporterResponse) Status() string { +func (r GetBackupRegionsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7768,7 +9533,7 @@ func (r GetExporterResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetExporterResponse) StatusCode() int { +func (r GetBackupRegionsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7776,39 +9541,39 @@ func (r GetExporterResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetExporterResponse) ContentType() string { +func (r GetBackupRegionsResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type UpdateExporterResponse struct { +type CreateBackupRegionResponse struct { Body []byte HTTPResponse *http.Response - // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *Exporter + // JSON201 the response for an HTTP 201 `application/json` response + JSON201 *BackupRegion // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r UpdateExporterResponse) GetJSON200() *Exporter { - return r.JSON200 +// GetJSON201 returns the response for an HTTP 201 `application/json` response +func (r CreateBackupRegionResponse) GetJSON201() *BackupRegion { + return r.JSON201 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r UpdateExporterResponse) GetJSON4XX() *ClientError { +func (r CreateBackupRegionResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r UpdateExporterResponse) GetBody() []byte { +func (r CreateBackupRegionResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r UpdateExporterResponse) Status() string { +func (r CreateBackupRegionResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7816,7 +9581,7 @@ func (r UpdateExporterResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UpdateExporterResponse) StatusCode() int { +func (r CreateBackupRegionResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7824,39 +9589,32 @@ func (r UpdateExporterResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r UpdateExporterResponse) ContentType() string { +func (r CreateBackupRegionResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetServicesResponse struct { +type DeleteBackupRegionResponse struct { Body []byte HTTPResponse *http.Response - // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *[]Service // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetServicesResponse) GetJSON200() *[]Service { - return r.JSON200 -} - // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetServicesResponse) GetJSON4XX() *ClientError { +func (r DeleteBackupRegionResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetServicesResponse) GetBody() []byte { +func (r DeleteBackupRegionResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetServicesResponse) Status() string { +func (r DeleteBackupRegionResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7864,7 +9622,7 @@ func (r GetServicesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetServicesResponse) StatusCode() int { +func (r DeleteBackupRegionResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7872,39 +9630,39 @@ func (r GetServicesResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetServicesResponse) ContentType() string { +func (r DeleteBackupRegionResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type CreateServiceResponse struct { +type GetBackupRetentionResponse struct { Body []byte HTTPResponse *http.Response - // JSON202 the response for an HTTP 202 `application/json` response - JSON202 *Service + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *BackupRetentionByTime // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON202 returns the response for an HTTP 202 `application/json` response -func (r CreateServiceResponse) GetJSON202() *Service { - return r.JSON202 +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r GetBackupRetentionResponse) GetJSON200() *BackupRetentionByTime { + return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r CreateServiceResponse) GetJSON4XX() *ClientError { +func (r GetBackupRetentionResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r CreateServiceResponse) GetBody() []byte { +func (r GetBackupRetentionResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r CreateServiceResponse) Status() string { +func (r GetBackupRetentionResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7912,7 +9670,7 @@ func (r CreateServiceResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateServiceResponse) StatusCode() int { +func (r GetBackupRetentionResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7920,32 +9678,39 @@ func (r CreateServiceResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CreateServiceResponse) ContentType() string { +func (r GetBackupRetentionResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type DeleteServiceResponse struct { +type SetBackupRetentionResponse struct { Body []byte HTTPResponse *http.Response + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *BackupRetentionByTime // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r SetBackupRetentionResponse) GetJSON200() *BackupRetentionByTime { + return r.JSON200 +} + // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r DeleteServiceResponse) GetJSON4XX() *ClientError { +func (r SetBackupRetentionResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r DeleteServiceResponse) GetBody() []byte { +func (r SetBackupRetentionResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r DeleteServiceResponse) Status() string { +func (r SetBackupRetentionResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -7953,7 +9718,7 @@ func (r DeleteServiceResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteServiceResponse) StatusCode() int { +func (r SetBackupRetentionResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7961,39 +9726,39 @@ func (r DeleteServiceResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeleteServiceResponse) ContentType() string { +func (r SetBackupRetentionResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetServiceResponse struct { +type GetBackupsResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *Service + JSON200 *[]Backup // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetServiceResponse) GetJSON200() *Service { +func (r GetBackupsResponse) GetJSON200() *[]Backup { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetServiceResponse) GetJSON4XX() *ClientError { +func (r GetBackupsResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetServiceResponse) GetBody() []byte { +func (r GetBackupsResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetServiceResponse) Status() string { +func (r GetBackupsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -8001,7 +9766,7 @@ func (r GetServiceResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetServiceResponse) StatusCode() int { +func (r GetBackupsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -8009,39 +9774,39 @@ func (r GetServiceResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetServiceResponse) ContentType() string { +func (r GetBackupsResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type AttachServiceToAllowListResponse struct { +type GetBranchesResponse struct { Body []byte HTTPResponse *http.Response - // JSON202 the response for an HTTP 202 `application/json` response - JSON202 *Service + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *[]Branch // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON202 returns the response for an HTTP 202 `application/json` response -func (r AttachServiceToAllowListResponse) GetJSON202() *Service { - return r.JSON202 +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r GetBranchesResponse) GetJSON200() *[]Branch { + return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r AttachServiceToAllowListResponse) GetJSON4XX() *ClientError { +func (r GetBranchesResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r AttachServiceToAllowListResponse) GetBody() []byte { +func (r GetBranchesResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r AttachServiceToAllowListResponse) Status() string { +func (r GetBranchesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -8049,7 +9814,7 @@ func (r AttachServiceToAllowListResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r AttachServiceToAllowListResponse) StatusCode() int { +func (r GetBranchesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -8057,39 +9822,39 @@ func (r AttachServiceToAllowListResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r AttachServiceToAllowListResponse) ContentType() string { +func (r GetBranchesResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type AttachServiceToExporterResponse struct { +type CreateBranchResponse struct { Body []byte HTTPResponse *http.Response // JSON202 the response for an HTTP 202 `application/json` response - JSON202 *Service + JSON202 *Branch // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON202 returns the response for an HTTP 202 `application/json` response -func (r AttachServiceToExporterResponse) GetJSON202() *Service { +func (r CreateBranchResponse) GetJSON202() *Branch { return r.JSON202 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r AttachServiceToExporterResponse) GetJSON4XX() *ClientError { +func (r CreateBranchResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r AttachServiceToExporterResponse) GetBody() []byte { +func (r CreateBranchResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r AttachServiceToExporterResponse) Status() string { +func (r CreateBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -8097,7 +9862,7 @@ func (r AttachServiceToExporterResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r AttachServiceToExporterResponse) StatusCode() int { +func (r CreateBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -8105,39 +9870,32 @@ func (r AttachServiceToExporterResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r AttachServiceToExporterResponse) ContentType() string { +func (r CreateBranchResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type AttachServiceToVPCResponse struct { +type DeleteBranchResponse struct { Body []byte HTTPResponse *http.Response - // JSON202 the response for an HTTP 202 `application/json` response - JSON202 *SuccessMessage // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON202 returns the response for an HTTP 202 `application/json` response -func (r AttachServiceToVPCResponse) GetJSON202() *SuccessMessage { - return r.JSON202 -} - // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r AttachServiceToVPCResponse) GetJSON4XX() *ClientError { +func (r DeleteBranchResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r AttachServiceToVPCResponse) GetBody() []byte { +func (r DeleteBranchResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r AttachServiceToVPCResponse) Status() string { +func (r DeleteBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -8145,7 +9903,7 @@ func (r AttachServiceToVPCResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r AttachServiceToVPCResponse) StatusCode() int { +func (r DeleteBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -8153,39 +9911,39 @@ func (r AttachServiceToVPCResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r AttachServiceToVPCResponse) ContentType() string { +func (r DeleteBranchResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetBackupRegionsResponse struct { +type GetBranchResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *[]BackupRegion + JSON200 *Branch // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetBackupRegionsResponse) GetJSON200() *[]BackupRegion { +func (r GetBranchResponse) GetJSON200() *Branch { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetBackupRegionsResponse) GetJSON4XX() *ClientError { +func (r GetBranchResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetBackupRegionsResponse) GetBody() []byte { +func (r GetBranchResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetBackupRegionsResponse) Status() string { +func (r GetBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -8193,7 +9951,7 @@ func (r GetBackupRegionsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetBackupRegionsResponse) StatusCode() int { +func (r GetBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -8201,39 +9959,39 @@ func (r GetBackupRegionsResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetBackupRegionsResponse) ContentType() string { +func (r GetBranchResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type CreateBackupRegionResponse struct { +type RenameBranchResponse struct { Body []byte HTTPResponse *http.Response - // JSON201 the response for an HTTP 201 `application/json` response - JSON201 *BackupRegion + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *Branch // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON201 returns the response for an HTTP 201 `application/json` response -func (r CreateBackupRegionResponse) GetJSON201() *BackupRegion { - return r.JSON201 +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r RenameBranchResponse) GetJSON200() *Branch { + return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r CreateBackupRegionResponse) GetJSON4XX() *ClientError { +func (r RenameBranchResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r CreateBackupRegionResponse) GetBody() []byte { +func (r RenameBranchResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r CreateBackupRegionResponse) Status() string { +func (r RenameBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -8241,7 +9999,7 @@ func (r CreateBackupRegionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateBackupRegionResponse) StatusCode() int { +func (r RenameBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -8249,32 +10007,39 @@ func (r CreateBackupRegionResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CreateBackupRegionResponse) ContentType() string { +func (r RenameBranchResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type DeleteBackupRegionResponse struct { +type ResizeBranchResponse struct { Body []byte HTTPResponse *http.Response + // JSON202 the response for an HTTP 202 `application/json` response + JSON202 *Branch // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } +// GetJSON202 returns the response for an HTTP 202 `application/json` response +func (r ResizeBranchResponse) GetJSON202() *Branch { + return r.JSON202 +} + // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r DeleteBackupRegionResponse) GetJSON4XX() *ClientError { +func (r ResizeBranchResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r DeleteBackupRegionResponse) GetBody() []byte { +func (r ResizeBranchResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r DeleteBackupRegionResponse) Status() string { +func (r ResizeBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -8282,7 +10047,7 @@ func (r DeleteBackupRegionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteBackupRegionResponse) StatusCode() int { +func (r ResizeBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -8290,39 +10055,39 @@ func (r DeleteBackupRegionResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeleteBackupRegionResponse) ContentType() string { +func (r ResizeBranchResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetBackupRetentionResponse struct { +type StartBranchResponse struct { Body []byte HTTPResponse *http.Response - // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *BackupRetentionByTime + // JSON202 the response for an HTTP 202 `application/json` response + JSON202 *Branch // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetBackupRetentionResponse) GetJSON200() *BackupRetentionByTime { - return r.JSON200 +// GetJSON202 returns the response for an HTTP 202 `application/json` response +func (r StartBranchResponse) GetJSON202() *Branch { + return r.JSON202 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetBackupRetentionResponse) GetJSON4XX() *ClientError { +func (r StartBranchResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetBackupRetentionResponse) GetBody() []byte { +func (r StartBranchResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetBackupRetentionResponse) Status() string { +func (r StartBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -8330,7 +10095,7 @@ func (r GetBackupRetentionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetBackupRetentionResponse) StatusCode() int { +func (r StartBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -8338,39 +10103,39 @@ func (r GetBackupRetentionResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetBackupRetentionResponse) ContentType() string { +func (r StartBranchResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type SetBackupRetentionResponse struct { +type StopBranchResponse struct { Body []byte HTTPResponse *http.Response - // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *BackupRetentionByTime + // JSON202 the response for an HTTP 202 `application/json` response + JSON202 *Branch // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r SetBackupRetentionResponse) GetJSON200() *BackupRetentionByTime { - return r.JSON200 +// GetJSON202 returns the response for an HTTP 202 `application/json` response +func (r StopBranchResponse) GetJSON202() *Branch { + return r.JSON202 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r SetBackupRetentionResponse) GetJSON4XX() *ClientError { +func (r StopBranchResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r SetBackupRetentionResponse) GetBody() []byte { +func (r StopBranchResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r SetBackupRetentionResponse) Status() string { +func (r StopBranchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -8378,7 +10143,7 @@ func (r SetBackupRetentionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r SetBackupRetentionResponse) StatusCode() int { +func (r StopBranchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -8386,39 +10151,32 @@ func (r SetBackupRetentionResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r SetBackupRetentionResponse) ContentType() string { +func (r StopBranchResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetBackupsResponse struct { +type UpdateBranchPasswordResponse struct { Body []byte HTTPResponse *http.Response - // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *[]Backup // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetBackupsResponse) GetJSON200() *[]Backup { - return r.JSON200 -} - // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetBackupsResponse) GetJSON4XX() *ClientError { +func (r UpdateBranchPasswordResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetBackupsResponse) GetBody() []byte { +func (r UpdateBranchPasswordResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetBackupsResponse) Status() string { +func (r UpdateBranchPasswordResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -8426,7 +10184,7 @@ func (r GetBackupsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetBackupsResponse) StatusCode() int { +func (r UpdateBranchPasswordResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -8434,7 +10192,7 @@ func (r GetBackupsResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetBackupsResponse) ContentType() string { +func (r UpdateBranchPasswordResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } @@ -9490,32 +11248,217 @@ func (r SetHAReplicaResponse) ContentType() string { return "" } -type StartServiceResponse struct { +type StartServiceResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON202 the response for an HTTP 202 `application/json` response + JSON202 *Service + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} + +// GetJSON202 returns the response for an HTTP 202 `application/json` response +func (r StartServiceResponse) GetJSON202() *Service { + return r.JSON202 +} + +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r StartServiceResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} + +// GetBody returns the raw response body bytes +func (r StartServiceResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r StartServiceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r StartServiceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r StartServiceResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type StopServiceResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON202 the response for an HTTP 202 `application/json` response + JSON202 *Service + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} + +// GetJSON202 returns the response for an HTTP 202 `application/json` response +func (r StopServiceResponse) GetJSON202() *Service { + return r.JSON202 +} + +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r StopServiceResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} + +// GetBody returns the raw response body bytes +func (r StopServiceResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r StopServiceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r StopServiceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r StopServiceResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type UpdatePasswordResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} + +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r UpdatePasswordResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} + +// GetBody returns the raw response body bytes +func (r UpdatePasswordResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r UpdatePasswordResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdatePasswordResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r UpdatePasswordResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type GetVPCsResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *[]VPC + // JSON4XX the response for an HTTP 4XX `application/json` response + JSON4XX *ClientError +} + +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r GetVPCsResponse) GetJSON200() *[]VPC { + return r.JSON200 +} + +// GetJSON4XX returns the response for an HTTP 4XX `application/json` response +func (r GetVPCsResponse) GetJSON4XX() *ClientError { + return r.JSON4XX +} + +// GetBody returns the raw response body bytes +func (r GetVPCsResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r GetVPCsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetVPCsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetVPCsResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type CreateVPCResponse struct { Body []byte HTTPResponse *http.Response // JSON202 the response for an HTTP 202 `application/json` response - JSON202 *Service + JSON202 *VPC // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON202 returns the response for an HTTP 202 `application/json` response -func (r StartServiceResponse) GetJSON202() *Service { +func (r CreateVPCResponse) GetJSON202() *VPC { return r.JSON202 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r StartServiceResponse) GetJSON4XX() *ClientError { +func (r CreateVPCResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r StartServiceResponse) GetBody() []byte { +func (r CreateVPCResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r StartServiceResponse) Status() string { +func (r CreateVPCResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -9523,7 +11466,7 @@ func (r StartServiceResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r StartServiceResponse) StatusCode() int { +func (r CreateVPCResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -9531,39 +11474,32 @@ func (r StartServiceResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r StartServiceResponse) ContentType() string { +func (r CreateVPCResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type StopServiceResponse struct { +type DeleteVPCResponse struct { Body []byte HTTPResponse *http.Response - // JSON202 the response for an HTTP 202 `application/json` response - JSON202 *Service // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } -// GetJSON202 returns the response for an HTTP 202 `application/json` response -func (r StopServiceResponse) GetJSON202() *Service { - return r.JSON202 -} - // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r StopServiceResponse) GetJSON4XX() *ClientError { +func (r DeleteVPCResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r StopServiceResponse) GetBody() []byte { +func (r DeleteVPCResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r StopServiceResponse) Status() string { +func (r DeleteVPCResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -9571,7 +11507,7 @@ func (r StopServiceResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r StopServiceResponse) StatusCode() int { +func (r DeleteVPCResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -9579,32 +11515,39 @@ func (r StopServiceResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r StopServiceResponse) ContentType() string { +func (r DeleteVPCResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type UpdatePasswordResponse struct { +type GetVPCResponse struct { Body []byte HTTPResponse *http.Response + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *VPC // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r GetVPCResponse) GetJSON200() *VPC { + return r.JSON200 +} + // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r UpdatePasswordResponse) GetJSON4XX() *ClientError { +func (r GetVPCResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r UpdatePasswordResponse) GetBody() []byte { +func (r GetVPCResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r UpdatePasswordResponse) Status() string { +func (r GetVPCResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -9612,7 +11555,7 @@ func (r UpdatePasswordResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UpdatePasswordResponse) StatusCode() int { +func (r GetVPCResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -9620,39 +11563,39 @@ func (r UpdatePasswordResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r UpdatePasswordResponse) ContentType() string { +func (r GetVPCResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetVPCsResponse struct { +type GetVPCPeeringsResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *[]VPC + JSON200 *[]Peering // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetVPCsResponse) GetJSON200() *[]VPC { +func (r GetVPCPeeringsResponse) GetJSON200() *[]Peering { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetVPCsResponse) GetJSON4XX() *ClientError { +func (r GetVPCPeeringsResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetVPCsResponse) GetBody() []byte { +func (r GetVPCPeeringsResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetVPCsResponse) Status() string { +func (r GetVPCPeeringsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -9660,7 +11603,7 @@ func (r GetVPCsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetVPCsResponse) StatusCode() int { +func (r GetVPCPeeringsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -9668,39 +11611,39 @@ func (r GetVPCsResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetVPCsResponse) ContentType() string { +func (r GetVPCPeeringsResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type CreateVPCResponse struct { +type CreateVPCPeeringResponse struct { Body []byte HTTPResponse *http.Response // JSON202 the response for an HTTP 202 `application/json` response - JSON202 *VPC + JSON202 *Peering // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON202 returns the response for an HTTP 202 `application/json` response -func (r CreateVPCResponse) GetJSON202() *VPC { +func (r CreateVPCPeeringResponse) GetJSON202() *Peering { return r.JSON202 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r CreateVPCResponse) GetJSON4XX() *ClientError { +func (r CreateVPCPeeringResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r CreateVPCResponse) GetBody() []byte { +func (r CreateVPCPeeringResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r CreateVPCResponse) Status() string { +func (r CreateVPCPeeringResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -9708,7 +11651,7 @@ func (r CreateVPCResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateVPCResponse) StatusCode() int { +func (r CreateVPCPeeringResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -9716,14 +11659,14 @@ func (r CreateVPCResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CreateVPCResponse) ContentType() string { +func (r CreateVPCPeeringResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type DeleteVPCResponse struct { +type DeleteVPCPeeringResponse struct { Body []byte HTTPResponse *http.Response // JSON4XX the response for an HTTP 4XX `application/json` response @@ -9731,17 +11674,17 @@ type DeleteVPCResponse struct { } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r DeleteVPCResponse) GetJSON4XX() *ClientError { +func (r DeleteVPCPeeringResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r DeleteVPCResponse) GetBody() []byte { +func (r DeleteVPCPeeringResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r DeleteVPCResponse) Status() string { +func (r DeleteVPCPeeringResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -9749,7 +11692,7 @@ func (r DeleteVPCResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteVPCResponse) StatusCode() int { +func (r DeleteVPCPeeringResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -9757,39 +11700,39 @@ func (r DeleteVPCResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeleteVPCResponse) ContentType() string { +func (r DeleteVPCPeeringResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetVPCResponse struct { +type GetVPCPeeringResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *VPC + JSON200 *Peering // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetVPCResponse) GetJSON200() *VPC { +func (r GetVPCPeeringResponse) GetJSON200() *Peering { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetVPCResponse) GetJSON4XX() *ClientError { +func (r GetVPCPeeringResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetVPCResponse) GetBody() []byte { +func (r GetVPCPeeringResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetVPCResponse) Status() string { +func (r GetVPCPeeringResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -9797,7 +11740,7 @@ func (r GetVPCResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetVPCResponse) StatusCode() int { +func (r GetVPCPeeringResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -9805,1869 +11748,2299 @@ func (r GetVPCResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetVPCResponse) ContentType() string { +func (r GetVPCPeeringResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } return "" } -type GetVPCPeeringsResponse struct { +type RenameVPCResponse struct { Body []byte HTTPResponse *http.Response // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *[]Peering + JSON200 *SuccessMessage // JSON4XX the response for an HTTP 4XX `application/json` response JSON4XX *ClientError } // GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetVPCPeeringsResponse) GetJSON200() *[]Peering { +func (r RenameVPCResponse) GetJSON200() *SuccessMessage { return r.JSON200 } // GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetVPCPeeringsResponse) GetJSON4XX() *ClientError { +func (r RenameVPCResponse) GetJSON4XX() *ClientError { return r.JSON4XX } // GetBody returns the raw response body bytes -func (r GetVPCPeeringsResponse) GetBody() []byte { +func (r RenameVPCResponse) GetBody() []byte { return r.Body } // Status returns HTTPResponse.Status -func (r GetVPCPeeringsResponse) Status() string { +func (r RenameVPCResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } - return http.StatusText(0) + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r RenameVPCResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r RenameVPCResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +// IdentifyUserWithBodyWithResponse Identify a user +// +// Identifies a user with optional properties for analytics tracking. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /analytics/identify (the `IdentifyUser` operationId). +func (c *ClientWithResponses) IdentifyUserWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*IdentifyUserResponse, error) { + rsp, err := c.IdentifyUserWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseIdentifyUserResponse(rsp) +} + +// IdentifyUserWithResponse Identify a user +// +// Identifies a user with optional properties for analytics tracking. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /analytics/identify (the `IdentifyUser` operationId). +func (c *ClientWithResponses) IdentifyUserWithResponse(ctx context.Context, body IdentifyUserJSONRequestBody, reqEditors ...RequestEditorFn) (*IdentifyUserResponse, error) { + rsp, err := c.IdentifyUser(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseIdentifyUserResponse(rsp) +} + +// TrackEventWithBodyWithResponse Track an analytics event +// +// Tracks an analytics event with optional properties. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /analytics/track (the `TrackEvent` operationId). +func (c *ClientWithResponses) TrackEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TrackEventResponse, error) { + rsp, err := c.TrackEventWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTrackEventResponse(rsp) +} + +// TrackEventWithResponse Track an analytics event +// +// Tracks an analytics event with optional properties. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /analytics/track (the `TrackEvent` operationId). +func (c *ClientWithResponses) TrackEventWithResponse(ctx context.Context, body TrackEventJSONRequestBody, reqEditors ...RequestEditorFn) (*TrackEventResponse, error) { + rsp, err := c.TrackEvent(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseTrackEventResponse(rsp) +} + +// GetAuthInfoWithResponse Get Authentication Info +// +// Returns the authenticated caller's identity. The response shape depends on the credential type: `apiKey` for PAT +// callers, `oauth` for OAuth user callers. Exactly one of `api_key` or `oauth` is populated; +// `type` discriminates. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /auth/info (the `GetAuthInfo` operationId). +func (c *ClientWithResponses) GetAuthInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetAuthInfoResponse, error) { + rsp, err := c.GetAuthInfo(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAuthInfoResponse(rsp) +} + +// LogoutWithBodyWithResponse Log out and revoke refresh token +// +// Revokes the caller's refresh token server-side. For OAuth Bearer +// callers, include the refresh token in the request body — it is held +// client-side and cannot be derived from the access token. Callers +// without a refresh token to revoke may post an +// empty body to clear server-side session state best-effort. +// +// Returns 204 No Content on success. The caller is still responsible +// for dropping any locally-cached credentials. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /auth/logout (the `Logout` operationId). +func (c *ClientWithResponses) LogoutWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LogoutResponse, error) { + rsp, err := c.LogoutWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLogoutResponse(rsp) +} + +// LogoutWithResponse Log out and revoke refresh token +// +// Revokes the caller's refresh token server-side. For OAuth Bearer +// callers, include the refresh token in the request body — it is held +// client-side and cannot be derived from the access token. Callers +// without a refresh token to revoke may post an +// empty body to clear server-side session state best-effort. +// +// Returns 204 No Content on success. The caller is still responsible +// for dropping any locally-cached credentials. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /auth/logout (the `Logout` operationId). +func (c *ClientWithResponses) LogoutWithResponse(ctx context.Context, body LogoutJSONRequestBody, reqEditors ...RequestEditorFn) (*LogoutResponse, error) { + rsp, err := c.Logout(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLogoutResponse(rsp) } -// StatusCode returns HTTPResponse.StatusCode -func (r GetVPCPeeringsResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode +// SubmitFeedbackWithBodyWithResponse Submit feedback +// +// Sends feedback, a bug report, or a support request to Tiger Data. +// +// The submitter's email address is resolved from the caller's +// credentials, and the client version and operating system from the +// `User-Agent` header, so neither is supplied in the body. +// +// Returns 204 No Content on success. This endpoint opens no support +// case and returns no ticket to track: use it for unsolicited feedback, +// and raise anything needing a tracked response through support. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /feedback (the `SubmitFeedback` operationId). +func (c *ClientWithResponses) SubmitFeedbackWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SubmitFeedbackResponse, error) { + rsp, err := c.SubmitFeedbackWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err } - return 0 + return ParseSubmitFeedbackResponse(rsp) } -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetVPCPeeringsResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") +// SubmitFeedbackWithResponse Submit feedback +// +// Sends feedback, a bug report, or a support request to Tiger Data. +// +// The submitter's email address is resolved from the caller's +// credentials, and the client version and operating system from the +// `User-Agent` header, so neither is supplied in the body. +// +// Returns 204 No Content on success. This endpoint opens no support +// case and returns no ticket to track: use it for unsolicited feedback, +// and raise anything needing a tracked response through support. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /feedback (the `SubmitFeedback` operationId). +func (c *ClientWithResponses) SubmitFeedbackWithResponse(ctx context.Context, body SubmitFeedbackJSONRequestBody, reqEditors ...RequestEditorFn) (*SubmitFeedbackResponse, error) { + rsp, err := c.SubmitFeedback(ctx, body, reqEditors...) + if err != nil { + return nil, err } - return "" -} - -type CreateVPCPeeringResponse struct { - Body []byte - HTTPResponse *http.Response - // JSON202 the response for an HTTP 202 `application/json` response - JSON202 *Peering - // JSON4XX the response for an HTTP 4XX `application/json` response - JSON4XX *ClientError + return ParseSubmitFeedbackResponse(rsp) } -// GetJSON202 returns the response for an HTTP 202 `application/json` response -func (r CreateVPCPeeringResponse) GetJSON202() *Peering { - return r.JSON202 +// GetProjectsWithResponse List projects accessible to the authenticated caller +// +// Returns the projects the caller can access. PAT callers see only the +// project their PAT is scoped to; OAuth user callers see all projects +// they belong to. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects (the `GetProjects` operationId). +func (c *ClientWithResponses) GetProjectsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetProjectsResponse, error) { + rsp, err := c.GetProjects(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetProjectsResponse(rsp) } -// GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r CreateVPCPeeringResponse) GetJSON4XX() *ClientError { - return r.JSON4XX +// GetAllowListsWithResponse List All IP Allow Lists +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Retrieves every IP allow list in a project. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects/{project_id}/allow-lists (the `GetAllowLists` operationId). +func (c *ClientWithResponses) GetAllowListsWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetAllowListsResponse, error) { + rsp, err := c.GetAllowLists(ctx, projectID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAllowListsResponse(rsp) } -// GetBody returns the raw response body bytes -func (r CreateVPCPeeringResponse) GetBody() []byte { - return r.Body +// CreateAllowListWithBodyWithResponse Create an IP Allow List +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Creates an IP allow list in a project. An IP allow list carries no +// effect until it is attached to a service. +// +// The number of CIDR blocks per list and the number of lists per +// project are plan-dependent. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/allow-lists (the `CreateAllowList` operationId). +func (c *ClientWithResponses) CreateAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateAllowListResponse, error) { + rsp, err := c.CreateAllowListWithBody(ctx, projectID, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateAllowListResponse(rsp) } -// Status returns HTTPResponse.Status -func (r CreateVPCPeeringResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status +// CreateAllowListWithResponse Create an IP Allow List +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Creates an IP allow list in a project. An IP allow list carries no +// effect until it is attached to a service. +// +// The number of CIDR blocks per list and the number of lists per +// project are plan-dependent. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/allow-lists (the `CreateAllowList` operationId). +func (c *ClientWithResponses) CreateAllowListWithResponse(ctx context.Context, projectID ProjectID, body CreateAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateAllowListResponse, error) { + rsp, err := c.CreateAllowList(ctx, projectID, body, reqEditors...) + if err != nil { + return nil, err } - return http.StatusText(0) + return ParseCreateAllowListResponse(rsp) } -// StatusCode returns HTTPResponse.StatusCode -func (r CreateVPCPeeringResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode +// DeleteAllowListWithResponse Delete an IP Allow List +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Deletes an IP allow list. Refused if the list is still attached to +// any service. Detach it first. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with DELETE /projects/{project_id}/allow-lists/{allow_list_id} (the `DeleteAllowList` operationId). +func (c *ClientWithResponses) DeleteAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, reqEditors ...RequestEditorFn) (*DeleteAllowListResponse, error) { + rsp, err := c.DeleteAllowList(ctx, projectID, allowListID, reqEditors...) + if err != nil { + return nil, err } - return 0 + return ParseDeleteAllowListResponse(rsp) } -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CreateVPCPeeringResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") +// GetAllowListWithResponse Get an IP Allow List +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Retrieves a single IP allow list. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects/{project_id}/allow-lists/{allow_list_id} (the `GetAllowList` operationId). +func (c *ClientWithResponses) GetAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, reqEditors ...RequestEditorFn) (*GetAllowListResponse, error) { + rsp, err := c.GetAllowList(ctx, projectID, allowListID, reqEditors...) + if err != nil { + return nil, err } - return "" + return ParseGetAllowListResponse(rsp) } -type DeleteVPCPeeringResponse struct { - Body []byte - HTTPResponse *http.Response - // JSON4XX the response for an HTTP 4XX `application/json` response - JSON4XX *ClientError +// UpdateAllowListWithBodyWithResponse Update an IP Allow List +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Updates an IP allow list in place. Send `description` and/or +// `cidr_blocks` to change them; omit either one to leave it +// unchanged. +// +// Every service the IP allow list is attached to is updated with the +// new CIDR blocks. To remove every CIDR block, delete the IP allow +// list instead of sending an empty `cidr_blocks`. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with PATCH /projects/{project_id}/allow-lists/{allow_list_id} (the `UpdateAllowList` operationId). +func (c *ClientWithResponses) UpdateAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateAllowListResponse, error) { + rsp, err := c.UpdateAllowListWithBody(ctx, projectID, allowListID, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateAllowListResponse(rsp) } -// GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r DeleteVPCPeeringResponse) GetJSON4XX() *ClientError { - return r.JSON4XX +// UpdateAllowListWithResponse Update an IP Allow List +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Updates an IP allow list in place. Send `description` and/or +// `cidr_blocks` to change them; omit either one to leave it +// unchanged. +// +// Every service the IP allow list is attached to is updated with the +// new CIDR blocks. To remove every CIDR block, delete the IP allow +// list instead of sending an empty `cidr_blocks`. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with PATCH /projects/{project_id}/allow-lists/{allow_list_id} (the `UpdateAllowList` operationId). +func (c *ClientWithResponses) UpdateAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, body UpdateAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateAllowListResponse, error) { + rsp, err := c.UpdateAllowList(ctx, projectID, allowListID, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateAllowListResponse(rsp) } -// GetBody returns the raw response body bytes -func (r DeleteVPCPeeringResponse) GetBody() []byte { - return r.Body +// GetProjectBranchesWithResponse List All Branches in a Project +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Retrieves a list of all branches within a specific project, regardless +// of parent service. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects/{project_id}/branches (the `GetProjectBranches` operationId). +func (c *ClientWithResponses) GetProjectBranchesWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetProjectBranchesResponse, error) { + rsp, err := c.GetProjectBranches(ctx, projectID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetProjectBranchesResponse(rsp) } -// Status returns HTTPResponse.Status -func (r DeleteVPCPeeringResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status +// GetExportersWithResponse List All Exporters +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Retrieves every exporter in a project, across every region. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects/{project_id}/exporters (the `GetExporters` operationId). +func (c *ClientWithResponses) GetExportersWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetExportersResponse, error) { + rsp, err := c.GetExporters(ctx, projectID, reqEditors...) + if err != nil { + return nil, err } - return http.StatusText(0) + return ParseGetExportersResponse(rsp) } -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteVPCPeeringResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode +// CreateExporterWithBodyWithResponse Create an Exporter +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Creates an exporter in a project and region. +// +// An exporter carries no data until it is attached to a service. +// +// Exporters aren't available on every plan. If your project's plan +// doesn't include them, this request is refused here rather than later, +// when you try to attach one. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/exporters (the `CreateExporter` operationId). +func (c *ClientWithResponses) CreateExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateExporterResponse, error) { + rsp, err := c.CreateExporterWithBody(ctx, projectID, contentType, body, reqEditors...) + if err != nil { + return nil, err } - return 0 + return ParseCreateExporterResponse(rsp) } -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r DeleteVPCPeeringResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") +// CreateExporterWithResponse Create an Exporter +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Creates an exporter in a project and region. +// +// An exporter carries no data until it is attached to a service. +// +// Exporters aren't available on every plan. If your project's plan +// doesn't include them, this request is refused here rather than later, +// when you try to attach one. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/exporters (the `CreateExporter` operationId). +func (c *ClientWithResponses) CreateExporterWithResponse(ctx context.Context, projectID ProjectID, body CreateExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateExporterResponse, error) { + rsp, err := c.CreateExporter(ctx, projectID, body, reqEditors...) + if err != nil { + return nil, err } - return "" + return ParseCreateExporterResponse(rsp) } -type GetVPCPeeringResponse struct { - Body []byte - HTTPResponse *http.Response - // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *Peering - // JSON4XX the response for an HTTP 4XX `application/json` response - JSON4XX *ClientError +// DeleteExporterWithResponse Delete an Exporter +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Deletes an exporter and stops the data it was sending. Any service +// still attached is detached automatically. You don't need to detach it +// first, and nothing errors if you don't. +// +// Data already delivered to the destination is untouched, and nothing on +// the services themselves changes. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with DELETE /projects/{project_id}/exporters/{exporter_id} (the `DeleteExporter` operationId). +func (c *ClientWithResponses) DeleteExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, reqEditors ...RequestEditorFn) (*DeleteExporterResponse, error) { + rsp, err := c.DeleteExporter(ctx, projectID, exporterID, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteExporterResponse(rsp) } -// GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r GetVPCPeeringResponse) GetJSON200() *Peering { - return r.JSON200 +// GetExporterWithResponse Get an Exporter +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Retrieves a single exporter. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects/{project_id}/exporters/{exporter_id} (the `GetExporter` operationId). +func (c *ClientWithResponses) GetExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, reqEditors ...RequestEditorFn) (*GetExporterResponse, error) { + rsp, err := c.GetExporter(ctx, projectID, exporterID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetExporterResponse(rsp) } -// GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r GetVPCPeeringResponse) GetJSON4XX() *ClientError { - return r.JSON4XX +// UpdateExporterWithBodyWithResponse Update an Exporter +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Updates an exporter in place. Send `config` to replace the whole +// configuration; omit it to leave the configuration unchanged. +// +// Every service the exporter is attached to picks up the change; there +// is no need to detach and reattach. +// +// `type` must be included and must match the exporter's existing type; +// it cannot be changed. To send data somewhere else, create a new +// exporter and move the services over. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with PATCH /projects/{project_id}/exporters/{exporter_id} (the `UpdateExporter` operationId). +func (c *ClientWithResponses) UpdateExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateExporterResponse, error) { + rsp, err := c.UpdateExporterWithBody(ctx, projectID, exporterID, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateExporterResponse(rsp) } -// GetBody returns the raw response body bytes -func (r GetVPCPeeringResponse) GetBody() []byte { - return r.Body +// UpdateExporterWithResponse Update an Exporter +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Updates an exporter in place. Send `config` to replace the whole +// configuration; omit it to leave the configuration unchanged. +// +// Every service the exporter is attached to picks up the change; there +// is no need to detach and reattach. +// +// `type` must be included and must match the exporter's existing type; +// it cannot be changed. To send data somewhere else, create a new +// exporter and move the services over. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with PATCH /projects/{project_id}/exporters/{exporter_id} (the `UpdateExporter` operationId). +func (c *ClientWithResponses) UpdateExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, body UpdateExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateExporterResponse, error) { + rsp, err := c.UpdateExporter(ctx, projectID, exporterID, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateExporterResponse(rsp) } -// Status returns HTTPResponse.Status -func (r GetVPCPeeringResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status +// ResolveRefWithResponse Resolve a Service, Branch, or Read Replica Set +// +// Resolves `ref` to the service, branch, or read replica set it names. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects/{project_id}/resolve (the `ResolveRef` operationId). +func (c *ClientWithResponses) ResolveRefWithResponse(ctx context.Context, projectID ProjectID, params *ResolveRefParams, reqEditors ...RequestEditorFn) (*ResolveRefResponse, error) { + rsp, err := c.ResolveRef(ctx, projectID, params, reqEditors...) + if err != nil { + return nil, err } - return http.StatusText(0) + return ParseResolveRefResponse(rsp) } -// StatusCode returns HTTPResponse.StatusCode -func (r GetVPCPeeringResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode +// GetServicesWithResponse List All Services +// +// Retrieves a list of all services within a specific project. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects/{project_id}/services (the `GetServices` operationId). +func (c *ClientWithResponses) GetServicesWithResponse(ctx context.Context, projectID ProjectID, params *GetServicesParams, reqEditors ...RequestEditorFn) (*GetServicesResponse, error) { + rsp, err := c.GetServices(ctx, projectID, params, reqEditors...) + if err != nil { + return nil, err } - return 0 + return ParseGetServicesResponse(rsp) } -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetVPCPeeringResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") +// CreateServiceWithBodyWithResponse Create a Service +// +// Creates a new database service within a project. This is an asynchronous operation. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/services (the `CreateService` operationId). +func (c *ClientWithResponses) CreateServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateServiceResponse, error) { + rsp, err := c.CreateServiceWithBody(ctx, projectID, contentType, body, reqEditors...) + if err != nil { + return nil, err } - return "" + return ParseCreateServiceResponse(rsp) } -type RenameVPCResponse struct { - Body []byte - HTTPResponse *http.Response - // JSON200 the response for an HTTP 200 `application/json` response - JSON200 *SuccessMessage - // JSON4XX the response for an HTTP 4XX `application/json` response - JSON4XX *ClientError +// CreateServiceWithResponse Create a Service +// +// Creates a new database service within a project. This is an asynchronous operation. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/services (the `CreateService` operationId). +func (c *ClientWithResponses) CreateServiceWithResponse(ctx context.Context, projectID ProjectID, body CreateServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateServiceResponse, error) { + rsp, err := c.CreateService(ctx, projectID, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateServiceResponse(rsp) } -// GetJSON200 returns the response for an HTTP 200 `application/json` response -func (r RenameVPCResponse) GetJSON200() *SuccessMessage { - return r.JSON200 +// DeleteServiceWithResponse Delete a Service +// +// Deletes a specific service. This is an asynchronous operation. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with DELETE /projects/{project_id}/services/{service_id} (the `DeleteService` operationId). +func (c *ClientWithResponses) DeleteServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*DeleteServiceResponse, error) { + rsp, err := c.DeleteService(ctx, projectID, serviceID, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteServiceResponse(rsp) } -// GetJSON4XX returns the response for an HTTP 4XX `application/json` response -func (r RenameVPCResponse) GetJSON4XX() *ClientError { - return r.JSON4XX +// GetServiceWithResponse Get a Service +// +// Retrieves the details of a specific service by its ID. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects/{project_id}/services/{service_id} (the `GetService` operationId). +func (c *ClientWithResponses) GetServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetServiceResponse, error) { + rsp, err := c.GetService(ctx, projectID, serviceID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetServiceResponse(rsp) } -// GetBody returns the raw response body bytes -func (r RenameVPCResponse) GetBody() []byte { - return r.Body +// AttachServiceToAllowListWithBodyWithResponse Attach Service to IP Allow List +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Restricts a service's connections to the IP ranges in an IP allow list. +// A service has at most one IP allow list; attaching a second one +// without detaching the first is refused. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToAllowList (the `AttachServiceToAllowList` operationId). +func (c *ClientWithResponses) AttachServiceToAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToAllowListResponse, error) { + rsp, err := c.AttachServiceToAllowListWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseAttachServiceToAllowListResponse(rsp) } -// Status returns HTTPResponse.Status -func (r RenameVPCResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status +// AttachServiceToAllowListWithResponse Attach Service to IP Allow List +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Restricts a service's connections to the IP ranges in an IP allow list. +// A service has at most one IP allow list; attaching a second one +// without detaching the first is refused. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToAllowList (the `AttachServiceToAllowList` operationId). +func (c *ClientWithResponses) AttachServiceToAllowListWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToAllowListResponse, error) { + rsp, err := c.AttachServiceToAllowList(ctx, projectID, serviceID, body, reqEditors...) + if err != nil { + return nil, err } - return http.StatusText(0) + return ParseAttachServiceToAllowListResponse(rsp) } -// StatusCode returns HTTPResponse.StatusCode -func (r RenameVPCResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode +// AttachServiceToExporterWithBodyWithResponse Attach Service to Exporter +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Starts sending a service's logs or metrics to an exporter. +// +// A service sends to at most one log exporter and one metric exporter at +// a time. Attaching a second exporter of the same kind is refused; +// detach the current one first. +// +// The first time a log exporter is attached to a service, the service +// restarts once. Attaching a different log exporter later does not +// restart anything, and neither does attaching a metric exporter. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToExporter (the `AttachServiceToExporter` operationId). +func (c *ClientWithResponses) AttachServiceToExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToExporterResponse, error) { + rsp, err := c.AttachServiceToExporterWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) + if err != nil { + return nil, err } - return 0 + return ParseAttachServiceToExporterResponse(rsp) } -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r RenameVPCResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") +// AttachServiceToExporterWithResponse Attach Service to Exporter +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Starts sending a service's logs or metrics to an exporter. +// +// A service sends to at most one log exporter and one metric exporter at +// a time. Attaching a second exporter of the same kind is refused; +// detach the current one first. +// +// The first time a log exporter is attached to a service, the service +// restarts once. Attaching a different log exporter later does not +// restart anything, and neither does attaching a metric exporter. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToExporter (the `AttachServiceToExporter` operationId). +func (c *ClientWithResponses) AttachServiceToExporterWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToExporterResponse, error) { + rsp, err := c.AttachServiceToExporter(ctx, projectID, serviceID, body, reqEditors...) + if err != nil { + return nil, err } - return "" + return ParseAttachServiceToExporterResponse(rsp) } -// IdentifyUserWithBodyWithResponse Identify a user +// AttachServiceToVPCWithBodyWithResponse Attach Service to VPC // -// Identifies a user with optional properties for analytics tracking. +// Associates a service with a VPC. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /analytics/identify (the `IdentifyUser` operationId). -func (c *ClientWithResponses) IdentifyUserWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*IdentifyUserResponse, error) { - rsp, err := c.IdentifyUserWithBody(ctx, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToVPC (the `AttachServiceToVPC` operationId). +func (c *ClientWithResponses) AttachServiceToVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToVPCResponse, error) { + rsp, err := c.AttachServiceToVPCWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseIdentifyUserResponse(rsp) + return ParseAttachServiceToVPCResponse(rsp) } -// IdentifyUserWithResponse Identify a user +// AttachServiceToVPCWithResponse Attach Service to VPC // -// Identifies a user with optional properties for analytics tracking. +// Associates a service with a VPC. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /analytics/identify (the `IdentifyUser` operationId). -func (c *ClientWithResponses) IdentifyUserWithResponse(ctx context.Context, body IdentifyUserJSONRequestBody, reqEditors ...RequestEditorFn) (*IdentifyUserResponse, error) { - rsp, err := c.IdentifyUser(ctx, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToVPC (the `AttachServiceToVPC` operationId). +func (c *ClientWithResponses) AttachServiceToVPCWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToVPCResponse, error) { + rsp, err := c.AttachServiceToVPC(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseIdentifyUserResponse(rsp) + return ParseAttachServiceToVPCResponse(rsp) } -// TrackEventWithBodyWithResponse Track an analytics event +// GetBackupRegionsWithResponse List All Backup Regions // -// Tracks an analytics event with optional properties. +// **Preview - this endpoint is experimental and may change without notice.** +// +// Retrieves the additional regions this service's backups are copied to +// (cross-region backups). The region the service runs in is not listed. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects/{project_id}/services/{service_id}/backup-regions (the `GetBackupRegions` operationId). +func (c *ClientWithResponses) GetBackupRegionsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupRegionsResponse, error) { + rsp, err := c.GetBackupRegions(ctx, projectID, serviceID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetBackupRegionsResponse(rsp) +} + +// CreateBackupRegionWithBodyWithResponse Add a Backup Region +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Starts copying this service's backups to another region. The region is +// added immediately; existing backups are copied to it in the background. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /analytics/track (the `TrackEvent` operationId). -func (c *ClientWithResponses) TrackEventWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TrackEventResponse, error) { - rsp, err := c.TrackEventWithBody(ctx, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/backup-regions (the `CreateBackupRegion` operationId). +func (c *ClientWithResponses) CreateBackupRegionWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBackupRegionResponse, error) { + rsp, err := c.CreateBackupRegionWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseTrackEventResponse(rsp) + return ParseCreateBackupRegionResponse(rsp) } -// TrackEventWithResponse Track an analytics event +// CreateBackupRegionWithResponse Add a Backup Region // -// Tracks an analytics event with optional properties. +// **Preview - this endpoint is experimental and may change without notice.** +// +// Starts copying this service's backups to another region. The region is +// added immediately; existing backups are copied to it in the background. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /analytics/track (the `TrackEvent` operationId). -func (c *ClientWithResponses) TrackEventWithResponse(ctx context.Context, body TrackEventJSONRequestBody, reqEditors ...RequestEditorFn) (*TrackEventResponse, error) { - rsp, err := c.TrackEvent(ctx, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/backup-regions (the `CreateBackupRegion` operationId). +func (c *ClientWithResponses) CreateBackupRegionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateBackupRegionJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBackupRegionResponse, error) { + rsp, err := c.CreateBackupRegion(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseTrackEventResponse(rsp) + return ParseCreateBackupRegionResponse(rsp) } -// GetAuthInfoWithResponse Get Authentication Info +// DeleteBackupRegionWithResponse Remove a Backup Region // -// Returns the authenticated caller's identity. The response shape depends on the credential type: `apiKey` for PAT -// callers, `oauth` for OAuth user callers. Exactly one of `api_key` or `oauth` is populated; -// `type` discriminates. +// **Preview - this endpoint is experimental and may change without notice.** +// +// Stops copying this service's backups to a region. Copies already stored +// there are deleted in the background and cannot be recovered. // // Returns a wrapper object for the known response body format(s). // -// Corresponds with GET /auth/info (the `GetAuthInfo` operationId). -func (c *ClientWithResponses) GetAuthInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetAuthInfoResponse, error) { - rsp, err := c.GetAuthInfo(ctx, reqEditors...) +// Corresponds with DELETE /projects/{project_id}/services/{service_id}/backup-regions/{region_code} (the `DeleteBackupRegion` operationId). +func (c *ClientWithResponses) DeleteBackupRegionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, regionCode BackupRegionCode, reqEditors ...RequestEditorFn) (*DeleteBackupRegionResponse, error) { + rsp, err := c.DeleteBackupRegion(ctx, projectID, serviceID, regionCode, reqEditors...) if err != nil { return nil, err } - return ParseGetAuthInfoResponse(rsp) + return ParseDeleteBackupRegionResponse(rsp) } -// LogoutWithBodyWithResponse Log out and revoke refresh token +// GetBackupRetentionWithResponse Get the Backup Retention Policy // -// Revokes the caller's refresh token server-side. For OAuth Bearer -// callers, include the refresh token in the request body — it is held -// client-side and cannot be derived from the access token. Callers -// without a refresh token to revoke may post an -// empty body to clear server-side session state best-effort. +// **Preview - this endpoint is experimental and may change without notice.** // -// Returns 204 No Content on success. The caller is still responsible -// for dropping any locally-cached credentials. +// Retrieves how long this service keeps its backups. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects/{project_id}/services/{service_id}/backup-retention (the `GetBackupRetention` operationId). +func (c *ClientWithResponses) GetBackupRetentionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupRetentionResponse, error) { + rsp, err := c.GetBackupRetention(ctx, projectID, serviceID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetBackupRetentionResponse(rsp) +} + +// SetBackupRetentionWithBodyWithResponse Set the Backup Retention Policy +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Sets how long this service keeps its backups. Shortening retention +// deletes the backups that no longer fit. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /auth/logout (the `Logout` operationId). -func (c *ClientWithResponses) LogoutWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LogoutResponse, error) { - rsp, err := c.LogoutWithBody(ctx, contentType, body, reqEditors...) +// Corresponds with PUT /projects/{project_id}/services/{service_id}/backup-retention (the `SetBackupRetention` operationId). +func (c *ClientWithResponses) SetBackupRetentionWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetBackupRetentionResponse, error) { + rsp, err := c.SetBackupRetentionWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseLogoutResponse(rsp) + return ParseSetBackupRetentionResponse(rsp) } -// LogoutWithResponse Log out and revoke refresh token +// SetBackupRetentionWithResponse Set the Backup Retention Policy // -// Revokes the caller's refresh token server-side. For OAuth Bearer -// callers, include the refresh token in the request body — it is held -// client-side and cannot be derived from the access token. Callers -// without a refresh token to revoke may post an -// empty body to clear server-side session state best-effort. +// **Preview - this endpoint is experimental and may change without notice.** // -// Returns 204 No Content on success. The caller is still responsible -// for dropping any locally-cached credentials. +// Sets how long this service keeps its backups. Shortening retention +// deletes the backups that no longer fit. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /auth/logout (the `Logout` operationId). -func (c *ClientWithResponses) LogoutWithResponse(ctx context.Context, body LogoutJSONRequestBody, reqEditors ...RequestEditorFn) (*LogoutResponse, error) { - rsp, err := c.Logout(ctx, body, reqEditors...) +// Corresponds with PUT /projects/{project_id}/services/{service_id}/backup-retention (the `SetBackupRetention` operationId). +func (c *ClientWithResponses) SetBackupRetentionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetBackupRetentionJSONRequestBody, reqEditors ...RequestEditorFn) (*SetBackupRetentionResponse, error) { + rsp, err := c.SetBackupRetention(ctx, projectID, serviceID, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseSetBackupRetentionResponse(rsp) +} + +// GetBackupsWithResponse List All Backups +// +// **Preview - this endpoint is experimental and may change without notice.** +// +// Retrieves a list of the full and incremental backups taken for a +// service. Backups run automatically on a schedule; there is no operation +// to create or delete one. To restore data, create a recovery fork with +// `fork_strategy: PITR`. +// +// Returns a wrapper object for the known response body format(s). +// +// Corresponds with GET /projects/{project_id}/services/{service_id}/backups (the `GetBackups` operationId). +func (c *ClientWithResponses) GetBackupsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupsResponse, error) { + rsp, err := c.GetBackups(ctx, projectID, serviceID, reqEditors...) if err != nil { return nil, err } - return ParseLogoutResponse(rsp) + return ParseGetBackupsResponse(rsp) } -// SubmitFeedbackWithBodyWithResponse Submit feedback +// GetBranchesWithResponse List All Branches // -// Sends feedback, a bug report, or a support request to Tiger Data. -// -// The submitter's email address is resolved from the caller's -// credentials, and the client version and operating system from the -// `User-Agent` header, so neither is supplied in the body. +// **Preview - this endpoint is experimental and may change without notice.** // -// Returns 204 No Content on success. This endpoint opens no support -// case and returns no ticket to track: use it for unsolicited feedback, -// and raise anything needing a tracked response through support. +// Retrieves a list of all branches created from a service within a project. // -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with POST /feedback (the `SubmitFeedback` operationId). -func (c *ClientWithResponses) SubmitFeedbackWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SubmitFeedbackResponse, error) { - rsp, err := c.SubmitFeedbackWithBody(ctx, contentType, body, reqEditors...) +// Corresponds with GET /projects/{project_id}/services/{service_id}/branches (the `GetBranches` operationId). +func (c *ClientWithResponses) GetBranchesWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, params *GetBranchesParams, reqEditors ...RequestEditorFn) (*GetBranchesResponse, error) { + rsp, err := c.GetBranches(ctx, projectID, serviceID, params, reqEditors...) if err != nil { return nil, err } - return ParseSubmitFeedbackResponse(rsp) + return ParseGetBranchesResponse(rsp) } -// SubmitFeedbackWithResponse Submit feedback +// CreateBranchWithBodyWithResponse Create a Branch // -// Sends feedback, a bug report, or a support request to Tiger Data. -// -// The submitter's email address is resolved from the caller's -// credentials, and the client version and operating system from the -// `User-Agent` header, so neither is supplied in the body. +// **Preview - this endpoint is experimental and may change without notice.** // -// Returns 204 No Content on success. This endpoint opens no support -// case and returns no ticket to track: use it for unsolicited feedback, -// and raise anything needing a tracked response through support. +// Creates a new branch from the service identified by `service_id`. A +// branch shares its parent's data at the moment of creation and gets +// its own, independently-sized compute. This is an asynchronous +// operation. // -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /feedback (the `SubmitFeedback` operationId). -func (c *ClientWithResponses) SubmitFeedbackWithResponse(ctx context.Context, body SubmitFeedbackJSONRequestBody, reqEditors ...RequestEditorFn) (*SubmitFeedbackResponse, error) { - rsp, err := c.SubmitFeedback(ctx, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches (the `CreateBranch` operationId). +func (c *ClientWithResponses) CreateBranchWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { + rsp, err := c.CreateBranchWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseSubmitFeedbackResponse(rsp) + return ParseCreateBranchResponse(rsp) } -// GetProjectsWithResponse List projects accessible to the authenticated caller +// CreateBranchWithResponse Create a Branch // -// Returns the projects the caller can access. PAT callers see only the -// project their PAT is scoped to; OAuth user callers see all projects -// they belong to. +// **Preview - this endpoint is experimental and may change without notice.** // -// Returns a wrapper object for the known response body format(s). +// Creates a new branch from the service identified by `service_id`. A +// branch shares its parent's data at the moment of creation and gets +// its own, independently-sized compute. This is an asynchronous +// operation. // -// Corresponds with GET /projects (the `GetProjects` operationId). -func (c *ClientWithResponses) GetProjectsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetProjectsResponse, error) { - rsp, err := c.GetProjects(ctx, reqEditors...) +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches (the `CreateBranch` operationId). +func (c *ClientWithResponses) CreateBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBranchResponse, error) { + rsp, err := c.CreateBranch(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseGetProjectsResponse(rsp) + return ParseCreateBranchResponse(rsp) } -// GetAllowListsWithResponse List All IP Allow Lists +// DeleteBranchWithResponse Delete a Branch // // **Preview - this endpoint is experimental and may change without notice.** // -// Retrieves every IP allow list in a project. +// Deletes a specific branch. This is an asynchronous operation. // // Returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/allow-lists (the `GetAllowLists` operationId). -func (c *ClientWithResponses) GetAllowListsWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetAllowListsResponse, error) { - rsp, err := c.GetAllowLists(ctx, projectID, reqEditors...) +// Corresponds with DELETE /projects/{project_id}/services/{service_id}/branches/{branch_id} (the `DeleteBranch` operationId). +func (c *ClientWithResponses) DeleteBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*DeleteBranchResponse, error) { + rsp, err := c.DeleteBranch(ctx, projectID, serviceID, branchID, reqEditors...) if err != nil { return nil, err } - return ParseGetAllowListsResponse(rsp) + return ParseDeleteBranchResponse(rsp) } -// CreateAllowListWithBodyWithResponse Create an IP Allow List +// GetBranchWithResponse Get a Branch // // **Preview - this endpoint is experimental and may change without notice.** // -// Creates an IP allow list in a project. An IP allow list carries no -// effect until it is attached to a service. -// -// The number of CIDR blocks per list and the number of lists per -// project are plan-dependent. +// Retrieves the details of a specific branch by its ID. // -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/allow-lists (the `CreateAllowList` operationId). -func (c *ClientWithResponses) CreateAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateAllowListResponse, error) { - rsp, err := c.CreateAllowListWithBody(ctx, projectID, contentType, body, reqEditors...) +// Corresponds with GET /projects/{project_id}/services/{service_id}/branches/{branch_id} (the `GetBranch` operationId). +func (c *ClientWithResponses) GetBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*GetBranchResponse, error) { + rsp, err := c.GetBranch(ctx, projectID, serviceID, branchID, reqEditors...) if err != nil { return nil, err } - return ParseCreateAllowListResponse(rsp) + return ParseGetBranchResponse(rsp) } -// CreateAllowListWithResponse Create an IP Allow List +// RenameBranchWithBodyWithResponse Rename a Branch // // **Preview - this endpoint is experimental and may change without notice.** // -// Creates an IP allow list in a project. An IP allow list carries no -// effect until it is attached to a service. -// -// The number of CIDR blocks per list and the number of lists per -// project are plan-dependent. +// Updates the name of a specific branch. // -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/allow-lists (the `CreateAllowList` operationId). -func (c *ClientWithResponses) CreateAllowListWithResponse(ctx context.Context, projectID ProjectID, body CreateAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateAllowListResponse, error) { - rsp, err := c.CreateAllowList(ctx, projectID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/rename (the `RenameBranch` operationId). +func (c *ClientWithResponses) RenameBranchWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RenameBranchResponse, error) { + rsp, err := c.RenameBranchWithBody(ctx, projectID, serviceID, branchID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseCreateAllowListResponse(rsp) + return ParseRenameBranchResponse(rsp) } -// DeleteAllowListWithResponse Delete an IP Allow List +// RenameBranchWithResponse Rename a Branch // // **Preview - this endpoint is experimental and may change without notice.** // -// Deletes an IP allow list. Refused if the list is still attached to -// any service. Detach it first. +// Updates the name of a specific branch. // -// Returns a wrapper object for the known response body format(s). +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with DELETE /projects/{project_id}/allow-lists/{allow_list_id} (the `DeleteAllowList` operationId). -func (c *ClientWithResponses) DeleteAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, reqEditors ...RequestEditorFn) (*DeleteAllowListResponse, error) { - rsp, err := c.DeleteAllowList(ctx, projectID, allowListID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/rename (the `RenameBranch` operationId). +func (c *ClientWithResponses) RenameBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body RenameBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*RenameBranchResponse, error) { + rsp, err := c.RenameBranch(ctx, projectID, serviceID, branchID, body, reqEditors...) if err != nil { return nil, err } - return ParseDeleteAllowListResponse(rsp) + return ParseRenameBranchResponse(rsp) } -// GetAllowListWithResponse Get an IP Allow List +// ResizeBranchWithBodyWithResponse Resize a Branch // // **Preview - this endpoint is experimental and may change without notice.** // -// Retrieves a single IP allow list. +// Changes the compute allocation for a specific branch. +// Storage size is shared with the parent and is not resized through +// this operation. // -// Returns a wrapper object for the known response body format(s). +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/allow-lists/{allow_list_id} (the `GetAllowList` operationId). -func (c *ClientWithResponses) GetAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, reqEditors ...RequestEditorFn) (*GetAllowListResponse, error) { - rsp, err := c.GetAllowList(ctx, projectID, allowListID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/resize (the `ResizeBranch` operationId). +func (c *ClientWithResponses) ResizeBranchWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ResizeBranchResponse, error) { + rsp, err := c.ResizeBranchWithBody(ctx, projectID, serviceID, branchID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseGetAllowListResponse(rsp) + return ParseResizeBranchResponse(rsp) } -// UpdateAllowListWithBodyWithResponse Update an IP Allow List +// ResizeBranchWithResponse Resize a Branch // // **Preview - this endpoint is experimental and may change without notice.** // -// Updates an IP allow list in place. Send `description` and/or -// `cidr_blocks` to change them; omit either one to leave it -// unchanged. -// -// Every service the IP allow list is attached to is updated with the -// new CIDR blocks. To remove every CIDR block, delete the IP allow -// list instead of sending an empty `cidr_blocks`. +// Changes the compute allocation for a specific branch. +// Storage size is shared with the parent and is not resized through +// this operation. // -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with PATCH /projects/{project_id}/allow-lists/{allow_list_id} (the `UpdateAllowList` operationId). -func (c *ClientWithResponses) UpdateAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateAllowListResponse, error) { - rsp, err := c.UpdateAllowListWithBody(ctx, projectID, allowListID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/resize (the `ResizeBranch` operationId). +func (c *ClientWithResponses) ResizeBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body ResizeBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*ResizeBranchResponse, error) { + rsp, err := c.ResizeBranch(ctx, projectID, serviceID, branchID, body, reqEditors...) if err != nil { return nil, err } - return ParseUpdateAllowListResponse(rsp) + return ParseResizeBranchResponse(rsp) } -// UpdateAllowListWithResponse Update an IP Allow List +// StartBranchWithResponse Start a Branch // // **Preview - this endpoint is experimental and may change without notice.** // -// Updates an IP allow list in place. Send `description` and/or -// `cidr_blocks` to change them; omit either one to leave it -// unchanged. -// -// Every service the IP allow list is attached to is updated with the -// new CIDR blocks. To remove every CIDR block, delete the IP allow -// list instead of sending an empty `cidr_blocks`. +// Starts a stopped branch. This is an asynchronous operation. // -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with PATCH /projects/{project_id}/allow-lists/{allow_list_id} (the `UpdateAllowList` operationId). -func (c *ClientWithResponses) UpdateAllowListWithResponse(ctx context.Context, projectID ProjectID, allowListID AllowListID, body UpdateAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateAllowListResponse, error) { - rsp, err := c.UpdateAllowList(ctx, projectID, allowListID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/start (the `StartBranch` operationId). +func (c *ClientWithResponses) StartBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*StartBranchResponse, error) { + rsp, err := c.StartBranch(ctx, projectID, serviceID, branchID, reqEditors...) if err != nil { return nil, err } - return ParseUpdateAllowListResponse(rsp) + return ParseStartBranchResponse(rsp) } -// GetExportersWithResponse List All Exporters +// StopBranchWithResponse Stop a Branch // // **Preview - this endpoint is experimental and may change without notice.** // -// Retrieves every exporter in a project, across every region. +// Stops a running branch. This is an asynchronous operation. // // Returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/exporters (the `GetExporters` operationId). -func (c *ClientWithResponses) GetExportersWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetExportersResponse, error) { - rsp, err := c.GetExporters(ctx, projectID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/stop (the `StopBranch` operationId). +func (c *ClientWithResponses) StopBranchWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, reqEditors ...RequestEditorFn) (*StopBranchResponse, error) { + rsp, err := c.StopBranch(ctx, projectID, serviceID, branchID, reqEditors...) if err != nil { return nil, err } - return ParseGetExportersResponse(rsp) + return ParseStopBranchResponse(rsp) } -// CreateExporterWithBodyWithResponse Create an Exporter +// UpdateBranchPasswordWithBodyWithResponse Update Branch Password // // **Preview - this endpoint is experimental and may change without notice.** // -// Creates an exporter in a project and region. -// -// An exporter carries no data until it is attached to a service. -// -// Exporters aren't available on every plan. If your project's plan -// doesn't include them, this request is refused here rather than later, -// when you try to attach one. +// Sets a new master password for the branch. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/exporters (the `CreateExporter` operationId). -func (c *ClientWithResponses) CreateExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateExporterResponse, error) { - rsp, err := c.CreateExporterWithBody(ctx, projectID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/updatePassword (the `UpdateBranchPassword` operationId). +func (c *ClientWithResponses) UpdateBranchPasswordWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateBranchPasswordResponse, error) { + rsp, err := c.UpdateBranchPasswordWithBody(ctx, projectID, serviceID, branchID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseCreateExporterResponse(rsp) + return ParseUpdateBranchPasswordResponse(rsp) } -// CreateExporterWithResponse Create an Exporter +// UpdateBranchPasswordWithResponse Update Branch Password // // **Preview - this endpoint is experimental and may change without notice.** // -// Creates an exporter in a project and region. -// -// An exporter carries no data until it is attached to a service. -// -// Exporters aren't available on every plan. If your project's plan -// doesn't include them, this request is refused here rather than later, -// when you try to attach one. +// Sets a new master password for the branch. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/exporters (the `CreateExporter` operationId). -func (c *ClientWithResponses) CreateExporterWithResponse(ctx context.Context, projectID ProjectID, body CreateExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateExporterResponse, error) { - rsp, err := c.CreateExporter(ctx, projectID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/branches/{branch_id}/updatePassword (the `UpdateBranchPassword` operationId). +func (c *ClientWithResponses) UpdateBranchPasswordWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, branchID BranchID, body UpdateBranchPasswordJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateBranchPasswordResponse, error) { + rsp, err := c.UpdateBranchPassword(ctx, projectID, serviceID, branchID, body, reqEditors...) if err != nil { return nil, err } - return ParseCreateExporterResponse(rsp) + return ParseUpdateBranchPasswordResponse(rsp) } -// DeleteExporterWithResponse Delete an Exporter +// DetachServiceFromAllowListWithBodyWithResponse Detach Service from IP Allow List // // **Preview - this endpoint is experimental and may change without notice.** // -// Deletes an exporter and stops the data it was sending. Any service -// still attached is detached automatically. You don't need to detach it -// first, and nothing errors if you don't. -// -// Data already delivered to the destination is untouched, and nothing on -// the services themselves changes. +// Removes the IP restriction an IP allow list placed on a service. The +// IP allow list itself is not deleted. // -// Returns a wrapper object for the known response body format(s). +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with DELETE /projects/{project_id}/exporters/{exporter_id} (the `DeleteExporter` operationId). -func (c *ClientWithResponses) DeleteExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, reqEditors ...RequestEditorFn) (*DeleteExporterResponse, error) { - rsp, err := c.DeleteExporter(ctx, projectID, exporterID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). +func (c *ClientWithResponses) DetachServiceFromAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromAllowListResponse, error) { + rsp, err := c.DetachServiceFromAllowListWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseDeleteExporterResponse(rsp) + return ParseDetachServiceFromAllowListResponse(rsp) } -// GetExporterWithResponse Get an Exporter +// DetachServiceFromAllowListWithResponse Detach Service from IP Allow List // // **Preview - this endpoint is experimental and may change without notice.** // -// Retrieves a single exporter. +// Removes the IP restriction an IP allow list placed on a service. The +// IP allow list itself is not deleted. // -// Returns a wrapper object for the known response body format(s). +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/exporters/{exporter_id} (the `GetExporter` operationId). -func (c *ClientWithResponses) GetExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, reqEditors ...RequestEditorFn) (*GetExporterResponse, error) { - rsp, err := c.GetExporter(ctx, projectID, exporterID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). +func (c *ClientWithResponses) DetachServiceFromAllowListWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromAllowListResponse, error) { + rsp, err := c.DetachServiceFromAllowList(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseGetExporterResponse(rsp) + return ParseDetachServiceFromAllowListResponse(rsp) } -// UpdateExporterWithBodyWithResponse Update an Exporter +// DetachServiceFromExporterWithBodyWithResponse Detach Service from Exporter // // **Preview - this endpoint is experimental and may change without notice.** // -// Updates an exporter in place. Send `config` to replace the whole -// configuration; omit it to leave the configuration unchanged. -// -// Every service the exporter is attached to picks up the change; there -// is no need to detach and reattach. +// Stops sending a service's logs or metrics to an exporter. The exporter +// is not deleted and keeps serving any other service attached to it. // -// `type` must be included and must match the exporter's existing type; -// it cannot be changed. To send data somewhere else, create a new -// exporter and move the services over. +// Data already delivered to the destination is untouched. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with PATCH /projects/{project_id}/exporters/{exporter_id} (the `UpdateExporter` operationId). -func (c *ClientWithResponses) UpdateExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateExporterResponse, error) { - rsp, err := c.UpdateExporterWithBody(ctx, projectID, exporterID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). +func (c *ClientWithResponses) DetachServiceFromExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromExporterResponse, error) { + rsp, err := c.DetachServiceFromExporterWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseUpdateExporterResponse(rsp) + return ParseDetachServiceFromExporterResponse(rsp) } -// UpdateExporterWithResponse Update an Exporter +// DetachServiceFromExporterWithResponse Detach Service from Exporter // // **Preview - this endpoint is experimental and may change without notice.** // -// Updates an exporter in place. Send `config` to replace the whole -// configuration; omit it to leave the configuration unchanged. -// -// Every service the exporter is attached to picks up the change; there -// is no need to detach and reattach. +// Stops sending a service's logs or metrics to an exporter. The exporter +// is not deleted and keeps serving any other service attached to it. // -// `type` must be included and must match the exporter's existing type; -// it cannot be changed. To send data somewhere else, create a new -// exporter and move the services over. +// Data already delivered to the destination is untouched. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with PATCH /projects/{project_id}/exporters/{exporter_id} (the `UpdateExporter` operationId). -func (c *ClientWithResponses) UpdateExporterWithResponse(ctx context.Context, projectID ProjectID, exporterID ExporterID, body UpdateExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateExporterResponse, error) { - rsp, err := c.UpdateExporter(ctx, projectID, exporterID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). +func (c *ClientWithResponses) DetachServiceFromExporterWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromExporterResponse, error) { + rsp, err := c.DetachServiceFromExporter(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseUpdateExporterResponse(rsp) + return ParseDetachServiceFromExporterResponse(rsp) } -// GetServicesWithResponse List All Services +// DetachServiceFromVPCWithBodyWithResponse Detach Service from VPC // -// Retrieves a list of all services within a specific project. +// Disassociates a service from its VPC. // -// Returns a wrapper object for the known response body format(s). +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/services (the `GetServices` operationId). -func (c *ClientWithResponses) GetServicesWithResponse(ctx context.Context, projectID ProjectID, params *GetServicesParams, reqEditors ...RequestEditorFn) (*GetServicesResponse, error) { - rsp, err := c.GetServices(ctx, projectID, params, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). +func (c *ClientWithResponses) DetachServiceFromVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromVPCResponse, error) { + rsp, err := c.DetachServiceFromVPCWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseGetServicesResponse(rsp) + return ParseDetachServiceFromVPCResponse(rsp) } -// CreateServiceWithBodyWithResponse Create a Service +// DetachServiceFromVPCWithResponse Detach Service from VPC // -// Creates a new database service within a project. This is an asynchronous operation. +// Disassociates a service from its VPC. // -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services (the `CreateService` operationId). -func (c *ClientWithResponses) CreateServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateServiceResponse, error) { - rsp, err := c.CreateServiceWithBody(ctx, projectID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). +func (c *ClientWithResponses) DetachServiceFromVPCWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromVPCResponse, error) { + rsp, err := c.DetachServiceFromVPC(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseCreateServiceResponse(rsp) + return ParseDetachServiceFromVPCResponse(rsp) } -// CreateServiceWithResponse Create a Service +// DisablePoolerWithResponse Disable Connection Pooler for a Service // -// Creates a new database service within a project. This is an asynchronous operation. +// Deactivates the connection pooler for a specific service within a project. // -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services (the `CreateService` operationId). -func (c *ClientWithResponses) CreateServiceWithResponse(ctx context.Context, projectID ProjectID, body CreateServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateServiceResponse, error) { - rsp, err := c.CreateService(ctx, projectID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/disablePooler (the `DisablePooler` operationId). +func (c *ClientWithResponses) DisablePoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*DisablePoolerResponse, error) { + rsp, err := c.DisablePooler(ctx, projectID, serviceID, reqEditors...) if err != nil { return nil, err } - return ParseCreateServiceResponse(rsp) + return ParseDisablePoolerResponse(rsp) } -// DeleteServiceWithResponse Delete a Service +// EnableDataTieringWithResponse Enable Data Tiering for a Service // -// Deletes a specific service. This is an asynchronous operation. +// Enables tiered storage on the service. Tiering policies move older, +// rarely-accessed data from the high-performance storage tier to a +// low-cost object storage tier. Tiered data stays queryable but becomes +// read-only. +// +// There is no way to disable tiering through this API. Contact Tiger +// Data support if you need to turn it off. // // Returns a wrapper object for the known response body format(s). // -// Corresponds with DELETE /projects/{project_id}/services/{service_id} (the `DeleteService` operationId). -func (c *ClientWithResponses) DeleteServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*DeleteServiceResponse, error) { - rsp, err := c.DeleteService(ctx, projectID, serviceID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/enableDataTiering (the `EnableDataTiering` operationId). +func (c *ClientWithResponses) EnableDataTieringWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*EnableDataTieringResponse, error) { + rsp, err := c.EnableDataTiering(ctx, projectID, serviceID, reqEditors...) if err != nil { return nil, err } - return ParseDeleteServiceResponse(rsp) + return ParseEnableDataTieringResponse(rsp) } -// GetServiceWithResponse Get a Service +// EnablePoolerWithResponse Enable Connection Pooler for a Service // -// Retrieves the details of a specific service by its ID. +// Activates the connection pooler for a specific service within a project. // // Returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/services/{service_id} (the `GetService` operationId). -func (c *ClientWithResponses) GetServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetServiceResponse, error) { - rsp, err := c.GetService(ctx, projectID, serviceID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/enablePooler (the `EnablePooler` operationId). +func (c *ClientWithResponses) EnablePoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*EnablePoolerResponse, error) { + rsp, err := c.EnablePooler(ctx, projectID, serviceID, reqEditors...) if err != nil { return nil, err } - return ParseGetServiceResponse(rsp) + return ParseEnablePoolerResponse(rsp) } -// AttachServiceToAllowListWithBodyWithResponse Attach Service to IP Allow List -// -// **Preview - this endpoint is experimental and may change without notice.** +// ForkServiceWithBodyWithResponse Fork a Service // -// Restricts a service's connections to the IP ranges in an IP allow list. -// A service has at most one IP allow list; attaching a second one -// without detaching the first is refused. +// Creates a new, independent service within a project by taking a snapshot of an existing one. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToAllowList (the `AttachServiceToAllowList` operationId). -func (c *ClientWithResponses) AttachServiceToAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToAllowListResponse, error) { - rsp, err := c.AttachServiceToAllowListWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). +func (c *ClientWithResponses) ForkServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ForkServiceResponse, error) { + rsp, err := c.ForkServiceWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseAttachServiceToAllowListResponse(rsp) + return ParseForkServiceResponse(rsp) } -// AttachServiceToAllowListWithResponse Attach Service to IP Allow List -// -// **Preview - this endpoint is experimental and may change without notice.** +// ForkServiceWithResponse Fork a Service // -// Restricts a service's connections to the IP ranges in an IP allow list. -// A service has at most one IP allow list; attaching a second one -// without detaching the first is refused. +// Creates a new, independent service within a project by taking a snapshot of an existing one. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToAllowList (the `AttachServiceToAllowList` operationId). -func (c *ClientWithResponses) AttachServiceToAllowListWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToAllowListResponse, error) { - rsp, err := c.AttachServiceToAllowList(ctx, projectID, serviceID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). +func (c *ClientWithResponses) ForkServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ForkServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*ForkServiceResponse, error) { + rsp, err := c.ForkService(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseAttachServiceToAllowListResponse(rsp) + return ParseForkServiceResponse(rsp) } -// AttachServiceToExporterWithBodyWithResponse Attach Service to Exporter -// -// **Preview - this endpoint is experimental and may change without notice.** -// -// Starts sending a service's logs or metrics to an exporter. +// GetServiceLogsWithResponse Get service logs // -// A service sends to at most one log exporter and one metric exporter at -// a time. Attaching a second exporter of the same kind is refused; -// detach the current one first. +// Fetch logs for a specific service. Returns up to 500 log entries in reverse +// chronological order. Supports cursor-based pagination via the `cursor` parameter +// and the `last_cursor` field in the response. // -// The first time a log exporter is attached to a service, the service -// restarts once. Attaching a different log exporter later does not -// restart anything, and neither does attaching a metric exporter. +// Use the returned `last_cursor` value as the `cursor` parameter on subsequent +// requests to page through all available logs. // -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToExporter (the `AttachServiceToExporter` operationId). -func (c *ClientWithResponses) AttachServiceToExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToExporterResponse, error) { - rsp, err := c.AttachServiceToExporterWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with GET /projects/{project_id}/services/{service_id}/logs (the `GetServiceLogs` operationId). +func (c *ClientWithResponses) GetServiceLogsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, params *GetServiceLogsParams, reqEditors ...RequestEditorFn) (*GetServiceLogsResponse, error) { + rsp, err := c.GetServiceLogs(ctx, projectID, serviceID, params, reqEditors...) if err != nil { return nil, err } - return ParseAttachServiceToExporterResponse(rsp) + return ParseGetServiceLogsResponse(rsp) } -// AttachServiceToExporterWithResponse Attach Service to Exporter -// -// **Preview - this endpoint is experimental and may change without notice.** -// -// Starts sending a service's logs or metrics to an exporter. -// -// A service sends to at most one log exporter and one metric exporter at -// a time. Attaching a second exporter of the same kind is refused; -// detach the current one first. +// GetServiceMetricsAvailableSeriesWithResponse List available metric series // -// The first time a log exporter is attached to a service, the service -// restarts once. Attaching a different log exporter later does not -// restart anything, and neither does attaching a metric exporter. +// Returns the names of all metric series available for a service. // -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToExporter (the `AttachServiceToExporter` operationId). -func (c *ClientWithResponses) AttachServiceToExporterWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToExporterResponse, error) { - rsp, err := c.AttachServiceToExporter(ctx, projectID, serviceID, body, reqEditors...) +// Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/available-series (the `GetServiceMetricsAvailableSeries` operationId). +func (c *ClientWithResponses) GetServiceMetricsAvailableSeriesWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetServiceMetricsAvailableSeriesResponse, error) { + rsp, err := c.GetServiceMetricsAvailableSeries(ctx, projectID, serviceID, reqEditors...) if err != nil { return nil, err } - return ParseAttachServiceToExporterResponse(rsp) + return ParseGetServiceMetricsAvailableSeriesResponse(rsp) } -// AttachServiceToVPCWithBodyWithResponse Attach Service to VPC +// GetServiceMetricDetailsWithResponse Get details about a metric // -// Associates a service with a VPC. +// Returns descriptive metadata for a named metric: what it measures, +// its type, default aggregation, and every label it can be filtered or +// grouped by — its own labels plus the region/role/ordinal labels most +// metrics also carry. Use getServiceMetricsAvailableSeries to discover +// valid metric names. // -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToVPC (the `AttachServiceToVPC` operationId). -func (c *ClientWithResponses) AttachServiceToVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachServiceToVPCResponse, error) { - rsp, err := c.AttachServiceToVPCWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/details/{metric_name} (the `GetServiceMetricDetails` operationId). +func (c *ClientWithResponses) GetServiceMetricDetailsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, metricName string, reqEditors ...RequestEditorFn) (*GetServiceMetricDetailsResponse, error) { + rsp, err := c.GetServiceMetricDetails(ctx, projectID, serviceID, metricName, reqEditors...) if err != nil { return nil, err } - return ParseAttachServiceToVPCResponse(rsp) + return ParseGetServiceMetricDetailsResponse(rsp) } -// AttachServiceToVPCWithResponse Attach Service to VPC +// GetServiceMetricsSeriesWithBodyWithResponse Get a metric series // -// Associates a service with a VPC. +// Returns time-series data points for a named metric within a time window. +// Use getServiceMetricsAvailableSeries to discover valid metric names. // -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// Parameters are sent as a JSON body so callers can pass an unbounded +// filter set without query-string length pressure. The response is a list +// of MetricSeries, each carrying its label set as a structured map +// alongside its per-bucket data points. // -// Corresponds with POST /projects/{project_id}/services/{service_id}/attachToVPC (the `AttachServiceToVPC` operationId). -func (c *ClientWithResponses) AttachServiceToVPCWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body AttachServiceToVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachServiceToVPCResponse, error) { - rsp, err := c.AttachServiceToVPC(ctx, projectID, serviceID, body, reqEditors...) +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). +func (c *ClientWithResponses) GetServiceMetricsSeriesWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*GetServiceMetricsSeriesResponse, error) { + rsp, err := c.GetServiceMetricsSeriesWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseAttachServiceToVPCResponse(rsp) + return ParseGetServiceMetricsSeriesResponse(rsp) } -// GetBackupRegionsWithResponse List All Backup Regions +// GetServiceMetricsSeriesWithResponse Get a metric series // -// **Preview - this endpoint is experimental and may change without notice.** +// Returns time-series data points for a named metric within a time window. +// Use getServiceMetricsAvailableSeries to discover valid metric names. // -// Retrieves the additional regions this service's backups are copied to -// (cross-region backups). The region the service runs in is not listed. +// Parameters are sent as a JSON body so callers can pass an unbounded +// filter set without query-string length pressure. The response is a list +// of MetricSeries, each carrying its label set as a structured map +// alongside its per-bucket data points. // -// Returns a wrapper object for the known response body format(s). +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/services/{service_id}/backup-regions (the `GetBackupRegions` operationId). -func (c *ClientWithResponses) GetBackupRegionsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupRegionsResponse, error) { - rsp, err := c.GetBackupRegions(ctx, projectID, serviceID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). +func (c *ClientWithResponses) GetServiceMetricsSeriesWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body GetServiceMetricsSeriesJSONRequestBody, reqEditors ...RequestEditorFn) (*GetServiceMetricsSeriesResponse, error) { + rsp, err := c.GetServiceMetricsSeries(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseGetBackupRegionsResponse(rsp) + return ParseGetServiceMetricsSeriesResponse(rsp) } -// CreateBackupRegionWithBodyWithResponse Add a Backup Region -// -// **Preview - this endpoint is experimental and may change without notice.** +// RenameServiceWithBodyWithResponse Rename a Service // -// Starts copying this service's backups to another region. The region is -// added immediately; existing backups are copied to it in the background. +// Updates the name of a specific service. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/backup-regions (the `CreateBackupRegion` operationId). -func (c *ClientWithResponses) CreateBackupRegionWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateBackupRegionResponse, error) { - rsp, err := c.CreateBackupRegionWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). +func (c *ClientWithResponses) RenameServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RenameServiceResponse, error) { + rsp, err := c.RenameServiceWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseCreateBackupRegionResponse(rsp) + return ParseRenameServiceResponse(rsp) } -// CreateBackupRegionWithResponse Add a Backup Region -// -// **Preview - this endpoint is experimental and may change without notice.** +// RenameServiceWithResponse Rename a Service // -// Starts copying this service's backups to another region. The region is -// added immediately; existing backups are copied to it in the background. +// Updates the name of a specific service. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/backup-regions (the `CreateBackupRegion` operationId). -func (c *ClientWithResponses) CreateBackupRegionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateBackupRegionJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateBackupRegionResponse, error) { - rsp, err := c.CreateBackupRegion(ctx, projectID, serviceID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). +func (c *ClientWithResponses) RenameServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body RenameServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*RenameServiceResponse, error) { + rsp, err := c.RenameService(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseCreateBackupRegionResponse(rsp) + return ParseRenameServiceResponse(rsp) } -// DeleteBackupRegionWithResponse Remove a Backup Region -// -// **Preview - this endpoint is experimental and may change without notice.** +// GetReplicaSetsWithResponse Get Read Replica Sets // -// Stops copying this service's backups to a region. Copies already stored -// there are deleted in the background and cannot be recovered. +// Retrieves a list of all read replica sets associated with a primary service within a project. // // Returns a wrapper object for the known response body format(s). // -// Corresponds with DELETE /projects/{project_id}/services/{service_id}/backup-regions/{region_code} (the `DeleteBackupRegion` operationId). -func (c *ClientWithResponses) DeleteBackupRegionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, regionCode BackupRegionCode, reqEditors ...RequestEditorFn) (*DeleteBackupRegionResponse, error) { - rsp, err := c.DeleteBackupRegion(ctx, projectID, serviceID, regionCode, reqEditors...) +// Corresponds with GET /projects/{project_id}/services/{service_id}/replicaSets (the `GetReplicaSets` operationId). +func (c *ClientWithResponses) GetReplicaSetsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetReplicaSetsResponse, error) { + rsp, err := c.GetReplicaSets(ctx, projectID, serviceID, reqEditors...) if err != nil { return nil, err } - return ParseDeleteBackupRegionResponse(rsp) + return ParseGetReplicaSetsResponse(rsp) } -// GetBackupRetentionWithResponse Get the Backup Retention Policy +// CreateReplicaSetWithBodyWithResponse Create a Read Replica Set // -// **Preview - this endpoint is experimental and may change without notice.** +// Creates a new read replica set for a service. This is an asynchronous operation. // -// Retrieves how long this service keeps its backups. +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Returns a wrapper object for the known response body format(s). +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). +func (c *ClientWithResponses) CreateReplicaSetWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateReplicaSetResponse, error) { + rsp, err := c.CreateReplicaSetWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateReplicaSetResponse(rsp) +} + +// CreateReplicaSetWithResponse Create a Read Replica Set +// +// Creates a new read replica set for a service. This is an asynchronous operation. +// +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/services/{service_id}/backup-retention (the `GetBackupRetention` operationId). -func (c *ClientWithResponses) GetBackupRetentionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupRetentionResponse, error) { - rsp, err := c.GetBackupRetention(ctx, projectID, serviceID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). +func (c *ClientWithResponses) CreateReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateReplicaSetResponse, error) { + rsp, err := c.CreateReplicaSet(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseGetBackupRetentionResponse(rsp) + return ParseCreateReplicaSetResponse(rsp) } -// SetBackupRetentionWithBodyWithResponse Set the Backup Retention Policy -// -// **Preview - this endpoint is experimental and may change without notice.** +// DeleteReplicaSetWithResponse Delete a Read Replica Set // -// Sets how long this service keeps its backups. Shortening retention -// deletes the backups that no longer fit. +// Deletes a specific read replica set. This is an asynchronous operation. // -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with PUT /projects/{project_id}/services/{service_id}/backup-retention (the `SetBackupRetention` operationId). -func (c *ClientWithResponses) SetBackupRetentionWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetBackupRetentionResponse, error) { - rsp, err := c.SetBackupRetentionWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with DELETE /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id} (the `DeleteReplicaSet` operationId). +func (c *ClientWithResponses) DeleteReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*DeleteReplicaSetResponse, error) { + rsp, err := c.DeleteReplicaSet(ctx, projectID, serviceID, replicaSetID, reqEditors...) if err != nil { return nil, err } - return ParseSetBackupRetentionResponse(rsp) + return ParseDeleteReplicaSetResponse(rsp) } -// SetBackupRetentionWithResponse Set the Backup Retention Policy -// -// **Preview - this endpoint is experimental and may change without notice.** +// DisableReplicaPoolerWithResponse Disable Connection Pooler for a Read Replica // -// Sets how long this service keeps its backups. Shortening retention -// deletes the backups that no longer fit. +// Deactivates the connection pooler for a specific read replica set. // -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with PUT /projects/{project_id}/services/{service_id}/backup-retention (the `SetBackupRetention` operationId). -func (c *ClientWithResponses) SetBackupRetentionWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetBackupRetentionJSONRequestBody, reqEditors ...RequestEditorFn) (*SetBackupRetentionResponse, error) { - rsp, err := c.SetBackupRetention(ctx, projectID, serviceID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/disablePooler (the `DisableReplicaPooler` operationId). +func (c *ClientWithResponses) DisableReplicaPoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*DisableReplicaPoolerResponse, error) { + rsp, err := c.DisableReplicaPooler(ctx, projectID, serviceID, replicaSetID, reqEditors...) if err != nil { return nil, err } - return ParseSetBackupRetentionResponse(rsp) + return ParseDisableReplicaPoolerResponse(rsp) } -// GetBackupsWithResponse List All Backups -// -// **Preview - this endpoint is experimental and may change without notice.** +// EnableReplicaPoolerWithResponse Enable Connection Pooler for a Read Replica // -// Retrieves a list of the full and incremental backups taken for a -// service. Backups run automatically on a schedule; there is no operation -// to create or delete one. To restore data, create a recovery fork with -// `fork_strategy: PITR`. +// Activates the connection pooler for a specific read replica set. // // Returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/services/{service_id}/backups (the `GetBackups` operationId). -func (c *ClientWithResponses) GetBackupsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetBackupsResponse, error) { - rsp, err := c.GetBackups(ctx, projectID, serviceID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/enablePooler (the `EnableReplicaPooler` operationId). +func (c *ClientWithResponses) EnableReplicaPoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*EnableReplicaPoolerResponse, error) { + rsp, err := c.EnableReplicaPooler(ctx, projectID, serviceID, replicaSetID, reqEditors...) if err != nil { return nil, err } - return ParseGetBackupsResponse(rsp) + return ParseEnableReplicaPoolerResponse(rsp) } -// DetachServiceFromAllowListWithBodyWithResponse Detach Service from IP Allow List -// -// **Preview - this endpoint is experimental and may change without notice.** +// ResizeReplicaSetWithBodyWithResponse Resize a Read Replica Set // -// Removes the IP restriction an IP allow list placed on a service. The -// IP allow list itself is not deleted. +// Changes the resource allocation for a specific read replica set. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). -func (c *ClientWithResponses) DetachServiceFromAllowListWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromAllowListResponse, error) { - rsp, err := c.DetachServiceFromAllowListWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). +func (c *ClientWithResponses) ResizeReplicaSetWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ResizeReplicaSetResponse, error) { + rsp, err := c.ResizeReplicaSetWithBody(ctx, projectID, serviceID, replicaSetID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseDetachServiceFromAllowListResponse(rsp) + return ParseResizeReplicaSetResponse(rsp) } -// DetachServiceFromAllowListWithResponse Detach Service from IP Allow List -// -// **Preview - this endpoint is experimental and may change without notice.** +// ResizeReplicaSetWithResponse Resize a Read Replica Set // -// Removes the IP restriction an IP allow list placed on a service. The -// IP allow list itself is not deleted. +// Changes the resource allocation for a specific read replica set. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromAllowList (the `DetachServiceFromAllowList` operationId). -func (c *ClientWithResponses) DetachServiceFromAllowListWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromAllowListJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromAllowListResponse, error) { - rsp, err := c.DetachServiceFromAllowList(ctx, projectID, serviceID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). +func (c *ClientWithResponses) ResizeReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body ResizeReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*ResizeReplicaSetResponse, error) { + rsp, err := c.ResizeReplicaSet(ctx, projectID, serviceID, replicaSetID, body, reqEditors...) if err != nil { return nil, err } - return ParseDetachServiceFromAllowListResponse(rsp) + return ParseResizeReplicaSetResponse(rsp) } -// DetachServiceFromExporterWithBodyWithResponse Detach Service from Exporter -// -// **Preview - this endpoint is experimental and may change without notice.** -// -// Stops sending a service's logs or metrics to an exporter. The exporter -// is not deleted and keeps serving any other service attached to it. +// SetReplicaEnvironmentWithBodyWithResponse Set Environment for a Read Replica // -// Data already delivered to the destination is untouched. +// Sets the environment type for the read replica set. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). -func (c *ClientWithResponses) DetachServiceFromExporterWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromExporterResponse, error) { - rsp, err := c.DetachServiceFromExporterWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). +func (c *ClientWithResponses) SetReplicaEnvironmentWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetReplicaEnvironmentResponse, error) { + rsp, err := c.SetReplicaEnvironmentWithBody(ctx, projectID, serviceID, replicaSetID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseDetachServiceFromExporterResponse(rsp) + return ParseSetReplicaEnvironmentResponse(rsp) } -// DetachServiceFromExporterWithResponse Detach Service from Exporter -// -// **Preview - this endpoint is experimental and may change without notice.** -// -// Stops sending a service's logs or metrics to an exporter. The exporter -// is not deleted and keeps serving any other service attached to it. +// SetReplicaEnvironmentWithResponse Set Environment for a Read Replica // -// Data already delivered to the destination is untouched. +// Sets the environment type for the read replica set. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromExporter (the `DetachServiceFromExporter` operationId). -func (c *ClientWithResponses) DetachServiceFromExporterWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromExporterJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromExporterResponse, error) { - rsp, err := c.DetachServiceFromExporter(ctx, projectID, serviceID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). +func (c *ClientWithResponses) SetReplicaEnvironmentWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body SetReplicaEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*SetReplicaEnvironmentResponse, error) { + rsp, err := c.SetReplicaEnvironment(ctx, projectID, serviceID, replicaSetID, body, reqEditors...) if err != nil { return nil, err } - return ParseDetachServiceFromExporterResponse(rsp) + return ParseSetReplicaEnvironmentResponse(rsp) } -// DetachServiceFromVPCWithBodyWithResponse Detach Service from VPC +// ResizeServiceWithBodyWithResponse Resize a Service // -// Disassociates a service from its VPC. +// Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). -func (c *ClientWithResponses) DetachServiceFromVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DetachServiceFromVPCResponse, error) { - rsp, err := c.DetachServiceFromVPCWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). +func (c *ClientWithResponses) ResizeServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ResizeServiceResponse, error) { + rsp, err := c.ResizeServiceWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseDetachServiceFromVPCResponse(rsp) + return ParseResizeServiceResponse(rsp) } -// DetachServiceFromVPCWithResponse Detach Service from VPC +// ResizeServiceWithResponse Resize a Service // -// Disassociates a service from its VPC. +// Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/detachFromVPC (the `DetachServiceFromVPC` operationId). -func (c *ClientWithResponses) DetachServiceFromVPCWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body DetachServiceFromVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*DetachServiceFromVPCResponse, error) { - rsp, err := c.DetachServiceFromVPC(ctx, projectID, serviceID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). +func (c *ClientWithResponses) ResizeServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ResizeServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*ResizeServiceResponse, error) { + rsp, err := c.ResizeService(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseDetachServiceFromVPCResponse(rsp) + return ParseResizeServiceResponse(rsp) } -// DisablePoolerWithResponse Disable Connection Pooler for a Service +// SetEnvironmentWithBodyWithResponse Set Environment for a Service // -// Deactivates the connection pooler for a specific service within a project. +// Sets the environment type for the service. // -// Returns a wrapper object for the known response body format(s). +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/disablePooler (the `DisablePooler` operationId). -func (c *ClientWithResponses) DisablePoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*DisablePoolerResponse, error) { - rsp, err := c.DisablePooler(ctx, projectID, serviceID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). +func (c *ClientWithResponses) SetEnvironmentWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetEnvironmentResponse, error) { + rsp, err := c.SetEnvironmentWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseDisablePoolerResponse(rsp) + return ParseSetEnvironmentResponse(rsp) } -// EnableDataTieringWithResponse Enable Data Tiering for a Service -// -// Enables tiered storage on the service. Tiering policies move older, -// rarely-accessed data from the high-performance storage tier to a -// low-cost object storage tier. Tiered data stays queryable but becomes -// read-only. +// SetEnvironmentWithResponse Set Environment for a Service // -// There is no way to disable tiering through this API. Contact Tiger -// Data support if you need to turn it off. +// Sets the environment type for the service. // -// Returns a wrapper object for the known response body format(s). +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/enableDataTiering (the `EnableDataTiering` operationId). -func (c *ClientWithResponses) EnableDataTieringWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*EnableDataTieringResponse, error) { - rsp, err := c.EnableDataTiering(ctx, projectID, serviceID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). +func (c *ClientWithResponses) SetEnvironmentWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*SetEnvironmentResponse, error) { + rsp, err := c.SetEnvironment(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseEnableDataTieringResponse(rsp) + return ParseSetEnvironmentResponse(rsp) } -// EnablePoolerWithResponse Enable Connection Pooler for a Service +// SetHAReplicaWithBodyWithResponse Change HA configuration for a Service // -// Activates the connection pooler for a specific service within a project. +// Changes the HA configuration for a specific service. This is an asynchronous operation. // -// Returns a wrapper object for the known response body format(s). +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/enablePooler (the `EnablePooler` operationId). -func (c *ClientWithResponses) EnablePoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*EnablePoolerResponse, error) { - rsp, err := c.EnablePooler(ctx, projectID, serviceID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/setHA (the `SetHAReplica` operationId). +func (c *ClientWithResponses) SetHAReplicaWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetHAReplicaResponse, error) { + rsp, err := c.SetHAReplicaWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseEnablePoolerResponse(rsp) + return ParseSetHAReplicaResponse(rsp) } -// ForkServiceWithBodyWithResponse Fork a Service +// SetHAReplicaWithResponse Change HA configuration for a Service // -// Creates a new, independent service within a project by taking a snapshot of an existing one. +// Changes the HA configuration for a specific service. This is an asynchronous operation. // -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). -func (c *ClientWithResponses) ForkServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ForkServiceResponse, error) { - rsp, err := c.ForkServiceWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/setHA (the `SetHAReplica` operationId). +func (c *ClientWithResponses) SetHAReplicaWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetHAReplicaJSONRequestBody, reqEditors ...RequestEditorFn) (*SetHAReplicaResponse, error) { + rsp, err := c.SetHAReplica(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseForkServiceResponse(rsp) + return ParseSetHAReplicaResponse(rsp) } -// ForkServiceWithResponse Fork a Service +// StartServiceWithResponse Start a Service // -// Creates a new, independent service within a project by taking a snapshot of an existing one. +// Starts a stopped service within a project. This is an asynchronous operation. // -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/forkService (the `ForkService` operationId). -func (c *ClientWithResponses) ForkServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ForkServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*ForkServiceResponse, error) { - rsp, err := c.ForkService(ctx, projectID, serviceID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/start (the `StartService` operationId). +func (c *ClientWithResponses) StartServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*StartServiceResponse, error) { + rsp, err := c.StartService(ctx, projectID, serviceID, reqEditors...) if err != nil { return nil, err } - return ParseForkServiceResponse(rsp) + return ParseStartServiceResponse(rsp) } -// GetServiceLogsWithResponse Get service logs -// -// Fetch logs for a specific service. Returns up to 500 log entries in reverse -// chronological order. Supports cursor-based pagination via the `cursor` parameter -// and the `last_cursor` field in the response. +// StopServiceWithResponse Stop a Service // -// Use the returned `last_cursor` value as the `cursor` parameter on subsequent -// requests to page through all available logs. +// Stops a running service within a project. This is an asynchronous operation. // // Returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/services/{service_id}/logs (the `GetServiceLogs` operationId). -func (c *ClientWithResponses) GetServiceLogsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, params *GetServiceLogsParams, reqEditors ...RequestEditorFn) (*GetServiceLogsResponse, error) { - rsp, err := c.GetServiceLogs(ctx, projectID, serviceID, params, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/stop (the `StopService` operationId). +func (c *ClientWithResponses) StopServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*StopServiceResponse, error) { + rsp, err := c.StopService(ctx, projectID, serviceID, reqEditors...) if err != nil { return nil, err } - return ParseGetServiceLogsResponse(rsp) + return ParseStopServiceResponse(rsp) +} + +// UpdatePasswordWithBodyWithResponse Update Service Password +// +// Sets a new master password for the service within a project. +// +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// +// Corresponds with POST /projects/{project_id}/services/{service_id}/updatePassword (the `UpdatePassword` operationId). +func (c *ClientWithResponses) UpdatePasswordWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdatePasswordResponse, error) { + rsp, err := c.UpdatePasswordWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdatePasswordResponse(rsp) } -// GetServiceMetricsAvailableSeriesWithResponse List available metric series +// UpdatePasswordWithResponse Update Service Password // -// Returns the names of all metric series available for a service. +// Sets a new master password for the service within a project. // -// Returns a wrapper object for the known response body format(s). +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/available-series (the `GetServiceMetricsAvailableSeries` operationId). -func (c *ClientWithResponses) GetServiceMetricsAvailableSeriesWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetServiceMetricsAvailableSeriesResponse, error) { - rsp, err := c.GetServiceMetricsAvailableSeries(ctx, projectID, serviceID, reqEditors...) +// Corresponds with POST /projects/{project_id}/services/{service_id}/updatePassword (the `UpdatePassword` operationId). +func (c *ClientWithResponses) UpdatePasswordWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body UpdatePasswordJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdatePasswordResponse, error) { + rsp, err := c.UpdatePassword(ctx, projectID, serviceID, body, reqEditors...) if err != nil { return nil, err } - return ParseGetServiceMetricsAvailableSeriesResponse(rsp) + return ParseUpdatePasswordResponse(rsp) } -// GetServiceMetricDetailsWithResponse Get details about a metric +// GetVPCsWithResponse List All VPCs // -// Returns descriptive metadata for a named metric: what it measures, -// its type, default aggregation, and every label it can be filtered or -// grouped by — its own labels plus the region/role/ordinal labels most -// metrics also carry. Use getServiceMetricsAvailableSeries to discover -// valid metric names. +// Retrieves a list of all Virtual Private Clouds (VPCs). // // Returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/services/{service_id}/metrics/details/{metric_name} (the `GetServiceMetricDetails` operationId). -func (c *ClientWithResponses) GetServiceMetricDetailsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, metricName string, reqEditors ...RequestEditorFn) (*GetServiceMetricDetailsResponse, error) { - rsp, err := c.GetServiceMetricDetails(ctx, projectID, serviceID, metricName, reqEditors...) +// Corresponds with GET /projects/{project_id}/vpcs (the `GetVPCs` operationId). +func (c *ClientWithResponses) GetVPCsWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetVPCsResponse, error) { + rsp, err := c.GetVPCs(ctx, projectID, reqEditors...) if err != nil { return nil, err } - return ParseGetServiceMetricDetailsResponse(rsp) + return ParseGetVPCsResponse(rsp) } -// GetServiceMetricsSeriesWithBodyWithResponse Get a metric series -// -// Returns time-series data points for a named metric within a time window. -// Use getServiceMetricsAvailableSeries to discover valid metric names. +// CreateVPCWithBodyWithResponse Create a VPC // -// Parameters are sent as a JSON body so callers can pass an unbounded -// filter set without query-string length pressure. The response is a list -// of MetricSeries, each carrying its label set as a structured map -// alongside its per-bucket data points. +// Creates a new Virtual Private Cloud (VPC). This is an asynchronous operation. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). -func (c *ClientWithResponses) GetServiceMetricsSeriesWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*GetServiceMetricsSeriesResponse, error) { - rsp, err := c.GetServiceMetricsSeriesWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/vpcs (the `CreateVPC` operationId). +func (c *ClientWithResponses) CreateVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVPCResponse, error) { + rsp, err := c.CreateVPCWithBody(ctx, projectID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseGetServiceMetricsSeriesResponse(rsp) + return ParseCreateVPCResponse(rsp) } -// GetServiceMetricsSeriesWithResponse Get a metric series -// -// Returns time-series data points for a named metric within a time window. -// Use getServiceMetricsAvailableSeries to discover valid metric names. +// CreateVPCWithResponse Create a VPC // -// Parameters are sent as a JSON body so callers can pass an unbounded -// filter set without query-string length pressure. The response is a list -// of MetricSeries, each carrying its label set as a structured map -// alongside its per-bucket data points. +// Creates a new Virtual Private Cloud (VPC). This is an asynchronous operation. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/metrics/series (the `GetServiceMetricsSeries` operationId). -func (c *ClientWithResponses) GetServiceMetricsSeriesWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body GetServiceMetricsSeriesJSONRequestBody, reqEditors ...RequestEditorFn) (*GetServiceMetricsSeriesResponse, error) { - rsp, err := c.GetServiceMetricsSeries(ctx, projectID, serviceID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/vpcs (the `CreateVPC` operationId). +func (c *ClientWithResponses) CreateVPCWithResponse(ctx context.Context, projectID ProjectID, body CreateVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVPCResponse, error) { + rsp, err := c.CreateVPC(ctx, projectID, body, reqEditors...) if err != nil { return nil, err } - return ParseGetServiceMetricsSeriesResponse(rsp) + return ParseCreateVPCResponse(rsp) } -// RenameServiceWithBodyWithResponse Rename a Service +// DeleteVPCWithResponse Delete a VPC // -// Updates the name of a specific service. +// Deletes a specific VPC. This is an asynchronous operation. // -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). -func (c *ClientWithResponses) RenameServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RenameServiceResponse, error) { - rsp, err := c.RenameServiceWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with DELETE /projects/{project_id}/vpcs/{vpc_id} (the `DeleteVPC` operationId). +func (c *ClientWithResponses) DeleteVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*DeleteVPCResponse, error) { + rsp, err := c.DeleteVPC(ctx, projectID, vpcID, reqEditors...) if err != nil { return nil, err } - return ParseRenameServiceResponse(rsp) + return ParseDeleteVPCResponse(rsp) } -// RenameServiceWithResponse Rename a Service +// GetVPCWithResponse Get a VPC // -// Updates the name of a specific service. +// Retrieves the details of a specific VPC by its ID. // -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). +// Returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/rename (the `RenameService` operationId). -func (c *ClientWithResponses) RenameServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body RenameServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*RenameServiceResponse, error) { - rsp, err := c.RenameService(ctx, projectID, serviceID, body, reqEditors...) +// Corresponds with GET /projects/{project_id}/vpcs/{vpc_id} (the `GetVPC` operationId). +func (c *ClientWithResponses) GetVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*GetVPCResponse, error) { + rsp, err := c.GetVPC(ctx, projectID, vpcID, reqEditors...) if err != nil { return nil, err } - return ParseRenameServiceResponse(rsp) + return ParseGetVPCResponse(rsp) } -// GetReplicaSetsWithResponse Get Read Replica Sets +// GetVPCPeeringsWithResponse List VPC Peerings // -// Retrieves a list of all read replica sets associated with a primary service within a project. +// Retrieves a list of all VPC peering connections for a given VPC. // // Returns a wrapper object for the known response body format(s). // -// Corresponds with GET /projects/{project_id}/services/{service_id}/replicaSets (the `GetReplicaSets` operationId). -func (c *ClientWithResponses) GetReplicaSetsWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*GetReplicaSetsResponse, error) { - rsp, err := c.GetReplicaSets(ctx, projectID, serviceID, reqEditors...) +// Corresponds with GET /projects/{project_id}/vpcs/{vpc_id}/peerings (the `GetVPCPeerings` operationId). +func (c *ClientWithResponses) GetVPCPeeringsWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*GetVPCPeeringsResponse, error) { + rsp, err := c.GetVPCPeerings(ctx, projectID, vpcID, reqEditors...) if err != nil { return nil, err } - return ParseGetReplicaSetsResponse(rsp) + return ParseGetVPCPeeringsResponse(rsp) } -// CreateReplicaSetWithBodyWithResponse Create a Read Replica Set +// CreateVPCPeeringWithBodyWithResponse Create a VPC Peering // -// Creates a new read replica set for a service. This is an asynchronous operation. +// Creates a new VPC peering connection. This is an asynchronous operation. // // Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). -func (c *ClientWithResponses) CreateReplicaSetWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateReplicaSetResponse, error) { - rsp, err := c.CreateReplicaSetWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/peerings (the `CreateVPCPeering` operationId). +func (c *ClientWithResponses) CreateVPCPeeringWithBodyWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVPCPeeringResponse, error) { + rsp, err := c.CreateVPCPeeringWithBody(ctx, projectID, vpcID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseCreateReplicaSetResponse(rsp) + return ParseCreateVPCPeeringResponse(rsp) } -// CreateReplicaSetWithResponse Create a Read Replica Set +// CreateVPCPeeringWithResponse Create a VPC Peering // -// Creates a new read replica set for a service. This is an asynchronous operation. +// Creates a new VPC peering connection. This is an asynchronous operation. // // Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets (the `CreateReplicaSet` operationId). -func (c *ClientWithResponses) CreateReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body CreateReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateReplicaSetResponse, error) { - rsp, err := c.CreateReplicaSet(ctx, projectID, serviceID, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/peerings (the `CreateVPCPeering` operationId). +func (c *ClientWithResponses) CreateVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, body CreateVPCPeeringJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVPCPeeringResponse, error) { + rsp, err := c.CreateVPCPeering(ctx, projectID, vpcID, body, reqEditors...) if err != nil { return nil, err } - return ParseCreateReplicaSetResponse(rsp) + return ParseCreateVPCPeeringResponse(rsp) } -// DeleteReplicaSetWithResponse Delete a Read Replica Set +// DeleteVPCPeeringWithResponse Delete a VPC Peering // -// Deletes a specific read replica set. This is an asynchronous operation. +// Deletes a specific VPC peering connection. This is an asynchronous operation. // // Returns a wrapper object for the known response body format(s). // -// Corresponds with DELETE /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id} (the `DeleteReplicaSet` operationId). -func (c *ClientWithResponses) DeleteReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*DeleteReplicaSetResponse, error) { - rsp, err := c.DeleteReplicaSet(ctx, projectID, serviceID, replicaSetID, reqEditors...) +// Corresponds with DELETE /projects/{project_id}/vpcs/{vpc_id}/peerings/{peering_id} (the `DeleteVPCPeering` operationId). +func (c *ClientWithResponses) DeleteVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, peeringID PeeringID, reqEditors ...RequestEditorFn) (*DeleteVPCPeeringResponse, error) { + rsp, err := c.DeleteVPCPeering(ctx, projectID, vpcID, peeringID, reqEditors...) if err != nil { return nil, err } - return ParseDeleteReplicaSetResponse(rsp) + return ParseDeleteVPCPeeringResponse(rsp) } -// DisableReplicaPoolerWithResponse Disable Connection Pooler for a Read Replica +// GetVPCPeeringWithResponse Get a VPC Peering // -// Deactivates the connection pooler for a specific read replica set. +// Retrieves the details of a specific VPC peering connection. // // Returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/disablePooler (the `DisableReplicaPooler` operationId). -func (c *ClientWithResponses) DisableReplicaPoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*DisableReplicaPoolerResponse, error) { - rsp, err := c.DisableReplicaPooler(ctx, projectID, serviceID, replicaSetID, reqEditors...) +// Corresponds with GET /projects/{project_id}/vpcs/{vpc_id}/peerings/{peering_id} (the `GetVPCPeering` operationId). +func (c *ClientWithResponses) GetVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, peeringID PeeringID, reqEditors ...RequestEditorFn) (*GetVPCPeeringResponse, error) { + rsp, err := c.GetVPCPeering(ctx, projectID, vpcID, peeringID, reqEditors...) if err != nil { return nil, err } - return ParseDisableReplicaPoolerResponse(rsp) + return ParseGetVPCPeeringResponse(rsp) } -// EnableReplicaPoolerWithResponse Enable Connection Pooler for a Read Replica +// RenameVPCWithBodyWithResponse Rename a VPC // -// Activates the connection pooler for a specific read replica set. +// Updates the name of a specific VPC. // -// Returns a wrapper object for the known response body format(s). +// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/enablePooler (the `EnableReplicaPooler` operationId). -func (c *ClientWithResponses) EnableReplicaPoolerWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, reqEditors ...RequestEditorFn) (*EnableReplicaPoolerResponse, error) { - rsp, err := c.EnableReplicaPooler(ctx, projectID, serviceID, replicaSetID, reqEditors...) +// Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/rename (the `RenameVPC` operationId). +func (c *ClientWithResponses) RenameVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RenameVPCResponse, error) { + rsp, err := c.RenameVPCWithBody(ctx, projectID, vpcID, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseEnableReplicaPoolerResponse(rsp) + return ParseRenameVPCResponse(rsp) } -// ResizeReplicaSetWithBodyWithResponse Resize a Read Replica Set +// RenameVPCWithResponse Rename a VPC // -// Changes the resource allocation for a specific read replica set. +// Updates the name of a specific VPC. // -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). +// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). // -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). -func (c *ClientWithResponses) ResizeReplicaSetWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ResizeReplicaSetResponse, error) { - rsp, err := c.ResizeReplicaSetWithBody(ctx, projectID, serviceID, replicaSetID, contentType, body, reqEditors...) +// Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/rename (the `RenameVPC` operationId). +func (c *ClientWithResponses) RenameVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, body RenameVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*RenameVPCResponse, error) { + rsp, err := c.RenameVPC(ctx, projectID, vpcID, body, reqEditors...) if err != nil { return nil, err } - return ParseResizeReplicaSetResponse(rsp) + return ParseRenameVPCResponse(rsp) } -// ResizeReplicaSetWithResponse Resize a Read Replica Set -// -// Changes the resource allocation for a specific read replica set. -// -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/resize (the `ResizeReplicaSet` operationId). -func (c *ClientWithResponses) ResizeReplicaSetWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body ResizeReplicaSetJSONRequestBody, reqEditors ...RequestEditorFn) (*ResizeReplicaSetResponse, error) { - rsp, err := c.ResizeReplicaSet(ctx, projectID, serviceID, replicaSetID, body, reqEditors...) +// ParseIdentifyUserResponse parses an HTTP response from a IdentifyUserWithResponse call +func ParseIdentifyUserResponse(rsp *http.Response) (*IdentifyUserResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseResizeReplicaSetResponse(rsp) + + response := &IdentifyUserResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AnalyticsResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: + var dest ClientError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON4XX = &dest + + } + + return response, nil } -// SetReplicaEnvironmentWithBodyWithResponse Set Environment for a Read Replica -// -// Sets the environment type for the read replica set. -// -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). -func (c *ClientWithResponses) SetReplicaEnvironmentWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetReplicaEnvironmentResponse, error) { - rsp, err := c.SetReplicaEnvironmentWithBody(ctx, projectID, serviceID, replicaSetID, contentType, body, reqEditors...) +// ParseTrackEventResponse parses an HTTP response from a TrackEventWithResponse call +func ParseTrackEventResponse(rsp *http.Response) (*TrackEventResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseSetReplicaEnvironmentResponse(rsp) + + response := &TrackEventResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AnalyticsResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: + var dest ClientError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON4XX = &dest + + } + + return response, nil } -// SetReplicaEnvironmentWithResponse Set Environment for a Read Replica -// -// Sets the environment type for the read replica set. -// -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/replicaSets/{replica_set_id}/setEnvironment (the `SetReplicaEnvironment` operationId). -func (c *ClientWithResponses) SetReplicaEnvironmentWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, replicaSetID ReplicaSetID, body SetReplicaEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*SetReplicaEnvironmentResponse, error) { - rsp, err := c.SetReplicaEnvironment(ctx, projectID, serviceID, replicaSetID, body, reqEditors...) +// ParseGetAuthInfoResponse parses an HTTP response from a GetAuthInfoWithResponse call +func ParseGetAuthInfoResponse(rsp *http.Response) (*GetAuthInfoResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseSetReplicaEnvironmentResponse(rsp) -} -// ResizeServiceWithBodyWithResponse Resize a Service -// -// Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. -// -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). -func (c *ClientWithResponses) ResizeServiceWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ResizeServiceResponse, error) { - rsp, err := c.ResizeServiceWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) - if err != nil { - return nil, err + response := &GetAuthInfoResponse{ + Body: bodyBytes, + HTTPResponse: rsp, } - return ParseResizeServiceResponse(rsp) -} -// ResizeServiceWithResponse Resize a Service -// -// Changes the CPU and memory allocation for a specific service within a project. This is an asynchronous operation. -// -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/resize (the `ResizeService` operationId). -func (c *ClientWithResponses) ResizeServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body ResizeServiceJSONRequestBody, reqEditors ...RequestEditorFn) (*ResizeServiceResponse, error) { - rsp, err := c.ResizeService(ctx, projectID, serviceID, body, reqEditors...) - if err != nil { - return nil, err + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AuthInfo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: + var dest ClientError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON4XX = &dest + } - return ParseResizeServiceResponse(rsp) + + return response, nil } -// SetEnvironmentWithBodyWithResponse Set Environment for a Service -// -// Sets the environment type for the service. -// -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). -func (c *ClientWithResponses) SetEnvironmentWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetEnvironmentResponse, error) { - rsp, err := c.SetEnvironmentWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// ParseLogoutResponse parses an HTTP response from a LogoutWithResponse call +func ParseLogoutResponse(rsp *http.Response) (*LogoutResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseSetEnvironmentResponse(rsp) -} -// SetEnvironmentWithResponse Set Environment for a Service -// -// Sets the environment type for the service. -// -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/setEnvironment (the `SetEnvironment` operationId). -func (c *ClientWithResponses) SetEnvironmentWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*SetEnvironmentResponse, error) { - rsp, err := c.SetEnvironment(ctx, projectID, serviceID, body, reqEditors...) - if err != nil { - return nil, err + response := &LogoutResponse{ + Body: bodyBytes, + HTTPResponse: rsp, } - return ParseSetEnvironmentResponse(rsp) -} -// SetHAReplicaWithBodyWithResponse Change HA configuration for a Service -// -// Changes the HA configuration for a specific service. This is an asynchronous operation. -// -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/setHA (the `SetHAReplica` operationId). -func (c *ClientWithResponses) SetHAReplicaWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetHAReplicaResponse, error) { - rsp, err := c.SetHAReplicaWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) - if err != nil { - return nil, err + switch { + case rsp.StatusCode == 204: + break // No content-type + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: + var dest ClientError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON4XX = &dest + } - return ParseSetHAReplicaResponse(rsp) + + return response, nil } -// SetHAReplicaWithResponse Change HA configuration for a Service -// -// Changes the HA configuration for a specific service. This is an asynchronous operation. -// -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/setHA (the `SetHAReplica` operationId). -func (c *ClientWithResponses) SetHAReplicaWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body SetHAReplicaJSONRequestBody, reqEditors ...RequestEditorFn) (*SetHAReplicaResponse, error) { - rsp, err := c.SetHAReplica(ctx, projectID, serviceID, body, reqEditors...) +// ParseSubmitFeedbackResponse parses an HTTP response from a SubmitFeedbackWithResponse call +func ParseSubmitFeedbackResponse(rsp *http.Response) (*SubmitFeedbackResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseSetHAReplicaResponse(rsp) -} -// StartServiceWithResponse Start a Service -// -// Starts a stopped service within a project. This is an asynchronous operation. -// -// Returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/start (the `StartService` operationId). -func (c *ClientWithResponses) StartServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*StartServiceResponse, error) { - rsp, err := c.StartService(ctx, projectID, serviceID, reqEditors...) - if err != nil { - return nil, err + response := &SubmitFeedbackResponse{ + Body: bodyBytes, + HTTPResponse: rsp, } - return ParseStartServiceResponse(rsp) -} -// StopServiceWithResponse Stop a Service -// -// Stops a running service within a project. This is an asynchronous operation. -// -// Returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/stop (the `StopService` operationId). -func (c *ClientWithResponses) StopServiceWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, reqEditors ...RequestEditorFn) (*StopServiceResponse, error) { - rsp, err := c.StopService(ctx, projectID, serviceID, reqEditors...) - if err != nil { - return nil, err + switch { + case rsp.StatusCode == 204: + break // No content-type + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: + var dest ClientError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON4XX = &dest + } - return ParseStopServiceResponse(rsp) + + return response, nil } -// UpdatePasswordWithBodyWithResponse Update Service Password -// -// Sets a new master password for the service within a project. -// -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/updatePassword (the `UpdatePassword` operationId). -func (c *ClientWithResponses) UpdatePasswordWithBodyWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdatePasswordResponse, error) { - rsp, err := c.UpdatePasswordWithBody(ctx, projectID, serviceID, contentType, body, reqEditors...) +// ParseGetProjectsResponse parses an HTTP response from a GetProjectsWithResponse call +func ParseGetProjectsResponse(rsp *http.Response) (*GetProjectsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseUpdatePasswordResponse(rsp) -} -// UpdatePasswordWithResponse Update Service Password -// -// Sets a new master password for the service within a project. -// -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/services/{service_id}/updatePassword (the `UpdatePassword` operationId). -func (c *ClientWithResponses) UpdatePasswordWithResponse(ctx context.Context, projectID ProjectID, serviceID ServiceID, body UpdatePasswordJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdatePasswordResponse, error) { - rsp, err := c.UpdatePassword(ctx, projectID, serviceID, body, reqEditors...) - if err != nil { - return nil, err + response := &GetProjectsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, } - return ParseUpdatePasswordResponse(rsp) + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Project + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: + var dest ClientError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON4XX = &dest + + } + + return response, nil } -// GetVPCsWithResponse List All VPCs -// -// Retrieves a list of all Virtual Private Clouds (VPCs). -// -// Returns a wrapper object for the known response body format(s). -// -// Corresponds with GET /projects/{project_id}/vpcs (the `GetVPCs` operationId). -func (c *ClientWithResponses) GetVPCsWithResponse(ctx context.Context, projectID ProjectID, reqEditors ...RequestEditorFn) (*GetVPCsResponse, error) { - rsp, err := c.GetVPCs(ctx, projectID, reqEditors...) +// ParseGetAllowListsResponse parses an HTTP response from a GetAllowListsWithResponse call +func ParseGetAllowListsResponse(rsp *http.Response) (*GetAllowListsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseGetVPCsResponse(rsp) + + response := &GetAllowListsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []AllowList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: + var dest ClientError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON4XX = &dest + + } + + return response, nil } -// CreateVPCWithBodyWithResponse Create a VPC -// -// Creates a new Virtual Private Cloud (VPC). This is an asynchronous operation. -// -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/vpcs (the `CreateVPC` operationId). -func (c *ClientWithResponses) CreateVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVPCResponse, error) { - rsp, err := c.CreateVPCWithBody(ctx, projectID, contentType, body, reqEditors...) +// ParseCreateAllowListResponse parses an HTTP response from a CreateAllowListWithResponse call +func ParseCreateAllowListResponse(rsp *http.Response) (*CreateAllowListResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseCreateVPCResponse(rsp) -} -// CreateVPCWithResponse Create a VPC -// -// Creates a new Virtual Private Cloud (VPC). This is an asynchronous operation. -// -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/vpcs (the `CreateVPC` operationId). -func (c *ClientWithResponses) CreateVPCWithResponse(ctx context.Context, projectID ProjectID, body CreateVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVPCResponse, error) { - rsp, err := c.CreateVPC(ctx, projectID, body, reqEditors...) - if err != nil { - return nil, err + response := &CreateAllowListResponse{ + Body: bodyBytes, + HTTPResponse: rsp, } - return ParseCreateVPCResponse(rsp) -} -// DeleteVPCWithResponse Delete a VPC -// -// Deletes a specific VPC. This is an asynchronous operation. -// -// Returns a wrapper object for the known response body format(s). -// -// Corresponds with DELETE /projects/{project_id}/vpcs/{vpc_id} (the `DeleteVPC` operationId). -func (c *ClientWithResponses) DeleteVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*DeleteVPCResponse, error) { - rsp, err := c.DeleteVPC(ctx, projectID, vpcID, reqEditors...) - if err != nil { - return nil, err + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest AllowList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: + var dest ClientError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON4XX = &dest + } - return ParseDeleteVPCResponse(rsp) + + return response, nil } -// GetVPCWithResponse Get a VPC -// -// Retrieves the details of a specific VPC by its ID. -// -// Returns a wrapper object for the known response body format(s). -// -// Corresponds with GET /projects/{project_id}/vpcs/{vpc_id} (the `GetVPC` operationId). -func (c *ClientWithResponses) GetVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*GetVPCResponse, error) { - rsp, err := c.GetVPC(ctx, projectID, vpcID, reqEditors...) +// ParseDeleteAllowListResponse parses an HTTP response from a DeleteAllowListWithResponse call +func ParseDeleteAllowListResponse(rsp *http.Response) (*DeleteAllowListResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseGetVPCResponse(rsp) -} -// GetVPCPeeringsWithResponse List VPC Peerings -// -// Retrieves a list of all VPC peering connections for a given VPC. -// -// Returns a wrapper object for the known response body format(s). -// -// Corresponds with GET /projects/{project_id}/vpcs/{vpc_id}/peerings (the `GetVPCPeerings` operationId). -func (c *ClientWithResponses) GetVPCPeeringsWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, reqEditors ...RequestEditorFn) (*GetVPCPeeringsResponse, error) { - rsp, err := c.GetVPCPeerings(ctx, projectID, vpcID, reqEditors...) - if err != nil { - return nil, err + response := &DeleteAllowListResponse{ + Body: bodyBytes, + HTTPResponse: rsp, } - return ParseGetVPCPeeringsResponse(rsp) -} -// CreateVPCPeeringWithBodyWithResponse Create a VPC Peering -// -// Creates a new VPC peering connection. This is an asynchronous operation. -// -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/peerings (the `CreateVPCPeering` operationId). -func (c *ClientWithResponses) CreateVPCPeeringWithBodyWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVPCPeeringResponse, error) { - rsp, err := c.CreateVPCPeeringWithBody(ctx, projectID, vpcID, contentType, body, reqEditors...) - if err != nil { - return nil, err + switch { + case rsp.StatusCode == 204: + break // No content-type + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: + var dest ClientError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON4XX = &dest + } - return ParseCreateVPCPeeringResponse(rsp) + + return response, nil } -// CreateVPCPeeringWithResponse Create a VPC Peering -// -// Creates a new VPC peering connection. This is an asynchronous operation. -// -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/peerings (the `CreateVPCPeering` operationId). -func (c *ClientWithResponses) CreateVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, body CreateVPCPeeringJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVPCPeeringResponse, error) { - rsp, err := c.CreateVPCPeering(ctx, projectID, vpcID, body, reqEditors...) +// ParseGetAllowListResponse parses an HTTP response from a GetAllowListWithResponse call +func ParseGetAllowListResponse(rsp *http.Response) (*GetAllowListResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseCreateVPCPeeringResponse(rsp) -} -// DeleteVPCPeeringWithResponse Delete a VPC Peering -// -// Deletes a specific VPC peering connection. This is an asynchronous operation. -// -// Returns a wrapper object for the known response body format(s). -// -// Corresponds with DELETE /projects/{project_id}/vpcs/{vpc_id}/peerings/{peering_id} (the `DeleteVPCPeering` operationId). -func (c *ClientWithResponses) DeleteVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, peeringID PeeringID, reqEditors ...RequestEditorFn) (*DeleteVPCPeeringResponse, error) { - rsp, err := c.DeleteVPCPeering(ctx, projectID, vpcID, peeringID, reqEditors...) - if err != nil { - return nil, err + response := &GetAllowListResponse{ + Body: bodyBytes, + HTTPResponse: rsp, } - return ParseDeleteVPCPeeringResponse(rsp) -} -// GetVPCPeeringWithResponse Get a VPC Peering -// -// Retrieves the details of a specific VPC peering connection. -// -// Returns a wrapper object for the known response body format(s). -// -// Corresponds with GET /projects/{project_id}/vpcs/{vpc_id}/peerings/{peering_id} (the `GetVPCPeering` operationId). -func (c *ClientWithResponses) GetVPCPeeringWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, peeringID PeeringID, reqEditors ...RequestEditorFn) (*GetVPCPeeringResponse, error) { - rsp, err := c.GetVPCPeering(ctx, projectID, vpcID, peeringID, reqEditors...) - if err != nil { - return nil, err + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AllowList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: + var dest ClientError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON4XX = &dest + } - return ParseGetVPCPeeringResponse(rsp) + + return response, nil } -// RenameVPCWithBodyWithResponse Rename a VPC -// -// Updates the name of a specific VPC. -// -// Takes any type of body and a specified content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/rename (the `RenameVPC` operationId). -func (c *ClientWithResponses) RenameVPCWithBodyWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RenameVPCResponse, error) { - rsp, err := c.RenameVPCWithBody(ctx, projectID, vpcID, contentType, body, reqEditors...) +// ParseUpdateAllowListResponse parses an HTTP response from a UpdateAllowListWithResponse call +func ParseUpdateAllowListResponse(rsp *http.Response) (*UpdateAllowListResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseRenameVPCResponse(rsp) -} -// RenameVPCWithResponse Rename a VPC -// -// Updates the name of a specific VPC. -// -// Takes a body of the `application/json` content type, and returns a wrapper object for the known response body format(s). -// -// Corresponds with POST /projects/{project_id}/vpcs/{vpc_id}/rename (the `RenameVPC` operationId). -func (c *ClientWithResponses) RenameVPCWithResponse(ctx context.Context, projectID ProjectID, vpcID VPCId, body RenameVPCJSONRequestBody, reqEditors ...RequestEditorFn) (*RenameVPCResponse, error) { - rsp, err := c.RenameVPC(ctx, projectID, vpcID, body, reqEditors...) - if err != nil { - return nil, err + response := &UpdateAllowListResponse{ + Body: bodyBytes, + HTTPResponse: rsp, } - return ParseRenameVPCResponse(rsp) + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AllowList + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: + var dest ClientError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON4XX = &dest + + } + + return response, nil } -// ParseIdentifyUserResponse parses an HTTP response from a IdentifyUserWithResponse call -func ParseIdentifyUserResponse(rsp *http.Response) (*IdentifyUserResponse, error) { +// ParseGetProjectBranchesResponse parses an HTTP response from a GetProjectBranchesWithResponse call +func ParseGetProjectBranchesResponse(rsp *http.Response) (*GetProjectBranchesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &IdentifyUserResponse{ + response := &GetProjectBranchesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AnalyticsResponse + var dest []Branch if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -11685,22 +14058,22 @@ func ParseIdentifyUserResponse(rsp *http.Response) (*IdentifyUserResponse, error return response, nil } -// ParseTrackEventResponse parses an HTTP response from a TrackEventWithResponse call -func ParseTrackEventResponse(rsp *http.Response) (*TrackEventResponse, error) { +// ParseGetExportersResponse parses an HTTP response from a GetExportersWithResponse call +func ParseGetExportersResponse(rsp *http.Response) (*GetExportersResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &TrackEventResponse{ + response := &GetExportersResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AnalyticsResponse + var dest []Exporter if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -11718,26 +14091,26 @@ func ParseTrackEventResponse(rsp *http.Response) (*TrackEventResponse, error) { return response, nil } -// ParseGetAuthInfoResponse parses an HTTP response from a GetAuthInfoWithResponse call -func ParseGetAuthInfoResponse(rsp *http.Response) (*GetAuthInfoResponse, error) { +// ParseCreateExporterResponse parses an HTTP response from a CreateExporterWithResponse call +func ParseCreateExporterResponse(rsp *http.Response) (*CreateExporterResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetAuthInfoResponse{ + response := &CreateExporterResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthInfo + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Exporter if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -11751,15 +14124,15 @@ func ParseGetAuthInfoResponse(rsp *http.Response) (*GetAuthInfoResponse, error) return response, nil } -// ParseLogoutResponse parses an HTTP response from a LogoutWithResponse call -func ParseLogoutResponse(rsp *http.Response) (*LogoutResponse, error) { +// ParseDeleteExporterResponse parses an HTTP response from a DeleteExporterWithResponse call +func ParseDeleteExporterResponse(rsp *http.Response) (*DeleteExporterResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &LogoutResponse{ + response := &DeleteExporterResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -11780,22 +14153,26 @@ func ParseLogoutResponse(rsp *http.Response) (*LogoutResponse, error) { return response, nil } -// ParseSubmitFeedbackResponse parses an HTTP response from a SubmitFeedbackWithResponse call -func ParseSubmitFeedbackResponse(rsp *http.Response) (*SubmitFeedbackResponse, error) { +// ParseGetExporterResponse parses an HTTP response from a GetExporterWithResponse call +func ParseGetExporterResponse(rsp *http.Response) (*GetExporterResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &SubmitFeedbackResponse{ + response := &GetExporterResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 204: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Exporter + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -11809,22 +14186,22 @@ func ParseSubmitFeedbackResponse(rsp *http.Response) (*SubmitFeedbackResponse, e return response, nil } -// ParseGetProjectsResponse parses an HTTP response from a GetProjectsWithResponse call -func ParseGetProjectsResponse(rsp *http.Response) (*GetProjectsResponse, error) { +// ParseUpdateExporterResponse parses an HTTP response from a UpdateExporterWithResponse call +func ParseUpdateExporterResponse(rsp *http.Response) (*UpdateExporterResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetProjectsResponse{ + response := &UpdateExporterResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Project + var dest Exporter if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -11842,22 +14219,22 @@ func ParseGetProjectsResponse(rsp *http.Response) (*GetProjectsResponse, error) return response, nil } -// ParseGetAllowListsResponse parses an HTTP response from a GetAllowListsWithResponse call -func ParseGetAllowListsResponse(rsp *http.Response) (*GetAllowListsResponse, error) { +// ParseResolveRefResponse parses an HTTP response from a ResolveRefWithResponse call +func ParseResolveRefResponse(rsp *http.Response) (*ResolveRefResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetAllowListsResponse{ + response := &ResolveRefResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []AllowList + var dest ResolveResult if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -11875,26 +14252,26 @@ func ParseGetAllowListsResponse(rsp *http.Response) (*GetAllowListsResponse, err return response, nil } -// ParseCreateAllowListResponse parses an HTTP response from a CreateAllowListWithResponse call -func ParseCreateAllowListResponse(rsp *http.Response) (*CreateAllowListResponse, error) { +// ParseGetServicesResponse parses an HTTP response from a GetServicesWithResponse call +func ParseGetServicesResponse(rsp *http.Response) (*GetServicesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateAllowListResponse{ + response := &GetServicesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest AllowList + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Service if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -11908,22 +14285,26 @@ func ParseCreateAllowListResponse(rsp *http.Response) (*CreateAllowListResponse, return response, nil } -// ParseDeleteAllowListResponse parses an HTTP response from a DeleteAllowListWithResponse call -func ParseDeleteAllowListResponse(rsp *http.Response) (*DeleteAllowListResponse, error) { +// ParseCreateServiceResponse parses an HTTP response from a CreateServiceWithResponse call +func ParseCreateServiceResponse(rsp *http.Response) (*CreateServiceResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteAllowListResponse{ + response := &CreateServiceResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 204: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest Service + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON202 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -11937,26 +14318,22 @@ func ParseDeleteAllowListResponse(rsp *http.Response) (*DeleteAllowListResponse, return response, nil } -// ParseGetAllowListResponse parses an HTTP response from a GetAllowListWithResponse call -func ParseGetAllowListResponse(rsp *http.Response) (*GetAllowListResponse, error) { +// ParseDeleteServiceResponse parses an HTTP response from a DeleteServiceWithResponse call +func ParseDeleteServiceResponse(rsp *http.Response) (*DeleteServiceResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetAllowListResponse{ + response := &DeleteServiceResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AllowList - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest + case rsp.StatusCode == 202: + break // No content-type case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -11970,22 +14347,22 @@ func ParseGetAllowListResponse(rsp *http.Response) (*GetAllowListResponse, error return response, nil } -// ParseUpdateAllowListResponse parses an HTTP response from a UpdateAllowListWithResponse call -func ParseUpdateAllowListResponse(rsp *http.Response) (*UpdateAllowListResponse, error) { +// ParseGetServiceResponse parses an HTTP response from a GetServiceWithResponse call +func ParseGetServiceResponse(rsp *http.Response) (*GetServiceResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateAllowListResponse{ + response := &GetServiceResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AllowList + var dest Service if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -12003,26 +14380,26 @@ func ParseUpdateAllowListResponse(rsp *http.Response) (*UpdateAllowListResponse, return response, nil } -// ParseGetExportersResponse parses an HTTP response from a GetExportersWithResponse call -func ParseGetExportersResponse(rsp *http.Response) (*GetExportersResponse, error) { +// ParseAttachServiceToAllowListResponse parses an HTTP response from a AttachServiceToAllowListWithResponse call +func ParseAttachServiceToAllowListResponse(rsp *http.Response) (*AttachServiceToAllowListResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetExportersResponse{ + response := &AttachServiceToAllowListResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Exporter + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest Service if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON202 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12036,26 +14413,26 @@ func ParseGetExportersResponse(rsp *http.Response) (*GetExportersResponse, error return response, nil } -// ParseCreateExporterResponse parses an HTTP response from a CreateExporterWithResponse call -func ParseCreateExporterResponse(rsp *http.Response) (*CreateExporterResponse, error) { +// ParseAttachServiceToExporterResponse parses an HTTP response from a AttachServiceToExporterWithResponse call +func ParseAttachServiceToExporterResponse(rsp *http.Response) (*AttachServiceToExporterResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateExporterResponse{ + response := &AttachServiceToExporterResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest Exporter + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest Service if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON202 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12069,22 +14446,26 @@ func ParseCreateExporterResponse(rsp *http.Response) (*CreateExporterResponse, e return response, nil } -// ParseDeleteExporterResponse parses an HTTP response from a DeleteExporterWithResponse call -func ParseDeleteExporterResponse(rsp *http.Response) (*DeleteExporterResponse, error) { +// ParseAttachServiceToVPCResponse parses an HTTP response from a AttachServiceToVPCWithResponse call +func ParseAttachServiceToVPCResponse(rsp *http.Response) (*AttachServiceToVPCResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteExporterResponse{ + response := &AttachServiceToVPCResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 204: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest SuccessMessage + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON202 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12098,22 +14479,22 @@ func ParseDeleteExporterResponse(rsp *http.Response) (*DeleteExporterResponse, e return response, nil } -// ParseGetExporterResponse parses an HTTP response from a GetExporterWithResponse call -func ParseGetExporterResponse(rsp *http.Response) (*GetExporterResponse, error) { +// ParseGetBackupRegionsResponse parses an HTTP response from a GetBackupRegionsWithResponse call +func ParseGetBackupRegionsResponse(rsp *http.Response) (*GetBackupRegionsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetExporterResponse{ + response := &GetBackupRegionsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Exporter + var dest []BackupRegion if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -12131,26 +14512,26 @@ func ParseGetExporterResponse(rsp *http.Response) (*GetExporterResponse, error) return response, nil } -// ParseUpdateExporterResponse parses an HTTP response from a UpdateExporterWithResponse call -func ParseUpdateExporterResponse(rsp *http.Response) (*UpdateExporterResponse, error) { +// ParseCreateBackupRegionResponse parses an HTTP response from a CreateBackupRegionWithResponse call +func ParseCreateBackupRegionResponse(rsp *http.Response) (*CreateBackupRegionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateExporterResponse{ + response := &CreateBackupRegionResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Exporter + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest BackupRegion if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12164,26 +14545,22 @@ func ParseUpdateExporterResponse(rsp *http.Response) (*UpdateExporterResponse, e return response, nil } -// ParseGetServicesResponse parses an HTTP response from a GetServicesWithResponse call -func ParseGetServicesResponse(rsp *http.Response) (*GetServicesResponse, error) { +// ParseDeleteBackupRegionResponse parses an HTTP response from a DeleteBackupRegionWithResponse call +func ParseDeleteBackupRegionResponse(rsp *http.Response) (*DeleteBackupRegionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetServicesResponse{ + response := &DeleteBackupRegionResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Service - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest + case rsp.StatusCode == 204: + break // No content-type case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12197,26 +14574,26 @@ func ParseGetServicesResponse(rsp *http.Response) (*GetServicesResponse, error) return response, nil } -// ParseCreateServiceResponse parses an HTTP response from a CreateServiceWithResponse call -func ParseCreateServiceResponse(rsp *http.Response) (*CreateServiceResponse, error) { +// ParseGetBackupRetentionResponse parses an HTTP response from a GetBackupRetentionWithResponse call +func ParseGetBackupRetentionResponse(rsp *http.Response) (*GetBackupRetentionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateServiceResponse{ + response := &GetBackupRetentionResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: - var dest Service + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest BackupRetentionByTime if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON202 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12230,22 +14607,26 @@ func ParseCreateServiceResponse(rsp *http.Response) (*CreateServiceResponse, err return response, nil } -// ParseDeleteServiceResponse parses an HTTP response from a DeleteServiceWithResponse call -func ParseDeleteServiceResponse(rsp *http.Response) (*DeleteServiceResponse, error) { +// ParseSetBackupRetentionResponse parses an HTTP response from a SetBackupRetentionWithResponse call +func ParseSetBackupRetentionResponse(rsp *http.Response) (*SetBackupRetentionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteServiceResponse{ + response := &SetBackupRetentionResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 202: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest BackupRetentionByTime + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12259,22 +14640,22 @@ func ParseDeleteServiceResponse(rsp *http.Response) (*DeleteServiceResponse, err return response, nil } -// ParseGetServiceResponse parses an HTTP response from a GetServiceWithResponse call -func ParseGetServiceResponse(rsp *http.Response) (*GetServiceResponse, error) { +// ParseGetBackupsResponse parses an HTTP response from a GetBackupsWithResponse call +func ParseGetBackupsResponse(rsp *http.Response) (*GetBackupsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetServiceResponse{ + response := &GetBackupsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Service + var dest []Backup if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -12292,26 +14673,26 @@ func ParseGetServiceResponse(rsp *http.Response) (*GetServiceResponse, error) { return response, nil } -// ParseAttachServiceToAllowListResponse parses an HTTP response from a AttachServiceToAllowListWithResponse call -func ParseAttachServiceToAllowListResponse(rsp *http.Response) (*AttachServiceToAllowListResponse, error) { +// ParseGetBranchesResponse parses an HTTP response from a GetBranchesWithResponse call +func ParseGetBranchesResponse(rsp *http.Response) (*GetBranchesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &AttachServiceToAllowListResponse{ + response := &GetBranchesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: - var dest Service + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Branch if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON202 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12325,22 +14706,22 @@ func ParseAttachServiceToAllowListResponse(rsp *http.Response) (*AttachServiceTo return response, nil } -// ParseAttachServiceToExporterResponse parses an HTTP response from a AttachServiceToExporterWithResponse call -func ParseAttachServiceToExporterResponse(rsp *http.Response) (*AttachServiceToExporterResponse, error) { +// ParseCreateBranchResponse parses an HTTP response from a CreateBranchWithResponse call +func ParseCreateBranchResponse(rsp *http.Response) (*CreateBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &AttachServiceToExporterResponse{ + response := &CreateBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: - var dest Service + var dest Branch if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -12358,26 +14739,22 @@ func ParseAttachServiceToExporterResponse(rsp *http.Response) (*AttachServiceToE return response, nil } -// ParseAttachServiceToVPCResponse parses an HTTP response from a AttachServiceToVPCWithResponse call -func ParseAttachServiceToVPCResponse(rsp *http.Response) (*AttachServiceToVPCResponse, error) { +// ParseDeleteBranchResponse parses an HTTP response from a DeleteBranchWithResponse call +func ParseDeleteBranchResponse(rsp *http.Response) (*DeleteBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &AttachServiceToVPCResponse{ + response := &DeleteBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: - var dest SuccessMessage - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON202 = &dest + case rsp.StatusCode == 202: + break // No content-type case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12391,22 +14768,22 @@ func ParseAttachServiceToVPCResponse(rsp *http.Response) (*AttachServiceToVPCRes return response, nil } -// ParseGetBackupRegionsResponse parses an HTTP response from a GetBackupRegionsWithResponse call -func ParseGetBackupRegionsResponse(rsp *http.Response) (*GetBackupRegionsResponse, error) { +// ParseGetBranchResponse parses an HTTP response from a GetBranchWithResponse call +func ParseGetBranchResponse(rsp *http.Response) (*GetBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetBackupRegionsResponse{ + response := &GetBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []BackupRegion + var dest Branch if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -12424,26 +14801,26 @@ func ParseGetBackupRegionsResponse(rsp *http.Response) (*GetBackupRegionsRespons return response, nil } -// ParseCreateBackupRegionResponse parses an HTTP response from a CreateBackupRegionWithResponse call -func ParseCreateBackupRegionResponse(rsp *http.Response) (*CreateBackupRegionResponse, error) { +// ParseRenameBranchResponse parses an HTTP response from a RenameBranchWithResponse call +func ParseRenameBranchResponse(rsp *http.Response) (*RenameBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateBackupRegionResponse{ + response := &RenameBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest BackupRegion + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Branch if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12457,22 +14834,26 @@ func ParseCreateBackupRegionResponse(rsp *http.Response) (*CreateBackupRegionRes return response, nil } -// ParseDeleteBackupRegionResponse parses an HTTP response from a DeleteBackupRegionWithResponse call -func ParseDeleteBackupRegionResponse(rsp *http.Response) (*DeleteBackupRegionResponse, error) { +// ParseResizeBranchResponse parses an HTTP response from a ResizeBranchWithResponse call +func ParseResizeBranchResponse(rsp *http.Response) (*ResizeBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteBackupRegionResponse{ + response := &ResizeBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 204: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest Branch + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON202 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12486,26 +14867,26 @@ func ParseDeleteBackupRegionResponse(rsp *http.Response) (*DeleteBackupRegionRes return response, nil } -// ParseGetBackupRetentionResponse parses an HTTP response from a GetBackupRetentionWithResponse call -func ParseGetBackupRetentionResponse(rsp *http.Response) (*GetBackupRetentionResponse, error) { +// ParseStartBranchResponse parses an HTTP response from a StartBranchWithResponse call +func ParseStartBranchResponse(rsp *http.Response) (*StartBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetBackupRetentionResponse{ + response := &StartBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest BackupRetentionByTime + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest Branch if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON202 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12519,26 +14900,26 @@ func ParseGetBackupRetentionResponse(rsp *http.Response) (*GetBackupRetentionRes return response, nil } -// ParseSetBackupRetentionResponse parses an HTTP response from a SetBackupRetentionWithResponse call -func ParseSetBackupRetentionResponse(rsp *http.Response) (*SetBackupRetentionResponse, error) { +// ParseStopBranchResponse parses an HTTP response from a StopBranchWithResponse call +func ParseStopBranchResponse(rsp *http.Response) (*StopBranchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &SetBackupRetentionResponse{ + response := &StopBranchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest BackupRetentionByTime + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest Branch if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON202 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError @@ -12552,26 +14933,22 @@ func ParseSetBackupRetentionResponse(rsp *http.Response) (*SetBackupRetentionRes return response, nil } -// ParseGetBackupsResponse parses an HTTP response from a GetBackupsWithResponse call -func ParseGetBackupsResponse(rsp *http.Response) (*GetBackupsResponse, error) { +// ParseUpdateBranchPasswordResponse parses an HTTP response from a UpdateBranchPasswordWithResponse call +func ParseUpdateBranchPasswordResponse(rsp *http.Response) (*UpdateBranchPasswordResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetBackupsResponse{ + response := &UpdateBranchPasswordResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []Backup - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest + case rsp.StatusCode == 204: + break // No content-type case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode/100 == 4: var dest ClientError diff --git a/internal/api/mocks/mock_client.go b/internal/api/mocks/mock_client.go index e83869e0..f9e215f4 100644 --- a/internal/api/mocks/mock_client.go +++ b/internal/api/mocks/mock_client.go @@ -282,6 +282,46 @@ func (mr *MockClientInterfaceMockRecorder) CreateBackupRegionWithBody(ctx, proje return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBackupRegionWithBody", reflect.TypeOf((*MockClientInterface)(nil).CreateBackupRegionWithBody), varargs...) } +// CreateBranch mocks base method. +func (m *MockClientInterface) CreateBranch(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, body api.CreateBranchJSONRequestBody, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateBranch", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateBranch indicates an expected call of CreateBranch. +func (mr *MockClientInterfaceMockRecorder) CreateBranch(ctx, projectID, serviceID, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBranch", reflect.TypeOf((*MockClientInterface)(nil).CreateBranch), varargs...) +} + +// CreateBranchWithBody mocks base method. +func (m *MockClientInterface) CreateBranchWithBody(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateBranchWithBody", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateBranchWithBody indicates an expected call of CreateBranchWithBody. +func (mr *MockClientInterfaceMockRecorder) CreateBranchWithBody(ctx, projectID, serviceID, contentType, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBranchWithBody", reflect.TypeOf((*MockClientInterface)(nil).CreateBranchWithBody), varargs...) +} + // CreateExporter mocks base method. func (m *MockClientInterface) CreateExporter(ctx context.Context, projectID api.ProjectID, body api.CreateExporterJSONRequestBody, reqEditors ...api.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -522,6 +562,26 @@ func (mr *MockClientInterfaceMockRecorder) DeleteBackupRegion(ctx, projectID, se return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBackupRegion", reflect.TypeOf((*MockClientInterface)(nil).DeleteBackupRegion), varargs...) } +// DeleteBranch mocks base method. +func (m *MockClientInterface) DeleteBranch(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteBranch", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteBranch indicates an expected call of DeleteBranch. +func (mr *MockClientInterfaceMockRecorder) DeleteBranch(ctx, projectID, serviceID, branchID any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBranch", reflect.TypeOf((*MockClientInterface)(nil).DeleteBranch), varargs...) +} + // DeleteExporter mocks base method. func (m *MockClientInterface) DeleteExporter(ctx context.Context, projectID api.ProjectID, exporterID api.ExporterID, reqEditors ...api.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -1002,6 +1062,46 @@ func (mr *MockClientInterfaceMockRecorder) GetBackups(ctx, projectID, serviceID return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBackups", reflect.TypeOf((*MockClientInterface)(nil).GetBackups), varargs...) } +// GetBranch mocks base method. +func (m *MockClientInterface) GetBranch(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetBranch", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBranch indicates an expected call of GetBranch. +func (mr *MockClientInterfaceMockRecorder) GetBranch(ctx, projectID, serviceID, branchID any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBranch", reflect.TypeOf((*MockClientInterface)(nil).GetBranch), varargs...) +} + +// GetBranches mocks base method. +func (m *MockClientInterface) GetBranches(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, params *api.GetBranchesParams, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, params} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetBranches", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBranches indicates an expected call of GetBranches. +func (mr *MockClientInterfaceMockRecorder) GetBranches(ctx, projectID, serviceID, params any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, params}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBranches", reflect.TypeOf((*MockClientInterface)(nil).GetBranches), varargs...) +} + // GetExporter mocks base method. func (m *MockClientInterface) GetExporter(ctx context.Context, projectID api.ProjectID, exporterID api.ExporterID, reqEditors ...api.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -1042,6 +1142,26 @@ func (mr *MockClientInterfaceMockRecorder) GetExporters(ctx, projectID any, reqE return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExporters", reflect.TypeOf((*MockClientInterface)(nil).GetExporters), varargs...) } +// GetProjectBranches mocks base method. +func (m *MockClientInterface) GetProjectBranches(ctx context.Context, projectID api.ProjectID, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetProjectBranches", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProjectBranches indicates an expected call of GetProjectBranches. +func (mr *MockClientInterfaceMockRecorder) GetProjectBranches(ctx, projectID any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectBranches", reflect.TypeOf((*MockClientInterface)(nil).GetProjectBranches), varargs...) +} + // GetProjects mocks base method. func (m *MockClientInterface) GetProjects(ctx context.Context, reqEditors ...api.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -1382,6 +1502,46 @@ func (mr *MockClientInterfaceMockRecorder) LogoutWithBody(ctx, contentType, body return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogoutWithBody", reflect.TypeOf((*MockClientInterface)(nil).LogoutWithBody), varargs...) } +// RenameBranch mocks base method. +func (m *MockClientInterface) RenameBranch(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, body api.RenameBranchJSONRequestBody, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "RenameBranch", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RenameBranch indicates an expected call of RenameBranch. +func (mr *MockClientInterfaceMockRecorder) RenameBranch(ctx, projectID, serviceID, branchID, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenameBranch", reflect.TypeOf((*MockClientInterface)(nil).RenameBranch), varargs...) +} + +// RenameBranchWithBody mocks base method. +func (m *MockClientInterface) RenameBranchWithBody(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "RenameBranchWithBody", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RenameBranchWithBody indicates an expected call of RenameBranchWithBody. +func (mr *MockClientInterfaceMockRecorder) RenameBranchWithBody(ctx, projectID, serviceID, branchID, contentType, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenameBranchWithBody", reflect.TypeOf((*MockClientInterface)(nil).RenameBranchWithBody), varargs...) +} + // RenameService mocks base method. func (m *MockClientInterface) RenameService(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, body api.RenameServiceJSONRequestBody, reqEditors ...api.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -1462,6 +1622,46 @@ func (mr *MockClientInterfaceMockRecorder) RenameVPCWithBody(ctx, projectID, vpc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenameVPCWithBody", reflect.TypeOf((*MockClientInterface)(nil).RenameVPCWithBody), varargs...) } +// ResizeBranch mocks base method. +func (m *MockClientInterface) ResizeBranch(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, body api.ResizeBranchJSONRequestBody, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ResizeBranch", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ResizeBranch indicates an expected call of ResizeBranch. +func (mr *MockClientInterfaceMockRecorder) ResizeBranch(ctx, projectID, serviceID, branchID, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResizeBranch", reflect.TypeOf((*MockClientInterface)(nil).ResizeBranch), varargs...) +} + +// ResizeBranchWithBody mocks base method. +func (m *MockClientInterface) ResizeBranchWithBody(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ResizeBranchWithBody", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ResizeBranchWithBody indicates an expected call of ResizeBranchWithBody. +func (mr *MockClientInterfaceMockRecorder) ResizeBranchWithBody(ctx, projectID, serviceID, branchID, contentType, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResizeBranchWithBody", reflect.TypeOf((*MockClientInterface)(nil).ResizeBranchWithBody), varargs...) +} + // ResizeReplicaSet mocks base method. func (m *MockClientInterface) ResizeReplicaSet(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, replicaSetID api.ReplicaSetID, body api.ResizeReplicaSetJSONRequestBody, reqEditors ...api.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -1542,6 +1742,26 @@ func (mr *MockClientInterfaceMockRecorder) ResizeServiceWithBody(ctx, projectID, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResizeServiceWithBody", reflect.TypeOf((*MockClientInterface)(nil).ResizeServiceWithBody), varargs...) } +// ResolveRef mocks base method. +func (m *MockClientInterface) ResolveRef(ctx context.Context, projectID api.ProjectID, params *api.ResolveRefParams, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, params} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ResolveRef", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ResolveRef indicates an expected call of ResolveRef. +func (mr *MockClientInterfaceMockRecorder) ResolveRef(ctx, projectID, params any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, params}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveRef", reflect.TypeOf((*MockClientInterface)(nil).ResolveRef), varargs...) +} + // SetBackupRetention mocks base method. func (m *MockClientInterface) SetBackupRetention(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, body api.SetBackupRetentionJSONRequestBody, reqEditors ...api.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -1702,6 +1922,26 @@ func (mr *MockClientInterfaceMockRecorder) SetReplicaEnvironmentWithBody(ctx, pr return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReplicaEnvironmentWithBody", reflect.TypeOf((*MockClientInterface)(nil).SetReplicaEnvironmentWithBody), varargs...) } +// StartBranch mocks base method. +func (m *MockClientInterface) StartBranch(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "StartBranch", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StartBranch indicates an expected call of StartBranch. +func (mr *MockClientInterfaceMockRecorder) StartBranch(ctx, projectID, serviceID, branchID any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartBranch", reflect.TypeOf((*MockClientInterface)(nil).StartBranch), varargs...) +} + // StartService mocks base method. func (m *MockClientInterface) StartService(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, reqEditors ...api.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -1722,6 +1962,26 @@ func (mr *MockClientInterfaceMockRecorder) StartService(ctx, projectID, serviceI return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartService", reflect.TypeOf((*MockClientInterface)(nil).StartService), varargs...) } +// StopBranch mocks base method. +func (m *MockClientInterface) StopBranch(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "StopBranch", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StopBranch indicates an expected call of StopBranch. +func (mr *MockClientInterfaceMockRecorder) StopBranch(ctx, projectID, serviceID, branchID any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopBranch", reflect.TypeOf((*MockClientInterface)(nil).StopBranch), varargs...) +} + // StopService mocks base method. func (m *MockClientInterface) StopService(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, reqEditors ...api.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -1862,6 +2122,46 @@ func (mr *MockClientInterfaceMockRecorder) UpdateAllowListWithBody(ctx, projectI return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAllowListWithBody", reflect.TypeOf((*MockClientInterface)(nil).UpdateAllowListWithBody), varargs...) } +// UpdateBranchPassword mocks base method. +func (m *MockClientInterface) UpdateBranchPassword(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, body api.UpdateBranchPasswordJSONRequestBody, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateBranchPassword", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateBranchPassword indicates an expected call of UpdateBranchPassword. +func (mr *MockClientInterfaceMockRecorder) UpdateBranchPassword(ctx, projectID, serviceID, branchID, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBranchPassword", reflect.TypeOf((*MockClientInterface)(nil).UpdateBranchPassword), varargs...) +} + +// UpdateBranchPasswordWithBody mocks base method. +func (m *MockClientInterface) UpdateBranchPasswordWithBody(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateBranchPasswordWithBody", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateBranchPasswordWithBody indicates an expected call of UpdateBranchPasswordWithBody. +func (mr *MockClientInterfaceMockRecorder) UpdateBranchPasswordWithBody(ctx, projectID, serviceID, branchID, contentType, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBranchPasswordWithBody", reflect.TypeOf((*MockClientInterface)(nil).UpdateBranchPasswordWithBody), varargs...) +} + // UpdateExporter mocks base method. func (m *MockClientInterface) UpdateExporter(ctx context.Context, projectID api.ProjectID, exporterID api.ExporterID, body api.UpdateExporterJSONRequestBody, reqEditors ...api.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -2166,6 +2466,46 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) CreateBackupRegionWithRe return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBackupRegionWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).CreateBackupRegionWithResponse), varargs...) } +// CreateBranchWithBodyWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) CreateBranchWithBodyWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*api.CreateBranchResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateBranchWithBodyWithResponse", varargs...) + ret0, _ := ret[0].(*api.CreateBranchResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateBranchWithBodyWithResponse indicates an expected call of CreateBranchWithBodyWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) CreateBranchWithBodyWithResponse(ctx, projectID, serviceID, contentType, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBranchWithBodyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).CreateBranchWithBodyWithResponse), varargs...) +} + +// CreateBranchWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) CreateBranchWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, body api.CreateBranchJSONRequestBody, reqEditors ...api.RequestEditorFn) (*api.CreateBranchResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateBranchWithResponse", varargs...) + ret0, _ := ret[0].(*api.CreateBranchResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateBranchWithResponse indicates an expected call of CreateBranchWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) CreateBranchWithResponse(ctx, projectID, serviceID, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBranchWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).CreateBranchWithResponse), varargs...) +} + // CreateExporterWithBodyWithResponse mocks base method. func (m *MockClientWithResponsesInterface) CreateExporterWithBodyWithResponse(ctx context.Context, projectID api.ProjectID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*api.CreateExporterResponse, error) { m.ctrl.T.Helper() @@ -2406,6 +2746,26 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) DeleteBackupRegionWithRe return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBackupRegionWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).DeleteBackupRegionWithResponse), varargs...) } +// DeleteBranchWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) DeleteBranchWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, reqEditors ...api.RequestEditorFn) (*api.DeleteBranchResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteBranchWithResponse", varargs...) + ret0, _ := ret[0].(*api.DeleteBranchResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteBranchWithResponse indicates an expected call of DeleteBranchWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) DeleteBranchWithResponse(ctx, projectID, serviceID, branchID any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBranchWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).DeleteBranchWithResponse), varargs...) +} + // DeleteExporterWithResponse mocks base method. func (m *MockClientWithResponsesInterface) DeleteExporterWithResponse(ctx context.Context, projectID api.ProjectID, exporterID api.ExporterID, reqEditors ...api.RequestEditorFn) (*api.DeleteExporterResponse, error) { m.ctrl.T.Helper() @@ -2886,6 +3246,46 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) GetBackupsWithResponse(c return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBackupsWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).GetBackupsWithResponse), varargs...) } +// GetBranchWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) GetBranchWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, reqEditors ...api.RequestEditorFn) (*api.GetBranchResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetBranchWithResponse", varargs...) + ret0, _ := ret[0].(*api.GetBranchResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBranchWithResponse indicates an expected call of GetBranchWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) GetBranchWithResponse(ctx, projectID, serviceID, branchID any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBranchWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).GetBranchWithResponse), varargs...) +} + +// GetBranchesWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) GetBranchesWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, params *api.GetBranchesParams, reqEditors ...api.RequestEditorFn) (*api.GetBranchesResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, params} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetBranchesWithResponse", varargs...) + ret0, _ := ret[0].(*api.GetBranchesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBranchesWithResponse indicates an expected call of GetBranchesWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) GetBranchesWithResponse(ctx, projectID, serviceID, params any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, params}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBranchesWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).GetBranchesWithResponse), varargs...) +} + // GetExporterWithResponse mocks base method. func (m *MockClientWithResponsesInterface) GetExporterWithResponse(ctx context.Context, projectID api.ProjectID, exporterID api.ExporterID, reqEditors ...api.RequestEditorFn) (*api.GetExporterResponse, error) { m.ctrl.T.Helper() @@ -2926,6 +3326,26 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) GetExportersWithResponse return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExportersWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).GetExportersWithResponse), varargs...) } +// GetProjectBranchesWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) GetProjectBranchesWithResponse(ctx context.Context, projectID api.ProjectID, reqEditors ...api.RequestEditorFn) (*api.GetProjectBranchesResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetProjectBranchesWithResponse", varargs...) + ret0, _ := ret[0].(*api.GetProjectBranchesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProjectBranchesWithResponse indicates an expected call of GetProjectBranchesWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) GetProjectBranchesWithResponse(ctx, projectID any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectBranchesWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).GetProjectBranchesWithResponse), varargs...) +} + // GetProjectsWithResponse mocks base method. func (m *MockClientWithResponsesInterface) GetProjectsWithResponse(ctx context.Context, reqEditors ...api.RequestEditorFn) (*api.GetProjectsResponse, error) { m.ctrl.T.Helper() @@ -3266,6 +3686,46 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) LogoutWithResponse(ctx, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogoutWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).LogoutWithResponse), varargs...) } +// RenameBranchWithBodyWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) RenameBranchWithBodyWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*api.RenameBranchResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "RenameBranchWithBodyWithResponse", varargs...) + ret0, _ := ret[0].(*api.RenameBranchResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RenameBranchWithBodyWithResponse indicates an expected call of RenameBranchWithBodyWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) RenameBranchWithBodyWithResponse(ctx, projectID, serviceID, branchID, contentType, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenameBranchWithBodyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).RenameBranchWithBodyWithResponse), varargs...) +} + +// RenameBranchWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) RenameBranchWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, body api.RenameBranchJSONRequestBody, reqEditors ...api.RequestEditorFn) (*api.RenameBranchResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "RenameBranchWithResponse", varargs...) + ret0, _ := ret[0].(*api.RenameBranchResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RenameBranchWithResponse indicates an expected call of RenameBranchWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) RenameBranchWithResponse(ctx, projectID, serviceID, branchID, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenameBranchWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).RenameBranchWithResponse), varargs...) +} + // RenameServiceWithBodyWithResponse mocks base method. func (m *MockClientWithResponsesInterface) RenameServiceWithBodyWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*api.RenameServiceResponse, error) { m.ctrl.T.Helper() @@ -3346,6 +3806,46 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) RenameVPCWithResponse(ct return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenameVPCWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).RenameVPCWithResponse), varargs...) } +// ResizeBranchWithBodyWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) ResizeBranchWithBodyWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*api.ResizeBranchResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ResizeBranchWithBodyWithResponse", varargs...) + ret0, _ := ret[0].(*api.ResizeBranchResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ResizeBranchWithBodyWithResponse indicates an expected call of ResizeBranchWithBodyWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) ResizeBranchWithBodyWithResponse(ctx, projectID, serviceID, branchID, contentType, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResizeBranchWithBodyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).ResizeBranchWithBodyWithResponse), varargs...) +} + +// ResizeBranchWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) ResizeBranchWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, body api.ResizeBranchJSONRequestBody, reqEditors ...api.RequestEditorFn) (*api.ResizeBranchResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ResizeBranchWithResponse", varargs...) + ret0, _ := ret[0].(*api.ResizeBranchResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ResizeBranchWithResponse indicates an expected call of ResizeBranchWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) ResizeBranchWithResponse(ctx, projectID, serviceID, branchID, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResizeBranchWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).ResizeBranchWithResponse), varargs...) +} + // ResizeReplicaSetWithBodyWithResponse mocks base method. func (m *MockClientWithResponsesInterface) ResizeReplicaSetWithBodyWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, replicaSetID api.ReplicaSetID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*api.ResizeReplicaSetResponse, error) { m.ctrl.T.Helper() @@ -3426,6 +3926,26 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) ResizeServiceWithRespons return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResizeServiceWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).ResizeServiceWithResponse), varargs...) } +// ResolveRefWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) ResolveRefWithResponse(ctx context.Context, projectID api.ProjectID, params *api.ResolveRefParams, reqEditors ...api.RequestEditorFn) (*api.ResolveRefResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, params} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ResolveRefWithResponse", varargs...) + ret0, _ := ret[0].(*api.ResolveRefResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ResolveRefWithResponse indicates an expected call of ResolveRefWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) ResolveRefWithResponse(ctx, projectID, params any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, params}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveRefWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).ResolveRefWithResponse), varargs...) +} + // SetBackupRetentionWithBodyWithResponse mocks base method. func (m *MockClientWithResponsesInterface) SetBackupRetentionWithBodyWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*api.SetBackupRetentionResponse, error) { m.ctrl.T.Helper() @@ -3586,6 +4106,26 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) SetReplicaEnvironmentWit return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReplicaEnvironmentWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).SetReplicaEnvironmentWithResponse), varargs...) } +// StartBranchWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) StartBranchWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, reqEditors ...api.RequestEditorFn) (*api.StartBranchResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "StartBranchWithResponse", varargs...) + ret0, _ := ret[0].(*api.StartBranchResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StartBranchWithResponse indicates an expected call of StartBranchWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) StartBranchWithResponse(ctx, projectID, serviceID, branchID any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartBranchWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).StartBranchWithResponse), varargs...) +} + // StartServiceWithResponse mocks base method. func (m *MockClientWithResponsesInterface) StartServiceWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, reqEditors ...api.RequestEditorFn) (*api.StartServiceResponse, error) { m.ctrl.T.Helper() @@ -3606,6 +4146,26 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) StartServiceWithResponse return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartServiceWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).StartServiceWithResponse), varargs...) } +// StopBranchWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) StopBranchWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, reqEditors ...api.RequestEditorFn) (*api.StopBranchResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "StopBranchWithResponse", varargs...) + ret0, _ := ret[0].(*api.StopBranchResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StopBranchWithResponse indicates an expected call of StopBranchWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) StopBranchWithResponse(ctx, projectID, serviceID, branchID any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopBranchWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).StopBranchWithResponse), varargs...) +} + // StopServiceWithResponse mocks base method. func (m *MockClientWithResponsesInterface) StopServiceWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, reqEditors ...api.RequestEditorFn) (*api.StopServiceResponse, error) { m.ctrl.T.Helper() @@ -3746,6 +4306,46 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) UpdateAllowListWithRespo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAllowListWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).UpdateAllowListWithResponse), varargs...) } +// UpdateBranchPasswordWithBodyWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) UpdateBranchPasswordWithBodyWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*api.UpdateBranchPasswordResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, contentType, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateBranchPasswordWithBodyWithResponse", varargs...) + ret0, _ := ret[0].(*api.UpdateBranchPasswordResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateBranchPasswordWithBodyWithResponse indicates an expected call of UpdateBranchPasswordWithBodyWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) UpdateBranchPasswordWithBodyWithResponse(ctx, projectID, serviceID, branchID, contentType, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, contentType, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBranchPasswordWithBodyWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).UpdateBranchPasswordWithBodyWithResponse), varargs...) +} + +// UpdateBranchPasswordWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) UpdateBranchPasswordWithResponse(ctx context.Context, projectID api.ProjectID, serviceID api.ServiceID, branchID api.BranchID, body api.UpdateBranchPasswordJSONRequestBody, reqEditors ...api.RequestEditorFn) (*api.UpdateBranchPasswordResponse, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, projectID, serviceID, branchID, body} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateBranchPasswordWithResponse", varargs...) + ret0, _ := ret[0].(*api.UpdateBranchPasswordResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateBranchPasswordWithResponse indicates an expected call of UpdateBranchPasswordWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) UpdateBranchPasswordWithResponse(ctx, projectID, serviceID, branchID, body any, reqEditors ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, projectID, serviceID, branchID, body}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBranchPasswordWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).UpdateBranchPasswordWithResponse), varargs...) +} + // UpdateExporterWithBodyWithResponse mocks base method. func (m *MockClientWithResponsesInterface) UpdateExporterWithBodyWithResponse(ctx context.Context, projectID api.ProjectID, exporterID api.ExporterID, contentType string, body io.Reader, reqEditors ...api.RequestEditorFn) (*api.UpdateExporterResponse, error) { m.ctrl.T.Helper() diff --git a/internal/api/types.go b/internal/api/types.go index bbf11842..5e5a253f 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -633,6 +633,99 @@ func (e ReadReplicaSetStatus) Valid() bool { } } +// Defines values for ResolveType. +const ( + ResolveTypeBRANCH ResolveType = "BRANCH" + ResolveTypeREADREPLICASET ResolveType = "READ_REPLICA_SET" + ResolveTypeSERVICE ResolveType = "SERVICE" +) + +// Valid indicates whether the value is a known member of the ResolveType enum. +func (e ResolveType) Valid() bool { + switch e { + case ResolveTypeBRANCH: + return true + case ResolveTypeREADREPLICASET: + return true + case ResolveTypeSERVICE: + return true + default: + return false + } +} + +// Defines values for ResolvedBranchType. +const ( + ResolvedBranchTypeBRANCH ResolvedBranchType = "BRANCH" +) + +// Valid indicates whether the value is a known member of the ResolvedBranchType enum. +func (e ResolvedBranchType) Valid() bool { + switch e { + case ResolvedBranchTypeBRANCH: + return true + default: + return false + } +} + +// Defines values for ResolvedReadReplicaSetStatus. +const ( + ResolvedReadReplicaSetStatusActive ResolvedReadReplicaSetStatus = "active" + ResolvedReadReplicaSetStatusCreating ResolvedReadReplicaSetStatus = "creating" + ResolvedReadReplicaSetStatusDeleting ResolvedReadReplicaSetStatus = "deleting" + ResolvedReadReplicaSetStatusError ResolvedReadReplicaSetStatus = "error" + ResolvedReadReplicaSetStatusResizing ResolvedReadReplicaSetStatus = "resizing" +) + +// Valid indicates whether the value is a known member of the ResolvedReadReplicaSetStatus enum. +func (e ResolvedReadReplicaSetStatus) Valid() bool { + switch e { + case ResolvedReadReplicaSetStatusActive: + return true + case ResolvedReadReplicaSetStatusCreating: + return true + case ResolvedReadReplicaSetStatusDeleting: + return true + case ResolvedReadReplicaSetStatusError: + return true + case ResolvedReadReplicaSetStatusResizing: + return true + default: + return false + } +} + +// Defines values for ResolvedReadReplicaSetType. +const ( + ResolvedReadReplicaSetTypeREADREPLICASET ResolvedReadReplicaSetType = "READ_REPLICA_SET" +) + +// Valid indicates whether the value is a known member of the ResolvedReadReplicaSetType enum. +func (e ResolvedReadReplicaSetType) Valid() bool { + switch e { + case ResolvedReadReplicaSetTypeREADREPLICASET: + return true + default: + return false + } +} + +// Defines values for ResolvedServiceType. +const ( + ResolvedServiceTypeSERVICE ResolvedServiceType = "SERVICE" +) + +// Valid indicates whether the value is a known member of the ResolvedServiceType enum. +func (e ResolvedServiceType) Valid() bool { + switch e { + case ResolvedServiceTypeSERVICE: + return true + default: + return false + } +} + // Defines values for ServiceCreateAddons. const ( ServiceCreateAddonsAi ServiceCreateAddons = "ai" @@ -1203,6 +1296,109 @@ type BackupRetentionByTimeType string // - `UNKNOWN`: a type this version of the API does not name. type BackupType string +// Branch A branch of a Fluid Service. +type Branch struct { + // CPUMillis CPU allocation in milli-cores. + // + // Example: 500 + CPUMillis int `json:"cpu_millis"` + + // Created Creation timestamp. + // + // Example: 2026-09-01T09:30:00Z + Created time.Time `json:"created"` + + // Endpoint A network endpoint for connecting to a service. + Endpoint *Endpoint `json:"endpoint,omitempty"` + + // ID The unique identifier for the branch. + // + // Example: br4nch1d9x + ID string `json:"id"` + + // InitialPassword The initial password for the branch. + // + // Example: a-very-secure-initial-password + InitialPassword *string `json:"initial_password,omitempty"` + + // Iops Provisioned IOPS for the storage volume. + // + // Example: 3000 + Iops *int `json:"iops,omitempty"` + + // MemoryGbs Memory allocation in gigabytes. + // + // Example: 4 + MemoryGbs int `json:"memory_gbs"` + + // Name The name of the branch. + // + // Example: my-production-db-pr-412 + Name string `json:"name"` + + // ProjectID The project this branch belongs to. + // + // Example: rp1pz7uyae + ProjectID string `json:"project_id"` + + // ServiceID The service this branch was created from. + // + // Example: 4rwj1h3fd0 + ServiceID string `json:"service_id"` + + // Status Current status of the branch. + Status DeployStatus `json:"status"` +} + +// BranchCreate Parameters for creating a branch of the service identified by `service_id` in the path. +type BranchCreate struct { + // CPUMillis The initial CPU allocation in milli-cores. Defaults to 250 if not provided. + // + // Example: 250 + CPUMillis *int `json:"cpu_millis,omitempty"` + + // Iops Requested provisioned IOPS for the branch's storage. + // + // Example: 4000 + Iops *int `json:"iops,omitempty"` + + // MemoryGbs The initial memory allocation in gigabytes. Defaults to 2 if not provided. + // + // Example: 2 + MemoryGbs *int `json:"memory_gbs,omitempty"` + + // Name A human-readable name for the branch. If not provided, defaults to "branch-", with "-1", "-2", ... appended if that name is already in use in the project. + // + // Example: my-production-db-pr-412 + Name *string `json:"name,omitempty"` +} + +// BranchRename Parameters for renaming a branch. +type BranchRename struct { + // Name The new name for the branch. + // + // Example: my-production-db-pr-412 + Name string `json:"name"` +} + +// BranchResizeInput At least one of cpu_millis, memory_gbs, or iops must be provided. cpu_millis and memory_gbs must be provided together. +type BranchResizeInput struct { + // CPUMillis The new CPU allocation in milli-cores. + // + // Example: 1000 + CPUMillis *string `json:"cpu_millis,omitempty"` + + // Iops The new provisioned IOPS for the branch's storage. + // + // Example: 4000 + Iops *int `json:"iops,omitempty"` + + // MemoryGbs The new memory allocation in gigabytes. + // + // Example: 4 + MemoryGbs *string `json:"memory_gbs,omitempty"` +} + // CloudWatchLogsConfig Where a CloudWatch logs exporter writes, and how it authenticates. type CloudWatchLogsConfig struct { // AwsRegion The AWS region the log group is in. Independent of the exporter's own @@ -2375,6 +2571,11 @@ type ReadReplicaSet struct { // Example: 2 Nodes int `json:"nodes"` + // ServiceID The ID of the parent service this read replica set was created from. + // + // Example: d1k5vk7hf2 + ServiceID string `json:"service_id"` + // Status The current status of the read replica set: // - creating: the replica set is being provisioned // - active: the replica set is provisioned and serving traffic @@ -2451,6 +2652,249 @@ type ResizeInput struct { MemoryGbs *string `json:"memory_gbs,omitempty"` } +// ResolveResult A service, branch, or read replica set resolved by ref. `type` determines which. +type ResolveResult struct { + union json.RawMessage +} + +// ResolveType What kind of resource a resolved ref is, or should be restricted to. +type ResolveType string + +// ResolvedBranch A Branch resolved by ref. +type ResolvedBranch struct { + // CPUMillis CPU allocation in milli-cores. + // + // Example: 500 + CPUMillis int `json:"cpu_millis"` + + // Created Creation timestamp. + // + // Example: 2026-09-01T09:30:00Z + Created time.Time `json:"created"` + + // Endpoint A network endpoint for connecting to a service. + Endpoint *Endpoint `json:"endpoint,omitempty"` + + // ID The unique identifier for the branch. + // + // Example: br4nch1d9x + ID string `json:"id"` + + // InitialPassword The initial password for the branch. + // + // Example: a-very-secure-initial-password + InitialPassword *string `json:"initial_password,omitempty"` + + // Iops Provisioned IOPS for the storage volume. + // + // Example: 3000 + Iops *int `json:"iops,omitempty"` + + // MemoryGbs Memory allocation in gigabytes. + // + // Example: 4 + MemoryGbs int `json:"memory_gbs"` + + // Name The name of the branch. + // + // Example: my-production-db-pr-412 + Name string `json:"name"` + + // ProjectID The project this branch belongs to. + // + // Example: rp1pz7uyae + ProjectID string `json:"project_id"` + + // ServiceID The service this branch was created from. + // + // Example: 4rwj1h3fd0 + ServiceID string `json:"service_id"` + + // Status Current status of the branch. + Status DeployStatus `json:"status"` + + // Type Always "BRANCH" for a resolved Branch. + Type ResolvedBranchType `json:"type"` +} + +// ResolvedBranchType Always "BRANCH" for a resolved Branch. +type ResolvedBranchType string + +// ResolvedReadReplicaSet A ReadReplicaSet resolved by ref. +type ResolvedReadReplicaSet struct { + // ConnectionPooler Connection pooler configuration for a service. + ConnectionPooler *ConnectionPooler `json:"connection_pooler,omitempty"` + + // CPUMillis CPU allocation in milli-cores. + // + // Example: 250 + CPUMillis int `json:"cpu_millis"` + + // Endpoint A network endpoint for connecting to a service. + Endpoint *Endpoint `json:"endpoint,omitempty"` + + // ID The unique identifier for the read replica set. + // + // Example: alb8jicdpr + ID string `json:"id"` + + // Iops Provisioned IOPS for the storage volume. + // + // Example: 3000 + Iops *int `json:"iops,omitempty"` + + // MemoryGbs Memory allocation in gigabytes. + // + // Example: 1 + MemoryGbs int `json:"memory_gbs"` + + // Metadata Additional metadata for the read replica set. + Metadata *ReplicaSetMetadata `json:"metadata,omitempty"` + + // Name The name of the read replica set. + // + // Example: reporting-replica-1 + Name string `json:"name"` + + // Nodes Number of nodes in the replica set. + // + // Example: 2 + Nodes int `json:"nodes"` + + // ServiceID The ID of the parent service this read replica set was created from. + // + // Example: d1k5vk7hf2 + ServiceID string `json:"service_id"` + + // Status The current status of the read replica set: + // - creating: the replica set is being provisioned + // - active: the replica set is provisioned and serving traffic + // - resizing: the replica set is being resized + // - deleting: the replica set is being deleted + // - error: the replica set is in an error state + // + // + // Example: active + Status ResolvedReadReplicaSetStatus `json:"status"` + + // Type Always "READ_REPLICA_SET" for a resolved ReadReplicaSet. + Type ResolvedReadReplicaSetType `json:"type"` +} + +// ResolvedReadReplicaSetStatus The current status of the read replica set: +// - creating: the replica set is being provisioned +// - active: the replica set is provisioned and serving traffic +// - resizing: the replica set is being resized +// - deleting: the replica set is being deleted +// - error: the replica set is in an error state +// +// Example: active +type ResolvedReadReplicaSetStatus string + +// ResolvedReadReplicaSetType Always "READ_REPLICA_SET" for a resolved ReadReplicaSet. +type ResolvedReadReplicaSetType string + +// ResolvedService A Service resolved by ref. +type ResolvedService struct { + // ConnectionPooler Connection pooler configuration for a service. + ConnectionPooler *ConnectionPooler `json:"connection_pooler,omitempty"` + + // Created Creation timestamp. + // + // Example: 2025-01-15T09:30:00Z + Created time.Time `json:"created"` + + // DataTiering Data tiering configuration for a service. + DataTiering *DataTiering `json:"data_tiering,omitempty"` + + // Endpoint A network endpoint for connecting to a service. + Endpoint *Endpoint `json:"endpoint,omitempty"` + + // ForkedFrom The service this service was forked from. + ForkedFrom *ForkSpec `json:"forked_from,omitempty"` + + // HaReplicas High-availability replica configuration for a service. + HaReplicas *HAReplica `json:"ha_replicas,omitempty"` + + // InitialPassword The initial password for the service. + // + // Example: a-very-secure-initial-password + InitialPassword *string `json:"initial_password,omitempty"` + + // LogExporterID The exporter this service sends its logs to. Absent when the + // service is not attached to a log exporter. Change it with the + // attach and detach operations. + // + // A newly attached log exporter only starts carrying logs once the + // service has restarted. + // + // + // Example: 9b2e4d61-7a30-4c58-8f0d-6e1a3c9b4d27 + LogExporterID *openapi_types.UUID `json:"log_exporter_id,omitempty"` + + // Metadata Additional metadata for the service. + Metadata *ServiceMetadata `json:"metadata,omitempty"` + + // MetricExporterID The exporter this service sends its metrics to. Absent when the + // service is not attached to a metric exporter. Change it with the + // attach and detach operations. + // + // + // Example: 3f7c1c9a-0d2e-4d76-9a1b-5c8e0f2a7b31 + MetricExporterID *openapi_types.UUID `json:"metric_exporter_id,omitempty"` + + // Metrics Resource usage metrics for the service. + Metrics *ServiceMetrics `json:"metrics,omitempty"` + + // Name The name of the service. + // + // Example: my-production-db + Name string `json:"name"` + + // Platform The storage platform the service runs on. + Platform *ServicePlatform `json:"platform,omitempty"` + + // ProjectID The project this service belongs to. + // + // Example: rp1pz7uyae + ProjectID string `json:"project_id"` + + // ReadReplicaSets The read replica sets attached to the service. + ReadReplicaSets *[]ReadReplicaSet `json:"read_replica_sets,omitempty"` + + // RegionCode The cloud region where the service is hosted. + // + // Example: us-east-1 + RegionCode string `json:"region_code"` + + // Resources List of resources allocated to the service. + Resources []Resource `json:"resources"` + + // ServiceID The unique identifier for the service. + // + // Example: kd9w2xp4mz + ServiceID string `json:"service_id"` + + // ServiceType The type of the service. + ServiceType ServiceType `json:"service_type"` + + // Status Current status of the service. + Status DeployStatus `json:"status"` + + // Type Always "SERVICE" for a resolved Service. + Type ResolvedServiceType `json:"type"` + + // VpcEndpointDeprecated Deprecated. Use `vpc_endpoint` instead. + // Deprecated: Use `vpc_endpoint` instead. + VpcEndpointDeprecated *VPCEndpoint `json:"vpcEndpoint,omitempty"` + + // VpcEndpoint VPC endpoint configuration if available. + VpcEndpoint *VPCEndpoint `json:"vpc_endpoint,omitempty"` +} + +// ResolvedServiceType Always "SERVICE" for a resolved Service. +type ResolvedServiceType string + // Resource A resource allocated to the service. type Resource struct { // ID Resource identifier. @@ -2881,6 +3325,9 @@ type AllowListID = string // BackupRegionCode Example: eu-central-1 type BackupRegionCode = string +// BranchID Example: br4nch1d9x +type BranchID = string + // ExporterID Example: 3f7c1c9a-0d2e-4d76-9a1b-5c8e0f2a7b31 type ExporterID = openapi_types.UUID @@ -2959,6 +3406,16 @@ type SubmitFeedbackJSONBody struct { // SubmitFeedbackJSONBodySource defines parameters for SubmitFeedback. type SubmitFeedbackJSONBodySource string +// ResolveRefParams defines parameters for ResolveRef. +type ResolveRefParams struct { + // Ref The ID or name to resolve, exactly and case-sensitively. A ref + // matching more than one resource is an error. + Ref string `form:"ref" json:"ref"` + + // Type Only match resources of this kind. + Type *ResolveType `form:"type,omitempty" json:"type,omitempty"` +} + // GetServicesParams defines parameters for GetServices. type GetServicesParams struct { // Ref Returns only the service this reference matches: one whose ID @@ -2971,6 +3428,15 @@ type GetServicesParams struct { Ref *string `form:"ref,omitempty" json:"ref,omitempty"` } +// GetBranchesParams defines parameters for GetBranches. +type GetBranchesParams struct { + // Ref Returns only the branch this reference matches: one whose ID + // equals it, or whose name equals it, exactly and case-sensitively, + // with no precedence between the two. A reference matching nothing + // returns an empty list. + Ref *string `form:"ref,omitempty" json:"ref,omitempty"` +} + // GetServiceLogsParams defines parameters for GetServiceLogs. type GetServiceLogsParams struct { // Node Specific service node to fetch logs from (for multi-node services). @@ -3037,6 +3503,18 @@ type CreateBackupRegionJSONRequestBody = BackupRegionCreate // SetBackupRetentionJSONRequestBody defines body for SetBackupRetention for application/json ContentType. type SetBackupRetentionJSONRequestBody = BackupRetentionByTime +// CreateBranchJSONRequestBody defines body for CreateBranch for application/json ContentType. +type CreateBranchJSONRequestBody = BranchCreate + +// RenameBranchJSONRequestBody defines body for RenameBranch for application/json ContentType. +type RenameBranchJSONRequestBody = BranchRename + +// ResizeBranchJSONRequestBody defines body for ResizeBranch for application/json ContentType. +type ResizeBranchJSONRequestBody = BranchResizeInput + +// UpdateBranchPasswordJSONRequestBody defines body for UpdateBranchPassword for application/json ContentType. +type UpdateBranchPasswordJSONRequestBody = UpdatePasswordInput + // DetachServiceFromAllowListJSONRequestBody defines body for DetachServiceFromAllowList for application/json ContentType. type DetachServiceFromAllowListJSONRequestBody = ServiceAllowListInput @@ -3913,3 +4391,140 @@ func (t *ExporterUpdate) UnmarshalJSON(b []byte) error { err := t.union.UnmarshalJSON(b) return err } + +// AsResolvedService returns the union data inside the ResolveResult as a ResolvedService +func (t ResolveResult) AsResolvedService() (ResolvedService, error) { + var body ResolvedService + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromResolvedService overwrites any union data inside the ResolveResult as the provided ResolvedService +func (t *ResolveResult) FromResolvedService(v ResolvedService) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + b, err = runtime.JSONMerge(b, []byte(`{"type":"SERVICE"}`)) + t.union = b + return err +} + +// MergeResolvedService performs a merge with any union data inside the ResolveResult, using the provided ResolvedService +func (t *ResolveResult) MergeResolvedService(v ResolvedService) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + b, err = runtime.JSONMerge(b, []byte(`{"type":"SERVICE"}`)) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsResolvedBranch returns the union data inside the ResolveResult as a ResolvedBranch +func (t ResolveResult) AsResolvedBranch() (ResolvedBranch, error) { + var body ResolvedBranch + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromResolvedBranch overwrites any union data inside the ResolveResult as the provided ResolvedBranch +func (t *ResolveResult) FromResolvedBranch(v ResolvedBranch) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + b, err = runtime.JSONMerge(b, []byte(`{"type":"BRANCH"}`)) + t.union = b + return err +} + +// MergeResolvedBranch performs a merge with any union data inside the ResolveResult, using the provided ResolvedBranch +func (t *ResolveResult) MergeResolvedBranch(v ResolvedBranch) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + b, err = runtime.JSONMerge(b, []byte(`{"type":"BRANCH"}`)) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsResolvedReadReplicaSet returns the union data inside the ResolveResult as a ResolvedReadReplicaSet +func (t ResolveResult) AsResolvedReadReplicaSet() (ResolvedReadReplicaSet, error) { + var body ResolvedReadReplicaSet + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromResolvedReadReplicaSet overwrites any union data inside the ResolveResult as the provided ResolvedReadReplicaSet +func (t *ResolveResult) FromResolvedReadReplicaSet(v ResolvedReadReplicaSet) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + b, err = runtime.JSONMerge(b, []byte(`{"type":"READ_REPLICA_SET"}`)) + t.union = b + return err +} + +// MergeResolvedReadReplicaSet performs a merge with any union data inside the ResolveResult, using the provided ResolvedReadReplicaSet +func (t *ResolveResult) MergeResolvedReadReplicaSet(v ResolvedReadReplicaSet) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + b, err = runtime.JSONMerge(b, []byte(`{"type":"READ_REPLICA_SET"}`)) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t ResolveResult) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"type"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t ResolveResult) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "BRANCH": + return t.AsResolvedBranch() + case "READ_REPLICA_SET": + return t.AsResolvedReadReplicaSet() + case "SERVICE": + return t.AsResolvedService() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + +func (t ResolveResult) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *ResolveResult) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} diff --git a/internal/cmd/auth_status.go b/internal/cmd/auth_status.go index 3e6414d8..009a2132 100644 --- a/internal/cmd/auth_status.go +++ b/internal/cmd/auth_status.go @@ -72,11 +72,11 @@ func outputAuthInfo(cmd *cobra.Command, authInfo api.AuthInfo, format string) er case "yaml": return util.SerializeToYAML(outputWriter, authInfo) default: // table format (default) - return outputAuthInfoTable(authInfo, outputWriter) + return outputAuthInfoTable(outputWriter, authInfo) } } -func outputAuthInfoTable(authInfo api.AuthInfo, output io.Writer) error { +func outputAuthInfoTable(output io.Writer, authInfo api.AuthInfo) error { table := tablewriter.NewWriter(output) table.Header("PROPERTY", "VALUE") table.Append("Status", "Logged in") diff --git a/internal/cmd/branch.go b/internal/cmd/branch.go new file mode 100644 index 00000000..d28d0bf3 --- /dev/null +++ b/internal/cmd/branch.go @@ -0,0 +1,151 @@ +package cmd + +import ( + "fmt" + "io" + "strings" + + "github.com/olekukonko/tablewriter" + "github.com/spf13/cobra" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/config" + "github.com/timescale/tiger-cli/internal/util" +) + +// branchCPUMemoryHelp is the table of CPU/Memory configurations a branch can +// be created or resized onto. +const branchCPUMemoryHelp = "Allowed CPU/Memory Configurations:\n" + fluidCPUMemoryHelp + +// buildBranchCmd creates the branch command with all subcommands. Branches are +// backed by gateway endpoints marked `x-tigerdata-preview: true`, so the whole +// group is only added to the tree behind the experimental gate (see root.go). +func buildBranchCmd(app *common.App) *cobra.Command { + cmd := &cobra.Command{ + Use: "branch", + Aliases: []string{"branches"}, + Short: "Manage branches of database services", + Long: `Manage branches of Fluid Services. + +A branch is a lightweight, ephemeral copy of a Fluid Service, for development, +testing, CI/CD, and agent workflows. It shares its parent's storage as of the +moment it was created and gets its own compute.`, + } + + cmd.AddCommand(buildBranchCreateCmd(app)) + cmd.AddCommand(buildBranchListCmd(app)) + cmd.AddCommand(buildBranchGetCmd(app)) + cmd.AddCommand(buildBranchDeleteCmd(app)) + cmd.AddCommand(buildBranchStartCmd(app)) + cmd.AddCommand(buildBranchStopCmd(app)) + cmd.AddCommand(buildBranchResizeCmd(app)) + cmd.AddCommand(buildBranchRenameCmd(app)) + cmd.AddCommand(buildBranchUpdatePasswordCmd(app)) + + return cmd +} + +// OutputBranch represents a branch with computed fields for output. +type OutputBranch struct { + api.Branch + // ServiceName is the parent service's name, when it's known. + ServiceName string `json:"service_name,omitempty"` + common.ConnectionDetails + ConnectionString string `json:"connection_string,omitempty"` +} + +// branchOutputOptions controls what the branch output functions include. +// The zero value leaves out every optional field. +type branchOutputOptions struct { + // ServiceName is the parent service's name, if it's known. A branch + // carries only its parent's ID. + ServiceName string +} + +// outputBranch outputs a single branch in the configured output format. +func outputBranch(cmd *cobra.Command, cfg *config.Config, branch api.Branch, opts branchOutputOptions) error { + outputBr := prepareBranchForOutput(cmd, cfg, branch, opts) + outputWriter := cmd.OutOrStdout() + + switch strings.ToLower(cfg.Output) { + case "json": + return util.SerializeToJSON(outputWriter, outputBr) + case "yaml": + return util.SerializeToYAML(outputWriter, outputBr) + case "env": + return outputBranchEnv(cmd, outputBr) + default: // table format (default) + return outputBranchTable(outputWriter, outputBr) + } +} + +// outputBranchEnv outputs branch connection details in environment variable +// format. Branch commands never output a password, so PGPASSWORD is never set. +func outputBranchEnv(cmd *cobra.Command, branch OutputBranch) error { + cmd.Printf("PGHOST=%s\n", branch.Host) + cmd.Printf("PGPORT=%d\n", branch.Port) + cmd.Printf("PGDATABASE=%s\n", branch.Database) + cmd.Printf("PGUSER=%s\n", branch.Role) + return nil +} + +// outputBranchTable outputs detailed branch information in a formatted table. +func outputBranchTable(output io.Writer, branch OutputBranch) error { + table := tablewriter.NewWriter(output) + table.Header("PROPERTY", "VALUE") + + table.Append("Branch ID", branch.ID) + table.Append("Name", branch.Name) + if branch.ServiceName != "" { + table.Append("Parent Service", fmt.Sprintf("%s (%s)", branch.ServiceName, branch.ServiceID)) + } else { + table.Append("Parent Service", branch.ServiceID) + } + table.Append("Status", string(branch.Status)) + table.Append("CPU", fmt.Sprintf("%s cores (%dm)", common.FormatCPUCores(branch.CPUMillis), branch.CPUMillis)) + table.Append("Memory", fmt.Sprintf("%d GB", branch.MemoryGbs)) + if branch.Iops != nil { + table.Append("IOPS", fmt.Sprintf("%d", *branch.Iops)) + } + if branch.Endpoint != nil && branch.Endpoint.Host != nil && branch.Endpoint.Port != nil { + table.Append("Direct Endpoint", fmt.Sprintf("%s:%d", *branch.Endpoint.Host, *branch.Endpoint.Port)) + } + table.Append("Created", branch.Created.Format("2006-01-02 15:04:05 MST")) + if branch.ConnectionString != "" { + table.Append("Connection String", branch.ConnectionString) + } + + return table.Render() +} + +// prepareBranchForOutput builds the output view of a branch. cmd may be nil, +// in which case the connection-details warning is dropped rather than printed. +func prepareBranchForOutput(cmd *cobra.Command, cfg *config.Config, branch api.Branch, opts branchOutputOptions) OutputBranch { + outputBr := OutputBranch{ + Branch: branch, + ServiceName: opts.ServiceName, + } + outputBr.Branch.InitialPassword = nil + + // A branch that's still provisioning may have no endpoint yet, which + // isn't worth a warning. + if branch.Endpoint != nil { + opts := common.ConnectionDetailsOptions{ + Role: "tsdbadmin", + ReadOnly: common.CheckReadOnly(cfg, common.BranchEnvironmentTag) != nil, + } + if details, err := common.GetConnectionDetails(cfg, common.NewBranchConnectionTarget(branch), opts); err != nil { + if cmd != nil { + cmd.PrintErrf("Warning: failed to get connection details: %v\n", err) + } + } else { + outputBr.ConnectionDetails = *details + outputBr.ConnectionString = details.String() + } + } + + // TODO: add a Console URL once the Console supports branches. + + return outputBr +} diff --git a/internal/cmd/branch_create.go b/internal/cmd/branch_create.go new file mode 100644 index 00000000..16d9acf1 --- /dev/null +++ b/internal/cmd/branch_create.go @@ -0,0 +1,184 @@ +package cmd + +import ( + "fmt" + "net/http" + "time" + + "github.com/spf13/cobra" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// buildBranchCreateCmd creates the create subcommand of `tiger branch`, the +// same command as `tiger service branch` (see newBranchCreateCmd). +func buildBranchCreateCmd(app *common.App) *cobra.Command { + return newBranchCreateCmd(app, "create [service-name-or-id]", "tiger branch create") +} + +// newBranchCreateCmd builds the command that creates a branch of a service. +// `tiger branch create` and `tiger service branch` are the same command in two +// places in the tree, so both are built here, differing only in their use line +// and the command path their examples show. +func newBranchCreateCmd(app *common.App, use, commandPath string) *cobra.Command { + var name string + var cpu string + var memory string + var iops int + var noWait bool + var waitTimeout time.Duration + + cmd := &cobra.Command{ + Use: use, + Short: "Create a branch of a database service", + Long: `Create a branch of a Fluid Service. + +A branch is a lightweight, ephemeral copy of a service, sharing the service's +storage as of the moment it is created and getting its own compute. The +tsdbadmin user gets a new password, which is saved the same way as a new +service's. + +By default: +- The name is auto-generated +- The branch gets 0.25 CPU and 2GB of memory, whatever the service's size + +The service can be given by ID or name as an argument, or will use the default +service from your configuration. Branching from a branch isn't supported. + +` + branchCPUMemoryHelp + ` + +The allowed --iops values depend on your plan and the branch's CPU/memory +allocation. See the Tiger Data documentation for details. If --iops is not +given, the branch gets the baseline IOPS for your plan and its CPU allocation.`, + Example: fmt.Sprintf(` # Branch a service + %[1]s svc-12345 + + # Branch a service with a custom name + %[1]s svc-12345 --name ci-4821 + + # Branch a service with custom compute + %[1]s svc-12345 --cpu 2000 --memory 16 + + # Branch a service with provisioned IOPS + %[1]s svc-12345 --cpu 2000 --iops 16000 + + # Branch for a script + %[1]s svc-12345 --name ci-4821 -o json`, commandPath), + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: serviceRefCompletion(app), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + cfg, client, projectID, err := app.GetAll() + if err != nil { + return err + } + + // Gate on the branch's own tag, which is always DEV: under prod + // mode, branching a PROD service is allowed, as forking one into + // a DEV fork is. + if err := common.CheckReadOnly(cfg, common.BranchEnvironmentTag); err != nil { + return err + } + + cpuMemoryCfg, err := common.FluidCPUMemoryConfigs().ValidateAndNormalize(cpu, memory) + if err != nil { + return err + } + + serviceRef, err := getServiceRef(cmd, cfg, args) + if err != nil { + return err + } + parent, err := resolveService(cmd.Context(), client, projectID, serviceRef) + if err != nil { + return err + } + + if name != "" { + cmd.PrintErrf("Creating branch '%s' from service %s...\n", name, serviceLabel(*parent)) + } else { + cmd.PrintErrf("Creating branch from service %s...\n", serviceLabel(*parent)) + } + + var createReq api.BranchCreate + if name != "" { + createReq.Name = &name + } + if cpuMemoryCfg != nil { + createReq.CPUMillis = &cpuMemoryCfg.CPUMillis + createReq.MemoryGbs = &cpuMemoryCfg.MemoryGBs + } + if cmd.Flags().Changed("iops") { + createReq.Iops = &iops + } + + resp, err := client.CreateBranchWithResponse(cmd.Context(), projectID, parent.ServiceID, createReq) + if err != nil { + return fmt.Errorf("failed to create branch: %w", err) + } + if resp.StatusCode() != http.StatusAccepted { + return common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON202 == nil { + return fmt.Errorf("empty response from API") + } + branch := *resp.JSON202 + + cmd.PrintErrf("Branch ID: %s\n", branch.ID) + + // Saved before any waiting, so the password is stored even if the + // wait fails or is interrupted. + passwordSaved := handlePasswordSaving(cmd, cfg, common.BranchPasswordTarget(branch), util.Deref(branch.InitialPassword)) + + var waitErr error + if noWait { + cmd.PrintErrf("Branch is being created. Use 'tiger branch get %s' to check branch status.\n", branch.ID) + } else { + cmd.PrintErrf("Waiting for branch to be ready (timeout: %v)...\n", waitTimeout) + if waitErr = common.WaitForBranch(cmd.Context(), common.WaitForBranchArgs{ + Client: client, + ProjectID: projectID, + ServiceID: parent.ServiceID, + BranchID: branch.ID, + Branch: &branch, + TargetStatus: api.DeployStatusREADY, + Input: cmd.InOrStdin(), + Output: cmd.ErrOrStderr(), + Timeout: waitTimeout, + TimeoutMsg: "branch may still be provisioning", + }); waitErr != nil { + cmd.PrintErrf("Error: %s\n", waitErr) + } else { + cmd.PrintErrln("Branch is ready.") + // A branch is never the default, so the command names it. + printConnectMessage(cmd, passwordSaved, true, branch.ID) + } + } + + if err := outputBranch(cmd, cfg, branch, branchOutputOptions{ServiceName: parent.Name}); err != nil { + cmd.PrintErrf("Warning: failed to output branch details: %v\n", err) + } + + // Return error for sake of exit code, but silence it since it was already output above + cmd.SilenceErrors = true + return waitErr + }, + } + + cmd.Flags().StringVar(&name, "name", "", "Name for the branch (auto-generated if not provided)") + cmd.Flags().StringVar(&cpu, "cpu", "", "CPU allocation in millicores (default 250)") + cmd.Flags().StringVar(&memory, "memory", "", "Memory allocation in gigabytes (default 2)") + cmd.Flags().IntVar(&iops, "iops", 0, "Provisioned IOPS") + cmd.Flags().BoolVar(&noWait, "no-wait", false, "Don't wait for the branch to be ready") + cmd.Flags().DurationVar(&waitTimeout, "wait-timeout", 10*time.Minute, "Wait timeout duration (e.g., 30m, 1h30m, 90s)") + cmd.Flags().VarP(new(outputWithEnvFlag), "output", "o", "Output format (json, yaml, env, table)") + + registerFlagCompletion(cmd, "output", outputCompletion("env")) + registerFlagCompletion(cmd, "cpu", branchCPUCompletion) + registerFlagCompletion(cmd, "memory", branchMemoryCompletion) + registerFlagCompletion(cmd, "iops", iopsCompletion) + + return cmd +} diff --git a/internal/cmd/branch_create_test.go b/internal/cmd/branch_create_test.go new file mode 100644 index 00000000..59e01fbb --- /dev/null +++ b/internal/cmd/branch_create_test.go @@ -0,0 +1,293 @@ +package cmd + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" + "github.com/timescale/tiger-cli/internal/common" +) + +func TestBranchCreateCmd(t *testing.T) { + parent := sampleService(fluidPlatform) + baseReq := api.BranchCreate{} + + // The create response carries the branch's new password, once. + created := sampleBranch(branchStatus(api.DeployStatusQUEUED), func(b *api.Branch) { + b.InitialPassword = new("branch-pw") + }) + + // setupCreate registers resolving svc-12345 onto parent and creating a + // branch of it with req, answered with branch. + setupCreate := func(req api.BranchCreate, branch api.Branch) func(m *mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRef(m, "svc-12345", parent) + m.EXPECT().CreateBranchWithResponse(validCtx, testProjectID, "svc-12345", req). + Return(&api.CreateBranchResponse{ + HTTPResponse: httpResponse(http.StatusAccepted), + JSON202: &branch, + }, nil) + } + } + + noWaitStderr := func(firstLine string) string { + return firstLine + ` +Branch ID: br-12345 +Password saved to system keyring +Branch is being created. Use 'tiger branch get br-12345' to check branch status. +` + } + defaultNoWaitStderr := noWaitStderr("Creating branch from service 'test-service' (svc-12345)...") + + runCmdTests(t, []cmdTest{ + { + // A branch never becomes the default, so there's nothing to opt out of. + name: "no --no-set-default flag", + args: []string{"branch", "create", "svc-12345", "--no-set-default"}, + opts: []runOption{withExperimental()}, + wantErr: "unknown flag: --no-set-default", + }, + { + name: "not logged in", + args: []string{"branch", "create", "svc-12345"}, + opts: []runOption{withExperimental(), withNotLoggedIn()}, + wantErr: notLoggedInMsg, + checks: []checkFunc{checkExitCode(common.ExitAuthenticationError)}, + }, + { + name: "read-only all refuses", + args: []string{"branch", "create", "svc-12345"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + // A branch always counts as DEV, so prod allows branching a PROD + // service, as it allows forking one into a DEV fork. + name: "read-only prod allows branching a PROD service", + args: []string{"branch", "create", "svc-12345", "--no-wait", "-o", "env"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "prod"})}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRef(m, "svc-12345", sampleService(fluidPlatform, envTag("PROD"))) + m.EXPECT().CreateBranchWithResponse(validCtx, testProjectID, "svc-12345", baseReq). + Return(&api.CreateBranchResponse{ + HTTPResponse: httpResponse(http.StatusAccepted), + JSON202: &created, + }, nil) + }, + wantStdout: sampleBranchEnv, + wantStderr: defaultNoWaitStderr, + }, + { + name: "invalid cpu/memory combination", + args: []string{"branch", "create", "svc-12345", "--cpu", "1000", "--memory", "4"}, + opts: []runOption{withExperimental()}, + wantErr: "invalid CPU/Memory combination. Allowed combinations: 0.25 CPU/2 GB, 0.5 CPU/4 GB, 1 CPU/8 GB, 2 CPU/16 GB, 4 CPU/32 GB, 8 CPU/64 GB, 16 CPU/128 GB, 32 CPU/256 GB", + }, + { + name: "missing service id", + args: []string{"branch", "create"}, + opts: []runOption{withExperimental()}, + wantErr: "service name or ID is required. Provide it as an argument or set a default with 'tiger config set service_id '", + }, + { + name: "service not found", + args: []string{"branch", "create", "svc-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRefNotFound(m, "svc-12345") + }, + wantErr: "service 'svc-12345' not found", + checks: []checkFunc{checkExitCode(common.ExitServiceNotFound)}, + }, + { + name: "network error", + args: []string{"branch", "create", "svc-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRef(m, "svc-12345", parent) + m.EXPECT().CreateBranchWithResponse(validCtx, testProjectID, "svc-12345", baseReq). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to create branch: connection refused", + wantStderr: "Creating branch from service 'test-service' (svc-12345)...\n" + + "Error: failed to create branch: connection refused\n", + }, + { + name: "API error", + args: []string{"branch", "create", "svc-12345", "--name", "ci-4821"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRef(m, "svc-12345", parent) + m.EXPECT().CreateBranchWithResponse(validCtx, testProjectID, "svc-12345", api.BranchCreate{Name: new("ci-4821")}). + Return(&api.CreateBranchResponse{ + HTTPResponse: httpResponse(http.StatusConflict), + JSON4XX: &api.ClientError{Message: new("a branch named ci-4821 already exists")}, + }, nil) + }, + wantErr: "a branch named ci-4821 already exists", + wantStderr: "Creating branch 'ci-4821' from service 'test-service' (svc-12345)...\n" + + "Error: a branch named ci-4821 already exists\n", + }, + { + name: "nil response body", + args: []string{"branch", "create", "svc-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRef(m, "svc-12345", parent) + m.EXPECT().CreateBranchWithResponse(validCtx, testProjectID, "svc-12345", baseReq). + Return(&api.CreateBranchResponse{ + HTTPResponse: httpResponse(http.StatusAccepted), + }, nil) + }, + wantErr: "empty response from API", + wantStderr: "Creating branch from service 'test-service' (svc-12345)...\n" + + "Error: empty response from API\n", + }, + { + // Only service IDs go in the default, so a branch never becomes it. + name: "no-wait saves the password and leaves the default alone", + args: []string{"branch", "create", "svc-12345", "--no-wait", "-o", "env"}, + opts: []runOption{withExperimental()}, + mock: setupCreate(baseReq, created), + wantStdout: sampleBranchEnv, + wantStderr: defaultNoWaitStderr, + checks: []checkFunc{checkStoredPassword("br-12345", "branch-pw"), checkDefaultService("")}, + }, + { + // Nothing to save, so nothing is said about saving. + name: "no initial password", + args: []string{"branch", "create", "svc-12345", "--no-wait", "-o", "env"}, + opts: []runOption{withExperimental()}, + mock: setupCreate(baseReq, sampleBranch(branchStatus(api.DeployStatusQUEUED))), + wantStdout: sampleBranchEnv, + wantStderr: `Creating branch from service 'test-service' (svc-12345)... +Branch ID: br-12345 +Branch is being created. Use 'tiger branch get br-12345' to check branch status. +`, + }, + { + name: "password storage none", + args: []string{"branch", "create", "svc-12345", "--no-wait", "--password-storage", "none", "-o", "env"}, + opts: []runOption{withExperimental()}, + mock: setupCreate(baseReq, created), + wantStdout: sampleBranchEnv, + wantStderr: `Creating branch from service 'test-service' (svc-12345)... +Branch ID: br-12345 +Password not saved (--password-storage=none). Make sure to store it securely. +Branch is being created. Use 'tiger branch get br-12345' to check branch status. +`, + }, + { + name: "custom name and compute", + args: []string{"branch", "create", "svc-12345", "--name", "ci-4821", "--cpu", "2000", "--memory", "16", "--no-wait", "-o", "env"}, + opts: []runOption{withExperimental()}, + mock: setupCreate(api.BranchCreate{ + Name: new("ci-4821"), + CPUMillis: new(2000), + MemoryGbs: new(16), + }, created), + wantStdout: sampleBranchEnv, + wantStderr: noWaitStderr("Creating branch 'ci-4821' from service 'test-service' (svc-12345)..."), + }, + { + name: "iops", + args: []string{"branch", "create", "svc-12345", "--iops", "16000", "--no-wait", "-o", "env"}, + opts: []runOption{withExperimental()}, + mock: setupCreate(api.BranchCreate{Iops: new(16000)}, created), + wantStdout: sampleBranchEnv, + wantStderr: defaultNoWaitStderr, + }, + { + name: "memory alone sets the matching cpu", + args: []string{"branch", "create", "svc-12345", "--memory", "4", "--no-wait", "-o", "env"}, + opts: []runOption{withExperimental()}, + mock: setupCreate(api.BranchCreate{ + CPUMillis: new(500), + MemoryGbs: new(4), + }, created), + wantStdout: sampleBranchEnv, + wantStderr: defaultNoWaitStderr, + }, + { + name: "wait polls until ready", + synctest: true, + args: []string{"branch", "create", "svc-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + setupCreate(baseReq, created)(m) + expectGetBranch(m, sampleBranch()) + }, + wantStdout: sampleBranchTable, + wantStderr: `Creating branch from service 'test-service' (svc-12345)... +Branch ID: br-12345 +Password saved to system keyring +Waiting for branch to be ready (timeout: 10m0s)... +⢎ Branch status: QUEUED +Branch is ready. +Connect with: tiger db psql br-12345 +`, + }, + { + name: "wait timeout", + synctest: true, + args: []string{"branch", "create", "svc-12345", "-o", "env"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + setupCreate(baseReq, created)(m) + queued := sampleBranch(branchStatus(api.DeployStatusQUEUED)) + m.EXPECT().GetBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(&api.GetBranchResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &queued, + }, nil).AnyTimes() + }, + wantErr: "wait timeout reached after 10m0s - branch may still be provisioning", + wantStdout: sampleBranchEnv, + wantStderr: `Creating branch from service 'test-service' (svc-12345)... +Branch ID: br-12345 +Password saved to system keyring +Waiting for branch to be ready (timeout: 10m0s)... +⢎ Branch status: QUEUED +Error: wait timeout reached after 10m0s - branch may still be provisioning +`, + checks: []checkFunc{checkExitCode(common.ExitTimeout)}, + }, + { + // The initial password is saved but never output. + name: "json", + args: []string{"branch", "create", "svc-12345", "-o", "json"}, + opts: []runOption{withExperimental()}, + mock: setupCreate(baseReq, sampleBranch(func(b *api.Branch) { + b.InitialPassword = new("branch-pw") + })), + wantStdout: sampleBranchJSON, + wantStderr: `Creating branch from service 'test-service' (svc-12345)... +Branch ID: br-12345 +Password saved to system keyring +Waiting for branch to be ready (timeout: 10m0s)... +Branch is ready. +Connect with: tiger db psql br-12345 +`, + }, + { + name: "default service from config", + args: []string{"branch", "create", "--no-wait", "-o", "env"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"service_id": "svc-12345"})}, + mock: setupCreate(baseReq, created), + wantStdout: sampleBranchEnv, + wantStderr: defaultNoWaitStderr, + checks: []checkFunc{checkDefaultService("svc-12345")}, + }, + { + name: "service branch is the same command", + args: []string{"service", "branch", "svc-12345", "--no-wait", "-o", "env"}, + opts: []runOption{withExperimental()}, + mock: setupCreate(baseReq, created), + wantStdout: sampleBranchEnv, + wantStderr: defaultNoWaitStderr, + checks: []checkFunc{checkStoredPassword("br-12345", "branch-pw"), checkDefaultService("")}, + }, + }) +} diff --git a/internal/cmd/branch_delete.go b/internal/cmd/branch_delete.go new file mode 100644 index 00000000..60f44d2a --- /dev/null +++ b/internal/cmd/branch_delete.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "fmt" + "net/http" + + "github.com/spf13/cobra" + + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// buildBranchDeleteCmd creates the delete subcommand +func buildBranchDeleteCmd(app *common.App) *cobra.Command { + var confirm bool + + cmd := &cobra.Command{ + Use: "delete ", + Aliases: []string{"rm"}, + Short: "Delete a branch", + Long: `Delete a branch permanently, along with any changes made on it. The parent +service is not affected. + +The branch can be given by ID or name. + +This operation is irreversible. By default, you will be prompted to type the +branch ID to confirm deletion — the ID, not the name — unless you use the +--confirm flag. + +Note for AI agents: Always confirm with the user before performing this destructive operation.`, + Example: ` # Delete a branch (with confirmation prompt) + tiger branch delete br-12345 + + # Delete a branch without confirmation prompt + tiger branch delete br-12345 --confirm`, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: branchRefCompletion(app), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + branchRef, err := getBranchRef(args) + if err != nil { + return err + } + + cfg, client, projectID, err := app.GetAll() + if err != nil { + return err + } + + // Gated here, ahead of the confirmation prompt, so read-only mode + // refuses without first asking the user to type the branch ID. + branch, err := resolveBranchForWrite(cmd.Context(), cfg, client, projectID, branchRef) + if err != nil { + return err + } + + if !confirm { + if !util.IsTerminal(cmd.InOrStdin()) || !util.IsTerminal(cmd.ErrOrStderr()) { + return fmt.Errorf("TTY not detected - cannot prompt for confirmation. Use --confirm to skip the prompt") + } + cmd.PrintErrf("Are you sure you want to delete branch %s? This operation cannot be undone.\n", branchLabel(*branch)) + cmd.PrintErrf("Type the branch ID '%s' to confirm: ", branch.ID) + confirmation, err := util.ReadLine(cmd.Context(), cmd.InOrStdin()) + if err != nil { + return fmt.Errorf("failed to read confirmation: %w", err) + } + if confirmation != branch.ID { + cmd.PrintErrln("Delete operation cancelled.") + return nil + } + } + + resp, err := client.DeleteBranchWithResponse(cmd.Context(), projectID, branch.ServiceID, branch.ID) + if err != nil { + return fmt.Errorf("failed to delete branch: %w", err) + } + if resp.StatusCode() != http.StatusAccepted { + return common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + + cmd.PrintErrf("Branch %s deleted.\n", branchLabel(*branch)) + return nil + }, + } + + cmd.Flags().BoolVar(&confirm, "confirm", false, "Skip confirmation prompt (AI agents must confirm with user first)") + + return cmd +} diff --git a/internal/cmd/branch_delete_test.go b/internal/cmd/branch_delete_test.go new file mode 100644 index 00000000..7da31432 --- /dev/null +++ b/internal/cmd/branch_delete_test.go @@ -0,0 +1,119 @@ +package cmd + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" + "github.com/timescale/tiger-cli/internal/common" +) + +func TestBranchDeleteCmd(t *testing.T) { + setupDelete := func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().DeleteBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(&api.DeleteBranchResponse{HTTPResponse: httpResponse(http.StatusAccepted)}, nil) + } + getOnly := func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + } + prompt := "Are you sure you want to delete branch 'test-branch' (br-12345)? This operation cannot be undone.\n" + + "Type the branch ID 'br-12345' to confirm: " + + runCmdTests(t, []cmdTest{ + { + // No default fallback: service_id only ever holds a service ID. + name: "missing branch id", + args: []string{"branch", "delete"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"service_id": "svc-12345"})}, + wantErr: "branch name or ID is required", + }, + { + name: "not logged in", + args: []string{"branch", "delete", "br-12345", "--confirm"}, + opts: []runOption{withExperimental(), withNotLoggedIn()}, + wantErr: notLoggedInMsg, + checks: []checkFunc{checkExitCode(common.ExitAuthenticationError)}, + }, + { + name: "read-only all refuses", + args: []string{"branch", "delete", "br-12345", "--confirm"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + // A branch always counts as DEV. + name: "read-only prod allows", + args: []string{"branch", "delete", "br-12345", "--confirm"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "prod"})}, + mock: setupDelete, + wantStderr: "Branch 'test-branch' (br-12345) deleted.\n", + }, + { + name: "branch not found", + args: []string{"branch", "delete", "br-12345", "--confirm"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranchStatus(m, "br-12345", http.StatusNotFound, &api.ClientError{Message: new("branch not found")}) + }, + wantErr: "branch 'br-12345' not found", + checks: []checkFunc{checkExitCode(common.ExitServiceNotFound)}, + }, + { + name: "non-TTY without confirm", + args: []string{"branch", "delete", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: getOnly, + wantErr: "TTY not detected - cannot prompt for confirmation. Use --confirm to skip the prompt", + }, + { + name: "confirmation mismatch", + args: []string{"branch", "delete", "br-12345"}, + opts: []runOption{withExperimental(), withIsTerminal(true), withStdin("test-branch\n")}, + mock: getOnly, + wantStderr: prompt + "Delete operation cancelled.\n", + }, + { + name: "confirmation match", + args: []string{"branch", "delete", "br-12345"}, + opts: []runOption{withExperimental(), withIsTerminal(true), withStdin("br-12345\n")}, + mock: setupDelete, + wantStderr: prompt + "Branch 'test-branch' (br-12345) deleted.\n", + }, + { + name: "network error", + args: []string{"branch", "delete", "br-12345", "--confirm"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().DeleteBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to delete branch: connection refused", + }, + { + name: "API error", + args: []string{"branch", "delete", "br-12345", "--confirm"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().DeleteBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(&api.DeleteBranchResponse{ + HTTPResponse: httpResponse(http.StatusBadRequest), + JSON4XX: &api.ClientError{Message: new("branch is busy")}, + }, nil) + }, + wantErr: "branch is busy", + checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, + }, + { + name: "rm alias", + args: []string{"branch", "rm", "br-12345", "--confirm"}, + opts: []runOption{withExperimental()}, + mock: setupDelete, + wantStderr: "Branch 'test-branch' (br-12345) deleted.\n", + }, + }) +} diff --git a/internal/cmd/branch_get.go b/internal/cmd/branch_get.go new file mode 100644 index 00000000..af3ab543 --- /dev/null +++ b/internal/cmd/branch_get.go @@ -0,0 +1,60 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/timescale/tiger-cli/internal/common" +) + +// buildBranchGetCmd creates the get subcommand +func buildBranchGetCmd(app *common.App) *cobra.Command { + cmd := &cobra.Command{ + Use: "get ", + Aliases: []string{"describe", "show"}, + Short: "Show detailed information about a branch", + Long: `Show detailed information about a branch, including its parent service, status, +compute, and connection details. + +The branch can be given by ID or name.`, + Example: ` # Get a branch's details + tiger branch get br-12345 + + # Get branch details in JSON format + tiger branch get br-12345 --output json`, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: branchRefCompletion(app), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + cfg, client, projectID, err := app.GetAll() + if err != nil { + return err + } + + branchRef, err := getBranchRef(args) + if err != nil { + return err + } + + branch, err := resolveBranch(cmd.Context(), client, projectID, branchRef) + if err != nil { + return err + } + + // The branch carries only its parent's ID, so the name takes a + // second fetch. The rest of the output doesn't depend on it. + var opts branchOutputOptions + if parent, err := common.GetService(cmd.Context(), client, projectID, branch.ServiceID); err != nil { + cmd.PrintErrf("Warning: failed to get parent service: %v\n", err) + } else { + opts.ServiceName = parent.Name + } + + return outputBranch(cmd, cfg, *branch, opts) + }, + } + + cmd.Flags().VarP(new(outputWithEnvFlag), "output", "o", "Output format (json, yaml, env, table)") + registerFlagCompletion(cmd, "output", outputCompletion("env")) + + return cmd +} diff --git a/internal/cmd/branch_get_test.go b/internal/cmd/branch_get_test.go new file mode 100644 index 00000000..dd73c6ae --- /dev/null +++ b/internal/cmd/branch_get_test.go @@ -0,0 +1,215 @@ +package cmd + +import ( + "errors" + "net/http" + "strings" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" + "github.com/timescale/tiger-cli/internal/common" +) + +func TestBranchGetCmd(t *testing.T) { + getSample := func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + expectGetService(m, "svc-12345", sampleService(fluidPlatform)) + } + + runCmdTests(t, []cmdTest{ + { + name: "not logged in", + args: []string{"branch", "get", "br-12345"}, + opts: []runOption{withExperimental(), withNotLoggedIn()}, + wantErr: notLoggedInMsg, + checks: []checkFunc{checkExitCode(common.ExitAuthenticationError)}, + }, + { + // No default fallback: service_id only ever holds a service ID. + name: "missing branch id", + args: []string{"branch", "get"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"service_id": "svc-12345"})}, + wantErr: "branch name or ID is required", + }, + { + name: "network error", + args: []string{"branch", "get", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveError(m, "br-12345", branchType, errors.New("connection refused")) + }, + wantErr: "failed to resolve branch 'br-12345': connection refused", + }, + { + name: "branch not found", + args: []string{"branch", "get", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranchStatus(m, "br-12345", http.StatusNotFound, &api.ClientError{Message: new("branch not found")}) + }, + wantErr: "branch 'br-12345' not found", + checks: []checkFunc{checkExitCode(common.ExitServiceNotFound)}, + }, + { + name: "ambiguous name refused", + args: []string{"branch", "get", "ci-4821"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranchStatus(m, "ci-4821", http.StatusBadRequest, &api.ClientError{Message: new("ref matches multiple branches")}) + }, + wantErr: "ref matches multiple branches\nRun 'tiger branch list' on the branch's service to find the ID you want", + checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, + }, + { + name: "API error", + args: []string{"branch", "get", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranchStatus(m, "br-12345", http.StatusForbidden, &api.ClientError{Message: new("forbidden")}) + }, + wantErr: "forbidden", + checks: []checkFunc{checkExitCode(common.ExitPermissionDenied)}, + }, + { + name: "nil response body", + args: []string{"branch", "get", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranchStatus(m, "br-12345", http.StatusOK, nil) + }, + wantErr: "empty response from API", + }, + { + name: "table", + args: []string{"branch", "get", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: getSample, + wantStdout: sampleBranchTable, + }, + { + name: "json", + args: []string{"branch", "get", "br-12345", "-o", "json"}, + opts: []runOption{withExperimental()}, + mock: getSample, + wantStdout: sampleBranchJSON, + }, + { + name: "yaml", + args: []string{"branch", "get", "br-12345", "-o", "yaml"}, + opts: []runOption{withExperimental()}, + mock: getSample, + wantStdout: `connection_string: postgresql://tsdbadmin@br-12345.test-project-123.branch.tsdb.cloud.timescale.com:36956/tsdb?sslmode=require +cpu_millis: 250 +created: "2025-01-16T09:00:00Z" +database: tsdb +endpoint: + host: br-12345.test-project-123.branch.tsdb.cloud.timescale.com + port: 36956 +host: br-12345.test-project-123.branch.tsdb.cloud.timescale.com +id: br-12345 +memory_gbs: 2 +name: test-branch +port: 36956 +project_id: test-project-123 +role: tsdbadmin +service_id: svc-12345 +service_name: test-service +status: READY +`, + }, + { + // The parent's name is the only thing the second fetch adds, so + // without it the branch is still shown, by parent ID. + name: "parent fetch fails", + args: []string{"branch", "get", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "svc-12345"). + Return(nil, errors.New("connection refused")) + }, + wantStdout: strings.Replace(sampleBranchTable, + "│ Parent Service │ test-service (svc-12345) ", + "│ Parent Service │ svc-12345 ", 1), + wantStderr: "Warning: failed to get parent service: failed to fetch service details: connection refused\n", + }, + { + name: "env", + args: []string{"branch", "get", "br-12345", "-o", "env"}, + opts: []runOption{withExperimental()}, + mock: getSample, + wantStdout: sampleBranchEnv, + }, + { + // A branch counts as DEV, so only read_only=all makes its + // connection string read-only. + name: "read-only all makes the connection string read-only", + args: []string{"branch", "get", "br-12345", "-o", "json"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "all"})}, + mock: getSample, + wantStdout: `{ + "cpu_millis": 250, + "created": "2025-01-16T09:00:00Z", + "endpoint": { + "host": "br-12345.test-project-123.branch.tsdb.cloud.timescale.com", + "port": 36956 + }, + "id": "br-12345", + "memory_gbs": 2, + "name": "test-branch", + "project_id": "test-project-123", + "service_id": "svc-12345", + "status": "READY", + "service_name": "test-service", + "role": "tsdbadmin", + "host": "br-12345.test-project-123.branch.tsdb.cloud.timescale.com", + "port": 36956, + "database": "tsdb", + "connection_string": "postgresql://tsdbadmin@br-12345.test-project-123.branch.tsdb.cloud.timescale.com:36956/tsdb?sslmode=require\u0026options=-c%20tsdb_admin.read_only_connection%3Dtrue" +} +`, + }, + { + // No port is guessed, so without one there's no endpoint to show + // or connect to. + name: "endpoint without a port", + args: []string{"branch", "get", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch(func(b *api.Branch) { b.Endpoint.Port = nil })) + expectGetService(m, "svc-12345", sampleService(fluidPlatform)) + }, + wantStdout: `┌────────────────┬──────────────────────────┐ +│ PROPERTY │ VALUE │ +├────────────────┼──────────────────────────┤ +│ Branch ID │ br-12345 │ +│ Name │ test-branch │ +│ Parent Service │ test-service (svc-12345) │ +│ Status │ READY │ +│ CPU │ 0.25 cores (250m) │ +│ Memory │ 2 GB │ +│ Created │ 2025-01-16 09:00:00 UTC │ +└────────────────┴──────────────────────────┘ +`, + wantStderr: "Warning: failed to get connection details: endpoint port not available\n", + }, + { + name: "by name", + args: []string{"branch", "get", "test-branch", "-o", "env"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "test-branch", sampleBranch()) + expectGetService(m, "svc-12345", sampleService(fluidPlatform)) + }, + wantStdout: sampleBranchEnv, + }, + { + name: "describe alias", + args: []string{"branch", "describe", "br-12345", "-o", "env"}, + opts: []runOption{withExperimental()}, + mock: getSample, + wantStdout: sampleBranchEnv, + }, + }) +} diff --git a/internal/cmd/branch_list.go b/internal/cmd/branch_list.go new file mode 100644 index 00000000..4c9e8dfb --- /dev/null +++ b/internal/cmd/branch_list.go @@ -0,0 +1,103 @@ +package cmd + +import ( + "io" + "strings" + + "github.com/olekukonko/tablewriter" + "github.com/spf13/cobra" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/config" + "github.com/timescale/tiger-cli/internal/util" +) + +// buildBranchListCmd creates the list subcommand +func buildBranchListCmd(app *common.App) *cobra.Command { + cmd := &cobra.Command{ + Use: "list [service-name-or-id]", + Aliases: []string{"ls"}, + Short: "List the branches of a service", + Long: `List the branches of a database service. + +The service can be given by ID or name as an argument, or will use the default +service from your configuration.`, + Example: ` # List the default service's branches + tiger branch list + + # List a specific service's branches + tiger branch list svc-12345`, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: serviceRefCompletion(app), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + cfg, client, projectID, err := app.GetAll() + if err != nil { + return err + } + + serviceRef, err := getServiceRef(cmd, cfg, args) + if err != nil { + return err + } + service, err := resolveService(cmd.Context(), client, projectID, serviceRef) + if err != nil { + return err + } + + branches, err := common.ListBranches(cmd.Context(), client, projectID, service.ServiceID) + if err != nil { + return err + } + + return outputBranches(cmd, cfg, *service, branches) + }, + } + + cmd.Flags().VarP(new(outputFlag), "output", "o", "Output format (json, yaml, table)") + registerFlagCompletion(cmd, "output", outputCompletion()) + + return cmd +} + +// outputBranches outputs the branches of service in the configured output +// format. +func outputBranches(cmd *cobra.Command, cfg *config.Config, service api.Service, branches []api.Branch) error { + opts := branchOutputOptions{ServiceName: service.Name} + prepared := make([]OutputBranch, len(branches)) + for i, branch := range branches { + prepared[i] = prepareBranchForOutput(cmd, cfg, branch, opts) + } + + outputWriter := cmd.OutOrStdout() + switch strings.ToLower(cfg.Output) { + case "json": + return util.SerializeToJSON(outputWriter, prepared) + case "yaml": + return util.SerializeToYAML(outputWriter, prepared) + default: // table format (default) + if len(prepared) == 0 { + cmd.PrintErrf("No branches found for service %s.\n", serviceLabel(service)) + return nil + } + return outputBranchesTable(outputWriter, prepared) + } +} + +// outputBranchesTable outputs branches in a formatted table. +func outputBranchesTable(output io.Writer, branches []OutputBranch) error { + table := tablewriter.NewWriter(output) + table.Header("BRANCH ID", "NAME", "STATUS", "CREATED") + + for _, branch := range branches { + table.Append( + branch.ID, + branch.Name, + string(branch.Status), + branch.Created.Format("2006-01-02 15:04"), + ) + } + + return table.Render() +} diff --git a/internal/cmd/branch_list_test.go b/internal/cmd/branch_list_test.go new file mode 100644 index 00000000..9eac788b --- /dev/null +++ b/internal/cmd/branch_list_test.go @@ -0,0 +1,169 @@ +package cmd + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" + "github.com/timescale/tiger-cli/internal/common" +) + +func TestBranchListCmd(t *testing.T) { + paused := sampleBranch(func(b *api.Branch) { + b.ID = "br-67890" + b.Name = "feature/user-auth" + b.Status = api.DeployStatusPAUSED + }) + expectList := func(branches ...api.Branch) func(m *mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRef(m, "svc-12345", sampleService()) + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "svc-12345", nil). + Return(&api.GetBranchesResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &branches, + }, nil) + } + } + + runCmdTests(t, []cmdTest{ + { + name: "not logged in", + args: []string{"branch", "list", "svc-12345"}, + opts: []runOption{withExperimental(), withNotLoggedIn()}, + wantErr: notLoggedInMsg, + checks: []checkFunc{checkExitCode(common.ExitAuthenticationError)}, + }, + { + name: "missing service id", + args: []string{"branch", "list"}, + opts: []runOption{withExperimental()}, + wantErr: "service name or ID is required. Provide it as an argument or set a default with 'tiger config set service_id '", + }, + { + name: "service not found", + args: []string{"branch", "list", "svc-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRefNotFound(m, "svc-12345") + }, + wantErr: "service 'svc-12345' not found", + checks: []checkFunc{checkExitCode(common.ExitServiceNotFound)}, + }, + { + name: "network error", + args: []string{"branch", "list", "svc-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRefID(m, "svc-12345") + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "svc-12345", nil). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to list branches: connection refused", + }, + { + name: "API error", + args: []string{"branch", "list", "svc-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRefID(m, "svc-12345") + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "svc-12345", nil). + Return(&api.GetBranchesResponse{ + HTTPResponse: httpResponse(http.StatusForbidden), + JSON4XX: &api.Error{Message: new("forbidden")}, + }, nil) + }, + wantErr: "forbidden", + checks: []checkFunc{checkExitCode(common.ExitPermissionDenied)}, + }, + { + name: "nil response body", + args: []string{"branch", "list", "svc-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRefID(m, "svc-12345") + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "svc-12345", nil). + Return(&api.GetBranchesResponse{HTTPResponse: httpResponse(http.StatusOK)}, nil) + }, + wantErr: "empty response from API", + }, + { + name: "no branches", + args: []string{"branch", "list", "svc-12345"}, + opts: []runOption{withExperimental()}, + mock: expectList(), + wantStderr: "No branches found for service 'test-service' (svc-12345).\n", + }, + { + // Structured output is always structured, so an empty list is + // still a list rather than a message. + name: "no branches json", + args: []string{"branch", "list", "svc-12345", "-o", "json"}, + opts: []runOption{withExperimental()}, + mock: expectList(), + wantStdout: "[]\n", + }, + { + name: "no branches yaml", + args: []string{"branch", "list", "svc-12345", "-o", "yaml"}, + opts: []runOption{withExperimental()}, + mock: expectList(), + wantStdout: "[]\n", + }, + { + name: "table", + args: []string{"branch", "list", "svc-12345"}, + opts: []runOption{withExperimental()}, + mock: expectList(sampleBranch(), paused), + wantStdout: `┌───────────┬───────────────────┬────────┬──────────────────┐ +│ BRANCH ID │ NAME │ STATUS │ CREATED │ +├───────────┼───────────────────┼────────┼──────────────────┤ +│ br-12345 │ test-branch │ READY │ 2025-01-16 09:00 │ +│ br-67890 │ feature/user-auth │ PAUSED │ 2025-01-16 09:00 │ +└───────────┴───────────────────┴────────┴──────────────────┘ +`, + }, + { + name: "json", + args: []string{"branch", "list", "svc-12345", "-o", "json"}, + opts: []runOption{withExperimental()}, + mock: expectList(sampleBranch()), + wantStdout: `[ + { + "cpu_millis": 250, + "created": "2025-01-16T09:00:00Z", + "endpoint": { + "host": "br-12345.test-project-123.branch.tsdb.cloud.timescale.com", + "port": 36956 + }, + "id": "br-12345", + "memory_gbs": 2, + "name": "test-branch", + "project_id": "test-project-123", + "service_id": "svc-12345", + "status": "READY", + "service_name": "test-service", + "role": "tsdbadmin", + "host": "br-12345.test-project-123.branch.tsdb.cloud.timescale.com", + "port": 36956, + "database": "tsdb", + "connection_string": "postgresql://tsdbadmin@br-12345.test-project-123.branch.tsdb.cloud.timescale.com:36956/tsdb?sslmode=require" + } +] +`, + }, + { + name: "default service from config", + args: []string{"branch", "list"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"service_id": "svc-12345"})}, + mock: expectList(sampleBranch()), + wantStdout: `┌───────────┬─────────────┬────────┬──────────────────┐ +│ BRANCH ID │ NAME │ STATUS │ CREATED │ +├───────────┼─────────────┼────────┼──────────────────┤ +│ br-12345 │ test-branch │ READY │ 2025-01-16 09:00 │ +└───────────┴─────────────┴────────┴──────────────────┘ +`, + }, + }) +} diff --git a/internal/cmd/branch_rename.go b/internal/cmd/branch_rename.go new file mode 100644 index 00000000..24a7644a --- /dev/null +++ b/internal/cmd/branch_rename.go @@ -0,0 +1,70 @@ +package cmd + +import ( + "errors" + "fmt" + "net/http" + "strings" + + "github.com/spf13/cobra" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" +) + +// buildBranchRenameCmd creates the rename subcommand +func buildBranchRenameCmd(app *common.App) *cobra.Command { + cmd := &cobra.Command{ + Use: "rename ", + Short: "Rename a branch", + Long: `Rename a branch. + +Only the branch's name changes: its ID, endpoint, and data are untouched, so +existing connections and connection strings keep working. + +Both the branch and the new name are required. The branch can be given by ID +or by its current name.`, + Example: ` # Rename a branch + tiger branch rename br-12345 feature/rbac`, + Args: cobra.ExactArgs(2), + ValidArgsFunction: branchRefCompletion(app), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + branchRef, newName := args[0], strings.TrimSpace(args[1]) + + if branchRef == "" { + return errBranchRequired + } + if newName == "" { + return errors.New("new name cannot be empty") + } + + cfg, client, projectID, err := app.GetAll() + if err != nil { + return err + } + + branch, err := resolveBranchForWrite(cmd.Context(), cfg, client, projectID, branchRef) + if err != nil { + return err + } + + resp, err := client.RenameBranchWithResponse(cmd.Context(), projectID, branch.ServiceID, branch.ID, api.BranchRename{Name: newName}) + if err != nil { + return fmt.Errorf("failed to rename branch: %w", err) + } + if resp.StatusCode() != http.StatusOK { + return common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON200 == nil { + return errors.New("empty response from API") + } + renamed := *resp.JSON200 + + cmd.Printf("Renamed branch %s to '%s'.\n", branchLabel(*branch), renamed.Name) + return nil + }, + } + + return cmd +} diff --git a/internal/cmd/branch_rename_test.go b/internal/cmd/branch_rename_test.go new file mode 100644 index 00000000..c427235e --- /dev/null +++ b/internal/cmd/branch_rename_test.go @@ -0,0 +1,101 @@ +package cmd + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" + "github.com/timescale/tiger-cli/internal/common" +) + +func TestBranchRenameCmd(t *testing.T) { + setupRename := func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + renamed := sampleBranch(func(b *api.Branch) { b.Name = "feature/rbac" }) + m.EXPECT().RenameBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345", api.BranchRename{Name: "feature/rbac"}). + Return(&api.RenameBranchResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &renamed, + }, nil) + } + + runCmdTests(t, []cmdTest{ + { + name: "missing new name", + args: []string{"branch", "rename", "br-12345"}, + opts: []runOption{withExperimental()}, + wantErr: "accepts 2 arg(s), received 1", + }, + { + name: "empty new name", + args: []string{"branch", "rename", "br-12345", " "}, + opts: []runOption{withExperimental()}, + wantErr: "new name cannot be empty", + }, + { + name: "not logged in", + args: []string{"branch", "rename", "br-12345", "feature/rbac"}, + opts: []runOption{withExperimental(), withNotLoggedIn()}, + wantErr: notLoggedInMsg, + checks: []checkFunc{checkExitCode(common.ExitAuthenticationError)}, + }, + { + name: "read-only all refuses", + args: []string{"branch", "rename", "br-12345", "feature/rbac"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + name: "read-only prod allows", + args: []string{"branch", "rename", "br-12345", "feature/rbac"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "prod"})}, + mock: setupRename, + wantStdout: "Renamed branch 'test-branch' (br-12345) to 'feature/rbac'.\n", + }, + { + name: "network error", + args: []string{"branch", "rename", "br-12345", "feature/rbac"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().RenameBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345", api.BranchRename{Name: "feature/rbac"}). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to rename branch: connection refused", + }, + { + name: "duplicate name", + args: []string{"branch", "rename", "br-12345", "feature/rbac"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().RenameBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345", api.BranchRename{Name: "feature/rbac"}). + Return(&api.RenameBranchResponse{ + HTTPResponse: httpResponse(http.StatusConflict), + JSON4XX: &api.ClientError{Message: new("a branch named feature/rbac already exists")}, + }, nil) + }, + wantErr: "a branch named feature/rbac already exists", + }, + { + name: "nil response body", + args: []string{"branch", "rename", "br-12345", "feature/rbac"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().RenameBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345", api.BranchRename{Name: "feature/rbac"}). + Return(&api.RenameBranchResponse{HTTPResponse: httpResponse(http.StatusOK)}, nil) + }, + wantErr: "empty response from API", + }, + { + name: "success", + args: []string{"branch", "rename", "br-12345", "feature/rbac"}, + opts: []runOption{withExperimental()}, + mock: setupRename, + wantStdout: "Renamed branch 'test-branch' (br-12345) to 'feature/rbac'.\n", + }, + }) +} diff --git a/internal/cmd/branch_resize.go b/internal/cmd/branch_resize.go new file mode 100644 index 00000000..e8ce20d5 --- /dev/null +++ b/internal/cmd/branch_resize.go @@ -0,0 +1,152 @@ +package cmd + +import ( + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/spf13/cobra" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" +) + +// buildBranchResizeCmd creates the resize subcommand +func buildBranchResizeCmd(app *common.App) *cobra.Command { + var cpu string + var memory string + var iops int + var noWait bool + var waitTimeout time.Duration + + cmd := &cobra.Command{ + Use: "resize ", + Short: "Resize a branch", + Long: `Resize a branch by changing its CPU, memory, and provisioned IOPS allocation. +A branch's storage is shared with its parent service and isn't resized. + +The branch can be given by ID or name. It may be temporarily unavailable +during the resize. + +` + branchCPUMemoryHelp + ` + +Note: You can specify both CPU and memory together, or specify only one (the +other will be automatically configured). + +The allowed --iops values depend on your plan and the branch's CPU/memory +allocation. See the Tiger Data documentation for details. If only --iops is +given, the branch keeps its current CPU and memory.`, + Example: ` # Resize a branch to 2 CPU cores and 16GB memory + tiger branch resize br-12345 --cpu 2000 --memory 16 + + # Resize a branch using only CPU (memory will be auto-configured) + tiger branch resize br-12345 --cpu 2000 + + # Change only the provisioned IOPS + tiger branch resize br-12345 --iops 16000 + + # Resize without waiting for completion + tiger branch resize br-12345 --cpu 2000 --no-wait`, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: branchRefCompletion(app), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + cfg, client, projectID, err := app.GetAll() + if err != nil { + return err + } + + branchRef, err := getBranchRef(args) + if err != nil { + return err + } + + iopsSet := cmd.Flags().Changed("iops") + if cpu == "" && memory == "" && !iopsSet { + return errors.New("must specify at least one of --cpu, --memory, or --iops") + } + cpuMemoryCfg, err := common.FluidCPUMemoryConfigs().ValidateAndNormalize(cpu, memory) + if err != nil { + return err + } + + resolved, err := resolveBranchForWrite(cmd.Context(), cfg, client, projectID, branchRef) + if err != nil { + return err + } + + // An IOPS-only resize omits CPU and memory, which the API leaves + // unchanged. + resizeReq := api.BranchResizeInput{ + CPUMillis: cpuMemoryCfg.CPUMillisString(), + MemoryGbs: cpuMemoryCfg.MemoryGBsString(), + } + var targets []string + if cpuMemoryCfg != nil { + targets = append(targets, cpuMemoryCfg.String()) + } + if iopsSet { + resizeReq.Iops = &iops + targets = append(targets, fmt.Sprintf("%d IOPS", iops)) + } + target := strings.Join(targets, ", ") + + cmd.PrintErrf("Resizing branch %s to %s...\n", branchLabel(*resolved), target) + + resp, err := client.ResizeBranchWithResponse(cmd.Context(), projectID, resolved.ServiceID, resolved.ID, resizeReq) + if err != nil { + return fmt.Errorf("failed to resize branch: %w", err) + } + if resp.StatusCode() != http.StatusAccepted { + return common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON202 == nil { + return fmt.Errorf("empty response from API") + } + branch := *resp.JSON202 + + cmd.PrintErrf("Resize request accepted for branch %s.\n", branchLabel(*resolved)) + + if noWait { + cmd.PrintErrln("Use 'tiger branch get' to check branch status.") + return nil + } + + cmd.PrintErrf("Waiting for resize to complete (timeout: %v)...\n", waitTimeout) + if err := common.WaitForBranch(cmd.Context(), common.WaitForBranchArgs{ + Client: client, + ProjectID: projectID, + ServiceID: resolved.ServiceID, + BranchID: resolved.ID, + Branch: &branch, + TargetStatus: api.DeployStatusREADY, + Input: cmd.InOrStdin(), + Output: cmd.ErrOrStderr(), + Timeout: waitTimeout, + TimeoutMsg: "branch may still be resizing", + }); err != nil { + // Return error for sake of exit code, but silence since we already output it + cmd.PrintErrf("Error: %s\n", err) + cmd.SilenceErrors = true + return err + } + + cmd.PrintErrf("Branch resized to %s.\n", target) + return nil + }, + } + + cmd.Flags().StringVar(&cpu, "cpu", "", "CPU allocation in millicores") + cmd.Flags().StringVar(&memory, "memory", "", "Memory allocation in gigabytes") + cmd.Flags().IntVar(&iops, "iops", 0, "Provisioned IOPS") + cmd.Flags().BoolVar(&noWait, "no-wait", false, "Don't wait for resize operation to complete") + cmd.Flags().DurationVar(&waitTimeout, "wait-timeout", 10*time.Minute, "Maximum time to wait for operation to complete") + + registerFlagCompletion(cmd, "cpu", branchCPUCompletion) + registerFlagCompletion(cmd, "memory", branchMemoryCompletion) + registerFlagCompletion(cmd, "iops", iopsCompletion) + + return cmd +} diff --git a/internal/cmd/branch_resize_test.go b/internal/cmd/branch_resize_test.go new file mode 100644 index 00000000..e61f71b1 --- /dev/null +++ b/internal/cmd/branch_resize_test.go @@ -0,0 +1,138 @@ +package cmd + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" + "github.com/timescale/tiger-cli/internal/common" +) + +func TestBranchResizeCmd(t *testing.T) { + setupResize := func(req api.BranchResizeInput, status api.DeployStatus) func(m *mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + branch := sampleBranch(branchStatus(status)) + m.EXPECT().ResizeBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345", req). + Return(&api.ResizeBranchResponse{ + HTTPResponse: httpResponse(http.StatusAccepted), + JSON202: &branch, + }, nil) + } + } + req2000 := api.BranchResizeInput{CPUMillis: new("2000"), MemoryGbs: new("16")} + noWaitStderr := "Resizing branch 'test-branch' (br-12345) to 2 CPU/16 GB...\n" + + "Resize request accepted for branch 'test-branch' (br-12345).\n" + + "Use 'tiger branch get' to check branch status.\n" + + runCmdTests(t, []cmdTest{ + { + name: "not logged in", + args: []string{"branch", "resize", "br-12345", "--cpu", "2000"}, + opts: []runOption{withExperimental(), withNotLoggedIn()}, + wantErr: notLoggedInMsg, + checks: []checkFunc{checkExitCode(common.ExitAuthenticationError)}, + }, + { + // No default fallback: service_id only ever holds a service ID. + name: "missing branch id", + args: []string{"branch", "resize", "--cpu", "2000"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"service_id": "svc-12345"})}, + wantErr: "branch name or ID is required", + }, + { + name: "missing cpu and memory", + args: []string{"branch", "resize", "br-12345"}, + opts: []runOption{withExperimental()}, + wantErr: "must specify at least one of --cpu, --memory, or --iops", + }, + { + name: "invalid cpu/memory combination", + args: []string{"branch", "resize", "br-12345", "--cpu", "2000", "--memory", "8"}, + opts: []runOption{withExperimental()}, + wantErr: "invalid CPU/Memory combination. Allowed combinations: 0.25 CPU/2 GB, 0.5 CPU/4 GB, 1 CPU/8 GB, 2 CPU/16 GB, 4 CPU/32 GB, 8 CPU/64 GB, 16 CPU/128 GB, 32 CPU/256 GB", + }, + { + name: "read-only all refuses", + args: []string{"branch", "resize", "br-12345", "--cpu", "2000"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + name: "read-only prod allows", + args: []string{"branch", "resize", "br-12345", "--cpu", "2000", "--no-wait"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "prod"})}, + mock: setupResize(req2000, api.DeployStatusCONFIGURING), + wantStderr: noWaitStderr, + }, + { + name: "network error", + args: []string{"branch", "resize", "br-12345", "--cpu", "2000"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().ResizeBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345", req2000). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to resize branch: connection refused", + wantStderr: "Resizing branch 'test-branch' (br-12345) to 2 CPU/16 GB...\n" + + "Error: failed to resize branch: connection refused\n", + }, + { + name: "nil response body", + args: []string{"branch", "resize", "br-12345", "--cpu", "2000"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().ResizeBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345", req2000). + Return(&api.ResizeBranchResponse{HTTPResponse: httpResponse(http.StatusAccepted)}, nil) + }, + wantErr: "empty response from API", + wantStderr: "Resizing branch 'test-branch' (br-12345) to 2 CPU/16 GB...\n" + + "Error: empty response from API\n", + }, + { + // CPU and memory are left out, so the API keeps the current ones. + name: "iops only omits cpu and memory", + args: []string{"branch", "resize", "br-12345", "--iops", "16000", "--no-wait"}, + opts: []runOption{withExperimental()}, + mock: setupResize(api.BranchResizeInput{Iops: new(16000)}, api.DeployStatusCONFIGURING), + wantStderr: "Resizing branch 'test-branch' (br-12345) to 16000 IOPS...\n" + + "Resize request accepted for branch 'test-branch' (br-12345).\n" + + "Use 'tiger branch get' to check branch status.\n", + }, + { + name: "cpu and iops", + args: []string{"branch", "resize", "br-12345", "--cpu", "2000", "--iops", "16000", "--no-wait"}, + opts: []runOption{withExperimental()}, + mock: setupResize(api.BranchResizeInput{CPUMillis: new("2000"), MemoryGbs: new("16"), Iops: new(16000)}, api.DeployStatusCONFIGURING), + wantStderr: "Resizing branch 'test-branch' (br-12345) to 2 CPU/16 GB, 16000 IOPS...\n" + + "Resize request accepted for branch 'test-branch' (br-12345).\n" + + "Use 'tiger branch get' to check branch status.\n", + }, + { + name: "no-wait with cpu only", + args: []string{"branch", "resize", "br-12345", "--cpu", "2000", "--no-wait"}, + opts: []runOption{withExperimental()}, + mock: setupResize(req2000, api.DeployStatusCONFIGURING), + wantStderr: noWaitStderr, + }, + { + name: "wait polls until ready", + synctest: true, + args: []string{"branch", "resize", "br-12345", "--memory", "16"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + setupResize(req2000, api.DeployStatusCONFIGURING)(m) + expectGetBranch(m, sampleBranch()) + }, + wantStderr: "Resizing branch 'test-branch' (br-12345) to 2 CPU/16 GB...\n" + + "Resize request accepted for branch 'test-branch' (br-12345).\n" + + "Waiting for resize to complete (timeout: 10m0s)...\n" + + "⢎ Branch status: CONFIGURING\n" + + "Branch resized to 2 CPU/16 GB.\n", + }, + }) +} diff --git a/internal/cmd/branch_start.go b/internal/cmd/branch_start.go new file mode 100644 index 00000000..ee47af1b --- /dev/null +++ b/internal/cmd/branch_start.go @@ -0,0 +1,99 @@ +package cmd + +import ( + "fmt" + "net/http" + "time" + + "github.com/spf13/cobra" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" +) + +// buildBranchStartCmd creates the start subcommand +func buildBranchStartCmd(app *common.App) *cobra.Command { + var noWait bool + var waitTimeout time.Duration + + cmd := &cobra.Command{ + Use: "start ", + Aliases: []string{"resume"}, + Short: "Start a stopped branch", + Long: `Start a stopped branch. + +Starting a branch resumes charging for its compute. + +The branch can be given by ID or name.`, + Example: ` # Start a branch (waits for completion by default) + tiger branch start br-12345 + + # Start a branch without waiting for completion + tiger branch start br-12345 --no-wait`, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: branchRefCompletion(app), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + cfg, client, projectID, err := app.GetAll() + if err != nil { + return err + } + + branchRef, err := getBranchRef(args) + if err != nil { + return err + } + + resolved, err := resolveBranchForWrite(cmd.Context(), cfg, client, projectID, branchRef) + if err != nil { + return err + } + + resp, err := client.StartBranchWithResponse(cmd.Context(), projectID, resolved.ServiceID, resolved.ID) + if err != nil { + return fmt.Errorf("failed to start branch: %w", err) + } + if resp.StatusCode() != http.StatusAccepted { + return common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON202 == nil { + return fmt.Errorf("empty response from API") + } + branch := *resp.JSON202 + + cmd.PrintErrf("Start request accepted for branch %s.\n", branchLabel(*resolved)) + + if noWait { + cmd.PrintErrln("Use 'tiger branch get' to check branch status.") + return nil + } + + cmd.PrintErrf("Waiting for branch to start (timeout: %v)...\n", waitTimeout) + if err := common.WaitForBranch(cmd.Context(), common.WaitForBranchArgs{ + Client: client, + ProjectID: projectID, + ServiceID: resolved.ServiceID, + BranchID: resolved.ID, + Branch: &branch, + TargetStatus: api.DeployStatusREADY, + Input: cmd.InOrStdin(), + Output: cmd.ErrOrStderr(), + Timeout: waitTimeout, + TimeoutMsg: "branch may still be starting", + }); err != nil { + // Return error for sake of exit code, but silence since we already output it + cmd.PrintErrf("Error: %s\n", err) + cmd.SilenceErrors = true + return err + } + + cmd.PrintErrln("Branch started.") + return nil + }, + } + + cmd.Flags().BoolVar(&noWait, "no-wait", false, "Don't wait for the operation to complete") + cmd.Flags().DurationVar(&waitTimeout, "wait-timeout", 10*time.Minute, "Maximum time to wait for operation to complete") + + return cmd +} diff --git a/internal/cmd/branch_start_test.go b/internal/cmd/branch_start_test.go new file mode 100644 index 00000000..fcf8e479 --- /dev/null +++ b/internal/cmd/branch_start_test.go @@ -0,0 +1,107 @@ +package cmd + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" + "github.com/timescale/tiger-cli/internal/common" +) + +func TestBranchStartCmd(t *testing.T) { + setupStart := func(status api.DeployStatus) func(m *mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch(branchStatus(api.DeployStatusPAUSED))) + branch := sampleBranch(branchStatus(status)) + m.EXPECT().StartBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(&api.StartBranchResponse{ + HTTPResponse: httpResponse(http.StatusAccepted), + JSON202: &branch, + }, nil) + } + } + noWaitStderr := "Start request accepted for branch 'test-branch' (br-12345).\n" + + "Use 'tiger branch get' to check branch status.\n" + + runCmdTests(t, []cmdTest{ + { + name: "not logged in", + args: []string{"branch", "start", "br-12345"}, + opts: []runOption{withExperimental(), withNotLoggedIn()}, + wantErr: notLoggedInMsg, + checks: []checkFunc{checkExitCode(common.ExitAuthenticationError)}, + }, + { + // No default fallback: service_id only ever holds a service ID. + name: "missing branch id", + args: []string{"branch", "start"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"service_id": "svc-12345"})}, + wantErr: "branch name or ID is required", + }, + { + name: "read-only all refuses", + args: []string{"branch", "start", "br-12345"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + name: "read-only prod allows", + args: []string{"branch", "start", "br-12345", "--no-wait"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "prod"})}, + mock: setupStart(api.DeployStatusRESUMING), + wantStderr: noWaitStderr, + }, + { + name: "network error", + args: []string{"branch", "start", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().StartBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to start branch: connection refused", + }, + { + name: "nil response body", + args: []string{"branch", "start", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().StartBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(&api.StartBranchResponse{HTTPResponse: httpResponse(http.StatusAccepted)}, nil) + }, + wantErr: "empty response from API", + }, + { + name: "no-wait", + args: []string{"branch", "start", "br-12345", "--no-wait"}, + opts: []runOption{withExperimental()}, + mock: setupStart(api.DeployStatusRESUMING), + wantStderr: noWaitStderr, + }, + { + name: "wait polls until ready", + synctest: true, + args: []string{"branch", "start", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + setupStart(api.DeployStatusRESUMING)(m) + expectGetBranch(m, sampleBranch()) + }, + wantStderr: "Start request accepted for branch 'test-branch' (br-12345).\n" + + "Waiting for branch to start (timeout: 10m0s)...\n" + + "⢎ Branch status: RESUMING\n" + + "Branch started.\n", + }, + { + name: "resume alias", + args: []string{"branch", "resume", "br-12345", "--no-wait"}, + opts: []runOption{withExperimental()}, + mock: setupStart(api.DeployStatusRESUMING), + wantStderr: noWaitStderr, + }, + }) +} diff --git a/internal/cmd/branch_stop.go b/internal/cmd/branch_stop.go new file mode 100644 index 00000000..532764aa --- /dev/null +++ b/internal/cmd/branch_stop.go @@ -0,0 +1,100 @@ +package cmd + +import ( + "fmt" + "net/http" + "time" + + "github.com/spf13/cobra" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" +) + +// buildBranchStopCmd creates the stop subcommand +func buildBranchStopCmd(app *common.App) *cobra.Command { + var noWait bool + var waitTimeout time.Duration + + cmd := &cobra.Command{ + Use: "stop ", + Aliases: []string{"pause"}, + Short: "Stop a running branch", + Long: `Stop a running branch. + +A stopped branch no longer accepts connections and stops charging for compute, +but keeps the changes made on it, whose storage is still billed. + +The branch can be given by ID or name.`, + Example: ` # Stop a branch (waits for completion by default) + tiger branch stop br-12345 + + # Stop a branch without waiting for completion + tiger branch stop br-12345 --no-wait`, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: branchRefCompletion(app), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + cfg, client, projectID, err := app.GetAll() + if err != nil { + return err + } + + branchRef, err := getBranchRef(args) + if err != nil { + return err + } + + resolved, err := resolveBranchForWrite(cmd.Context(), cfg, client, projectID, branchRef) + if err != nil { + return err + } + + resp, err := client.StopBranchWithResponse(cmd.Context(), projectID, resolved.ServiceID, resolved.ID) + if err != nil { + return fmt.Errorf("failed to stop branch: %w", err) + } + if resp.StatusCode() != http.StatusAccepted { + return common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON202 == nil { + return fmt.Errorf("empty response from API") + } + branch := *resp.JSON202 + + cmd.PrintErrf("Stop request accepted for branch %s.\n", branchLabel(*resolved)) + + if noWait { + cmd.PrintErrln("Use 'tiger branch get' to check branch status.") + return nil + } + + cmd.PrintErrf("Waiting for branch to stop (timeout: %v)...\n", waitTimeout) + if err := common.WaitForBranch(cmd.Context(), common.WaitForBranchArgs{ + Client: client, + ProjectID: projectID, + ServiceID: resolved.ServiceID, + BranchID: resolved.ID, + Branch: &branch, + TargetStatus: api.DeployStatusPAUSED, + Input: cmd.InOrStdin(), + Output: cmd.ErrOrStderr(), + Timeout: waitTimeout, + TimeoutMsg: "branch may still be stopping", + }); err != nil { + // Return error for sake of exit code, but silence since we already output it + cmd.PrintErrf("Error: %s\n", err) + cmd.SilenceErrors = true + return err + } + + cmd.PrintErrln("Branch stopped.") + return nil + }, + } + + cmd.Flags().BoolVar(&noWait, "no-wait", false, "Don't wait for the operation to complete") + cmd.Flags().DurationVar(&waitTimeout, "wait-timeout", 10*time.Minute, "Maximum time to wait for operation to complete") + + return cmd +} diff --git a/internal/cmd/branch_stop_test.go b/internal/cmd/branch_stop_test.go new file mode 100644 index 00000000..f4c1bba0 --- /dev/null +++ b/internal/cmd/branch_stop_test.go @@ -0,0 +1,143 @@ +package cmd + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" + "github.com/timescale/tiger-cli/internal/common" +) + +func TestBranchStopCmd(t *testing.T) { + setupStop := func(status api.DeployStatus) func(m *mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + branch := sampleBranch(branchStatus(status)) + m.EXPECT().StopBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(&api.StopBranchResponse{ + HTTPResponse: httpResponse(http.StatusAccepted), + JSON202: &branch, + }, nil) + } + } + noWaitStderr := "Stop request accepted for branch 'test-branch' (br-12345).\n" + + "Use 'tiger branch get' to check branch status.\n" + + runCmdTests(t, []cmdTest{ + { + name: "not logged in", + args: []string{"branch", "stop", "br-12345"}, + opts: []runOption{withExperimental(), withNotLoggedIn()}, + wantErr: notLoggedInMsg, + checks: []checkFunc{checkExitCode(common.ExitAuthenticationError)}, + }, + { + // No default fallback: service_id only ever holds a service ID. + name: "missing branch id", + args: []string{"branch", "stop"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"service_id": "svc-12345"})}, + wantErr: "branch name or ID is required", + }, + { + name: "read-only all refuses", + args: []string{"branch", "stop", "br-12345"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + name: "read-only prod allows", + args: []string{"branch", "stop", "br-12345", "--no-wait"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "prod"})}, + mock: setupStop(api.DeployStatusPAUSING), + wantStderr: noWaitStderr, + }, + { + name: "network error", + args: []string{"branch", "stop", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().StopBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to stop branch: connection refused", + }, + { + name: "API error", + args: []string{"branch", "stop", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().StopBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(&api.StopBranchResponse{ + HTTPResponse: httpResponse(http.StatusBadRequest), + JSON4XX: &api.ClientError{Message: new("branch is already stopped")}, + }, nil) + }, + wantErr: "branch is already stopped", + checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, + }, + { + name: "nil response body", + args: []string{"branch", "stop", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().StopBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(&api.StopBranchResponse{HTTPResponse: httpResponse(http.StatusAccepted)}, nil) + }, + wantErr: "empty response from API", + }, + { + name: "no-wait", + args: []string{"branch", "stop", "br-12345", "--no-wait"}, + opts: []runOption{withExperimental()}, + mock: setupStop(api.DeployStatusPAUSING), + wantStderr: noWaitStderr, + }, + { + name: "wait polls until paused", + synctest: true, + args: []string{"branch", "stop", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + setupStop(api.DeployStatusPAUSING)(m) + expectGetBranch(m, sampleBranch(branchStatus(api.DeployStatusPAUSED))) + }, + wantStderr: "Stop request accepted for branch 'test-branch' (br-12345).\n" + + "Waiting for branch to stop (timeout: 10m0s)...\n" + + "⢎ Branch status: PAUSING\n" + + "Branch stopped.\n", + }, + { + name: "wait timeout", + synctest: true, + args: []string{"branch", "stop", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + setupStop(api.DeployStatusPAUSING)(m) + pausing := sampleBranch(branchStatus(api.DeployStatusPAUSING)) + m.EXPECT().GetBranchWithResponse(validCtx, testProjectID, "svc-12345", "br-12345"). + Return(&api.GetBranchResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &pausing, + }, nil).AnyTimes() + }, + wantErr: "wait timeout reached after 10m0s - branch may still be stopping", + wantStderr: "Stop request accepted for branch 'test-branch' (br-12345).\n" + + "Waiting for branch to stop (timeout: 10m0s)...\n" + + "⢎ Branch status: PAUSING\n" + + "Error: wait timeout reached after 10m0s - branch may still be stopping\n", + checks: []checkFunc{checkExitCode(common.ExitTimeout)}, + }, + { + name: "pause alias", + args: []string{"branch", "pause", "br-12345", "--no-wait"}, + opts: []runOption{withExperimental()}, + mock: setupStop(api.DeployStatusPAUSING), + wantStderr: noWaitStderr, + }, + }) +} diff --git a/internal/cmd/branch_test.go b/internal/cmd/branch_test.go new file mode 100644 index 00000000..5b2c81f9 --- /dev/null +++ b/internal/cmd/branch_test.go @@ -0,0 +1,157 @@ +package cmd + +import ( + "net/http" + "testing" + "time" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" +) + +// TestBranchExperimentalGate covers the registration gate for the branch +// commands: `tiger branch` and `tiger service branch` exist only behind the +// experimental env var. +func TestBranchExperimentalGate(t *testing.T) { + runCmdTests(t, []cmdTest{ + { + name: "branch unregistered by default", + args: []string{"branch", "list"}, + wantErr: `unknown command "branch" for "tiger"`, + wantStderr: "Error: unknown command \"branch\" for \"tiger\"\n" + + "Run 'tiger --help' for usage.\n", + }, + { + // Cobra treats an unknown name under a group as a stray argument + // and prints the group's help (see TestServiceExperimentalGate). + name: "service branch unregistered by default", + args: []string{"service", "branch"}, + wantStdout: matchHelp("Available Commands:", "branch"), + }, + { + name: "branch registered when experimental", + args: []string{"branch", "--help"}, + opts: []runOption{withExperimental()}, + wantStdout: matchHelp("Manage branches of Fluid Services."), + }, + { + name: "branches alias", + args: []string{"branches", "--help"}, + opts: []runOption{withExperimental()}, + wantStdout: matchHelp("Manage branches of Fluid Services."), + }, + { + name: "service branch registered when experimental", + args: []string{"service", "branch", "--help"}, + opts: []runOption{withExperimental()}, + wantStdout: matchHelp("tiger service branch svc-12345 --name ci-4821"), + }, + }) +} + +// withExperimental turns on the experimental gate, which every branch command +// sits behind. +func withExperimental() runOption { + return withEnv("TIGER_EXPERIMENTAL", "true") +} + +// sampleBranch returns a branch of sampleService with reasonable defaults. +// Use overrides to customize specific fields. +func sampleBranch(overrides ...func(*api.Branch)) api.Branch { + branch := api.Branch{ + ID: "br-12345", + ProjectID: testProjectID, + ServiceID: "svc-12345", + Name: "test-branch", + Status: api.DeployStatusREADY, + CPUMillis: 250, + MemoryGbs: 2, + Created: time.Date(2025, 1, 16, 9, 0, 0, 0, time.UTC), + Endpoint: &api.Endpoint{ + Host: new("br-12345.test-project-123.branch.tsdb.cloud.timescale.com"), + Port: new(36956), + }, + } + for _, o := range overrides { + o(&branch) + } + return branch +} + +// branchStatus returns a sampleBranch override setting the branch's status. +func branchStatus(status api.DeployStatus) func(*api.Branch) { + return func(b *api.Branch) { b.Status = status } +} + +// branchType is the resolve type every `tiger branch` command restricts its +// lookup to. +var branchType = new(api.ResolveTypeBRANCH) + +// expectResolveBranch registers resolving ref as a branch, answered with +// branch. +func expectResolveBranch(m *mocks.MockClientWithResponsesInterface, ref string, branch api.Branch) { + expectResolve(m, ref, branchType, branch) +} + +// expectResolveBranchStatus registers resolving ref as a branch, answered with +// status. A nil apiErr stands in for a response with no body. +func expectResolveBranchStatus(m *mocks.MockClientWithResponsesInterface, ref string, status int, apiErr *api.ClientError) { + expectResolveStatus(m, ref, branchType, status, apiErr) +} + +// expectGetBranch registers one fetch of branch, through its parent, as a +// wait loop's poll makes it. +func expectGetBranch(m *mocks.MockClientWithResponsesInterface, branch api.Branch) { + m.EXPECT().GetBranchWithResponse(validCtx, testProjectID, branch.ServiceID, branch.ID). + Return(&api.GetBranchResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &branch, + }, nil) +} + +// sampleBranchEnv is the env output of sampleBranch. +const sampleBranchEnv = `PGHOST=br-12345.test-project-123.branch.tsdb.cloud.timescale.com +PGPORT=36956 +PGDATABASE=tsdb +PGUSER=tsdbadmin +` + +// sampleBranchTable and sampleBranchJSON are the table and JSON output of +// sampleBranch. +const ( + sampleBranchTable = `┌───────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ PROPERTY │ VALUE │ +├───────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ +│ Branch ID │ br-12345 │ +│ Name │ test-branch │ +│ Parent Service │ test-service (svc-12345) │ +│ Status │ READY │ +│ CPU │ 0.25 cores (250m) │ +│ Memory │ 2 GB │ +│ Direct Endpoint │ br-12345.test-project-123.branch.tsdb.cloud.timescale.com:36956 │ +│ Created │ 2025-01-16 09:00:00 UTC │ +│ Connection String │ postgresql://tsdbadmin@br-12345.test-project-123.branch.tsdb.cloud.timescale.com:36956/tsdb?sslmode=require │ +└───────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +` + sampleBranchJSON = `{ + "cpu_millis": 250, + "created": "2025-01-16T09:00:00Z", + "endpoint": { + "host": "br-12345.test-project-123.branch.tsdb.cloud.timescale.com", + "port": 36956 + }, + "id": "br-12345", + "memory_gbs": 2, + "name": "test-branch", + "project_id": "test-project-123", + "service_id": "svc-12345", + "status": "READY", + "service_name": "test-service", + "role": "tsdbadmin", + "host": "br-12345.test-project-123.branch.tsdb.cloud.timescale.com", + "port": 36956, + "database": "tsdb", + "connection_string": "postgresql://tsdbadmin@br-12345.test-project-123.branch.tsdb.cloud.timescale.com:36956/tsdb?sslmode=require" +} +` +) diff --git a/internal/cmd/branch_update_password.go b/internal/cmd/branch_update_password.go new file mode 100644 index 00000000..58a86b1e --- /dev/null +++ b/internal/cmd/branch_update_password.go @@ -0,0 +1,94 @@ +package cmd + +import ( + "errors" + "os" + + "github.com/spf13/cobra" + + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// buildBranchUpdatePasswordCmd creates the update-password subcommand +func buildBranchUpdatePasswordCmd(app *common.App) *cobra.Command { + var newPassword string + var autoGenerate bool + + cmd := &cobra.Command{ + Use: "update-password ", + Short: "Update the master password for a branch", + Long: `Update the master password for the 'tsdbadmin' user of a branch. + +Changing a branch's password doesn't change its parent service's. + +The branch can be given by ID or name.`, + Example: ` # Update a branch's password, interactively prompts + tiger branch update-password br-12345 + + # Update a specific branch's password + tiger branch update-password br-12345 --new-password new-secure-password + + # Update password using environment variable (TIGER_NEW_PASSWORD) + export TIGER_NEW_PASSWORD="new-secure-password" + tiger branch update-password br-12345 + + # Auto-generate a secure password + tiger branch update-password br-12345 --auto-generate`, + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: branchRefCompletion(app), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + cfg, client, projectID, err := app.GetAll() + if err != nil { + return err + } + + // The password comes from the flag, falling back to the env var + password := newPassword + if password == "" { + password = os.Getenv("TIGER_NEW_PASSWORD") + } + if autoGenerate && password != "" { + return errors.New("cannot use --auto-generate and --new-password together") + } + + branchRef, err := getBranchRef(args) + if err != nil { + return err + } + + ctx := cmd.Context() + + // Gated here, ahead of any prompt or write. The branch is needed + // to save the new password under it. + branch, err := resolveBranchForWrite(ctx, cfg, client, projectID, branchRef) + if err != nil { + return err + } + + credentials := common.BranchCredentials(*branch) + if autoGenerate || password != "" { + // An empty password is generated. + if _, err := resetPassword(ctx, cmd, cfg, client, credentials, "tsdbadmin", password); err != nil { + return err + } + } else { + if !util.IsTerminal(cmd.InOrStdin()) || !util.IsTerminal(cmd.ErrOrStderr()) { + return errors.New("TTY not detected - use --new-password flag, --auto-generate flag, or TIGER_NEW_PASSWORD environment variable") + } + if _, err := promptAndResetPassword(ctx, cmd, cfg, client, credentials, "tsdbadmin"); err != nil { + return err + } + } + + cmd.PrintErrf("Password updated for tsdbadmin\n") + return nil + }, + } + + cmd.Flags().StringVar(&newPassword, "new-password", "", "New password for the tsdbadmin user (can also be set via TIGER_NEW_PASSWORD env var)") + cmd.Flags().BoolVar(&autoGenerate, "auto-generate", false, "Auto-generate a secure password") + cmd.MarkFlagsMutuallyExclusive("new-password", "auto-generate") + return cmd +} diff --git a/internal/cmd/branch_update_password_test.go b/internal/cmd/branch_update_password_test.go new file mode 100644 index 00000000..1640c4b2 --- /dev/null +++ b/internal/cmd/branch_update_password_test.go @@ -0,0 +1,144 @@ +package cmd + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" + "github.com/timescale/tiger-cli/internal/common" +) + +func TestBranchUpdatePasswordCmd(t *testing.T) { + setupUpdate := func(password string) func(m *mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().UpdateBranchPasswordWithResponse(validCtx, testProjectID, "svc-12345", "br-12345", api.UpdatePasswordInput{Password: password}). + Return(&api.UpdateBranchPasswordResponse{HTTPResponse: httpResponse(http.StatusNoContent)}, nil) + } + } + successStderr := "Password saved to system keyring\nPassword updated for tsdbadmin\n" + + runCmdTests(t, []cmdTest{ + { + name: "auto-generate with new password env var", + args: []string{"branch", "update-password", "br-12345", "--auto-generate"}, + opts: []runOption{withExperimental(), withEnv("TIGER_NEW_PASSWORD", "secret")}, + wantErr: "cannot use --auto-generate and --new-password together", + }, + { + name: "not logged in", + args: []string{"branch", "update-password", "br-12345", "--new-password", "secret"}, + opts: []runOption{withExperimental(), withNotLoggedIn()}, + wantErr: notLoggedInMsg, + checks: []checkFunc{checkExitCode(common.ExitAuthenticationError)}, + }, + { + // No default fallback: service_id only ever holds a service ID. + name: "missing branch id", + args: []string{"branch", "update-password", "--new-password", "secret"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"service_id": "svc-12345"})}, + wantErr: "branch name or ID is required", + }, + { + name: "read-only all refuses", + args: []string{"branch", "update-password", "br-12345", "--new-password", "secret"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + name: "read-only prod allows", + args: []string{"branch", "update-password", "br-12345", "--new-password", "secret"}, + opts: []runOption{withExperimental(), withConfig(map[string]any{"read_only": "prod"})}, + mock: setupUpdate("secret"), + wantStderr: successStderr, + }, + { + name: "non-TTY without password", + args: []string{"branch", "update-password", "br-12345"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { expectResolveBranch(m, "br-12345", sampleBranch()) }, + wantErr: "TTY not detected - use --new-password flag, --auto-generate flag, or TIGER_NEW_PASSWORD environment variable", + }, + { + name: "network error", + args: []string{"branch", "update-password", "br-12345", "--new-password", "secret"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().UpdateBranchPasswordWithResponse(validCtx, testProjectID, "svc-12345", "br-12345", api.UpdatePasswordInput{Password: "secret"}). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to update password: connection refused", + }, + { + name: "API error", + args: []string{"branch", "update-password", "br-12345", "--new-password", "secret"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().UpdateBranchPasswordWithResponse(validCtx, testProjectID, "svc-12345", "br-12345", api.UpdatePasswordInput{Password: "secret"}). + Return(&api.UpdateBranchPasswordResponse{ + HTTPResponse: httpResponse(http.StatusBadRequest), + JSON4XX: &api.ClientError{Message: new("password too short")}, + }, nil) + }, + wantErr: "password too short", + checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, + }, + { + // The endpoint answers success with a 204 and nothing else. + name: "unexpected 200 is an error", + args: []string{"branch", "update-password", "br-12345", "--new-password", "secret"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + m.EXPECT().UpdateBranchPasswordWithResponse(validCtx, testProjectID, "svc-12345", "br-12345", api.UpdatePasswordInput{Password: "secret"}). + Return(&api.UpdateBranchPasswordResponse{HTTPResponse: httpResponse(http.StatusOK)}, nil) + }, + wantErr: "unknown error", + }, + { + name: "new password flag", + args: []string{"branch", "update-password", "br-12345", "--new-password", "secret"}, + opts: []runOption{withExperimental()}, + mock: setupUpdate("secret"), + wantStderr: successStderr, + checks: []checkFunc{checkStoredPassword("br-12345", "secret")}, + }, + { + name: "new password env var", + args: []string{"branch", "update-password", "br-12345"}, + opts: []runOption{withExperimental(), withEnv("TIGER_NEW_PASSWORD", "from-env")}, + mock: setupUpdate("from-env"), + wantStderr: successStderr, + checks: []checkFunc{checkStoredPassword("br-12345", "from-env")}, + }, + { + name: "prompt", + args: []string{"branch", "update-password", "br-12345"}, + opts: []runOption{withExperimental(), withIsTerminal(true), withReadPassword("typed")}, + mock: setupUpdate("typed"), + wantStderr: "Enter new password (leave empty to generate): \n" + successStderr, + checks: []checkFunc{checkStoredPassword("br-12345", "typed")}, + }, + { + name: "auto-generate", + args: []string{"branch", "update-password", "br-12345", "--auto-generate"}, + opts: []runOption{withExperimental(), withGenerateSecurePassword(32, "generated", nil)}, + mock: setupUpdate("generated"), + wantStderr: successStderr, + checks: []checkFunc{checkStoredPassword("br-12345", "generated")}, + }, + { + name: "password generation fails", + args: []string{"branch", "update-password", "br-12345", "--auto-generate"}, + opts: []runOption{withExperimental(), withGenerateSecurePassword(32, "", errors.New("entropy unavailable"))}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, "br-12345", sampleBranch()) + }, + wantErr: "failed to generate new password: entropy unavailable", + }, + }) +} diff --git a/internal/cmd/completion_helper.go b/internal/cmd/completion_helper.go index 94ce0689..02b992d5 100644 --- a/internal/cmd/completion_helper.go +++ b/internal/cmd/completion_helper.go @@ -115,6 +115,38 @@ func listServices(cmd *cobra.Command, app *common.App) ([]api.Service, error) { return *resp.JSON200, nil } +// branchRefCompletion completes the branch argument of the branch commands. +// Like serviceRefCompletion, it offers each branch at most once, by name, +// falling back to its ID when what is typed matches the ID instead. +func branchRefCompletion(app *common.App) cobra.CompletionFunc { + return withAppLoad(app, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // The branch ref is always the first positional argument + if len(args) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + client, projectID, err := app.GetClient() + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + resp, err := client.GetProjectBranchesWithResponse(cmd.Context(), projectID) + if err != nil || resp.StatusCode() != http.StatusOK || resp.JSON200 == nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + branches := *resp.JSON200 + results := make([]string, 0, len(branches)) + for _, branch := range branches { + if strings.HasPrefix(branch.Name, toComplete) { + results = append(results, cobra.CompletionWithDesc(branch.Name, branch.ID)) + } else if strings.HasPrefix(branch.ID, toComplete) { + results = append(results, cobra.CompletionWithDesc(branch.ID, branch.Name)) + } + } + return results, cobra.ShellCompDirectiveNoFileComp + }) +} + func projectIDCompletion(app *common.App) cobra.CompletionFunc { return withAppLoad(app, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { // Project ID is always first positional argument @@ -264,11 +296,34 @@ func outputCompletion(extra ...string) cobra.CompletionFunc { // service can't be resized onto, and experimental includes the fluid platform's // configurations, which are a preview. func cpuCompletion(shared, experimental bool) cobra.CompletionFunc { - return sizeCompletion(shared, experimental, func(c common.CPUMemoryConfig) int { return c.CPUMillis }) + return sizeCompletion(serviceConfigs(shared, experimental), cpuMillis) } func memoryCompletion(shared, experimental bool) cobra.CompletionFunc { - return sizeCompletion(shared, experimental, func(c common.CPUMemoryConfig) int { return c.MemoryGBs }) + return sizeCompletion(serviceConfigs(shared, experimental), memoryGBs) +} + +// branchCPUCompletion and branchMemoryCompletion complete the --cpu and +// --memory flags of the branch commands, which take the fluid platform's +// configurations, since only fluid services can be branched. +var ( + branchCPUCompletion = sizeCompletion(fluidConfigs, cpuMillis) + branchMemoryCompletion = sizeCompletion(fluidConfigs, memoryGBs) +) + +func cpuMillis(c common.CPUMemoryConfig) int { return c.CPUMillis } +func memoryGBs(c common.CPUMemoryConfig) int { return c.MemoryGBs } + +// serviceConfigs returns the configurations the service commands' sizes are +// drawn from (see completionCPUMemoryConfigs). +func serviceConfigs(shared, experimental bool) func(*cobra.Command) common.CPUMemoryConfigs { + return func(cmd *cobra.Command) common.CPUMemoryConfigs { + return completionCPUMemoryConfigs(cmd, shared, experimental) + } +} + +func fluidConfigs(*cobra.Command) common.CPUMemoryConfigs { + return common.FluidCPUMemoryConfigs() } // sizeCompletion completes the sizes size reads off the allowed @@ -276,9 +331,9 @@ func memoryCompletion(shared, experimental bool) cobra.CompletionFunc { // The configurations can come from more than one platform, so the order is // imposed here, and the shell is told to keep it rather than sort the values // as strings. -func sizeCompletion(shared, experimental bool, size func(common.CPUMemoryConfig) int) cobra.CompletionFunc { +func sizeCompletion(configsFor func(*cobra.Command) common.CPUMemoryConfigs, size func(common.CPUMemoryConfig) int) cobra.CompletionFunc { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - configs := completionCPUMemoryConfigs(cmd, shared, experimental) + configs := configsFor(cmd) sizes := make([]int, 0, len(configs)) hasShared := false @@ -332,10 +387,10 @@ const ( iopsIncrement = 1000 ) -// iopsCompletion completes --iops on `service create`, `fork`, and `resize` -// with every value in the widest range. Which of them a service accepts -// depends on its plan, platform, and CPU/memory allocation, which is left to -// the API to validate. +// iopsCompletion completes --iops on `service create`, `fork`, and `resize`, +// and on `branch create` and `resize`, with every value in the widest range. +// Which of them a service accepts depends on its plan, platform, and +// CPU/memory allocation, which is left to the API to validate. var iopsCompletion = func() cobra.CompletionFunc { values := make([]string, 0, (maxCompletionIOPS-minCompletionIOPS)/iopsIncrement+1) for v := minCompletionIOPS; v <= maxCompletionIOPS; v += iopsIncrement { diff --git a/internal/cmd/completion_helper_test.go b/internal/cmd/completion_helper_test.go index a39660d5..5c8a7fbc 100644 --- a/internal/cmd/completion_helper_test.go +++ b/internal/cmd/completion_helper_test.go @@ -57,6 +57,21 @@ func TestCompletion(t *testing.T) { }, nil) } + listBranches := func(m *mocks.MockClientWithResponsesInterface) { + branches := []api.Branch{ + sampleBranch(), + sampleBranch(func(b *api.Branch) { + b.ID = "br-67890" + b.Name = "other-branch" + }), + } + m.EXPECT().GetProjectBranchesWithResponse(validCtx, testProjectID). + Return(&api.GetProjectBranchesResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &branches, + }, nil) + } + runCmdTests(t, []cmdTest{ { name: "service argument lists services by name with their IDs", @@ -127,6 +142,42 @@ func TestCompletion(t *testing.T) { wantStdout: noFileComp, wantStderr: directive, }, + { + name: "branch argument lists branches by name with their IDs", + args: []string{"__complete", "branch", "get", ""}, + opts: []runOption{withExperimental()}, + mock: listBranches, + wantStdout: "test-branch\tbr-12345\nother-branch\tbr-67890\n" + noFileComp, + wantStderr: directive, + }, + { + // A prefix that matches no name falls back to IDs, with the name + // as the description. + name: "branch argument offers IDs matching what is typed", + args: []string{"__complete", "branch", "stop", "br-6"}, + opts: []runOption{withExperimental()}, + mock: listBranches, + wantStdout: "br-67890\tother-branch\n" + noFileComp, + wantStderr: directive, + }, + { + name: "branch argument completes only the first argument", + args: []string{"__complete", "branch", "rename", "br-12345", ""}, + opts: []runOption{withExperimental()}, + wantStdout: noFileComp, + wantStderr: directive, + }, + { + name: "branch argument offers nothing when the API fails", + args: []string{"__complete", "branch", "delete", ""}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + m.EXPECT().GetProjectBranchesWithResponse(validCtx, testProjectID). + Return(nil, errors.New("connection refused")) + }, + wantStdout: noFileComp, + wantStderr: directive, + }, { name: "--service-id lists services with their names", args: []string{"__complete", "service", "get", "--service-id", ""}, @@ -280,6 +331,31 @@ func TestCompletion(t *testing.T) { wantStderr: keepOrderDirective, checks: []checkFunc{checkNotLoaded}, }, + { + // Only fluid services can be branched. + name: "--cpu flag values for branches are the fluid platform's", + args: []string{"__complete", "branch", "create", "--cpu", ""}, + opts: []runOption{withExperimental()}, + wantStdout: "250\n500\n1000\n2000\n4000\n8000\n16000\n32000\n" + keepOrder, + wantStderr: keepOrderDirective, + checks: []checkFunc{checkNotLoaded}, + }, + { + name: "--iops flag values for branch create", + args: []string{"__complete", "branch", "create", "--iops", ""}, + opts: []runOption{withExperimental()}, + wantStdout: iopsSteps(3000, 256000) + keepOrder, + wantStderr: keepOrderDirective, + checks: []checkFunc{checkNotLoaded}, + }, + { + name: "--memory flag values for branch resize are the fluid platform's", + args: []string{"__complete", "branch", "resize", "--memory", ""}, + opts: []runOption{withExperimental()}, + wantStdout: "2\n4\n8\n16\n32\n64\n128\n256\n" + keepOrder, + wantStderr: keepOrderDirective, + checks: []checkFunc{checkNotLoaded}, + }, { // The widest range across every plan, platform, and CPU/memory // allocation: the API validates the value. diff --git a/internal/cmd/db.go b/internal/cmd/db.go index 36e81d24..37ebe354 100644 --- a/internal/cmd/db.go +++ b/internal/cmd/db.go @@ -1,12 +1,14 @@ package cmd import ( + "context" "errors" "fmt" "github.com/jackc/pgx/v5/pgconn" "github.com/spf13/cobra" + "github.com/timescale/tiger-cli/internal/api" "github.com/timescale/tiger-cli/internal/common" ) @@ -28,26 +30,63 @@ func buildDbCmd(app *common.App) *cobra.Command { return cmd } +// branchRefHelp is the paragraph a db command's help gains for branches, saying +// what passing one does. Branches are a preview, so it is empty without the +// experimental gate. +func branchRefHelp(experimental bool, text string) string { + if !experimental { + return "" + } + return "\n\n" + text +} + // handleDatabaseError adds guidance to the failures whose fix isn't evident from // the message: the readiness sentinels, and a Postgres authentication failure, // which otherwise arrives as a bare pgx error saying nothing about the password // it was rejected for. Every other error passes through unchanged. func handleDatabaseError(err error, target *common.ConnectionTarget) error { + // A branch is managed through `tiger branch`, everything else through + // `tiger service`. The group also names the target in the readiness + // errors, whose sentinels always say "service". + group := "service" + if target.Kind == common.TargetBranch { + group = "branch" + } + switch { case errors.Is(err, common.ErrPaused): - return fmt.Errorf("%w — start it with 'tiger service start %s'", common.ErrPaused, target.ConnectionService.ServiceID) + return fmt.Errorf("%s is paused — start it with 'tiger %s start %s'", group, group, target.ID) case errors.Is(err, common.ErrNotReady): - return fmt.Errorf("%w — check its status with 'tiger service get %s' and try again", common.ErrNotReady, target.ConnectionService.ServiceID) + return fmt.Errorf("%s is not ready — check its status with 'tiger %s get %s' and try again", group, group, target.ID) case isPostgresAuthenticationError(err): // A read replica shares its primary's credentials, so the password - // belongs to the credential service rather than the one connected to. - serviceID := target.CredentialService.ServiceID - return fmt.Errorf("%w\n\nThe stored password is missing or invalid. Save the current one with 'tiger db save-password %s', or reset it with 'tiger service update-password %s'", - err, serviceID, serviceID) + // belongs to the primary rather than the replica connected to. + serviceID := target.Credentials.ID + return fmt.Errorf("%w\n\nThe stored password is missing or invalid. Save the current one with 'tiger db save-password %s', or reset it with 'tiger %s update-password %s'", + err, serviceID, group, serviceID) } return err } +// resolveConnectionTarget resolves the ref a db command was given to what it +// connects to: a service, a read replica set, or (behind the experimental +// gate) a branch, by ID or name. +func resolveConnectionTarget(ctx context.Context, client api.ClientWithResponsesInterface, projectID string, ref serviceRef, experimental bool) (*common.ConnectionTarget, error) { + hint := "Run 'tiger service list' to find the ID you want" + if experimental { + hint = "Run 'tiger service list' or 'tiger branch list' to find the ID you want" + } + + target, err := common.ResolveConnectionTarget(ctx, client, projectID, ref.ref, experimental) + if err != nil { + return nil, withAmbiguousRefHint(err, hint) + } + if err := checkDefaultIsID(target.ID, ref); err != nil { + return nil, err + } + return target, nil +} + // isPostgresAuthenticationError checks if the error is a PostgreSQL authentication failure func isPostgresAuthenticationError(err error) bool { // Check for PostgreSQL error code 28P01 (invalid_password) or 28000 (invalid_authorization_specification) diff --git a/internal/cmd/db_create_role.go b/internal/cmd/db_create_role.go index 132a4836..9c5f13cd 100644 --- a/internal/cmd/db_create_role.go +++ b/internal/cmd/db_create_role.go @@ -29,7 +29,7 @@ func buildDbCreateRoleCmd(app *common.App) *cobra.Command { The service can be given by ID or name as an argument, or will use the default service from your configuration. A read replica ID is rejected, since replicas -are read-only; create the role on the primary instead. +are read-only; create the role on the primary instead.` + branchRefHelp(app.Experimental, "You can also pass a branch, by ID or name, to create the role on that branch.") + ` By default, a secure random password is auto-generated for the new role. You can: - Provide an explicit password with --password= @@ -100,15 +100,23 @@ PostgreSQL Configuration Parameters That May Be Set: } // Gated here rather than letting the server reject the CREATE ROLE. - service, err := resolveServiceForWrite(cmd.Context(), cfg, client, projectID, serviceRef) + // Refuse the blanket case before resolving, as resolveServiceForWrite + // does, then gate on the target's own tag. + if cfg.ReadOnly.BlocksAll() { + return common.ErrReadOnly + } + target, err := resolveConnectionTarget(cmd.Context(), client, projectID, serviceRef, app.Experimental) if err != nil { return err } + if err := common.CheckReadOnly(cfg, target.Environment); err != nil { + return err + } // A read replica is read-only, so a role can't be created there. - if common.IsReadReplica(*service) { + if target.Kind == common.TargetReadReplica { return fmt.Errorf("'%s' is a read replica; create the role on its primary service '%s' instead", - service.ServiceID, util.DerefStr(service.ForkedFrom.ServiceID)) + target.ID, target.Credentials.ID) } // Get password @@ -118,7 +126,7 @@ PostgreSQL Configuration Parameters That May Be Set: } // Build connection string - details, err := common.GetConnectionDetails(cfg, *service, common.ConnectionDetailsOptions{ + details, err := common.GetConnectionDetails(cfg, target, common.ConnectionDetailsOptions{ Pooled: false, Role: "tsdbadmin", // Use admin role to create new roles WithPassword: true, @@ -143,7 +151,7 @@ PostgreSQL Configuration Parameters That May Be Set: } // Save password to storage with the new role name - result, err := common.SavePasswordWithResult(cfg, *service, rolePassword, name) + result, err := common.SavePasswordWithResult(cfg, target.Credentials.PasswordTarget, rolePassword, name) if err != nil { cmd.PrintErrf("Warning: %s\n", result.Message) } else if !result.Success { diff --git a/internal/cmd/db_create_role_test.go b/internal/cmd/db_create_role_test.go index f0921cf7..6c5c034d 100644 --- a/internal/cmd/db_create_role_test.go +++ b/internal/cmd/db_create_role_test.go @@ -51,7 +51,7 @@ func TestDbCreateRoleCmd(t *testing.T) { name: "read-only prod refuses PROD service", args: []string{"db", "create", "role", "svc-12345", "--name", "ai_analyst"}, opts: []runOption{withConfig(map[string]any{"read_only": "prod"})}, - mock: expectTaggedService("PROD"), + mock: expectTaggedTarget("PROD"), wantErr: `this operation is not allowed on services tagged PROD while read_only is set to "prod"`, }, { @@ -62,7 +62,7 @@ func TestDbCreateRoleCmd(t *testing.T) { opts: []runOption{withConfig(map[string]any{"read_only": "prod"})}, mock: func(m *mocks.MockClientWithResponsesInterface) { tag := "DEV" - expectResolveRef(m, "svc-12345", sampleService(func(s *api.Service) { + expectResolveTarget(m, "svc-12345", sampleService(func(s *api.Service) { s.Metadata = &api.ServiceMetadata{Environment: &tag} s.Endpoint = nil })) @@ -76,7 +76,7 @@ func TestDbCreateRoleCmd(t *testing.T) { args: []string{"db", "create", "role", "--name", "ai_analyst"}, opts: []runOption{withConfig(map[string]any{"service_id": "svc-12345"})}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(func(s *api.Service) { + expectResolveTarget(m, "svc-12345", sampleService(func(s *api.Service) { s.Endpoint = nil })) }, @@ -86,24 +86,24 @@ func TestDbCreateRoleCmd(t *testing.T) { name: "network error", args: []string{"db", "create", "role", "svc-12345", "--name", "ai_analyst"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefError(m, "svc-12345", errors.New("connection refused")) + expectResolveTargetError(m, "svc-12345", errors.New("connection refused")) }, - wantErr: `failed to resolve service 'svc-12345': connection refused`, + wantErr: `failed to resolve service or read replica set 'svc-12345': connection refused`, }, { name: "service not found", args: []string{"db", "create", "role", "svc-12345", "--name", "ai_analyst"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefNotFound(m, "svc-12345") + expectResolveTargetNotFound(m, "svc-12345") }, - wantErr: "service 'svc-12345' not found", + wantErr: "service or read replica set 'svc-12345' not found", checks: []checkFunc{checkExitCode(common.ExitServiceNotFound)}, }, { name: "ambiguous name refused", args: []string{"db", "create", "role", "my-api-db", "--name", "ai_analyst"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "my-api-db", http.StatusBadRequest, &api.Error{Message: new("ambiguous service name matches multiple services")}) + expectResolveTargetStatus(m, "my-api-db", http.StatusBadRequest, &api.ClientError{Message: new("ambiguous service name matches multiple services")}) }, wantErr: "ambiguous service name matches multiple services\nRun 'tiger service list' to find the ID you want", checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, @@ -112,7 +112,7 @@ func TestDbCreateRoleCmd(t *testing.T) { name: "nil response body", args: []string{"db", "create", "role", "svc-12345", "--name", "ai_analyst"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "svc-12345", http.StatusOK, nil) + expectResolveTargetStatus(m, "svc-12345", http.StatusOK, nil) }, wantErr: "empty response from API", }, @@ -120,20 +120,41 @@ func TestDbCreateRoleCmd(t *testing.T) { name: "read replica rejected", args: []string{"db", "create", "role", "rep-67890", "--name", "ai_analyst"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "rep-67890", sampleReplica()) + expectResolveTarget(m, "rep-67890", sampleReplica()) + expectGetService(m, "svc-12345", sampleService()) }, wantErr: "'rep-67890' is a read replica; create the role on its primary service 'svc-12345' instead", }, + { + // With no --password or TIGER_NEW_PASSWORD, the password is + // generated before connecting. + name: "password generation fails", + args: []string{"db", "create", "role", "svc-12345", "--name", "ai_analyst"}, + opts: []runOption{withGenerateSecurePassword(32, "", errors.New("entropy unavailable"))}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveTarget(m, "svc-12345", sampleService()) + }, + wantErr: "failed to determine password: entropy unavailable", + }, { name: "endpoint not available", args: []string{"db", "create", "role", "svc-12345", "--name", "ai_analyst"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(func(s *api.Service) { + expectResolveTarget(m, "svc-12345", sampleService(func(s *api.Service) { s.Endpoint = nil })) }, wantErr: "failed to build connection string: service endpoint not available", }, + { + // Branches are a preview, so the help mentions them only behind + // the gate. The generated docs/cli reference covers the ungated + // help. + name: "help mentions branches when experimental", + args: []string{"db", "create", "role", "--help"}, + opts: []runOption{withExperimental()}, + wantStdout: matchHelp("You can also pass a branch, by ID or name, to create the role on that branch."), + }, }) } diff --git a/internal/cmd/db_ping.go b/internal/cmd/db_ping.go index 0556fb88..86cd14e0 100644 --- a/internal/cmd/db_ping.go +++ b/internal/cmd/db_ping.go @@ -29,7 +29,7 @@ The service can be given by ID or name as an argument, or will use the default service from your configuration. This command tests if the database is accepting connections and returns appropriate exit codes following pg_isready conventions. -You can also pass a read replica set ID to test connectivity to that replica. +You can also pass a read replica set ID to test connectivity to that replica.` + branchRefHelp(app.Experimental, "You can also pass a branch, by ID or name, to test connectivity to that branch.") + ` Return Codes: 0: Server is accepting connections normally @@ -64,12 +64,7 @@ Return Codes: return common.ExitWithCode(common.ExitInvalidParameters, err) } - service, err := resolveService(cmd.Context(), client, projectID, serviceRef) - if err != nil { - return common.ExitWithCode(common.ExitInvalidParameters, err) - } - - target, err := common.NewConnectionTarget(cmd.Context(), client, projectID, *service) + target, err := resolveConnectionTarget(cmd.Context(), client, projectID, serviceRef, app.Experimental) if err != nil { return common.ExitWithCode(common.ExitInvalidParameters, err) } diff --git a/internal/cmd/db_ping_test.go b/internal/cmd/db_ping_test.go index d3d08665..3edc7ed5 100644 --- a/internal/cmd/db_ping_test.go +++ b/internal/cmd/db_ping_test.go @@ -19,7 +19,7 @@ import ( func TestDbPingCmd(t *testing.T) { setupGet := func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService()) + expectResolveTarget(m, "svc-12345", sampleService()) } runCmdTests(t, []cmdTest{ @@ -51,9 +51,9 @@ func TestDbPingCmd(t *testing.T) { args: []string{"db", "ping"}, opts: []runOption{withConfig(map[string]any{"service_id": "svc-12345"})}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefError(m, "svc-12345", errors.New("connection refused")) + expectResolveTargetError(m, "svc-12345", errors.New("connection refused")) }, - wantErr: `failed to resolve service 'svc-12345': connection refused`, + wantErr: `failed to resolve service or read replica set 'svc-12345': connection refused`, checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, }, { @@ -65,25 +65,25 @@ func TestDbPingCmd(t *testing.T) { name: "network error", args: []string{"db", "ping", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefError(m, "svc-12345", errors.New("connection refused")) + expectResolveTargetError(m, "svc-12345", errors.New("connection refused")) }, - wantErr: `failed to resolve service 'svc-12345': connection refused`, + wantErr: `failed to resolve service or read replica set 'svc-12345': connection refused`, checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, }, { name: "service not found", args: []string{"db", "ping", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefNotFound(m, "svc-12345") + expectResolveTargetNotFound(m, "svc-12345") }, - wantErr: "service 'svc-12345' not found", + wantErr: "service or read replica set 'svc-12345' not found", checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, }, { name: "ambiguous name refused", args: []string{"db", "ping", "my-api-db"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "my-api-db", http.StatusBadRequest, &api.Error{Message: new("ambiguous service name matches multiple services")}) + expectResolveTargetStatus(m, "my-api-db", http.StatusBadRequest, &api.ClientError{Message: new("ambiguous service name matches multiple services")}) }, wantErr: "ambiguous service name matches multiple services\nRun 'tiger service list' to find the ID you want", checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, @@ -92,7 +92,7 @@ func TestDbPingCmd(t *testing.T) { name: "nil response body", args: []string{"db", "ping", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "svc-12345", http.StatusOK, nil) + expectResolveTargetStatus(m, "svc-12345", http.StatusOK, nil) }, wantErr: "empty response from API", checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, @@ -117,7 +117,7 @@ func TestDbPingCmd(t *testing.T) { name: "unreachable server", args: []string{"db", "ping", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(func(s *api.Service) { + expectResolveTarget(m, "svc-12345", sampleService(func(s *api.Service) { s.Endpoint = &api.Endpoint{Host: new("127.0.0.1"), Port: new(1)} })) }, @@ -136,7 +136,7 @@ func TestDbPingCmd(t *testing.T) { name: "connection timeout", args: []string{"db", "ping", "svc-12345", "--timeout", "250ms"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(func(s *api.Service) { + expectResolveTarget(m, "svc-12345", sampleService(func(s *api.Service) { s.Endpoint = &api.Endpoint{Host: new("192.0.2.1"), Port: new(5432)} })) }, @@ -148,6 +148,15 @@ func TestDbPingCmd(t *testing.T) { wantStderr: "Connection timeout after 250ms\n", checks: []checkFunc{checkExitCode(common.ExitTimeout)}, }, + { + // Branches are a preview, so the help mentions them only behind + // the gate. The generated docs/cli reference covers the ungated + // help. + name: "help mentions branches when experimental", + args: []string{"db", "ping", "--help"}, + opts: []runOption{withExperimental()}, + wantStdout: matchHelp("You can also pass a branch, by ID or name, to test connectivity to that branch."), + }, }) } diff --git a/internal/cmd/db_psql.go b/internal/cmd/db_psql.go index 34ca6967..559a9e6f 100644 --- a/internal/cmd/db_psql.go +++ b/internal/cmd/db_psql.go @@ -56,7 +56,7 @@ requested service. The prompt is automatically skipped when stdin is not a terminal (e.g. in scripts) or when the service has no read replicas. You can also pass a read replica set ID to connect straight to that replica, -skipping the prompt. Read replicas share the primary's credentials.`, +skipping the prompt. Read replicas share the primary's credentials.` + branchRefHelp(app.Experimental, "You can also pass a branch, by ID or name, to connect to that branch. A branch\nhas its own credentials and no read replicas."), Example: ` # Connect to default service tiger db psql @@ -98,12 +98,7 @@ skipping the prompt. Read replicas share the primary's credentials.`, return err } - service, err := resolveService(cmd.Context(), client, projectID, serviceRef) - if err != nil { - return err - } - - target, err := common.NewConnectionTarget(cmd.Context(), client, projectID, *service) + target, err := resolveConnectionTarget(cmd.Context(), client, projectID, serviceRef, app.Experimental) if err != nil { return err } @@ -130,9 +125,7 @@ skipping the prompt. Read replicas share the primary's credentials.`, return nil } - // Read replicas share the primary's credentials, so password storage - // and recovery always operate on the credential service. - return connectWithPasswordMenu(cmd.Context(), cmd, app, target.CredentialService, details, psqlPath, psqlFlags) + return connectWithPasswordMenu(cmd.Context(), cmd, app, target, details, psqlPath, psqlFlags) }, } @@ -186,14 +179,14 @@ func selectConnection( chosen := target // Offer the replica menu only for a primary on an interactive terminal. - if !target.IsReplica && !noReplicaPrompt && util.IsTerminal(cmd.InOrStdin()) && util.IsTerminal(cmd.ErrOrStderr()) { - primary := target.ConnectionService - replicas, err := fetchReplicaSets(ctx, client, projectID, primary.ServiceID) + // Branches have no read replicas. + if target.Kind == common.TargetService && !noReplicaPrompt && util.IsTerminal(cmd.InOrStdin()) && util.IsTerminal(cmd.ErrOrStderr()) { + replicas, err := fetchReplicaSets(ctx, client, projectID, target.ID) if err != nil { // Don't block the connection if we can't list replicas. cmd.PrintErrf("Warning: could not list read replicas: %v\n", err) } else if connectable := connectableReplicas(replicas); len(connectable) > 0 { - choice, err := selectConnectTargetOption(cmd, primary, connectable) + choice, err := selectConnectTargetOption(cmd, target.ID, connectable) if err != nil { return nil, err } @@ -201,14 +194,14 @@ func selectConnection( case targetCancel: return nil, nil case targetReplica: - chosen = common.NewReplicaConnectionTarget(primary, *choice.replica) + chosen = common.NewReplicaConnectionTarget(*choice.replica, target.Credentials) } } } // The target is settled now, so read-only mode can have its say. --read-only // is already in opts and only adds to the restriction. - opts.ReadOnly = opts.ReadOnly || common.CheckReadOnly(cfg, common.ServiceEnvironmentTag(chosen.ConnectionService)) != nil + opts.ReadOnly = opts.ReadOnly || common.CheckReadOnly(cfg, chosen.Environment) != nil warnReplicaPooler(cmd, chosen, opts.Pooled) @@ -217,8 +210,8 @@ func selectConnection( return nil, err } - if chosen.IsReplica { - cmd.PrintErrf("Connecting to read replica %s...\n", serviceLabel(chosen.ConnectionService)) + if chosen.Kind == common.TargetReadReplica { + cmd.PrintErrf("Connecting to read replica '%s' (%s)...\n", chosen.Name, chosen.ID) } return details, nil } @@ -275,10 +268,10 @@ type connectTargetModel struct { chosen connectTargetChoice } -func newConnectTargetModel(primary api.Service, replicas []api.ReadReplicaSet) connectTargetModel { +func newConnectTargetModel(primaryID string, replicas []api.ReadReplicaSet) connectTargetModel { choices := []connectTargetChoice{{ kind: targetPrimary, - label: fmt.Sprintf("Connect to primary service (%s)", primary.ServiceID), + label: fmt.Sprintf("Connect to primary service (%s)", primaryID), }} for i := range replicas { @@ -352,8 +345,8 @@ func (m connectTargetModel) View() tea.View { // selectConnectTargetOption shows the interactive menu for choosing a // connection target. -func selectConnectTargetOption(cmd *cobra.Command, primary api.Service, replicas []api.ReadReplicaSet) (connectTargetChoice, error) { - model := newConnectTargetModel(primary, replicas) +func selectConnectTargetOption(cmd *cobra.Command, primaryID string, replicas []api.ReadReplicaSet) (connectTargetChoice, error) { + model := newConnectTargetModel(primaryID, replicas) program := tea.NewProgram(model, tea.WithInput(cmd.InOrStdin()), @@ -374,7 +367,7 @@ func connectWithPasswordMenu( ctx context.Context, cmd *cobra.Command, app *common.App, - service api.Service, + target *common.ConnectionTarget, details *common.ConnectionDetails, psqlPath string, psqlFlags []string, @@ -384,9 +377,13 @@ func connectWithPasswordMenu( return err } + // Read replicas share the primary's credentials, so password storage and + // recovery always operate on the credential owner. + credentials := target.Credentials + // Interactive mode: Get stored password (if any) storage := common.GetPasswordStorage(cfg) - storedPassword, err := storage.Get(service, details.Role) + storedPassword, err := storage.Get(credentials.PasswordTarget, details.Role) if err != nil { cmd.PrintErrf("Warning: could not retrieve stored password: %v\n", err) } @@ -395,7 +392,7 @@ func connectWithPasswordMenu( err = testConnectionWithPassword(ctx, details, storedPassword) if err == nil { // Password works, launch psql - return launchPsql(cfg, details, psqlPath, psqlFlags, service, cmd) + return launchPsql(cfg, details, psqlPath, psqlFlags, credentials.PasswordTarget, cmd) } // Check if it's an auth error @@ -415,7 +412,7 @@ func connectWithPasswordMenu( // control-plane write, so read-only mode hides the option rather than // offering what it would refuse. canResetPassword := details.Role == "tsdbadmin" && - common.CheckReadOnly(cfg, common.ServiceEnvironmentTag(service)) == nil + common.CheckReadOnly(cfg, credentials.Environment) == nil // Interactive recovery loop for { @@ -440,7 +437,7 @@ func connectWithPasswordMenu( // Test, save, and launch details.Password = password - if err = testSaveAndLaunchPsqlWithPassword(ctx, cmd, cfg, details, psqlPath, psqlFlags, service); err != nil { + if err = testSaveAndLaunchPsqlWithPassword(ctx, cmd, cfg, details, psqlPath, psqlFlags, credentials.PasswordTarget); err != nil { if isPostgresAuthenticationError(err) { cmd.PrintErrf("Password incorrect.\n\n") continue @@ -451,7 +448,7 @@ func connectWithPasswordMenu( case optionResetPassword: // Prompt and reset - password, err := promptAndResetPassword(ctx, cmd, cfg, client, service, details.Role) + password, err := promptAndResetPassword(ctx, cmd, cfg, client, credentials, details.Role) if err != nil { if errors.Is(err, context.Canceled) { return nil // user cancelled @@ -462,7 +459,7 @@ func connectWithPasswordMenu( cmd.PrintErrf("Password updated for %s\n", details.Role) // Launch psql (password is now in storage) details.Password = password - return launchPsql(cfg, details, psqlPath, psqlFlags, service, cmd) + return launchPsql(cfg, details, psqlPath, psqlFlags, credentials.PasswordTarget, cmd) case optionExit: return nil @@ -607,7 +604,7 @@ func testSaveAndLaunchPsqlWithPassword( details *common.ConnectionDetails, psqlPath string, psqlFlags []string, - service api.Service, + credentials common.PasswordTarget, ) error { // Test the password if err := testConnectionWithPassword(ctx, details, details.Password); err != nil { @@ -615,7 +612,7 @@ func testSaveAndLaunchPsqlWithPassword( } // Password works! Save it - result, saveErr := common.SavePasswordWithResult(cfg, service, details.Password, details.Role) + result, saveErr := common.SavePasswordWithResult(cfg, credentials, details.Password, details.Role) if saveErr != nil { cmd.PrintErrf("Warning: could not save password: %v\n", saveErr) } else if result.Success { @@ -623,13 +620,13 @@ func testSaveAndLaunchPsqlWithPassword( } // Launch psql - return launchPsql(cfg, details, psqlPath, psqlFlags, service, cmd) + return launchPsql(cfg, details, psqlPath, psqlFlags, credentials, cmd) } // launchPsql launches psql using the connection string and additional flags. // It retrieves the password from storage and sets PGPASSWORD environment variable. -func launchPsql(cfg *config.Config, details *common.ConnectionDetails, psqlPath string, additionalFlags []string, service api.Service, cmd *cobra.Command) error { - psqlCmd := buildPsqlCommand(cfg, details, psqlPath, additionalFlags, service, cmd) +func launchPsql(cfg *config.Config, details *common.ConnectionDetails, psqlPath string, additionalFlags []string, credentials common.PasswordTarget, cmd *cobra.Command) error { + psqlCmd := buildPsqlCommand(cfg, details, psqlPath, additionalFlags, credentials, cmd) err := psqlCmd.Run() // Propagate psql's own exit code; a signal death (ExitCode() == -1) // falls through to the generic exit code 1. @@ -641,7 +638,7 @@ func launchPsql(cfg *config.Config, details *common.ConnectionDetails, psqlPath } // buildPsqlCommand creates the psql command with proper environment setup -func buildPsqlCommand(cfg *config.Config, details *common.ConnectionDetails, psqlPath string, additionalFlags []string, service api.Service, cmd *cobra.Command) *exec.Cmd { +func buildPsqlCommand(cfg *config.Config, details *common.ConnectionDetails, psqlPath string, additionalFlags []string, credentials common.PasswordTarget, cmd *cobra.Command) *exec.Cmd { password := details.Password // Ensure we don't include password in the connection string to make it not show up in process lists // Passwords are passed via PGPASSWORD environment variable (see below) @@ -667,7 +664,7 @@ func buildPsqlCommand(cfg *config.Config, details *common.ConnectionDetails, psq // Only set PGPASSWORD for keyring storage method // pgpass storage relies on psql automatically reading ~/.pgpass file if _, isKeyring := storage.(*common.KeyringStorage); isKeyring { - if storedPassword, err := storage.Get(service, details.Role); err == nil && storedPassword != "" { + if storedPassword, err := storage.Get(credentials, details.Role); err == nil && storedPassword != "" { // Set PGPASSWORD environment variable for psql when using keyring psqlCmd.Env = append(os.Environ(), "PGPASSWORD="+storedPassword) } diff --git a/internal/cmd/db_psql_test.go b/internal/cmd/db_psql_test.go index a0747d7c..8fdfaf17 100644 --- a/internal/cmd/db_psql_test.go +++ b/internal/cmd/db_psql_test.go @@ -59,32 +59,32 @@ func TestDbPsqlCmd(t *testing.T) { args: []string{"db", "psql", "--", "-c", "SELECT 1;"}, opts: []runOption{withConfig(map[string]any{"service_id": "svc-12345"})}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefError(m, "svc-12345", errors.New("connection refused")) + expectResolveTargetError(m, "svc-12345", errors.New("connection refused")) }, - wantErr: `failed to resolve service 'svc-12345': connection refused`, + wantErr: `failed to resolve service or read replica set 'svc-12345': connection refused`, }, { name: "service ID before -- separator", args: []string{"db", "psql", "svc-12345", "--", "-c", "SELECT 1;"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefError(m, "svc-12345", errors.New("connection refused")) + expectResolveTargetError(m, "svc-12345", errors.New("connection refused")) }, - wantErr: `failed to resolve service 'svc-12345': connection refused`, + wantErr: `failed to resolve service or read replica set 'svc-12345': connection refused`, }, { name: "service not found", args: []string{"db", "psql", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefNotFound(m, "svc-12345") + expectResolveTargetNotFound(m, "svc-12345") }, - wantErr: "service 'svc-12345' not found", + wantErr: "service or read replica set 'svc-12345' not found", checks: []checkFunc{checkExitCode(common.ExitServiceNotFound)}, }, { name: "ambiguous name refused", args: []string{"db", "psql", "my-api-db"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "my-api-db", http.StatusBadRequest, &api.Error{Message: new("ambiguous service name matches multiple services")}) + expectResolveTargetStatus(m, "my-api-db", http.StatusBadRequest, &api.ClientError{Message: new("ambiguous service name matches multiple services")}) }, wantErr: "ambiguous service name matches multiple services\nRun 'tiger service list' to find the ID you want", checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, @@ -93,7 +93,7 @@ func TestDbPsqlCmd(t *testing.T) { name: "nil response body", args: []string{"db", "psql", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "svc-12345", http.StatusOK, nil) + expectResolveTargetStatus(m, "svc-12345", http.StatusOK, nil) }, wantErr: "empty response from API", }, @@ -101,7 +101,7 @@ func TestDbPsqlCmd(t *testing.T) { name: "parent fetch fails for read replica", args: []string{"db", "psql", "rep-67890"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "rep-67890", sampleReplica()) + expectResolveTarget(m, "rep-67890", sampleReplica()) m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "svc-12345"). Return(nil, errors.New("connection refused")) }, @@ -112,7 +112,7 @@ func TestDbPsqlCmd(t *testing.T) { args: []string{"db", "psql", "svc-12345"}, opts: []runOption{withEnv("PATH", "/nonexistent")}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService()) + expectResolveTarget(m, "svc-12345", sampleService()) }, wantErr: "psql not found. Install the PostgreSQL client tools", }, @@ -123,7 +123,7 @@ func TestDbPsqlCmd(t *testing.T) { args: []string{"db", "psql", "svc-12345"}, opts: []runOption{withEnv("PATH", psqlDir)}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(noEndpoint)) + expectResolveTarget(m, "svc-12345", sampleService(noEndpoint)) }, wantErr: "failed to build connection string: service endpoint not available", }, @@ -132,7 +132,7 @@ func TestDbPsqlCmd(t *testing.T) { args: []string{"db", "psql", "svc-12345", "--no-replica-prompt"}, opts: []runOption{withIsTerminal(true), withEnv("PATH", psqlDir)}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(noEndpoint)) + expectResolveTarget(m, "svc-12345", sampleService(noEndpoint)) }, wantErr: "failed to build connection string: service endpoint not available", }, @@ -141,7 +141,7 @@ func TestDbPsqlCmd(t *testing.T) { args: []string{"db", "psql", "rep-67890"}, opts: []runOption{withIsTerminal(true), withEnv("PATH", psqlDir)}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "rep-67890", sampleReplica(noEndpoint)) + expectResolveTarget(m, "rep-67890", sampleReplica(func(r *api.ReadReplicaSet) { r.Endpoint = nil })) expectGetService(m, "svc-12345", sampleService()) }, wantErr: "failed to build connection string: service endpoint not available", @@ -151,7 +151,7 @@ func TestDbPsqlCmd(t *testing.T) { args: []string{"db", "psql", "svc-12345"}, opts: []runOption{withIsTerminal(true), withEnv("PATH", psqlDir)}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(noEndpoint)) + expectResolveTarget(m, "svc-12345", sampleService(noEndpoint)) m.EXPECT().GetReplicaSetsWithResponse(validCtx, testProjectID, "svc-12345"). Return(nil, errors.New("connection refused")) }, @@ -163,7 +163,7 @@ func TestDbPsqlCmd(t *testing.T) { args: []string{"db", "psql", "svc-12345"}, opts: []runOption{withIsTerminal(true), withEnv("PATH", psqlDir)}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(noEndpoint)) + expectResolveTarget(m, "svc-12345", sampleService(noEndpoint)) m.EXPECT().GetReplicaSetsWithResponse(validCtx, testProjectID, "svc-12345"). Return(&api.GetReplicaSetsResponse{ HTTPResponse: httpResponse(http.StatusOK), @@ -177,7 +177,7 @@ func TestDbPsqlCmd(t *testing.T) { args: []string{"db", "psql", "svc-12345"}, opts: []runOption{withIsTerminal(true), withEnv("PATH", psqlDir)}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(noEndpoint)) + expectResolveTarget(m, "svc-12345", sampleService(noEndpoint)) replicas := []api.ReadReplicaSet{ { ID: "rep-1", @@ -203,7 +203,7 @@ func TestDbPsqlCmd(t *testing.T) { args: []string{"db", "psql", "svc-12345", "--pooled"}, opts: []runOption{withEnv("PATH", psqlDir)}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService()) + expectResolveTarget(m, "svc-12345", sampleService()) }, wantErr: "connection pooler not available for this service", }, @@ -216,7 +216,7 @@ func TestDbPsqlCmd(t *testing.T) { name: "non-auth connection error surfaces directly", args: []string{"db", "psql", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(func(s *api.Service) { + expectResolveTarget(m, "svc-12345", sampleService(func(s *api.Service) { s.Endpoint = &api.Endpoint{Host: new("127.0.0.1"), Port: new(1)} })) }, @@ -228,6 +228,15 @@ func TestDbPsqlCmd(t *testing.T) { }), wantStderr: matchPrefix("Warning: could not retrieve stored password: secret not found in keyring\n"), }, + { + // Branches are a preview, so the help mentions them only behind + // the gate. The generated docs/cli reference covers the ungated + // help. + name: "help mentions branches when experimental", + args: []string{"db", "psql", "--help"}, + opts: []runOption{withExperimental()}, + wantStdout: matchHelp("You can also pass a branch, by ID or name, to connect to that branch."), + }, }) } @@ -279,13 +288,13 @@ func TestConnectTargetModel(t *testing.T) { t.Run("choices", func(t *testing.T) { // No replicas: primary, cancel. - m := newConnectTargetModel(primary, nil) + m := newConnectTargetModel(primary.ServiceID, nil) if len(m.choices) != 2 || m.choices[0].kind != targetPrimary || m.choices[1].kind != targetCancel { t.Errorf("expected [primary, cancel] with no replicas, got %+v", m.choices) } // Two replicas: primary, replica-a, replica-b, cancel. - m = newConnectTargetModel(primary, replicas) + m = newConnectTargetModel(primary.ServiceID, replicas) if len(m.choices) != 4 { t.Fatalf("expected 4 choices with two replicas, got %d: %+v", len(m.choices), m.choices) } @@ -322,7 +331,7 @@ func TestConnectTargetModel(t *testing.T) { for _, tt := range keyTests { t.Run(tt.name, func(t *testing.T) { - m := newConnectTargetModel(primary, replicas) + m := newConnectTargetModel(primary.ServiceID, replicas) updated, _ := m.Update(tt.key) choice := updated.(connectTargetModel).chosen if choice.kind != tt.wantKind { @@ -354,7 +363,7 @@ func TestBuildPsqlCommand(t *testing.T) { d := details() d.Password = "explicit-pw" flags := []string{"--single-transaction", "-c", "SELECT 1;"} - psqlCmd := buildPsqlCommand(&config.Config{PasswordStorage: "keyring"}, d, psqlPath, flags, api.Service{}, discardCmd()) + psqlCmd := buildPsqlCommand(&config.Config{PasswordStorage: "keyring"}, d, psqlPath, flags, common.PasswordTarget{}, discardCmd()) wantArgs := []string{psqlPath, connString, "--single-transaction", "-c", "SELECT 1;"} if !slices.Equal(psqlCmd.Args, wantArgs) { @@ -368,11 +377,11 @@ func TestBuildPsqlCommand(t *testing.T) { t.Run("keyring password becomes PGPASSWORD", func(t *testing.T) { keyring.MockInit() service := api.Service{ServiceID: "svc-psql", ProjectID: "proj-psql"} - if err := (&common.KeyringStorage{}).Save(service, "keyring-pw", "testuser"); err != nil { + if err := (&common.KeyringStorage{}).Save(common.ServicePasswordTarget(service), "keyring-pw", "testuser"); err != nil { t.Fatalf("failed to save test password: %v", err) } - psqlCmd := buildPsqlCommand(&config.Config{PasswordStorage: "keyring"}, details(), psqlPath, nil, service, discardCmd()) + psqlCmd := buildPsqlCommand(&config.Config{PasswordStorage: "keyring"}, details(), psqlPath, nil, common.ServicePasswordTarget(service), discardCmd()) if !slices.Contains(psqlCmd.Env, "PGPASSWORD=keyring-pw") { t.Errorf("expected PGPASSWORD=keyring-pw in env, got %v", psqlCmd.Env) } @@ -380,7 +389,7 @@ func TestBuildPsqlCommand(t *testing.T) { t.Run("pgpass storage sets no PGPASSWORD", func(t *testing.T) { // psql reads ~/.pgpass itself, so the env var must stay unset. - psqlCmd := buildPsqlCommand(&config.Config{PasswordStorage: "pgpass"}, details(), psqlPath, nil, api.Service{}, discardCmd()) + psqlCmd := buildPsqlCommand(&config.Config{PasswordStorage: "pgpass"}, details(), psqlPath, nil, common.PasswordTarget{}, discardCmd()) for _, env := range psqlCmd.Env { if strings.HasPrefix(env, "PGPASSWORD=") { t.Errorf("expected no PGPASSWORD for pgpass storage, got %s", env) @@ -403,7 +412,7 @@ func TestLaunchPsql(t *testing.T) { Database: "testdb", Role: "testuser", } - err := launchPsql(&config.Config{PasswordStorage: "none"}, details, "/nonexistent/psql", []string{"--quiet"}, api.Service{}, cmd) + err := launchPsql(&config.Config{PasswordStorage: "none"}, details, "/nonexistent/psql", []string{"--quiet"}, common.PasswordTarget{}, cmd) if err == nil { t.Error("expected error for nonexistent psql path") } diff --git a/internal/cmd/db_query.go b/internal/cmd/db_query.go index ff3c9487..8319904d 100644 --- a/internal/cmd/db_query.go +++ b/internal/cmd/db_query.go @@ -34,7 +34,7 @@ local psql installation. The service can be given by ID or name as an argument, or will use the default service from your configuration. You can also pass a read replica set ID to -query that replica. +query that replica.` + branchRefHelp(app.Experimental, "You can also pass a branch, by ID or name, to query that branch.") + ` The query comes from --command, from the SQL file named by --file, or, if neither is given, from stdin. @@ -95,12 +95,7 @@ read-only while leaving DEV services writable.`, return err } - service, err := resolveService(cmd.Context(), client, projectID, serviceRef) - if err != nil { - return err - } - - target, err := common.NewConnectionTarget(cmd.Context(), client, projectID, *service) + target, err := resolveConnectionTarget(cmd.Context(), client, projectID, serviceRef, app.Experimental) if err != nil { return err } @@ -125,7 +120,7 @@ read-only while leaving DEV services writable.`, Pooled: pooled, // --read-only only adds to whatever read-only mode already // imposes, which under prod mode depends on the target's tag. - ReadOnly: readOnly || common.CheckReadOnly(cfg, common.ServiceEnvironmentTag(target.ConnectionService)) != nil, + ReadOnly: readOnly || common.CheckReadOnly(cfg, target.Environment) != nil, }) if err != nil { return handleDatabaseError(err, target) diff --git a/internal/cmd/db_query_test.go b/internal/cmd/db_query_test.go index ea8ad05e..636368ec 100644 --- a/internal/cmd/db_query_test.go +++ b/internal/cmd/db_query_test.go @@ -73,7 +73,7 @@ func selectResult() *common.QueryResult { func TestDbQueryCmd(t *testing.T) { setupGetService := func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService()) + expectResolveTarget(m, "svc-12345", sampleService()) } sqlDir := t.TempDir() @@ -165,7 +165,7 @@ func TestDbQueryCmd(t *testing.T) { { name: "read_only prod, PROD service", args: []string{"db", "query", "svc-12345", "-c", "SELECT 1"}, - mock: expectTaggedService("PROD"), + mock: expectTaggedTarget("PROD"), opts: []runOption{ withConfig(map[string]any{"read_only": "prod"}), withExecuteQuery(common.ExecuteQueryArgs{ @@ -179,7 +179,7 @@ func TestDbQueryCmd(t *testing.T) { // prod mode leaves DEV services writable. name: "read_only prod, DEV service", args: []string{"db", "query", "svc-12345", "-c", "SELECT 1"}, - mock: expectTaggedService("DEV"), + mock: expectTaggedTarget("DEV"), opts: []runOption{ withConfig(map[string]any{"read_only": "prod"}), withExecuteQuery(common.ExecuteQueryArgs{ @@ -192,24 +192,24 @@ func TestDbQueryCmd(t *testing.T) { name: "network error", args: []string{"db", "query", "svc-12345", "-c", "SELECT 1"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefError(m, "svc-12345", errors.New("connection refused")) + expectResolveTargetError(m, "svc-12345", errors.New("connection refused")) }, - wantErr: `failed to resolve service 'svc-12345': connection refused`, + wantErr: `failed to resolve service or read replica set 'svc-12345': connection refused`, }, { name: "service not found", args: []string{"db", "query", "svc-12345", "-c", "SELECT 1"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefNotFound(m, "svc-12345") + expectResolveTargetNotFound(m, "svc-12345") }, - wantErr: "service 'svc-12345' not found", + wantErr: "service or read replica set 'svc-12345' not found", checks: []checkFunc{checkExitCode(common.ExitServiceNotFound)}, }, { name: "ambiguous name refused", args: []string{"db", "query", "my-api-db", "-c", "SELECT 1"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "my-api-db", http.StatusBadRequest, &api.Error{Message: new("ambiguous service name matches multiple services")}) + expectResolveTargetStatus(m, "my-api-db", http.StatusBadRequest, &api.ClientError{Message: new("ambiguous service name matches multiple services")}) }, wantErr: "ambiguous service name matches multiple services\nRun 'tiger service list' to find the ID you want", checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, @@ -224,6 +224,27 @@ func TestDbQueryCmd(t *testing.T) { }, nil, common.ErrPaused)}, wantErr: pausedMsg("svc-12345"), }, + { + // A branch is started through `tiger branch`, not `tiger service`. + name: "branch paused", + args: []string{"db", "query", "br-12345", "-c", "SELECT 1"}, + opts: []runOption{withExperimental(), withExecuteQuery(common.ExecuteQueryArgs{ + Query: "SELECT 1", + Role: "tsdbadmin", + }, nil, common.ErrPaused)}, + mock: func(m *mocks.MockClientWithResponsesInterface) { expectResolveTarget(m, "br-12345", sampleBranch()) }, + wantErr: "branch is paused — start it with 'tiger branch start br-12345'", + }, + { + name: "branch not ready", + args: []string{"db", "query", "br-12345", "-c", "SELECT 1"}, + opts: []runOption{withExperimental(), withExecuteQuery(common.ExecuteQueryArgs{ + Query: "SELECT 1", + Role: "tsdbadmin", + }, nil, common.ErrNotReady)}, + mock: func(m *mocks.MockClientWithResponsesInterface) { expectResolveTarget(m, "br-12345", sampleBranch()) }, + wantErr: "branch is not ready — check its status with 'tiger branch get br-12345' and try again", + }, { // A bare pgx auth failure says nothing about which password is // wrong or how to fix it, so the command appends the recovery @@ -508,5 +529,14 @@ result_sets: }, &common.QueryResult{}, nil), }, }, + { + // Branches are a preview, so the help mentions them only behind + // the gate. The generated docs/cli reference covers the ungated + // help. + name: "help mentions branches when experimental", + args: []string{"db", "query", "--help"}, + opts: []runOption{withExperimental()}, + wantStdout: matchHelp("You can also pass a branch, by ID or name, to query that branch."), + }, }) } diff --git a/internal/cmd/db_save_password.go b/internal/cmd/db_save_password.go index 5c3e09e7..252f3648 100644 --- a/internal/cmd/db_save_password.go +++ b/internal/cmd/db_save_password.go @@ -26,7 +26,7 @@ service from your configuration. The password can be provided via: 3. Interactive prompt (if neither provided) The password will be saved according to your --password-storage setting -(keyring, pgpass, or none).`, +(keyring, pgpass, or none).` + branchRefHelp(app.Experimental, "You can also pass a branch, by ID or name, to save the password for that branch."), Example: ` # Save password with explicit value (highest precedence) tiger db save-password svc-12345 --password=your-password @@ -56,19 +56,17 @@ The password will be saved according to your --password-storage setting return err } - resolved, err := resolveService(cmd.Context(), client, projectID, serviceRef) - if err != nil { - return err - } - - // Resolve the target so a read replica id stores the password against + // Resolve the target so a read replica stores the password against // its parent primary: replicas share the primary's credentials, and // psql/ping look the password up against the primary. - target, err := common.NewConnectionTarget(cmd.Context(), client, projectID, *resolved) + target, err := resolveConnectionTarget(cmd.Context(), client, projectID, serviceRef, app.Experimental) if err != nil { return err } - service := target.CredentialService + // Read replicas share the primary's credentials, so this is the + // primary's for one. + credentials := target.Credentials + label := fmt.Sprintf("'%s' (%s)", credentials.Name, credentials.ID) // Determine password based on precedence: // 1. --password flag with value @@ -104,16 +102,18 @@ The password will be saved according to your --password-storage setting // Save password using configured storage storage := common.GetPasswordStorage(cfg) - if err := storage.Save(service, passwordToSave, role); err != nil { + if err := storage.Save(credentials.PasswordTarget, passwordToSave, role); err != nil { return fmt.Errorf("failed to save password: %w", err) } - if target.IsReplica { - cmd.PrintErrf("Read replicas share the primary's credentials; saving against primary %s.\n", - serviceLabel(service)) + if target.Kind == common.TargetReadReplica { + cmd.PrintErrf("Read replicas share the primary's credentials; saving against primary %s.\n", label) + } + kind := "service" + if target.Kind == common.TargetBranch { + kind = "branch" } - cmd.PrintErrf("Password saved for service %s, role %s\n", - serviceLabel(service), role) + cmd.PrintErrf("Password saved for %s %s, role %s\n", kind, label, role) return nil }, } diff --git a/internal/cmd/db_save_password_test.go b/internal/cmd/db_save_password_test.go index 1cf5ab43..661ede93 100644 --- a/internal/cmd/db_save_password_test.go +++ b/internal/cmd/db_save_password_test.go @@ -14,14 +14,14 @@ import ( func TestDbSavePasswordCmd(t *testing.T) { setupGetService := func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService()) + expectResolveTarget(m, "svc-12345", sampleService()) } // checkKeyringPassword asserts the mock keyring holds want for role. checkKeyringPassword := func(role, want string) checkFunc { return func(t *testing.T, result cmdResult) { t.Helper() - got, err := (&common.KeyringStorage{}).Get(sampleService(), role) + got, err := (&common.KeyringStorage{}).Get(common.ServicePasswordTarget(sampleService()), role) if err != nil { t.Fatalf("failed to read saved password: %v", err) } @@ -53,24 +53,24 @@ func TestDbSavePasswordCmd(t *testing.T) { name: "network error fetching service", args: []string{"db", "save-password", "svc-12345", "--password=pw"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefError(m, "svc-12345", errors.New("connection refused")) + expectResolveTargetError(m, "svc-12345", errors.New("connection refused")) }, - wantErr: `failed to resolve service 'svc-12345': connection refused`, + wantErr: `failed to resolve service or read replica set 'svc-12345': connection refused`, }, { name: "service not found", args: []string{"db", "save-password", "svc-12345", "--password=pw"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefNotFound(m, "svc-12345") + expectResolveTargetNotFound(m, "svc-12345") }, - wantErr: "service 'svc-12345' not found", + wantErr: "service or read replica set 'svc-12345' not found", checks: []checkFunc{checkExitCode(common.ExitServiceNotFound)}, }, { name: "ambiguous name refused", args: []string{"db", "save-password", "my-api-db", "--password=pw"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "my-api-db", http.StatusBadRequest, &api.Error{Message: new("ambiguous service name matches multiple services")}) + expectResolveTargetStatus(m, "my-api-db", http.StatusBadRequest, &api.ClientError{Message: new("ambiguous service name matches multiple services")}) }, wantErr: "ambiguous service name matches multiple services\nRun 'tiger service list' to find the ID you want", checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, @@ -79,7 +79,7 @@ func TestDbSavePasswordCmd(t *testing.T) { name: "nil response body", args: []string{"db", "save-password", "svc-12345", "--password=pw"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "svc-12345", http.StatusOK, nil) + expectResolveTargetStatus(m, "svc-12345", http.StatusOK, nil) }, wantErr: "empty response from API", }, @@ -148,7 +148,7 @@ func TestDbSavePasswordCmd(t *testing.T) { mock: setupGetService, wantStderr: "Password saved for service 'test-service' (svc-12345), role readonly\n", checks: []checkFunc{checkKeyringPassword("readonly", "readonly-pw"), func(t *testing.T, result cmdResult) { - if pw, err := (&common.KeyringStorage{}).Get(sampleService(), "tsdbadmin"); err == nil { + if pw, err := (&common.KeyringStorage{}).Get(common.ServicePasswordTarget(sampleService()), "tsdbadmin"); err == nil { t.Errorf("expected no password stored for tsdbadmin, got %q", pw) } }}, @@ -194,7 +194,7 @@ func TestDbSavePasswordCmd(t *testing.T) { mock: setupGetService, wantStderr: "Password saved for service 'test-service' (svc-12345), role tsdbadmin\n", checks: []checkFunc{func(t *testing.T, result cmdResult) { - if pw, err := (&common.KeyringStorage{}).Get(sampleService(), "tsdbadmin"); err == nil { + if pw, err := (&common.KeyringStorage{}).Get(common.ServicePasswordTarget(sampleService()), "tsdbadmin"); err == nil { t.Errorf("expected no stored password, got %q", pw) } }}, @@ -203,17 +203,37 @@ func TestDbSavePasswordCmd(t *testing.T) { name: "replica ID saves against parent primary", args: []string{"db", "save-password", "rep-67890", "--password=replica-pw"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "rep-67890", sampleReplica()) + expectResolveTarget(m, "rep-67890", sampleReplica()) expectGetService(m, "svc-12345", sampleService()) }, wantStderr: "Read replicas share the primary's credentials; saving against primary 'test-service' (svc-12345).\nPassword saved for service 'test-service' (svc-12345), role tsdbadmin\n", checks: []checkFunc{func(t *testing.T, result cmdResult) { // Stored against the parent primary, matching the connect read path. checkKeyringPassword("tsdbadmin", "replica-pw")(t, result) - if pw, err := (&common.KeyringStorage{}).Get(sampleReplica(), "tsdbadmin"); err == nil { + if pw, err := (&common.KeyringStorage{}).Get(common.PasswordTarget{ID: "rep-67890", ProjectID: testProjectID}, "tsdbadmin"); err == nil { t.Errorf("expected no password stored under the replica ID, got %q", pw) } }}, }, + { + // A branch has its own password, saved under its own ID. + name: "branch saves against itself", + args: []string{"db", "save-password", "test-branch", "--password=branch-pw"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveTarget(m, "test-branch", sampleBranch()) + }, + wantStderr: "Password saved for branch 'test-branch' (br-12345), role tsdbadmin\n", + checks: []checkFunc{checkStoredPassword("br-12345", "branch-pw")}, + }, + { + // Branches are a preview, so the help mentions them only behind + // the gate. The generated docs/cli reference covers the ungated + // help. + name: "help mentions branches when experimental", + args: []string{"db", "save-password", "--help"}, + opts: []runOption{withExperimental()}, + wantStdout: matchHelp("You can also pass a branch, by ID or name, to save the password for that branch."), + }, }) } diff --git a/internal/cmd/db_schema.go b/internal/cmd/db_schema.go index 8ff27407..14552725 100644 --- a/internal/cmd/db_schema.go +++ b/internal/cmd/db_schema.go @@ -25,7 +25,7 @@ metadata. The service can be given by ID or name as an argument, or will use the default service from your configuration. You can also pass a read replica set ID to introspect that replica. Only objects the connecting role can access are -returned. The connection is opened in Tiger Cloud's immutable read-only mode. +returned. The connection is opened in Tiger Cloud's immutable read-only mode.` + branchRefHelp(app.Experimental, "You can also pass a branch, by ID or name, to introspect that branch.") + ` By default only user-facing schemas and objects are shown. View and routine definitions and object comments are omitted unless requested, since they can be @@ -58,12 +58,7 @@ large and may embed implementation details.`, return err } - service, err := resolveService(cmd.Context(), client, projectID, serviceRef) - if err != nil { - return err - } - - target, err := common.NewConnectionTarget(cmd.Context(), client, projectID, *service) + target, err := resolveConnectionTarget(cmd.Context(), client, projectID, serviceRef, app.Experimental) if err != nil { return err } diff --git a/internal/cmd/db_schema_test.go b/internal/cmd/db_schema_test.go index 4b12f1a2..7f2589a6 100644 --- a/internal/cmd/db_schema_test.go +++ b/internal/cmd/db_schema_test.go @@ -36,7 +36,7 @@ func withFetchServiceSchema(want common.FetchServiceSchemaArgs, schema *common.D func TestDbSchemaCmd(t *testing.T) { setupGetWithStatus := func(status api.DeployStatus) func(m *mocks.MockClientWithResponsesInterface) { return func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(func(s *api.Service) { + expectResolveTarget(m, "svc-12345", sampleService(func(s *api.Service) { s.Status = status })) } @@ -82,24 +82,24 @@ func TestDbSchemaCmd(t *testing.T) { name: "network error", args: []string{"db", "schema", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefError(m, "svc-12345", errors.New("connection refused")) + expectResolveTargetError(m, "svc-12345", errors.New("connection refused")) }, - wantErr: `failed to resolve service 'svc-12345': connection refused`, + wantErr: `failed to resolve service or read replica set 'svc-12345': connection refused`, }, { name: "service not found", args: []string{"db", "schema", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefNotFound(m, "svc-12345") + expectResolveTargetNotFound(m, "svc-12345") }, - wantErr: "service 'svc-12345' not found", + wantErr: "service or read replica set 'svc-12345' not found", checks: []checkFunc{checkExitCode(common.ExitServiceNotFound)}, }, { name: "ambiguous name refused", args: []string{"db", "schema", "my-api-db"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "my-api-db", http.StatusBadRequest, &api.Error{Message: new("ambiguous service name matches multiple services")}) + expectResolveTargetStatus(m, "my-api-db", http.StatusBadRequest, &api.ClientError{Message: new("ambiguous service name matches multiple services")}) }, wantErr: "ambiguous service name matches multiple services\nRun 'tiger service list' to find the ID you want", checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, @@ -108,7 +108,7 @@ func TestDbSchemaCmd(t *testing.T) { name: "nil response body", args: []string{"db", "schema", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "svc-12345", http.StatusOK, nil) + expectResolveTargetStatus(m, "svc-12345", http.StatusOK, nil) }, wantErr: "empty response from API", }, @@ -142,8 +142,8 @@ func TestDbSchemaCmd(t *testing.T) { name: "replica pooled without pooler warns before readiness check", args: []string{"db", "schema", "rep-67890", "--pooled"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "rep-67890", sampleReplica(func(s *api.Service) { - s.Status = api.DeployStatusQUEUED + expectResolveTarget(m, "rep-67890", sampleReplica(func(r *api.ReadReplicaSet) { + r.Status = api.ReadReplicaSetStatusCreating })) expectGetService(m, "svc-12345", sampleService()) }, @@ -179,7 +179,7 @@ func TestDbSchemaCmd(t *testing.T) { name: "prints the schema of a read replica", args: []string{"db", "schema", "rep-67890", "--pooled"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "rep-67890", sampleReplica()) + expectResolveTarget(m, "rep-67890", sampleReplica()) expectGetService(m, "svc-12345", sampleService()) }, opts: []runOption{withFetchServiceSchema(common.FetchServiceSchemaArgs{Role: "tsdbadmin", Pooled: true}, schema, nil)}, @@ -194,5 +194,14 @@ func TestDbSchemaCmd(t *testing.T) { opts: []runOption{withFetchServiceSchema(common.FetchServiceSchemaArgs{Role: "tsdbadmin"}, nil, errors.New("failed to connect to database: no route to host"))}, wantErr: "failed to connect to database: no route to host", }, + { + // Branches are a preview, so the help mentions them only behind + // the gate. The generated docs/cli reference covers the ungated + // help. + name: "help mentions branches when experimental", + args: []string{"db", "schema", "--help"}, + opts: []runOption{withExperimental()}, + wantStdout: matchHelp("You can also pass a branch, by ID or name, to introspect that branch."), + }, }) } diff --git a/internal/cmd/db_test.go b/internal/cmd/db_test.go index 9b00dfd3..e1376e4f 100644 --- a/internal/cmd/db_test.go +++ b/internal/cmd/db_test.go @@ -11,26 +11,54 @@ import ( "github.com/timescale/tiger-cli/internal/api/mocks" ) -// sampleReplica returns a standby read replica of sampleService, shaped the way -// GetService returns it when given a read replica set ID. -func sampleReplica(overrides ...func(*api.Service)) api.Service { - svc := sampleService(func(s *api.Service) { - s.ServiceID = "rep-67890" - s.Name = "replica-service" - s.Endpoint = &api.Endpoint{ +// sampleReplica returns an active read replica set of sampleService, as the +// resolve endpoint returns it. +func sampleReplica(overrides ...func(*api.ReadReplicaSet)) api.ReadReplicaSet { + replica := api.ReadReplicaSet{ + ID: "rep-67890", + ServiceID: "svc-12345", + Name: "replica-service", + Status: api.ReadReplicaSetStatusActive, + Nodes: 1, + Endpoint: &api.Endpoint{ Host: new("rep-67890.project.tsdb.cloud.timescale.com"), Port: new(5432), - } - s.ForkedFrom = &api.ForkSpec{ - IsStandby: new(true), - ProjectID: new(testProjectID), - ServiceID: new("svc-12345"), - } - }) + }, + } for _, o := range overrides { - o(&svc) + o(&replica) + } + return replica +} + +// expectResolveTarget registers the unrestricted resolve a `tiger db` command +// makes for what it connects to, answered with v (see expectResolve). +func expectResolveTarget(m *mocks.MockClientWithResponsesInterface, ref string, v any) { + expectResolve(m, ref, nil, v) +} + +// expectTaggedTarget is expectTaggedService for a `tiger db` command: the +// resolution of svc-12345 onto a service carrying the given environment tag. +func expectTaggedTarget(tag string) func(*mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + expectResolveTarget(m, "svc-12345", sampleService(envTag(tag))) } - return svc +} + +// expectResolveTargetNotFound registers a ref that matches nothing. +func expectResolveTargetNotFound(m *mocks.MockClientWithResponsesInterface, ref string) { + expectResolveStatus(m, ref, nil, http.StatusNotFound, &api.ClientError{Message: new("resource not found")}) +} + +// expectResolveTargetError registers a resolution that never reaches the API. +func expectResolveTargetError(m *mocks.MockClientWithResponsesInterface, ref string, err error) { + expectResolveError(m, ref, nil, err) +} + +// expectResolveTargetStatus registers a resolution answered with status. A +// nil apiErr stands in for a response with no body. +func expectResolveTargetStatus(m *mocks.MockClientWithResponsesInterface, ref string, status int, apiErr *api.ClientError) { + expectResolveStatus(m, ref, nil, status, apiErr) } // pausedMsg and notReadyMsg build the readiness errors handleDatabaseError diff --git a/internal/cmd/db_uri.go b/internal/cmd/db_uri.go index 4d85f814..2f660af3 100644 --- a/internal/cmd/db_uri.go +++ b/internal/cmd/db_uri.go @@ -25,7 +25,7 @@ service from your configuration. The connection string includes all necessary parameters for establishing a database connection to the TimescaleDB/PostgreSQL service. -You can also pass a read replica set ID to get a connection string for that replica. +You can also pass a read replica set ID to get a connection string for that replica.` + branchRefHelp(app.Experimental, "You can also pass a branch, by ID or name, to get a connection string for that branch.") + ` By default, passwords are excluded from the connection string for security. Use --with-password to include the password directly in the connection string. @@ -67,12 +67,7 @@ services writable.`, return err } - service, err := resolveService(cmd.Context(), client, projectID, serviceRef) - if err != nil { - return err - } - - target, err := common.NewConnectionTarget(cmd.Context(), client, projectID, *service) + target, err := resolveConnectionTarget(cmd.Context(), client, projectID, serviceRef, app.Experimental) if err != nil { return err } @@ -83,7 +78,7 @@ services writable.`, Pooled: pooled, Role: role, WithPassword: withPassword, - ReadOnly: readOnly || common.CheckReadOnly(cfg, common.ServiceEnvironmentTag(target.ConnectionService)) != nil, + ReadOnly: readOnly || common.CheckReadOnly(cfg, target.Environment) != nil, }) if err != nil { return err diff --git a/internal/cmd/db_uri_test.go b/internal/cmd/db_uri_test.go index 1e0114cf..e1a9143a 100644 --- a/internal/cmd/db_uri_test.go +++ b/internal/cmd/db_uri_test.go @@ -15,13 +15,14 @@ func TestDbURICmd(t *testing.T) { directURI = "postgresql://tsdbadmin@svc-12345.project.tsdb.cloud.timescale.com:5432/tsdb?sslmode=require\n" readOnlyURI = "postgresql://tsdbadmin@svc-12345.project.tsdb.cloud.timescale.com:5432/tsdb?sslmode=require&options=-c%20tsdb_admin.read_only_connection%3Dtrue\n" replicaURI = "postgresql://tsdbadmin@rep-67890.project.tsdb.cloud.timescale.com:5432/tsdb?sslmode=require\n" + branchURI = "postgresql://tsdbadmin@br-12345.test-project-123.branch.tsdb.cloud.timescale.com:36956/tsdb?sslmode=require\n" ) setupGet := func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService()) + expectResolveTarget(m, "svc-12345", sampleService()) } setupGetReplica := func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "rep-67890", sampleReplica()) + expectResolveTarget(m, "rep-67890", sampleReplica()) expectGetService(m, "svc-12345", sampleService()) } withPooler := func(s *api.Service) { @@ -50,33 +51,70 @@ func TestDbURICmd(t *testing.T) { name: "network error", args: []string{"db", "uri", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefError(m, "svc-12345", errors.New("connection refused")) + expectResolveTargetError(m, "svc-12345", errors.New("connection refused")) }, - wantErr: `failed to resolve service 'svc-12345': connection refused`, + wantErr: `failed to resolve service or read replica set 'svc-12345': connection refused`, }, { name: "service not found", args: []string{"db", "uri", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefNotFound(m, "svc-12345") + expectResolveTargetNotFound(m, "svc-12345") }, - wantErr: "service 'svc-12345' not found", + wantErr: "service or read replica set 'svc-12345' not found", checks: []checkFunc{checkExitCode(common.ExitServiceNotFound)}, }, { name: "ambiguous name refused", args: []string{"db", "uri", "my-api-db"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "my-api-db", http.StatusBadRequest, &api.Error{Message: new("ambiguous service name matches multiple services")}) + expectResolveTargetStatus(m, "my-api-db", http.StatusBadRequest, &api.ClientError{Message: new("ambiguous service name matches multiple services")}) }, wantErr: "ambiguous service name matches multiple services\nRun 'tiger service list' to find the ID you want", checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, }, + { + // Branches are a preview, so the hint names `tiger branch list` + // only behind the gate. + name: "ambiguous name refused when experimental", + args: []string{"db", "uri", "my-api-db"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveTargetStatus(m, "my-api-db", http.StatusBadRequest, &api.ClientError{Message: new("ambiguous ref matches multiple resources")}) + }, + wantErr: "ambiguous ref matches multiple resources\nRun 'tiger service list' or 'tiger branch list' to find the ID you want", + checks: []checkFunc{checkExitCode(common.ExitInvalidParameters)}, + }, + { + // Branches are a preview, so without the gate a ref naming one is + // reported as not found, without mentioning branches. + name: "branch not found without experimental", + args: []string{"db", "uri", "test-branch"}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveTarget(m, "test-branch", sampleBranch()) + }, + wantErr: "service or read replica set 'test-branch' not found", + checks: []checkFunc{checkExitCode(common.ExitServiceNotFound)}, + }, + { + name: "branch by name when experimental", + args: []string{"db", "uri", "test-branch"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { expectResolveTarget(m, "test-branch", sampleBranch()) }, + wantStdout: branchURI, + }, + { + name: "branch has no pooler", + args: []string{"db", "uri", "br-12345", "--pooled"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { expectResolveTarget(m, "br-12345", sampleBranch()) }, + wantErr: "connection pooler not available for this service", + }, { name: "nil response body", args: []string{"db", "uri", "svc-12345"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRefStatus(m, "svc-12345", http.StatusOK, nil) + expectResolveTargetStatus(m, "svc-12345", http.StatusOK, nil) }, wantErr: "empty response from API", }, @@ -109,7 +147,7 @@ func TestDbURICmd(t *testing.T) { name: "pooled with pooler available", args: []string{"db", "uri", "svc-12345", "--pooled"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "svc-12345", sampleService(withPooler)) + expectResolveTarget(m, "svc-12345", sampleService(withPooler)) }, wantStdout: "postgresql://tsdbadmin@pooler.svc-12345.project.tsdb.cloud.timescale.com:6432/tsdb?sslmode=require\n", }, @@ -148,14 +186,14 @@ func TestDbURICmd(t *testing.T) { { name: "config prod, PROD service", args: []string{"db", "uri", "svc-12345"}, - mock: expectTaggedService("PROD"), + mock: expectTaggedTarget("PROD"), opts: []runOption{withConfig(map[string]any{"read_only": "prod"})}, wantStdout: readOnlyURI, }, { name: "config prod, DEV service", args: []string{"db", "uri", "svc-12345"}, - mock: expectTaggedService("DEV"), + mock: expectTaggedTarget("DEV"), opts: []runOption{withConfig(map[string]any{"read_only": "prod"})}, wantStdout: directURI, }, @@ -169,7 +207,7 @@ func TestDbURICmd(t *testing.T) { { name: "flag on beats config prod", args: []string{"db", "uri", "svc-12345", "--read-only"}, - mock: expectTaggedService("DEV"), + mock: expectTaggedTarget("DEV"), opts: []runOption{withConfig(map[string]any{"read_only": "prod"})}, wantStdout: readOnlyURI, }, @@ -183,8 +221,8 @@ func TestDbURICmd(t *testing.T) { name: "replica pooled with pooler uses replica pooler", args: []string{"db", "uri", "rep-67890", "--pooled"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "rep-67890", sampleReplica(func(s *api.Service) { - s.ConnectionPooler = &api.ConnectionPooler{ + expectResolveTarget(m, "rep-67890", sampleReplica(func(r *api.ReadReplicaSet) { + r.ConnectionPooler = &api.ConnectionPooler{ Endpoint: &api.Endpoint{ Host: new("pooler.rep-67890.project.tsdb.cloud.timescale.com"), Port: new(6432), @@ -206,7 +244,7 @@ func TestDbURICmd(t *testing.T) { name: "replica parent fetch error", args: []string{"db", "uri", "rep-67890"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectResolveRef(m, "rep-67890", sampleReplica()) + expectResolveTarget(m, "rep-67890", sampleReplica()) m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "svc-12345"). Return(nil, errors.New("connection refused")) }, @@ -216,25 +254,32 @@ func TestDbURICmd(t *testing.T) { name: "with-password includes stored password", args: []string{"db", "uri", "svc-12345", "--with-password"}, mock: setupGet, - opts: []runOption{withStoredPassword(sampleService(), "secret-pw")}, + opts: []runOption{withStoredPassword(common.ServicePasswordTarget(sampleService()), "secret-pw")}, wantStdout: "postgresql://tsdbadmin:secret-pw@svc-12345.project.tsdb.cloud.timescale.com:5432/tsdb?sslmode=require\n", }, { name: "replica uses primary credentials", args: []string{"db", "uri", "rep-67890", "--with-password"}, mock: setupGetReplica, - opts: []runOption{withStoredPassword(sampleService(), "primary-pw")}, + opts: []runOption{withStoredPassword(common.ServicePasswordTarget(sampleService()), "primary-pw")}, wantStdout: "postgresql://tsdbadmin:primary-pw@rep-67890.project.tsdb.cloud.timescale.com:5432/tsdb?sslmode=require\n", }, - }) -} - -// withStoredPassword seeds a password for svc in the mock keyring before the -// command runs. -func withStoredPassword(svc api.Service, password string) runOption { - return withSetup(func(t *testing.T) { - if err := (&common.KeyringStorage{}).Save(svc, password, "tsdbadmin"); err != nil { - t.Fatalf("failed to seed password: %v", err) - } + { + // A branch has its own password, saved under its own ID. + name: "branch uses its own credentials", + args: []string{"db", "uri", "br-12345", "--with-password"}, + opts: []runOption{withExperimental(), withStoredPassword(common.BranchPasswordTarget(sampleBranch()), "branch-pw"), withStoredPassword(common.ServicePasswordTarget(sampleService()), "parent-pw")}, + mock: func(m *mocks.MockClientWithResponsesInterface) { expectResolveTarget(m, "br-12345", sampleBranch()) }, + wantStdout: "postgresql://tsdbadmin:branch-pw@br-12345.test-project-123.branch.tsdb.cloud.timescale.com:36956/tsdb?sslmode=require\n", + }, + { + // Branches are a preview, so the help mentions them only behind + // the gate. The generated docs/cli reference covers the ungated + // help. + name: "help mentions branches when experimental", + args: []string{"db", "uri", "--help"}, + opts: []runOption{withExperimental()}, + wantStdout: matchHelp("You can also pass a branch, by ID or name, to get a connection string for that branch."), + }, }) } diff --git a/internal/cmd/integration_test.go b/internal/cmd/integration_test.go index 0f402d50..a8d813c7 100644 --- a/internal/cmd/integration_test.go +++ b/internal/cmd/integration_test.go @@ -472,7 +472,7 @@ func TestServiceLifecycleIntegration(t *testing.T) { if err != nil { t.Fatalf("failed to build API client: %v", err) } - target, err := common.ResolveConnectionTargetByID(t.Context(), client, projectID, serviceID) + target, err := common.ResolveConnectionTargetByID(t.Context(), client, projectID, serviceID, false) if err != nil { t.Fatalf("failed to resolve connection target: %v", err) } diff --git a/internal/cmd/main_test.go b/internal/cmd/main_test.go index 11e27ac4..31396244 100644 --- a/internal/cmd/main_test.go +++ b/internal/cmd/main_test.go @@ -3,6 +3,7 @@ package cmd import ( "bytes" "context" + "encoding/json" "errors" "fmt" "io" @@ -424,6 +425,23 @@ func withIsTerminal(isTerminal bool) runOption { }) } +// withGenerateSecurePassword stubs common.GenerateSecurePassword, asserting the +// requested length and returning password and err in place of a random +// password, so a case can expect the exact update request and stored password. +func withGenerateSecurePassword(wantLength int, password string, err error) runOption { + return withSetup(func(t *testing.T) { + original := common.GenerateSecurePassword + common.GenerateSecurePassword = func(length int) (string, error) { + t.Helper() + if length != wantLength { + t.Errorf("GenerateSecurePassword length = %d, want %d", length, wantLength) + } + return password, err + } + t.Cleanup(func() { common.GenerateSecurePassword = original }) + }) +} + // withReadPassword makes util.ReadPassword return the given password. The real // implementation needs stdin to be an *os.File, so password prompts can't be // driven with withStdin. @@ -526,6 +544,32 @@ func checkDefaultService(want string) checkFunc { } } +// withStoredPassword seeds a password for target in the mock keyring before +// the command runs. +func withStoredPassword(target common.PasswordTarget, password string) runOption { + return withSetup(func(t *testing.T) { + if err := (&common.KeyringStorage{}).Save(target, password, "tsdbadmin"); err != nil { + t.Fatalf("failed to seed password: %v", err) + } + }) +} + +// checkStoredPassword returns a check asserting the password stored in the +// (mock) keyring for the given service. +func checkStoredPassword(serviceID, want string) checkFunc { + return func(t *testing.T, result cmdResult) { + t.Helper() + target := common.PasswordTarget{ID: serviceID, ProjectID: testProjectID} + got, err := (&common.KeyringStorage{}).Get(target, "tsdbadmin") + if err != nil { + t.Fatalf("failed to read stored password: %v", err) + } + if got != want { + t.Errorf("stored password = %q, want %q", got, want) + } + } +} + // writeConfigFile writes exactly the given keys to the config file in // configDir, creating the directory if needed. Only these keys are written, so // unspecified ones still resolve from the environment or their defaults — @@ -585,6 +629,61 @@ func httpResponse(statusCode int) *http.Response { return &http.Response{StatusCode: statusCode} } +// expectResolve registers resolving ref through the resolve endpoint, +// restricted to resolveType (nil for none), answered with v: an api.Service, +// api.Branch, or api.ReadReplicaSet, whose Go type picks the discriminator. +func expectResolve(m *mocks.MockClientWithResponsesInterface, ref string, resolveType *api.ResolveType, v any) { + m.EXPECT().ResolveRefWithResponse(validCtx, testProjectID, &api.ResolveRefParams{Ref: ref, Type: resolveType}). + Return(&api.ResolveRefResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: resolveResult(v), + }, nil) +} + +// expectResolveStatus registers a resolve answered with status. A nil apiErr +// stands in for a response with no body. +func expectResolveStatus(m *mocks.MockClientWithResponsesInterface, ref string, resolveType *api.ResolveType, status int, apiErr *api.ClientError) { + m.EXPECT().ResolveRefWithResponse(validCtx, testProjectID, &api.ResolveRefParams{Ref: ref, Type: resolveType}). + Return(&api.ResolveRefResponse{ + HTTPResponse: httpResponse(status), + JSON4XX: apiErr, + }, nil) +} + +// expectResolveError registers a resolve that never reaches the API. +func expectResolveError(m *mocks.MockClientWithResponsesInterface, ref string, resolveType *api.ResolveType, err error) { + m.EXPECT().ResolveRefWithResponse(validCtx, testProjectID, &api.ResolveRefParams{Ref: ref, Type: resolveType}). + Return(nil, err) +} + +// resolveResult encodes v as the resolve endpoint's union, discriminated by +// v's Go type. Each member is its plain type plus the discriminator. +func resolveResult(v any) *api.ResolveResult { + var resolveType api.ResolveType + switch v.(type) { + case api.Service: + resolveType = api.ResolveTypeSERVICE + case api.Branch: + resolveType = api.ResolveTypeBRANCH + case api.ReadReplicaSet: + resolveType = api.ResolveTypeREADREPLICASET + default: + panic(fmt.Sprintf("resolveResult: unsupported type %T", v)) + } + + var body map[string]any + if err := json.Unmarshal(util.Must(json.Marshal(v)), &body); err != nil { + panic(err) + } + body["type"] = resolveType + + var result api.ResolveResult + if err := result.UnmarshalJSON(util.Must(json.Marshal(body))); err != nil { + panic(err) + } + return &result +} + // expectResolveRef registers the resolve call a command makes for its service // ref — the ref filter on the service list — answered with svc as the one // match. diff --git a/internal/cmd/mcp_list_test.go b/internal/cmd/mcp_list_test.go index e8d9716e..439a80cf 100644 --- a/internal/cmd/mcp_list_test.go +++ b/internal/cmd/mcp_list_test.go @@ -69,11 +69,20 @@ func TestMCPListCmd(t *testing.T) { └──────┴───────────────────────────┘ ` - // TIGER_EXPERIMENTAL registers the preview backup tools on top of the - // (always-registered) default set, which already includes metrics. + // TIGER_EXPERIMENTAL registers the preview backup and branch tools on top + // of the (always-registered) default set, which already includes metrics. wantTextExperimental := `┌──────┬──────────────────────────────┐ │ TYPE │ NAME │ ├──────┼──────────────────────────────┤ +│ tool │ branch_create │ +│ tool │ branch_delete │ +│ tool │ branch_get │ +│ tool │ branch_list │ +│ tool │ branch_rename │ +│ tool │ branch_resize │ +│ tool │ branch_start │ +│ tool │ branch_stop │ +│ tool │ branch_update_password │ │ tool │ db_query │ │ tool │ db_schema │ │ tool │ feedback │ @@ -178,7 +187,7 @@ func TestMCPListCmd(t *testing.T) { wantStdout: wantTextReadOnly, }, { - name: "experimental adds backups tool", + name: "experimental adds backup and branch tools", args: []string{"mcp", "list"}, opts: append(noDocsProxy(nil), withEnv("TIGER_EXPERIMENTAL", "true")), diff --git a/internal/cmd/password_helper.go b/internal/cmd/password_helper.go index 7a606e00..79212ac1 100644 --- a/internal/cmd/password_helper.go +++ b/internal/cmd/password_helper.go @@ -3,6 +3,7 @@ package cmd import ( "context" "fmt" + "net/http" "github.com/spf13/cobra" @@ -12,41 +13,63 @@ import ( "github.com/timescale/tiger-cli/internal/util" ) -// updateAndSaveServicePassword updates a service password via API and saves it locally. -// It handles the API call and password storage. -func updateAndSaveServicePassword( +// updateAndSavePassword sets the password of credentials' owner, a service or +// a branch, through the matching API endpoint, and saves it locally. +func updateAndSavePassword( ctx context.Context, cmd *cobra.Command, cfg *config.Config, client api.ClientWithResponsesInterface, - service api.Service, + credentials common.CredentialOwner, newPassword string, role string, ) error { - // Call API to update password - updateReq := api.UpdatePasswordInput{Password: newPassword} - resp, err := client.UpdatePasswordWithResponse(ctx, service.ProjectID, service.ServiceID, updateReq) - if err != nil { - return fmt.Errorf("failed to update password: %w", err) - } - - if resp.StatusCode() != 200 && resp.StatusCode() != 204 { - return common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + if err := updatePassword(ctx, client, credentials, newPassword); err != nil { + return err } // Save password locally - if result, err := common.SavePasswordWithResult(cfg, service, newPassword, role); err != nil { + if result, err := common.SavePasswordWithResult(cfg, credentials.PasswordTarget, newPassword, role); err != nil { cmd.PrintErrf("Warning: could not save password: %v\n", err) } else if result.Success { cmd.PrintErrf("%s\n", result.Message) - cmd.PrintErrf("View it with: tiger service get %s --with-password\n", service.ServiceID) + // Only service get can show a stored password. + if credentials.BranchServiceID == "" { + cmd.PrintErrf("View it with: tiger service get %s --with-password\n", credentials.ID) + } } return nil } -// resetServicePassword resets the password via API. If newPassword is empty, generates one. -func resetServicePassword(ctx context.Context, cmd *cobra.Command, cfg *config.Config, client api.ClientWithResponsesInterface, service api.Service, role string, newPassword string) (string, error) { +// updatePassword sets the password of credentials' owner through the service +// or branch password endpoint. +func updatePassword(ctx context.Context, client api.ClientWithResponsesInterface, credentials common.CredentialOwner, newPassword string) error { + input := api.UpdatePasswordInput{Password: newPassword} + if credentials.BranchServiceID != "" { + resp, err := client.UpdateBranchPasswordWithResponse(ctx, credentials.ProjectID, credentials.BranchServiceID, credentials.ID, input) + if err != nil { + return fmt.Errorf("failed to update password: %w", err) + } + if resp.StatusCode() != http.StatusNoContent { + return common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + return nil + } + + resp, err := client.UpdatePasswordWithResponse(ctx, credentials.ProjectID, credentials.ID, input) + if err != nil { + return fmt.Errorf("failed to update password: %w", err) + } + if resp.StatusCode() != http.StatusNoContent { + return common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + return nil +} + +// resetPassword resets the password of credentials' owner via the API. If +// newPassword is empty, it generates one. +func resetPassword(ctx context.Context, cmd *cobra.Command, cfg *config.Config, client api.ClientWithResponsesInterface, credentials common.CredentialOwner, role string, newPassword string) (string, error) { // Generate password if not provided if newPassword == "" { var err error @@ -55,8 +78,7 @@ func resetServicePassword(ctx context.Context, cmd *cobra.Command, cfg *config.C } } - // Update and save password - if err := updateAndSaveServicePassword(ctx, cmd, cfg, client, service, newPassword, role); err != nil { + if err := updateAndSavePassword(ctx, cmd, cfg, client, credentials, newPassword, role); err != nil { return "", err } return newPassword, nil @@ -70,7 +92,7 @@ func promptAndResetPassword( cmd *cobra.Command, cfg *config.Config, client api.ClientWithResponsesInterface, - service api.Service, + credentials common.CredentialOwner, role string, ) (string, error) { cmd.PrintErr("Enter new password (leave empty to generate): ") @@ -80,5 +102,49 @@ func promptAndResetPassword( return "", fmt.Errorf("error reading password: %w", err) } - return resetServicePassword(ctx, cmd, cfg, client, service, role, newPassword) + return resetPassword(ctx, cmd, cfg, client, credentials, role, newPassword) +} + +// printPasswordStorageResult reports the outcome of saving a password and +// returns whether it was saved. +func printPasswordStorageResult(cmd *cobra.Command, result common.PasswordStorageResult) bool { + if result.Method == "none" && result.Message == "No password provided" { + // Don't output anything for empty password + return false + } + + // Only a real storage failure is a warning; the "none" method is informational. + if result.Success { + cmd.PrintErrf("%s\n", result.Message) + return true + } else if result.Method == "none" { + cmd.PrintErrf("%s\n", result.Message) + } else { + cmd.PrintErrf("Warning: %s\n", result.Message) + } + return false +} + +// handlePasswordSaving handles saving password using the configured storage +// method and displaying appropriate messages. Returns true if the password was +// successfully saved, or false if not. +func handlePasswordSaving(cmd *cobra.Command, cfg *config.Config, target common.PasswordTarget, initialPassword string) bool { + // Note: We don't fail the service creation if password saving fails + // The error is handled by displaying the appropriate message below + result, _ := common.SavePasswordWithResult(cfg, target, initialPassword, "tsdbadmin") + return printPasswordStorageResult(cmd, result) +} + +// printConnectMessage tells the user how to connect to a new service or branch. +func printConnectMessage(cmd *cobra.Command, passwordSaved, noSetDefault bool, serviceID string) { + if !passwordSaved { + // We can't connect if no password was saved, so don't show message + return + } else if noSetDefault { + // If the service wasn't set as the default, include the serviceID in the command + cmd.PrintErrf("Connect with: tiger db psql %s\n", serviceID) + } else { + // If the service was set as the default, no need to include the serviceID in the command + cmd.PrintErrf("Connect with: tiger db psql\n") + } } diff --git a/internal/cmd/project_list.go b/internal/cmd/project_list.go index 665f742e..3976af77 100644 --- a/internal/cmd/project_list.go +++ b/internal/cmd/project_list.go @@ -82,7 +82,7 @@ func outputProjects(cmd *cobra.Command, projects []api.Project, currentProjectID // but TIGER_OUTPUT and the config file aren't validated on load. return fmt.Errorf("environment variable output is not supported for multiple projects") default: // table format (default) - return outputProjectsTable(outputProjects, outputWriter) + return outputProjectsTable(outputWriter, outputProjects) } } @@ -99,7 +99,7 @@ func prepareProjectsForOutput(projects []api.Project, currentProjectID string) [ } // outputProjectsTable outputs projects in a formatted table using tablewriter -func outputProjectsTable(projects []OutputProject, output io.Writer) error { +func outputProjectsTable(output io.Writer, projects []OutputProject) error { table := tablewriter.NewWriter(output) table.Header("PROJECT ID", "NAME", "CURRENT") diff --git a/internal/cmd/service_ref_helper.go b/internal/cmd/ref_helper.go similarity index 64% rename from internal/cmd/service_ref_helper.go rename to internal/cmd/ref_helper.go index b665c500..9c93590b 100644 --- a/internal/cmd/service_ref_helper.go +++ b/internal/cmd/ref_helper.go @@ -18,6 +18,9 @@ import ( // default identifies a service. var errServiceRequired = errors.New("service name or ID is required. Provide it as an argument or set a default with 'tiger config set service_id '") +// errBranchRequired is returned when no argument identifies a branch. +var errBranchRequired = errors.New("branch name or ID is required") + // serviceRef is what the user gave to identify a service, and where it came // from. The two resolve differently: an argument may be an ID, a read replica // set ID, or a name, and the API matches all of them together, while a @@ -68,7 +71,13 @@ func defaultServiceSource(cmd *cobra.Command) string { // resolveService resolves a ref to the service it names, through the ref // filter on the service list: a match is a one-item list, and no match is an -// empty one rather than a 404. +// empty one rather than a 404. A read replica set ID matches too, and comes +// back as a service whose ForkedFrom links to its parent. +// +// TODO: move to the API's resolve endpoint, like the branch and db +// commands, once read replica sets have their own commands. Its SERVICE type +// doesn't match replica sets, and until those commands exist this lookup is +// the only way to reach one. func resolveService(ctx context.Context, client api.ClientWithResponsesInterface, projectID string, ref serviceRef) (*api.Service, error) { resp, err := client.GetServicesWithResponse(ctx, projectID, &api.GetServicesParams{Ref: &ref.ref}) if err != nil { @@ -100,23 +109,23 @@ func resolveService(ctx context.Context, client api.ClientWithResponsesInterface } service := services[0] - if err := checkDefaultIsID(service, ref); err != nil { + if err := checkDefaultIsID(service.ServiceID, ref); err != nil { return nil, err } return &service, nil } // checkDefaultIsID refuses a configured default that turned out to be a name -// (see serviceRef for why). A ref equal to the service's own ID matched by ID -// or replica set ID; anything else matched by name. Checking after the lookup -// rather than before is what lets the error name the ID to store instead. -func checkDefaultIsID(service api.Service, ref serviceRef) error { - if ref.source == "" || service.ServiceID == ref.ref { +// (see serviceRef for why). A ref equal to the resolved ID matched by ID; +// anything else matched by name. Checking after the lookup rather than before +// is what lets the error name the ID to store instead. +func checkDefaultIsID(id string, ref serviceRef) error { + if ref.source == "" || id == ref.ref { return nil } return common.ExitWithCode(common.ExitInvalidParameters, fmt.Errorf( "the default service must be an ID, but %s is set to the name '%s'. Set it to %s, or pass the name as an argument", - ref.source, ref.ref, service.ServiceID)) + ref.source, ref.ref, id)) } // serviceLabel identifies a service in status output by name and ID together. @@ -154,3 +163,50 @@ func resolveServiceForWrite(ctx context.Context, cfg *config.Config, client api. return service, nil } + +// getBranchRef returns the branch ref, an ID or a name, given as an argument. +// There is no default branch: the service_id default only ever holds a +// service ID. +func getBranchRef(args []string) (string, error) { + if len(args) == 0 || args[0] == "" { + return "", errBranchRequired + } + return args[0], nil +} + +// resolveBranch resolves a branch ref to the branch it names, through the +// API's resolve endpoint restricted to branches. The branch carries its +// parent's ID, which every other branch endpoint needs. +func resolveBranch(ctx context.Context, client api.ClientWithResponsesInterface, projectID, ref string) (*api.Branch, error) { + branch, err := common.ResolveBranch(ctx, client, projectID, ref) + if err != nil { + return nil, withAmbiguousRefHint(err, "Run 'tiger branch list' on the branch's service to find the ID you want") + } + return branch, nil +} + +// branchLabel identifies a branch in status output by name and ID together. +func branchLabel(branch api.Branch) string { + return fmt.Sprintf("'%s' (%s)", branch.Name, branch.ID) +} + +// resolveBranchForWrite is resolveBranch for a command that changes the branch +// it resolves. A branch always counts as DEV (see common.BranchEnvironmentTag), +// so the gate needs nothing off the branch and runs before the lookup. +func resolveBranchForWrite(ctx context.Context, cfg *config.Config, client api.ClientWithResponsesInterface, projectID, ref string) (*api.Branch, error) { + if err := common.CheckReadOnly(cfg, common.BranchEnvironmentTag); err != nil { + return nil, err + } + return resolveBranch(ctx, client, projectID, ref) +} + +// withAmbiguousRefHint adds hint to a resolve error that refused a ref +// matching more than one resource. The resolve endpoint's only validation +// error is that refusal, so a 400 means it. The server's message doesn't name +// the candidates, so the hint points at the command that lists them. +func withAmbiguousRefHint(err error, hint string) error { + if exitErr, ok := errors.AsType[common.ExitCodeError](err); ok && exitErr.ExitCode() == common.ExitInvalidParameters { + return fmt.Errorf("%w\n%s", err, hint) + } + return err +} diff --git a/internal/cmd/root.go b/internal/cmd/root.go index ff92590c..e3141d10 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -112,6 +112,12 @@ To get started, run: cmd.AddCommand(buildProjectCmd(app)) cmd.AddCommand(buildServiceCmd(app)) cmd.AddCommand(buildDbCmd(app)) + + // Branches are a preview, unregistered until it graduates. + if app.Experimental { + cmd.AddCommand(buildBranchCmd(app)) + } + cmd.AddCommand(buildMCPCmd(app)) cmd.AddCommand(buildSkillsCmd(app)) cmd.AddCommand(buildFeedbackCmd(app)) diff --git a/internal/cmd/service.go b/internal/cmd/service.go index 4a5edbd7..35164d55 100644 --- a/internal/cmd/service.go +++ b/internal/cmd/service.go @@ -15,9 +15,9 @@ import ( ) // buildServiceCmd creates the main service command with all subcommands. -// experimental still gates `backups`, which is backed by a gateway endpoint -// marked `x-tigerdata-preview: true`; when false, that subtree is not added to -// the tree at all. See CLAUDE.md's "Experimental Feature Gating". +// experimental still gates `backups` and `branch`, which are backed by gateway +// endpoints marked `x-tigerdata-preview: true`; when false, they are not added +// to the tree at all. See CLAUDE.md's "Experimental Feature Gating". func buildServiceCmd(app *common.App) *cobra.Command { cmd := &cobra.Command{ Use: "service", @@ -40,9 +40,10 @@ func buildServiceCmd(app *common.App) *cobra.Command { cmd.AddCommand(buildServiceLogsCmd(app)) cmd.AddCommand(buildServiceMetricsCmd(app)) - // Experimental command, unregistered until the preview graduates. + // Experimental commands, unregistered until the previews graduate. if app.Experimental { cmd.AddCommand(buildServiceBackupCmd(app)) + cmd.AddCommand(buildServiceBranchCmd(app)) } return cmd @@ -78,21 +79,36 @@ func cpuMemoryHelp(shared, experimental bool) string { // OutputService represents a service with computed fields for output type OutputService struct { api.Service + // BranchCount is set only by service get, behind the experimental gate, + // for a service that can have branches. + BranchCount *int `json:"branch_count,omitempty"` common.ConnectionDetails ConnectionString string `json:"connection_string,omitempty"` ConsoleURL string `json:"console_url,omitempty"` } -// outputService formats and outputs a single service based on the specified format -func outputService(cmd *cobra.Command, cfg *config.Config, service api.Service, experimental bool, format string, withPassword bool, strict bool) error { - // Prepare the output service with computed fields - outputSvc := prepareServiceForOutput(cmd, cfg, service, experimental, withPassword) - if strict && withPassword && outputSvc.Password == "" { +// serviceOutputOptions controls what the service output functions include. +// The zero value leaves out every optional field. +type serviceOutputOptions struct { + // Experimental includes preview fields (the platform). + Experimental bool + // WithPassword includes the tsdbadmin password, if one is available. + WithPassword bool + // RequirePassword fails the output when WithPassword finds no password. + RequirePassword bool + // BranchCount is the service's branch count, if it was counted. + BranchCount *int +} + +// outputService outputs a single service in the configured output format. +func outputService(cmd *cobra.Command, cfg *config.Config, service api.Service, opts serviceOutputOptions) error { + outputSvc := prepareServiceForOutput(cmd, cfg, service, opts) + if opts.RequirePassword && opts.WithPassword && outputSvc.Password == "" { return fmt.Errorf("password requested but not available for service %s", outputSvc.ServiceID) } outputWriter := cmd.OutOrStdout() - switch strings.ToLower(format) { + switch strings.ToLower(cfg.Output) { case "json": return util.SerializeToJSON(outputWriter, outputSvc) case "yaml": @@ -100,7 +116,7 @@ func outputService(cmd *cobra.Command, cfg *config.Config, service api.Service, case "env": return outputServiceEnv(cmd, outputSvc) default: // table format (default) - return outputServiceTable(outputSvc, outputWriter) + return outputServiceTable(outputWriter, outputSvc) } } @@ -117,7 +133,7 @@ func outputServiceEnv(cmd *cobra.Command, service OutputService) error { } // outputServiceTable outputs detailed service information in a formatted table -func outputServiceTable(service OutputService, output io.Writer) error { +func outputServiceTable(output io.Writer, service OutputService) error { table := tablewriter.NewWriter(output) table.Header("PROPERTY", "VALUE") @@ -167,25 +183,19 @@ func outputServiceTable(service OutputService, output io.Writer) error { } } + if service.BranchCount != nil { + table.Append("Branches", fmt.Sprintf("%d", *service.BranchCount)) + } + // Endpoint information - if service.Endpoint != nil { - if service.Endpoint.Host != nil { - port := "5432" - if service.Endpoint.Port != nil { - port = fmt.Sprintf("%d", *service.Endpoint.Port) - } - table.Append("Direct Endpoint", fmt.Sprintf("%s:%s", *service.Endpoint.Host, port)) - } + if service.Endpoint != nil && service.Endpoint.Host != nil && service.Endpoint.Port != nil { + table.Append("Direct Endpoint", fmt.Sprintf("%s:%d", *service.Endpoint.Host, *service.Endpoint.Port)) } // Connection pooler information if service.ConnectionPooler != nil && service.ConnectionPooler.Endpoint != nil { - if service.ConnectionPooler.Endpoint.Host != nil { - port := "6432" - if service.ConnectionPooler.Endpoint.Port != nil { - port = fmt.Sprintf("%d", *service.ConnectionPooler.Endpoint.Port) - } - table.Append("Pooler Endpoint", fmt.Sprintf("%s:%s", *service.ConnectionPooler.Endpoint.Host, port)) + if pooler := service.ConnectionPooler.Endpoint; pooler.Host != nil && pooler.Port != nil { + table.Append("Pooler Endpoint", fmt.Sprintf("%s:%d", *pooler.Host, *pooler.Port)) } } @@ -211,25 +221,26 @@ func outputServiceTable(service OutputService, output io.Writer) error { // prepareServiceForOutput builds the output view of a service. cmd may be nil, // in which case the connection-details warning is dropped rather than printed. // The platform and IOPS fields are preview fields, so they are dropped unless -// experimental is set. -func prepareServiceForOutput(cmd *cobra.Command, cfg *config.Config, service api.Service, experimental bool, withPassword bool) OutputService { +// opts.Experimental is set. +func prepareServiceForOutput(cmd *cobra.Command, cfg *config.Config, service api.Service, opts serviceOutputOptions) OutputService { outputSvc := OutputService{ - Service: service, + Service: service, + BranchCount: opts.BranchCount, } outputSvc.Service.InitialPassword = nil - if !experimental { + if !opts.Experimental { outputSvc.Service.Platform = nil outputSvc.Service.Resources = resourcesWithoutIOPS(service.Resources) } - opts := common.ConnectionDetailsOptions{ + detailsOpts := common.ConnectionDetailsOptions{ Role: "tsdbadmin", - WithPassword: withPassword, + WithPassword: opts.WithPassword, InitialPassword: util.Deref(service.InitialPassword), ReadOnly: common.CheckReadOnly(cfg, common.ServiceEnvironmentTag(service)) != nil, } - if connectionDetails, err := common.GetConnectionDetails(cfg, service, opts); err != nil { + if connectionDetails, err := common.GetConnectionDetails(cfg, common.NewServiceConnectionTarget(service), detailsOpts); err != nil { if cmd != nil { cmd.PrintErrf("Warning: Failed to get connection details: %v\n", err) } @@ -262,31 +273,6 @@ func resourcesWithoutIOPS(resources []api.Resource) []api.Resource { return stripped } -// handlePasswordSaving handles saving password using the configured storage -// method and displaying appropriate messages. Returns true if the password was -// successfully saved, or false if not. -func handlePasswordSaving(cmd *cobra.Command, cfg *config.Config, service api.Service, initialPassword string) bool { - // Note: We don't fail the service creation if password saving fails - // The error is handled by displaying the appropriate message below - result, _ := common.SavePasswordWithResult(cfg, service, initialPassword, "tsdbadmin") - - if result.Method == "none" && result.Message == "No password provided" { - // Don't output anything for empty password - return false - } - - // Only a real storage failure is a warning; the "none" method is informational. - if result.Success { - cmd.PrintErrf("%s\n", result.Message) - return true - } else if result.Method == "none" { - cmd.PrintErrf("%s\n", result.Message) - } else { - cmd.PrintErrf("Warning: %s\n", result.Message) - } - return false -} - // setDefaultService sets the given service as the default service in the configuration func setDefaultService(cmd *cobra.Command, cfg *config.Config, serviceID string) error { if _, err := cfg.Set("service_id", serviceID); err != nil { @@ -296,16 +282,3 @@ func setDefaultService(cmd *cobra.Command, cfg *config.Config, serviceID string) cmd.PrintErrf("Default service set to %s.\n", serviceID) return nil } - -func printConnectMessage(cmd *cobra.Command, passwordSaved, noSetDefault bool, serviceID string) { - if !passwordSaved { - // We can't connect if no password was saved, so don't show message - return - } else if noSetDefault { - // If the service wasn't set as the default, include the serviceID in the command - cmd.PrintErrf("Connect with: tiger db psql %s\n", serviceID) - } else { - // If the service was set as the default, no need to include the serviceID in the command - cmd.PrintErrf("Connect with: tiger db psql\n") - } -} diff --git a/internal/cmd/service_backup_list.go b/internal/cmd/service_backup_list.go index 2aa3099f..59730d96 100644 --- a/internal/cmd/service_backup_list.go +++ b/internal/cmd/service_backup_list.go @@ -78,11 +78,6 @@ service from your configuration.`, backups = *resp.JSON200 } - if len(backups) == 0 { - cmd.PrintErrln("No backups found.") - return nil - } - return outputBackups(cmd, backups, cfg.Output) }, } @@ -104,13 +99,17 @@ func outputBackups(cmd *cobra.Command, backups []api.Backup, format string) erro case "env": return fmt.Errorf("environment variable output is not supported for backups") default: - return outputBackupsTable(backups, out) + if len(backups) == 0 { + cmd.PrintErrln("No backups found.") + return nil + } + return outputBackupsTable(out, backups) } } // outputBackupsTable omits the label: it repeats STARTED and TYPE, and no command // takes it as input. It stays in the json and yaml output. -func outputBackupsTable(backups []api.Backup, output io.Writer) error { +func outputBackupsTable(output io.Writer, backups []api.Backup) error { table := tablewriter.NewWriter(output) table.Header("STARTED", "TYPE", "DURATION", "SIZE", "REGIONS") diff --git a/internal/cmd/service_backup_list_test.go b/internal/cmd/service_backup_list_test.go index 8bc641e1..7cb15a3f 100644 --- a/internal/cmd/service_backup_list_test.go +++ b/internal/cmd/service_backup_list_test.go @@ -159,6 +159,22 @@ func TestServiceBackupListCmd(t *testing.T) { mock: setupList([]api.Backup{}), wantStderr: "No backups found.\n", }, + { + // Structured output is always structured, so an empty list is + // still a list rather than a message. + name: "empty list json", + args: []string{"service", "backup", "list", "svc-12345", "-o", "json"}, + opts: []runOption{experimental}, + mock: setupList([]api.Backup{}), + wantStdout: "[]\n", + }, + { + name: "empty list yaml", + args: []string{"service", "backup", "list", "svc-12345", "-o", "yaml"}, + opts: []runOption{experimental}, + mock: setupList([]api.Backup{}), + wantStdout: "[]\n", + }, { // The label is omitted from the table: it repeats STARTED and TYPE, // and no command takes it as input. diff --git a/internal/cmd/service_backup_region_list.go b/internal/cmd/service_backup_region_list.go index 62a09d98..dbdb33d7 100644 --- a/internal/cmd/service_backup_region_list.go +++ b/internal/cmd/service_backup_region_list.go @@ -94,16 +94,15 @@ func outputBackupRegions(cmd *cobra.Command, regions []api.BackupRegion, format case "env": return fmt.Errorf("environment variable output is not supported for backup regions") default: - return outputBackupRegionsTable(regions, out) + if len(regions) == 0 { + cmd.PrintErrln("No backup regions configured for this service.") + return nil + } + return outputBackupRegionsTable(out, regions) } } -func outputBackupRegionsTable(regions []api.BackupRegion, output io.Writer) error { - if len(regions) == 0 { - _, err := fmt.Fprintln(output, "No backup regions configured for this service.") - return err - } - +func outputBackupRegionsTable(output io.Writer, regions []api.BackupRegion) error { table := tablewriter.NewWriter(output) table.Header("REGION", "ADDED") diff --git a/internal/cmd/service_backup_region_list_test.go b/internal/cmd/service_backup_region_list_test.go index fa4acb74..ba08ee59 100644 --- a/internal/cmd/service_backup_region_list_test.go +++ b/internal/cmd/service_backup_region_list_test.go @@ -109,7 +109,7 @@ func TestServiceBackupRegionListCmd(t *testing.T) { args: []string{"service", "backup", "region", "list", "svc-12345"}, opts: []runOption{experimental}, mock: setupList([]api.BackupRegion{}), - wantStdout: "No backup regions configured for this service.\n", + wantStderr: "No backup regions configured for this service.\n", }, { // An empty list still serializes as a valid array in structured diff --git a/internal/cmd/service_branch.go b/internal/cmd/service_branch.go new file mode 100644 index 00000000..0a229be9 --- /dev/null +++ b/internal/cmd/service_branch.go @@ -0,0 +1,13 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/timescale/tiger-cli/internal/common" +) + +// buildServiceBranchCmd creates the branch subcommand of `tiger service`, the +// same command as `tiger branch create` (see newBranchCreateCmd). +func buildServiceBranchCmd(app *common.App) *cobra.Command { + return newBranchCreateCmd(app, "branch [name-or-id]", "tiger service branch") +} diff --git a/internal/cmd/service_create.go b/internal/cmd/service_create.go index 9d8fd13d..f0c5e44e 100644 --- a/internal/cmd/service_create.go +++ b/internal/cmd/service_create.go @@ -191,7 +191,7 @@ other will be automatically configured).` + iopsSectionHelp, // Save password immediately after service creation, before any waiting // This ensures users have access even if they interrupt the wait or it fails - passwordSaved := handlePasswordSaving(cmd, cfg, service, util.Deref(service.InitialPassword)) + passwordSaved := handlePasswordSaving(cmd, cfg, common.ServicePasswordTarget(service), util.Deref(service.InitialPassword)) // Set as default service unless --no-set-default is specified if !noSetDefault { @@ -226,7 +226,10 @@ other will be automatically configured).` + iopsSectionHelp, } } - if err := outputService(cmd, cfg, service, app.Experimental, cfg.Output, withPassword, false); err != nil { + if err := outputService(cmd, cfg, service, serviceOutputOptions{ + Experimental: app.Experimental, + WithPassword: withPassword, + }); err != nil { cmd.PrintErrf("Warning: Failed to output service details: %v\n", err) } diff --git a/internal/cmd/service_delete.go b/internal/cmd/service_delete.go index 65357961..b1cb0430 100644 --- a/internal/cmd/service_delete.go +++ b/internal/cmd/service_delete.go @@ -16,11 +16,7 @@ import ( func buildServiceDeleteCmd(app *common.App) *cobra.Command { var confirm bool - cmd := &cobra.Command{ - Use: "delete ", - Aliases: []string{"rm"}, - Short: "Delete a database service", - Long: `Delete a database service permanently. + long := `Delete a database service permanently. The service can be given by ID or name, but must be given explicitly: there is no fallback to the default service. @@ -28,8 +24,23 @@ no fallback to the default service. This operation is irreversible. By default, you will be prompted to type the service ID to confirm deletion — the ID, not the name — unless you use the --confirm flag. +` + // Branches are a preview, so only the experimental help mentions them. The + // command lists them regardless: they're deleted either way. + if app.Experimental { + long += ` +Deleting a service also deletes all of its branches. They are listed in the +confirmation prompt and again once the service is deleted. +` + } + long += ` +Note for AI agents: Always confirm with the user before performing this destructive operation.` -Note for AI agents: Always confirm with the user before performing this destructive operation.`, + cmd := &cobra.Command{ + Use: "delete ", + Aliases: []string{"rm"}, + Short: "Delete a database service", + Long: long, Example: ` # Delete a service (with confirmation prompt) tiger service delete svc-12345 @@ -57,10 +68,17 @@ Note for AI agents: Always confirm with the user before performing this destruct return err } + if !confirm && (!util.IsTerminal(cmd.InOrStdin()) || !util.IsTerminal(cmd.ErrOrStderr())) { + return fmt.Errorf("TTY not detected - cannot prompt for confirmation. Use --confirm to skip the prompt") + } + + branches := listBranchesToDelete(cmd, client, projectID, *service) + // Prompt for confirmation unless --confirm is used if !confirm { - if !util.IsTerminal(cmd.InOrStdin()) || !util.IsTerminal(cmd.ErrOrStderr()) { - return fmt.Errorf("TTY not detected - cannot prompt for confirmation. Use --confirm to skip the prompt") + if len(branches) > 0 { + cmd.PrintErrf("This service has %s, which will also be deleted:\n", common.FormatBranchCount(len(branches))) + printBranchLabels(cmd, branches) } // Show both forms, but take only the ID: a name can move to a // different service through a rename, so it is the wrong @@ -92,7 +110,12 @@ Note for AI agents: Always confirm with the user before performing this destruct return common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) } - cmd.PrintErrf("Service %s deleted.\n", serviceLabel(*service)) + if len(branches) == 0 { + cmd.PrintErrf("Service %s deleted.\n", serviceLabel(*service)) + return nil + } + cmd.PrintErrf("Service %s deleted, along with its %s:\n", serviceLabel(*service), common.FormatBranchCount(len(branches))) + printBranchLabels(cmd, branches) return nil }, } @@ -101,3 +124,28 @@ Note for AI agents: Always confirm with the user before performing this destruct return cmd } + +// listBranchesToDelete returns the branches that deleting service takes with +// it, so the command can show them before and after the delete. The API +// deletes them without asking, so this isn't gated on the experimental env +// var: whoever has branches gets told. A failed listing is a warning rather +// than an error, so the delete can still go ahead. +func listBranchesToDelete(cmd *cobra.Command, client api.ClientWithResponsesInterface, projectID string, service api.Service) []api.Branch { + if !common.CanHaveBranches(service) { + return nil + } + branches, err := common.ListBranches(cmd.Context(), client, projectID, service.ServiceID) + if err != nil { + cmd.PrintErrf("Warning: failed to list branches: %v\n", err) + cmd.PrintErrln("Any branches of this service will also be deleted.") + return nil + } + return branches +} + +// printBranchLabels prints one indented line per branch. +func printBranchLabels(cmd *cobra.Command, branches []api.Branch) { + for _, branch := range branches { + cmd.PrintErrf(" %s\n", branchLabel(branch)) + } +} diff --git a/internal/cmd/service_delete_test.go b/internal/cmd/service_delete_test.go index 3b6981d2..ceed40e5 100644 --- a/internal/cmd/service_delete_test.go +++ b/internal/cmd/service_delete_test.go @@ -23,6 +23,23 @@ func TestServiceDeleteCmd(t *testing.T) { } } + // setupFluidDelete is setupDelete for a fluid service, which can have + // branches, so the prompt lists them first. + setupFluidDelete := func(branches ...api.Branch) func(m *mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + setupDelete("svc-12345", fluidPlatform)(m) + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "svc-12345", nil). + Return(&api.GetBranchesResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &branches, + }, nil) + } + } + otherBranch := sampleBranch(func(b *api.Branch) { + b.ID = "br-67890" + b.Name = "other-branch" + }) + confirmPrompt := "Are you sure you want to delete service 'test-service' (svc-12345)? This operation cannot be undone.\n" + "Type the service ID 'svc-12345' to confirm: " @@ -119,6 +136,66 @@ func TestServiceDeleteCmd(t *testing.T) { mock: setupDelete("test-service"), wantStderr: confirmPrompt + "Service 'test-service' (svc-12345) deleted.\n", }, + { + // The API deletes a service's branches along with it, so they're + // listed before the prompt and after the delete, even without the + // experimental gate. + name: "confirmation lists the branches deleted with the service", + args: []string{"service", "delete", "svc-12345"}, + opts: []runOption{withIsTerminal(true), withStdin("svc-12345\n")}, + mock: setupFluidDelete(sampleBranch(), otherBranch), + wantStderr: "This service has 2 branches, which will also be deleted:\n" + + " 'test-branch' (br-12345)\n" + + " 'other-branch' (br-67890)\n" + + confirmPrompt + + "Service 'test-service' (svc-12345) deleted, along with its 2 branches:\n" + + " 'test-branch' (br-12345)\n" + + " 'other-branch' (br-67890)\n", + }, + { + name: "confirmation lists a single branch", + args: []string{"service", "delete", "svc-12345"}, + opts: []runOption{withIsTerminal(true), withStdin("svc-12345\n")}, + mock: setupFluidDelete(sampleBranch()), + wantStderr: "This service has 1 branch, which will also be deleted:\n" + + " 'test-branch' (br-12345)\n" + + confirmPrompt + + "Service 'test-service' (svc-12345) deleted, along with its 1 branch:\n" + + " 'test-branch' (br-12345)\n", + }, + { + name: "fluid service without branches lists none", + args: []string{"service", "delete", "svc-12345"}, + opts: []runOption{withIsTerminal(true), withStdin("svc-12345\n")}, + mock: setupFluidDelete(), + wantStderr: confirmPrompt + "Service 'test-service' (svc-12345) deleted.\n", + }, + { + // The listing only informs the decision, so a failure still lets + // the user decide. + name: "branch listing fails", + args: []string{"service", "delete", "svc-12345"}, + opts: []runOption{withIsTerminal(true), withStdin("svc-12345\n")}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + setupDelete("svc-12345", fluidPlatform)(m) + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "svc-12345", nil). + Return(nil, errors.New("connection refused")) + }, + wantStderr: "Warning: failed to list branches: failed to list branches: connection refused\n" + + "Any branches of this service will also be deleted.\n" + + confirmPrompt + + "Service 'test-service' (svc-12345) deleted.\n", + }, + { + // Without a prompt, the branches are still listed once the service + // is deleted. + name: "confirm flag still lists the deleted branches", + args: []string{"service", "delete", "svc-12345", "--confirm"}, + mock: setupFluidDelete(sampleBranch(), otherBranch), + wantStderr: "Service 'test-service' (svc-12345) deleted, along with its 2 branches:\n" + + " 'test-branch' (br-12345)\n" + + " 'other-branch' (br-67890)\n", + }, { name: "confirm flag skips prompt", args: []string{"service", "delete", "svc-12345", "--confirm"}, @@ -155,5 +232,18 @@ func TestServiceDeleteCmd(t *testing.T) { mock: setupDelete("svc-12345"), wantStderr: "Service 'test-service' (svc-12345) deleted.\n", }, + { + // Branches are a preview, so the help mentions them only behind + // the gate. + name: "help mentions branches when experimental", + args: []string{"service", "delete", "--help"}, + opts: []runOption{withExperimental()}, + wantStdout: matchHelp("Deleting a service also deletes all of its branches."), + }, + { + name: "help omits branches by default", + args: []string{"service", "delete", "--help"}, + wantStdout: matchHelp("Delete a database service permanently.", "branch"), + }, }) } diff --git a/internal/cmd/service_fork.go b/internal/cmd/service_fork.go index 03815b52..0905511e 100644 --- a/internal/cmd/service_fork.go +++ b/internal/cmd/service_fork.go @@ -190,7 +190,7 @@ default service from your configuration.`, cmd.PrintErrf("Service ID: %s\n", forkedServiceID) // Save password immediately after service fork - passwordSaved := handlePasswordSaving(cmd, cfg, forkedService, util.Deref(forkedService.InitialPassword)) + passwordSaved := handlePasswordSaving(cmd, cfg, common.ServicePasswordTarget(forkedService), util.Deref(forkedService.InitialPassword)) // Set as default service unless --no-set-default is used if !noSetDefault { @@ -225,7 +225,10 @@ default service from your configuration.`, } } - if err := outputService(cmd, cfg, forkedService, app.Experimental, cfg.Output, withPassword, false); err != nil { + if err := outputService(cmd, cfg, forkedService, serviceOutputOptions{ + Experimental: app.Experimental, + WithPassword: withPassword, + }); err != nil { cmd.PrintErrf("Warning: Failed to output service details: %v\n", err) } diff --git a/internal/cmd/service_get.go b/internal/cmd/service_get.go index 87af176d..126b35d2 100644 --- a/internal/cmd/service_get.go +++ b/internal/cmd/service_get.go @@ -53,8 +53,24 @@ resource usage.`, } service := *resolved + // Branches are a preview, so they're counted only behind the gate. + // The rest of the output doesn't depend on the count. + var branchCount *int + if app.Experimental && common.CanHaveBranches(service) { + if count, err := common.CountBranches(cmd.Context(), client, projectID, service.ServiceID); err != nil { + cmd.PrintErrf("Warning: failed to count branches: %v\n", err) + } else { + branchCount = &count + } + } + // Output service in requested format - return outputService(cmd, cfg, service, app.Experimental, cfg.Output, withPassword, true) + return outputService(cmd, cfg, service, serviceOutputOptions{ + Experimental: app.Experimental, + WithPassword: withPassword, + RequirePassword: true, + BranchCount: branchCount, + }) }, } diff --git a/internal/cmd/service_get_test.go b/internal/cmd/service_get_test.go index e65f4341..815e780c 100644 --- a/internal/cmd/service_get_test.go +++ b/internal/cmd/service_get_test.go @@ -45,6 +45,18 @@ func TestServiceGetCmd(t *testing.T) { } } + // setupGetFluid also lists the fluid service's two branches, which only + // service get does, behind the experimental gate. + setupGetFluid := func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRef(m, "svc-12345", fluidService) + branches := []api.Branch{sampleBranch(), sampleBranch()} + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "svc-12345", nil). + Return(&api.GetBranchesResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &branches, + }, nil) + } + runCmdTests(t, []cmdTest{ { name: "not logged in", @@ -300,12 +312,12 @@ status: READY `, }, { - // The platform and IOPS are preview fields: the output cases above - // carry them from sampleService and drop them, while these show them - // with the gate. - name: "experimental table output shows platform and IOPS", + // The platform, IOPS, and branch count are preview fields: the + // output cases above carry a platform and IOPS from sampleService + // and drop them, while these show all three with the gate. + name: "experimental table output shows platform, IOPS, and branches", args: []string{"service", "get", "svc-12345"}, - mock: setupGet(fluidService), + mock: setupGetFluid, opts: []runOption{experimental}, wantStdout: `┌───────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────┐ │ PROPERTY │ VALUE │ @@ -319,6 +331,7 @@ status: READY │ CPU │ 1 cores (1000m) │ │ Memory │ 4 GB │ │ IOPS │ 6000 │ +│ Branches │ 2 │ │ Direct Endpoint │ svc-12345.project.tsdb.cloud.timescale.com:5432 │ │ Created │ 2025-01-15 10:30:00 UTC │ │ Connection String │ postgresql://tsdbadmin@svc-12345.project.tsdb.cloud.timescale.com:5432/tsdb?sslmode=require │ @@ -327,9 +340,9 @@ status: READY `, }, { - name: "experimental json output includes platform and IOPS", + name: "experimental json output includes platform, IOPS, and branches", args: []string{"service", "get", "svc-12345", "-o", "json"}, - mock: setupGet(fluidService), + mock: setupGetFluid, opts: []runOption{experimental}, wantStdout: `{ "created": "2025-01-15T10:30:00Z", @@ -354,6 +367,7 @@ status: READY "service_id": "svc-12345", "service_type": "TIMESCALEDB", "status": "READY", + "branch_count": 2, "role": "tsdbadmin", "host": "svc-12345.project.tsdb.cloud.timescale.com", "port": 5432, @@ -364,11 +378,12 @@ status: READY `, }, { - name: "experimental yaml output includes platform and IOPS", + name: "experimental yaml output includes platform, IOPS, and branches", args: []string{"service", "get", "svc-12345", "-o", "yaml"}, - mock: setupGet(fluidService), + mock: setupGetFluid, opts: []runOption{experimental}, - wantStdout: `connection_string: postgresql://tsdbadmin@svc-12345.project.tsdb.cloud.timescale.com:5432/tsdb?sslmode=require + wantStdout: `branch_count: 2 +connection_string: postgresql://tsdbadmin@svc-12345.project.tsdb.cloud.timescale.com:5432/tsdb?sslmode=require console_url: https://console.cloud.tigerdata.com/dashboard/services/svc-12345 created: "2025-01-15T10:30:00Z" database: tsdb @@ -393,6 +408,35 @@ service_type: TIMESCALEDB status: READY `, }, + { + // Only fluid services can be branched, so a standard one isn't + // counted. The mock fails the test on any branch listing. + name: "experimental standard service lists no branches", + args: []string{"service", "get", "svc-12345", "-o", "env"}, + mock: setupGet(sampleService()), + opts: []runOption{experimental}, + wantStdout: "PGHOST=svc-12345.project.tsdb.cloud.timescale.com\nPGPORT=5432\nPGDATABASE=tsdb\nPGUSER=tsdbadmin\n", + }, + { + name: "fluid service lists no branches without the gate", + args: []string{"service", "get", "svc-12345", "-o", "env"}, + mock: setupGet(fluidService), + wantStdout: "PGHOST=svc-12345.project.tsdb.cloud.timescale.com\nPGPORT=5432\nPGDATABASE=tsdb\nPGUSER=tsdbadmin\n", + }, + { + // The count is the only thing the branch listing adds, so the + // service is still shown without it. + name: "branch count fails", + args: []string{"service", "get", "svc-12345", "-o", "env"}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveRef(m, "svc-12345", fluidService) + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "svc-12345", nil). + Return(nil, errors.New("connection refused")) + }, + opts: []runOption{experimental}, + wantStdout: "PGHOST=svc-12345.project.tsdb.cloud.timescale.com\nPGPORT=5432\nPGDATABASE=tsdb\nPGUSER=tsdbadmin\n", + wantStderr: "Warning: failed to count branches: failed to list branches: connection refused\n", + }, { name: "env output", args: []string{"service", "get", "svc-12345", "-o", "env"}, @@ -455,6 +499,33 @@ status: READY `, wantStderr: "Warning: Failed to get connection details: service endpoint not available\n", }, + { + // No port is guessed, for the direct endpoint or the pooler, so + // an endpoint without one isn't shown or connected to. + name: "endpoints without a port", + args: []string{"service", "get", "svc-12345"}, + mock: setupGet(sampleService(func(s *api.Service) { + s.Endpoint.Port = nil + s.ConnectionPooler = &api.ConnectionPooler{ + Endpoint: &api.Endpoint{Host: new("svc-12345.project.pooler.tsdb.cloud.timescale.com")}, + } + })), + wantStdout: `┌─────────────┬──────────────────────────────────────────────────────────────────┐ +│ PROPERTY │ VALUE │ +├─────────────┼──────────────────────────────────────────────────────────────────┤ +│ Service ID │ svc-12345 │ +│ Name │ test-service │ +│ Status │ READY │ +│ Type │ TIMESCALEDB │ +│ Region │ us-east-1 │ +│ CPU │ 1 cores (1000m) │ +│ Memory │ 4 GB │ +│ Created │ 2025-01-15 10:30:00 UTC │ +│ Console URL │ https://console.cloud.tigerdata.com/dashboard/services/svc-12345 │ +└─────────────┴──────────────────────────────────────────────────────────────────┘ +`, + wantStderr: "Warning: Failed to get connection details: endpoint port not available\n", + }, { // The ref reaches the API as typed: no client-side classification // stands between a name and the service it names. diff --git a/internal/cmd/service_list.go b/internal/cmd/service_list.go index e08cc31d..d177bc9c 100644 --- a/internal/cmd/service_list.go +++ b/internal/cmd/service_list.go @@ -44,16 +44,7 @@ func buildServiceListCmd(app *common.App) *cobra.Command { if resp.JSON200 == nil { return fmt.Errorf("empty response from API") } - services := *resp.JSON200 - - if len(services) == 0 { - cmd.PrintErrln("No services found.") - cmd.PrintErrln("Create your first service with: tiger service create") - return nil - } - - // Output services in requested format - return outputServices(cmd, cfg, services, app.Experimental, cfg.Output) + return outputServices(cmd, cfg, *resp.JSON200, serviceOutputOptions{Experimental: app.Experimental}) }, } @@ -63,13 +54,13 @@ func buildServiceListCmd(app *common.App) *cobra.Command { return cmd } -// outputServices formats and outputs the services list based on the specified format -func outputServices(cmd *cobra.Command, cfg *config.Config, services []api.Service, experimental bool, format string) error { - outputServices := prepareServicesForOutput(cmd, cfg, services, experimental) +// outputServices outputs the services list in the configured output format. +func outputServices(cmd *cobra.Command, cfg *config.Config, services []api.Service, opts serviceOutputOptions) error { + outputServices := prepareServicesForOutput(cmd, cfg, services, opts) outputWriter := cmd.OutOrStdout() - switch strings.ToLower(format) { + switch strings.ToLower(cfg.Output) { case "json": return util.SerializeToJSON(outputWriter, outputServices) case "yaml": @@ -77,21 +68,26 @@ func outputServices(cmd *cobra.Command, cfg *config.Config, services []api.Servi case "env": return fmt.Errorf("environment variable output is not supported for multiple services") default: // table format (default) - return outputServicesTable(outputServices, outputWriter) + if len(outputServices) == 0 { + cmd.PrintErrln("No services found.") + cmd.PrintErrln("Create your first service with: tiger service create") + return nil + } + return outputServicesTable(outputWriter, outputServices) } } // prepareServicesForOutput creates copies of services with sensitive fields removed -func prepareServicesForOutput(cmd *cobra.Command, cfg *config.Config, services []api.Service, experimental bool) []OutputService { +func prepareServicesForOutput(cmd *cobra.Command, cfg *config.Config, services []api.Service, opts serviceOutputOptions) []OutputService { prepared := make([]OutputService, len(services)) for i, service := range services { - prepared[i] = prepareServiceForOutput(cmd, cfg, service, experimental, false) + prepared[i] = prepareServiceForOutput(cmd, cfg, service, opts) } return prepared } // outputServicesTable outputs services in a formatted table using tablewriter -func outputServicesTable(services []OutputService, output io.Writer) error { +func outputServicesTable(output io.Writer, services []OutputService) error { table := tablewriter.NewWriter(output) table.Header("SERVICE ID", "NAME", "STATUS", "TYPE", "REGION", "CREATED") diff --git a/internal/cmd/service_list_test.go b/internal/cmd/service_list_test.go index f0b79009..90da403f 100644 --- a/internal/cmd/service_list_test.go +++ b/internal/cmd/service_list_test.go @@ -91,6 +91,20 @@ func TestServiceListCmd(t *testing.T) { mock: setupList(nil), wantStderr: noServicesStderr, }, + { + // Structured output is always structured, so an empty list is + // still a list rather than a message. + name: "empty list json", + args: []string{"service", "list", "-o", "json"}, + mock: setupList(nil), + wantStdout: "[]\n", + }, + { + name: "empty list yaml", + args: []string{"service", "list", "-o", "yaml"}, + mock: setupList(nil), + wantStdout: "[]\n", + }, { name: "table output", args: []string{"service", "list"}, diff --git a/internal/cmd/service_metrics_details.go b/internal/cmd/service_metrics_details.go index 1071a54f..6fe568e7 100644 --- a/internal/cmd/service_metrics_details.go +++ b/internal/cmd/service_metrics_details.go @@ -92,13 +92,13 @@ func outputMetricDetails(output io.Writer, format string, details api.MetricDeta case "yaml": return util.SerializeToYAML(output, details) default: - return outputMetricDetailsTable(details, output) + return outputMetricDetailsTable(output, details) } } // outputMetricDetailsTable renders metric metadata as a PROPERTY/VALUE table, // plus a LABEL/DESCRIPTION table when the metric has labels. -func outputMetricDetailsTable(details api.MetricDetails, output io.Writer) error { +func outputMetricDetailsTable(output io.Writer, details api.MetricDetails) error { table := tablewriter.NewWriter(output) table.Header("PROPERTY", "VALUE") diff --git a/internal/cmd/service_metrics_series.go b/internal/cmd/service_metrics_series.go index eacbe454..f41494b9 100644 --- a/internal/cmd/service_metrics_series.go +++ b/internal/cmd/service_metrics_series.go @@ -238,7 +238,7 @@ func renderMetricSeries(cmd *cobra.Command, output string, series []api.MetricSe return util.SerializeToYAML(out, series) default: if len(series) == 0 { - cmd.Println("No metric data returned for the requested window.") + cmd.PrintErrln("No metric data returned for the requested window.") return nil } table := tablewriter.NewWriter(out) diff --git a/internal/cmd/service_metrics_series_test.go b/internal/cmd/service_metrics_series_test.go index 8a79d712..2c3f235b 100644 --- a/internal/cmd/service_metrics_series_test.go +++ b/internal/cmd/service_metrics_series_test.go @@ -49,7 +49,7 @@ func TestServiceMetricsSeriesCmd(t *testing.T) { name: "key=value filter builds an EQUAL request", args: args("ordinal=0"), mock: expectSeries([]api.MetricLabelFilter{{Key: "ordinal", Value: "0"}}), - wantStdout: noDataMsg, + wantStderr: noDataMsg, }, { name: "key!=value filter builds a NOT_EQUAL request", @@ -57,7 +57,7 @@ func TestServiceMetricsSeriesCmd(t *testing.T) { mock: expectSeries([]api.MetricLabelFilter{ {Key: "role", Value: "replica", MatchType: new(api.MetricMatchTypeNOTEQUAL)}, }), - wantStdout: noDataMsg, + wantStderr: noDataMsg, }, { name: "malformed filter", @@ -102,7 +102,7 @@ func TestServiceMetricsSeriesCmd(t *testing.T) { JSON200: &empty, }, nil) }, - wantStdout: noDataMsg, + wantStderr: noDataMsg, }, { // synctest's bubble clock always starts at 2000-01-01 UTC, so the @@ -124,7 +124,7 @@ func TestServiceMetricsSeriesCmd(t *testing.T) { JSON200: &empty, }, nil) }, - wantStdout: noDataMsg, + wantStderr: noDataMsg, }, { // Defaulting only applies when both --from and --to are omitted — @@ -153,7 +153,7 @@ func TestServiceMetricsSeriesCmd(t *testing.T) { JSON200: &empty, }, nil) }, - wantStdout: noDataMsg, + wantStderr: noDataMsg, }, }) } diff --git a/internal/cmd/service_test.go b/internal/cmd/service_test.go index 8f4764bd..7c5f7571 100644 --- a/internal/cmd/service_test.go +++ b/internal/cmd/service_test.go @@ -7,7 +7,6 @@ import ( "github.com/timescale/tiger-cli/internal/api" "github.com/timescale/tiger-cli/internal/api/mocks" - "github.com/timescale/tiger-cli/internal/common" ) func TestServiceCommandAliases(t *testing.T) { @@ -89,22 +88,6 @@ func matchHelp(want string, absent ...string) matcher { }) } -// checkStoredPassword returns a check asserting the password stored in the -// (mock) keyring for the given service. -func checkStoredPassword(serviceID, want string) checkFunc { - return func(t *testing.T, result cmdResult) { - t.Helper() - svc := api.Service{ServiceID: serviceID, ProjectID: testProjectID} - got, err := (&common.KeyringStorage{}).Get(svc, "tsdbadmin") - if err != nil { - t.Fatalf("failed to read stored password: %v", err) - } - if got != want { - t.Errorf("stored password = %q, want %q", got, want) - } - } -} - // envTag returns a sampleService override setting the service's environment // tag, which is what the prod read-only gate reads off the resolved service. func envTag(tag string) func(*api.Service) { diff --git a/internal/cmd/service_update_password.go b/internal/cmd/service_update_password.go index 555a3d8c..84238edb 100644 --- a/internal/cmd/service_update_password.go +++ b/internal/cmd/service_update_password.go @@ -89,7 +89,7 @@ so update the password on the primary instead.`, if autoGenerate { // Auto-generate password using existing function - if _, err := resetServicePassword(ctx, cmd, cfg, client, service, "tsdbadmin", ""); err != nil { + if _, err := resetPassword(ctx, cmd, cfg, client, common.ServiceCredentials(service), "tsdbadmin", ""); err != nil { return err } } else if password == "" { @@ -97,12 +97,12 @@ so update the password on the primary instead.`, if !util.IsTerminal(cmd.InOrStdin()) || !util.IsTerminal(cmd.ErrOrStderr()) { return fmt.Errorf("TTY not detected - use --new-password flag, --auto-generate flag, or TIGER_NEW_PASSWORD environment variable") } - _, err := promptAndResetPassword(ctx, cmd, cfg, client, service, "tsdbadmin") + _, err := promptAndResetPassword(ctx, cmd, cfg, client, common.ServiceCredentials(service), "tsdbadmin") if err != nil { return err } } else { - if _, err := resetServicePassword(ctx, cmd, cfg, client, service, "tsdbadmin", password); err != nil { + if _, err := resetPassword(ctx, cmd, cfg, client, common.ServiceCredentials(service), "tsdbadmin", password); err != nil { return err } } diff --git a/internal/cmd/service_update_password_test.go b/internal/cmd/service_update_password_test.go index 50c6fcfb..2d09cb81 100644 --- a/internal/cmd/service_update_password_test.go +++ b/internal/cmd/service_update_password_test.go @@ -5,8 +5,6 @@ import ( "net/http" "testing" - "go.uber.org/mock/gomock" - "github.com/timescale/tiger-cli/internal/api" "github.com/timescale/tiger-cli/internal/api/mocks" "github.com/timescale/tiger-cli/internal/common" @@ -22,27 +20,19 @@ func TestServiceUpdatePasswordCmd(t *testing.T) { return func(m *mocks.MockClientWithResponsesInterface) { m.EXPECT().UpdatePasswordWithResponse(validCtx, testProjectID, "svc-12345", api.UpdatePasswordInput{Password: password}). Return(&api.UpdatePasswordResponse{ - HTTPResponse: httpResponse(http.StatusOK), + HTTPResponse: httpResponse(http.StatusNoContent), }, nil) } } - // setupUpdateAny is for the paths that generate a random password. - setupUpdateAny := func(m *mocks.MockClientWithResponsesInterface) { - m.EXPECT().UpdatePasswordWithResponse(validCtx, testProjectID, "svc-12345", gomock.Any()). - Return(&api.UpdatePasswordResponse{ - HTTPResponse: httpResponse(http.StatusOK), - }, nil) - } - // storedPassword reads the password the command saved for the sample // service in the per-test mock keyring ("" if none is stored). The shared - // checkStoredPassword helper can't express "nothing stored" or "random, - // but 32 characters", so those cases read the keyring through this. + // checkStoredPassword helper can't express "nothing stored", so that case + // reads the keyring through this. storedPassword := func(t *testing.T) string { t.Helper() storage := &common.KeyringStorage{} - password, err := storage.Get(api.Service{ProjectID: testProjectID, ServiceID: "svc-12345"}, "tsdbadmin") + password, err := storage.Get(common.PasswordTarget{ProjectID: testProjectID, ID: "svc-12345"}, "tsdbadmin") if err != nil { return "" } @@ -179,6 +169,17 @@ func TestServiceUpdatePasswordCmd(t *testing.T) { wantErr: "permission denied", checks: []checkFunc{checkExitCode(common.ExitPermissionDenied)}, }, + { + // The endpoint answers success with a 204 and nothing else. + name: "unexpected 200 is an error", + args: []string{"service", "update-password", "svc-12345", "--new-password", "newpass123"}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + setupGet(m) + m.EXPECT().UpdatePasswordWithResponse(validCtx, testProjectID, "svc-12345", api.UpdatePasswordInput{Password: "newpass123"}). + Return(&api.UpdatePasswordResponse{HTTPResponse: httpResponse(http.StatusOK)}, nil) + }, + wantErr: "unknown error", + }, { name: "new-password flag", args: []string{"service", "update-password", "svc-12345", "--new-password", "newpass123"}, @@ -203,16 +204,20 @@ func TestServiceUpdatePasswordCmd(t *testing.T) { { name: "auto-generate", args: []string{"service", "update-password", "svc-12345", "--auto-generate"}, + opts: []runOption{withGenerateSecurePassword(32, "generated-pw", nil)}, mock: func(m *mocks.MockClientWithResponsesInterface) { setupGet(m) - setupUpdateAny(m) + setupUpdate("generated-pw")(m) }, wantStderr: savedStderr, - checks: []checkFunc{func(t *testing.T, result cmdResult) { - if got := storedPassword(t); len(got) != 32 { - t.Errorf("stored password length = %d, want 32", len(got)) - } - }}, + checks: []checkFunc{checkStoredPassword("svc-12345", "generated-pw")}, + }, + { + name: "password generation fails", + args: []string{"service", "update-password", "svc-12345", "--auto-generate"}, + opts: []runOption{withGenerateSecurePassword(32, "", errors.New("entropy unavailable"))}, + mock: setupGet, + wantErr: "failed to generate new password: entropy unavailable", }, { name: "interactive prompt", @@ -228,18 +233,14 @@ func TestServiceUpdatePasswordCmd(t *testing.T) { { name: "interactive prompt empty generates", args: []string{"service", "update-password", "svc-12345"}, - opts: []runOption{withIsTerminal(true), withReadPassword("")}, + opts: []runOption{withIsTerminal(true), withReadPassword(""), withGenerateSecurePassword(32, "generated-pw", nil)}, mock: func(m *mocks.MockClientWithResponsesInterface) { setupGet(m) - setupUpdateAny(m) + setupUpdate("generated-pw")(m) }, wantStderr: "Enter new password (leave empty to generate): \n" + savedStderr, - checks: []checkFunc{func(t *testing.T, result cmdResult) { - if got := storedPassword(t); len(got) != 32 { - t.Errorf("stored password length = %d, want 32", len(got)) - } - }}, + checks: []checkFunc{checkStoredPassword("svc-12345", "generated-pw")}, }, { name: "password storage none", diff --git a/internal/common/branch.go b/internal/common/branch.go new file mode 100644 index 00000000..e53fbfd2 --- /dev/null +++ b/internal/common/branch.go @@ -0,0 +1,68 @@ +package common + +import ( + "context" + "errors" + "fmt" + "net/http" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/util" +) + +// BranchEnvironmentTag is the tag every branch is judged by. Branches have no +// environment of their own and always count as DEV, so read_only=prod leaves +// them writable even when their parent is tagged PROD: branching production +// to work against it safely is the main use case. read_only=all still blocks +// them. +const BranchEnvironmentTag = api.EnvironmentTagDEV + +// ResolveBranch resolves ref, a branch ID or name, to the branch it names. +// The branch carries its parent's ID, which every other branch endpoint needs. +func ResolveBranch(ctx context.Context, client api.ClientWithResponsesInterface, projectID, ref string) (*api.Branch, error) { + resolved, err := resolve(ctx, client, projectID, ref, new(api.ResolveTypeBRANCH), "branch") + if err != nil { + return nil, err + } + return resolved.Branch, nil +} + +// CanHaveBranches reports whether service can have branches, so callers can +// skip listing them for services that never do. Only fluid services can be +// branched. +func CanHaveBranches(service api.Service) bool { + return util.Deref(service.Platform) == api.ServicePlatformFLUID +} + +// ListBranches returns serviceID's branches. +func ListBranches(ctx context.Context, client api.ClientWithResponsesInterface, projectID, serviceID string) ([]api.Branch, error) { + resp, err := client.GetBranchesWithResponse(ctx, projectID, serviceID, nil) + if err != nil { + return nil, fmt.Errorf("failed to list branches: %w", err) + } + if resp.StatusCode() != http.StatusOK { + return nil, ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON200 == nil { + return nil, errors.New("empty response from API") + } + return *resp.JSON200, nil +} + +// CountBranches returns how many branches serviceID has. +func CountBranches(ctx context.Context, client api.ClientWithResponsesInterface, projectID, serviceID string) (int, error) { + branches, err := ListBranches(ctx, client, projectID, serviceID) + if err != nil { + return 0, err + } + return len(branches), nil +} + +// FormatBranchCount phrases a number of branches, as "1 branch" or +// "2 branches". +func FormatBranchCount(n int) string { + if n == 1 { + return "1 branch" + } + return fmt.Sprintf("%d branches", n) +} diff --git a/internal/common/connection.go b/internal/common/connection.go index 6f6cecfe..5a57067a 100644 --- a/internal/common/connection.go +++ b/internal/common/connection.go @@ -15,12 +15,12 @@ func hasPooler(pooler *api.ConnectionPooler) bool { // ReplicaPoolerWarning returns the warning to show when pooling was requested // for a read replica with no pooler (the connection falls back to direct), or "" -// otherwise — including for a non-replica target, so callers need no IsReplica guard. +// otherwise — including for a non-replica target, so callers need no kind check. func ReplicaPoolerWarning(target *ConnectionTarget, pooled bool) string { - if !target.IsReplica || !pooled || hasPooler(target.ConnectionService.ConnectionPooler) { + if target.Kind != TargetReadReplica || !pooled || hasPooler(target.Pooler) { return "" } - return fmt.Sprintf("read replica %q has no connection pooler; connecting directly instead", target.ConnectionService.Name) + return fmt.Sprintf("read replica %q has no connection pooler; connecting directly instead", target.Name) } // ConnectionDetailsOptions configures how the connection string is built @@ -60,27 +60,19 @@ type ConnectionDetails struct { // activates Tiger Cloud's immutable read-only connection mode. const readOnlyConnectionOption = "options=-c%20tsdb_admin.read_only_connection%3Dtrue" -// GetConnectionDetails builds the connection details for a service: it selects +// GetConnectionDetails builds the connection details for a target: it selects // the endpoint (the pooler when requested and available, otherwise the direct -// one) and looks up the password when one is wanted. -func GetConnectionDetails(cfg *config.Config, service api.Service, opts ConnectionDetailsOptions) (*ConnectionDetails, error) { - return getConnectionDetails(cfg, service, service, opts) -} - -// getConnectionDetails is GetConnectionDetails with the endpoint and the -// credentials taken from different services: connService supplies the endpoint -// and pooler, credService the password. Only a read replica needs them to -// differ — it connects to its own endpoint with the parent primary's -// credentials — so ConnectionTarget.Details is the only caller. -func getConnectionDetails(cfg *config.Config, connService, credService api.Service, opts ConnectionDetailsOptions) (*ConnectionDetails, error) { - if connService.Endpoint == nil { +// one) and looks up the password, from the target's credentials, when one is +// wanted. ConnectionTarget.Details adds the pooler check connecting needs. +func GetConnectionDetails(cfg *config.Config, target *ConnectionTarget, opts ConnectionDetailsOptions) (*ConnectionDetails, error) { + if target.Endpoint == nil { return nil, fmt.Errorf("service endpoint not available") } - endpoint := connService.Endpoint + endpoint := target.Endpoint isPooler := false - if opts.Pooled && hasPooler(connService.ConnectionPooler) { - endpoint = connService.ConnectionPooler.Endpoint + if opts.Pooled && hasPooler(target.Pooler) { + endpoint = target.Pooler.Endpoint isPooler = true } @@ -105,7 +97,7 @@ func getConnectionDetails(cfg *config.Config, connService, credService api.Servi if opts.WithPassword { if opts.InitialPassword != "" { details.Password = opts.InitialPassword - } else if password, err := GetPassword(cfg, credService, opts.Role); err == nil { + } else if password, err := GetPassword(cfg, target.Credentials.PasswordTarget, opts.Role); err == nil { details.Password = password } } @@ -129,12 +121,12 @@ func (d *ConnectionDetails) String() string { return fmt.Sprintf("postgresql://%s@%s:%d/%s?%s", userinfo, d.Host, d.Port, d.Database, query) } -// GetPassword fetches the password for the specified service from the -// configured password storage mechanism. It returns an error if it fails to -// find the password. -func GetPassword(cfg *config.Config, service api.Service, role string) (string, error) { +// GetPassword fetches the password stored for the target from the configured +// password storage mechanism. It returns an error if it fails to find the +// password. +func GetPassword(cfg *config.Config, target PasswordTarget, role string) (string, error) { storage := GetPasswordStorage(cfg) - password, err := storage.Get(service, role) + password, err := storage.Get(target, role) if err != nil { // Provide specific error messages based on storage type switch storage.(type) { diff --git a/internal/common/connection_target.go b/internal/common/connection_target.go new file mode 100644 index 00000000..26764c76 --- /dev/null +++ b/internal/common/connection_target.go @@ -0,0 +1,221 @@ +package common + +import ( + "context" + "fmt" + "strings" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/config" + "github.com/timescale/tiger-cli/internal/util" +) + +// ConnectionTarget is what a database connection goes to: a service, one of +// its read replica sets, or a branch, reduced to what connecting to it and +// reporting on it need. +type ConnectionTarget struct { + Kind TargetKind + ID string + Name string + + // Status decides whether the target accepts connections (see CheckReady). + Status api.DeployStatus + + // Environment alone decides a session's read-only verdict, so a replica is + // judged on its own tag rather than its primary's, and a branch, which has + // none, counts as DEV. + Environment api.EnvironmentTag + + Endpoint *api.Endpoint + Pooler *api.ConnectionPooler + + // Credentials is whose passwords the connection uses: the target itself + // for a service or a branch, the primary for a read replica, which shares + // them. + Credentials CredentialOwner +} + +// CredentialOwner is the service or branch whose passwords a connection uses. +type CredentialOwner struct { + PasswordTarget + + // Name names the owner in messages. + Name string + + // BranchServiceID is the parent service of an owner that is a branch, + // whose password endpoint is nested under it. It is empty for a service. + BranchServiceID string + + // Environment is the owner's own tag, which decides whether read-only mode + // lets its passwords be changed. + Environment api.EnvironmentTag +} + +// TargetKind is the kind of resource a ConnectionTarget is. +type TargetKind int + +const ( + TargetService TargetKind = iota + TargetReadReplica + TargetBranch +) + +// NewServiceConnectionTarget returns the ConnectionTarget of a service, +// connecting to its own endpoint with its own credentials. +func NewServiceConnectionTarget(service api.Service) *ConnectionTarget { + return &ConnectionTarget{ + Kind: TargetService, + ID: service.ServiceID, + Name: service.Name, + Status: service.Status, + Environment: ServiceEnvironmentTag(service), + Endpoint: service.Endpoint, + Pooler: service.ConnectionPooler, + Credentials: ServiceCredentials(service), + } +} + +// ServiceCredentials returns a service as the owner of the credentials its +// connections use, which its read replica sets share. +func ServiceCredentials(service api.Service) CredentialOwner { + return CredentialOwner{ + PasswordTarget: ServicePasswordTarget(service), + Name: service.Name, + Environment: ServiceEnvironmentTag(service), + } +} + +// NewBranchConnectionTarget returns the ConnectionTarget of a branch, which +// has its own credentials and no pooler. +func NewBranchConnectionTarget(branch api.Branch) *ConnectionTarget { + return &ConnectionTarget{ + Kind: TargetBranch, + ID: branch.ID, + Name: branch.Name, + Status: branch.Status, + Environment: BranchEnvironmentTag, + Endpoint: branch.Endpoint, + Credentials: BranchCredentials(branch), + } +} + +// BranchCredentials returns a branch as the owner of the credentials its +// connections use. A branch has its own, separate from its parent's. +func BranchCredentials(branch api.Branch) CredentialOwner { + return CredentialOwner{ + PasswordTarget: BranchPasswordTarget(branch), + Name: branch.Name, + BranchServiceID: branch.ServiceID, + Environment: BranchEnvironmentTag, + } +} + +// NewReplicaConnectionTarget returns the ConnectionTarget of a read replica +// set. The replica supplies the endpoint, and credentials is its primary's +// (see ServiceCredentials), since a replica shares its primary's passwords. +func NewReplicaConnectionTarget(replica api.ReadReplicaSet, credentials CredentialOwner) *ConnectionTarget { + // Replica sets carry their own status enum. Map only "active" onto READY so + // a readiness check on this target (CheckReady) treats every other state — + // creating, resizing, error — as not ready. + status := api.DeployStatus("") + if replica.Status == api.ReadReplicaSetStatusActive { + status = api.DeployStatusREADY + } + + var environment *string + if replica.Metadata != nil { + environment = replica.Metadata.Environment + } + + return &ConnectionTarget{ + Kind: TargetReadReplica, + ID: replica.ID, + Name: replica.Name, + Status: status, + Environment: environmentTag(environment), + Endpoint: replica.Endpoint, + Pooler: replica.ConnectionPooler, + Credentials: credentials, + } +} + +// CheckReady returns nil only when the target accepts connections, and +// ErrPaused or ErrNotReady otherwise (see the CheckReady function). +func (t *ConnectionTarget) CheckReady() error { + return CheckReady(t.Status) +} + +// Details builds the target's connection details. A requested-but-unavailable +// pooler is a hard error for a service but silently falls back to direct for a +// replica (see ReplicaPoolerWarning). +func (t *ConnectionTarget) Details(cfg *config.Config, opts ConnectionDetailsOptions) (*ConnectionDetails, error) { + details, err := GetConnectionDetails(cfg, t, opts) + if err != nil { + return nil, fmt.Errorf("failed to build connection string: %w", err) + } + if opts.Pooled && !details.IsPooler && t.Kind != TargetReadReplica { + return nil, fmt.Errorf("connection pooler not available for this service") + } + return details, nil +} + +// ResolveConnectionTarget resolves ref, an ID or a name, to the service, +// branch, or read replica set it names and works out whose credentials it +// uses. Branches are a preview, so unless allowBranches is set a ref naming +// one is reported as not found. +func ResolveConnectionTarget(ctx context.Context, client api.ClientWithResponsesInterface, projectID, ref string, allowBranches bool) (*ConnectionTarget, error) { + kind := connectionTargetKind(allowBranches) + resolved, err := resolve(ctx, client, projectID, ref, nil, kind) + if err != nil { + return nil, err + } + + switch { + case resolved.Branch != nil: + if !allowBranches { + return nil, ExitWithCode(ExitServiceNotFound, fmt.Errorf("%s '%s' not found", kind, ref)) + } + return NewBranchConnectionTarget(*resolved.Branch), nil + case resolved.ReadReplicaSet != nil: + replica := *resolved.ReadReplicaSet + parent, err := GetService(ctx, client, projectID, replica.ServiceID) + if err != nil { + return nil, fmt.Errorf("failed to fetch parent service %q for read replica: %w", replica.ServiceID, err) + } + return NewReplicaConnectionTarget(replica, ServiceCredentials(*parent)), nil + default: + return NewServiceConnectionTarget(*resolved.Service), nil + } +} + +// ResolveConnectionTargetByID is ResolveConnectionTarget for a caller that +// takes only IDs: a ref that matched by name is reported as not found. +func ResolveConnectionTargetByID(ctx context.Context, client api.ClientWithResponsesInterface, projectID, id string, allowBranches bool) (*ConnectionTarget, error) { + target, err := ResolveConnectionTarget(ctx, client, projectID, id, allowBranches) + if err != nil { + return nil, err + } + if target.ID != id { + return nil, ExitWithCode(ExitServiceNotFound, fmt.Errorf("%s '%s' not found", connectionTargetKind(allowBranches), id)) + } + return target, nil +} + +// connectionTargetKind names what ResolveConnectionTarget looks for, in its +// errors. Branches are left out unless they are allowed. +func connectionTargetKind(allowBranches bool) string { + if allowBranches { + return "service, branch, or read replica set" + } + return "service or read replica set" +} + +// environmentTag reads an optional environment tag. Anything but PROD reads +// as DEV, since the tag is optional and treating unknowns as protected would +// have read_only=prod block untagged services. +func environmentTag(environment *string) api.EnvironmentTag { + if strings.EqualFold(util.DerefStr(environment), string(api.EnvironmentTagPROD)) { + return api.EnvironmentTagPROD + } + return api.EnvironmentTagDEV +} diff --git a/internal/common/replica_test.go b/internal/common/connection_target_test.go similarity index 54% rename from internal/common/replica_test.go rename to internal/common/connection_target_test.go index bca4e5cb..2293e849 100644 --- a/internal/common/replica_test.go +++ b/internal/common/connection_target_test.go @@ -15,20 +15,48 @@ import ( "github.com/timescale/tiger-cli/internal/util" ) -// serviceTestClient builds an API client backed by an httptest server that -// serves the getService endpoint from services keyed by service ID (404 when -// absent). -func serviceTestClient(t *testing.T, services map[string]api.Service) *api.ClientWithResponses { +// resolveTestClient builds an API client backed by an httptest server that +// answers the resolve endpoint from resolved, keyed by ref, and the getService +// endpoint from services, keyed by service ID (404 when absent). Each resolved +// value is a service, branch, or read replica set, encoded with the given +// discriminator. +func resolveTestClient(t *testing.T, resolved map[string]resolvedFixture, services map[string]api.Service) *api.ClientWithResponses { t.Helper() + notFound := func(w http.ResponseWriter) { + w.WriteHeader(http.StatusNotFound) + _ = json.NewEncoder(w).Encode(map[string]string{"message": "not found"}) + } + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/") - id := parts[len(parts)-1] - svc, ok := services[id] + last := parts[len(parts)-1] + + if last == "resolve" { + fixture, ok := resolved[r.URL.Query().Get("ref")] + if !ok { + notFound(w) + return + } + raw, err := json.Marshal(fixture.value) + if err != nil { + t.Errorf("failed to encode fixture: %v", err) + return + } + var body map[string]any + if err := json.Unmarshal(raw, &body); err != nil { + t.Errorf("failed to decode fixture: %v", err) + return + } + body["type"] = fixture.typ + _ = json.NewEncoder(w).Encode(body) + return + } + + svc, ok := services[last] if !ok { - w.WriteHeader(http.StatusNotFound) - _ = json.NewEncoder(w).Encode(map[string]string{"message": "service not found"}) + notFound(w) return } _ = json.NewEncoder(w).Encode(svc) @@ -42,6 +70,12 @@ func serviceTestClient(t *testing.T, services map[string]api.Service) *api.Clien return client } +// resolvedFixture is one answer of the resolve endpoint in resolveTestClient. +type resolvedFixture struct { + typ api.ResolveType + value any +} + func primaryService() api.Service { host := "svcprimary.example.com" port := 5432 @@ -53,51 +87,102 @@ func primaryService() api.Service { } } -func replicaService() api.Service { +// replicaSet returns an active read replica set of primaryService. +func replicaSet() api.ReadReplicaSet { host := "replica.example.com" port := 5432 - return api.Service{ - ServiceID: "rep1234567", - ProjectID: "proj1", + return api.ReadReplicaSet{ + ID: "rep1234567", + ServiceID: "svcprimary", Name: "reporting-replica", + Status: api.ReadReplicaSetStatusActive, Endpoint: &api.Endpoint{Host: &host, Port: &port}, - ForkedFrom: &api.ForkSpec{ - IsStandby: new(true), - ProjectID: new("proj1"), - ServiceID: new("svcprimary"), - }, } } +// TestResolveConnectionTargetByID is helper-level because the MCP db tools +// that call it can't reach the branch and replica cases without a live +// connection past it. func TestResolveConnectionTargetByID(t *testing.T) { primary := primaryService() - replica := replicaService() - client := serviceTestClient(t, map[string]api.Service{"svcprimary": primary, "rep1234567": replica}) + replicaHost := "replica.example.com" + replica := api.ReadReplicaSet{ + ID: "rep1234567", + ServiceID: "svcprimary", + Name: "reporting-replica", + Status: api.ReadReplicaSetStatusActive, + Endpoint: &api.Endpoint{Host: &replicaHost, Port: new(5432)}, + } + branch := api.Branch{ + ID: "br12345678", + ProjectID: "proj1", + ServiceID: "svcprimary", + Name: "my-branch", + Status: api.DeployStatusREADY, + Endpoint: &api.Endpoint{Host: new("br12345678.example.com"), Port: new(5432)}, + } + client := resolveTestClient(t, map[string]resolvedFixture{ + "svcprimary": {api.ResolveTypeSERVICE, primary}, + "my-db": {api.ResolveTypeSERVICE, primary}, + "rep1234567": {api.ResolveTypeREADREPLICASET, replica}, + "br12345678": {api.ResolveTypeBRANCH, branch}, + }, map[string]api.Service{"svcprimary": primary}) + ctx := context.Background() // Primary ID → primary target. - target, err := ResolveConnectionTargetByID(context.Background(), client, "proj1", "svcprimary") + target, err := ResolveConnectionTargetByID(ctx, client, "proj1", "svcprimary", false) if err != nil { t.Fatalf("unexpected error: %v", err) } - if target.IsReplica { - t.Error("expected primary target for a primary ID") + if target.Kind != TargetService || target.ID != "svcprimary" || target.Credentials.ID != "svcprimary" { + t.Errorf("expected primary target for a primary ID, got %+v", target) } - // Replica ID → replica target with parent credentials. - target, err = ResolveConnectionTargetByID(context.Background(), client, "proj1", "rep1234567") + // Replica set ID → replica target with the parent's credentials. + target, err = ResolveConnectionTargetByID(ctx, client, "proj1", "rep1234567", false) if err != nil { t.Fatalf("unexpected error: %v", err) } - if !target.IsReplica || target.CredentialService.ServiceID != "svcprimary" { + if target.Kind != TargetReadReplica || target.ID != "rep1234567" || target.Credentials.ID != "svcprimary" { t.Errorf("expected replica target with parent credentials, got %+v", target) } - // Unknown ID → error. - if _, err := ResolveConnectionTargetByID(context.Background(), client, "proj1", "missing9999"); err == nil { - t.Error("expected an error for an unknown ID, got nil") + // Branch ID → branch target with its own credentials, when allowed. + target, err = ResolveConnectionTargetByID(ctx, client, "proj1", "br12345678", true) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if target.Kind != TargetBranch || target.ID != "br12345678" || target.Credentials.ID != "br12345678" { + t.Errorf("expected branch target with its own credentials, got %+v", target) + } + + // Branch ID without branches allowed → not found, without naming branches. + _, err = ResolveConnectionTargetByID(ctx, client, "proj1", "br12345678", false) + if diff := cmp.Diff("service or read replica set 'br12345678' not found", errString(err)); diff != "" { + t.Errorf("gated branch error mismatch (-want +got):\n%s", diff) + } + + // A name resolves, but isn't an ID. + _, err = ResolveConnectionTargetByID(ctx, client, "proj1", "my-db", true) + if diff := cmp.Diff("service, branch, or read replica set 'my-db' not found", errString(err)); diff != "" { + t.Errorf("name match error mismatch (-want +got):\n%s", diff) + } + + // Unknown ID → not found. + _, err = ResolveConnectionTargetByID(ctx, client, "proj1", "missing999", true) + if diff := cmp.Diff("service, branch, or read replica set 'missing999' not found", errString(err)); diff != "" { + t.Errorf("unknown ID error mismatch (-want +got):\n%s", diff) } } +// errString returns err's message, or "" for nil. +func errString(err error) string { + if err == nil { + return "" + } + return err.Error() +} + func TestNewReplicaConnectionTarget(t *testing.T) { primary := primaryService() host := "menu-replica.example.com" @@ -108,15 +193,15 @@ func TestNewReplicaConnectionTarget(t *testing.T) { Endpoint: &api.Endpoint{Host: &host, Port: &port}, } - target := NewReplicaConnectionTarget(primary, replica) - if !target.IsReplica { + target := NewReplicaConnectionTarget(replica, ServiceCredentials(primary)) + if target.Kind != TargetReadReplica { t.Error("expected a replica target") } - if target.ConnectionService.ServiceID != "rep7654321" || util.DerefStr(target.ConnectionService.Endpoint.Host) != host { - t.Errorf("expected connect to carry the replica endpoint, got %+v", target.ConnectionService) + if target.ID != "rep7654321" || util.DerefStr(target.Endpoint.Host) != host { + t.Errorf("expected connect to carry the replica endpoint, got %+v", target) } - if target.CredentialService.ServiceID != "svcprimary" { - t.Errorf("expected credential to be the primary, got %q", target.CredentialService.ServiceID) + if target.Credentials.ID != "svcprimary" || target.Credentials.Name != "my-db" { + t.Errorf("expected credential to be the primary, got %+v", target.Credentials) } } @@ -154,9 +239,9 @@ func TestConnectionTargetReadOnlyVerdict(t *testing.T) { replica.Metadata = &api.ReplicaSetMetadata{Environment: new(tt.replicaTag)} } - target := NewReplicaConnectionTarget(primary, replica) + target := NewReplicaConnectionTarget(replica, ServiceCredentials(primary)) cfg := &config.Config{ReadOnly: tt.mode} - got := CheckReadOnly(cfg, ServiceEnvironmentTag(target.ConnectionService)) != nil + got := CheckReadOnly(cfg, target.Environment) != nil if got != tt.wantSession { t.Errorf("CheckReadOnly(read_only=%q, primary=%q, replica=%q) != nil = %t, want %t", tt.mode, tt.primaryTag, tt.replicaTag, got, tt.wantSession) @@ -178,12 +263,12 @@ func TestConnectionTargetDetails(t *testing.T) { // The replica supplies the endpoint, the primary the credentials — which // aren't exercised here, since WithPassword is off. - replicaTarget := func(overrides ...func(*api.Service)) *ConnectionTarget { - svc := replicaService() + replicaTarget := func(overrides ...func(*api.ReadReplicaSet)) *ConnectionTarget { + replica := replicaSet() for _, override := range overrides { - override(&svc) + override(&replica) } - return &ConnectionTarget{ConnectionService: svc, CredentialService: primaryService(), IsReplica: true} + return NewReplicaConnectionTarget(replica, ServiceCredentials(primaryService())) } cases := []struct { @@ -205,8 +290,8 @@ func TestConnectionTargetDetails(t *testing.T) { }, { name: "replica uses its pooler when one is available", - target: replicaTarget(func(s *api.Service) { - s.ConnectionPooler = pooler + target: replicaTarget(func(r *api.ReadReplicaSet) { + r.ConnectionPooler = pooler }), pooled: true, want: &ConnectionDetails{ @@ -233,14 +318,14 @@ func TestConnectionTargetDetails(t *testing.T) { { // For a primary the same request is a hard error instead. name: "primary refuses when its pooler is missing", - target: &ConnectionTarget{ConnectionService: primaryService(), CredentialService: primaryService()}, + target: NewServiceConnectionTarget(primaryService()), pooled: true, wantErr: "connection pooler not available for this service", }, { name: "missing endpoint is reported", - target: replicaTarget(func(s *api.Service) { - s.Endpoint = nil + target: replicaTarget(func(r *api.ReadReplicaSet) { + r.Endpoint = nil }), wantErr: "failed to build connection string: service endpoint not available", }, @@ -276,14 +361,14 @@ func TestConnectionTargetDetails(t *testing.T) { func TestReplicaPoolerWarning(t *testing.T) { host := "h" port := 6432 - withPooler := api.Service{ + withPooler := api.ReadReplicaSet{ Name: "rep-a", ConnectionPooler: &api.ConnectionPooler{Endpoint: &api.Endpoint{Host: &host, Port: &port}}, } - noPooler := api.Service{Name: "rep-b"} + noPooler := api.ReadReplicaSet{Name: "rep-b"} - replica := func(svc api.Service) *ConnectionTarget { - return &ConnectionTarget{ConnectionService: svc, CredentialService: primaryService(), IsReplica: true} + replica := func(replica api.ReadReplicaSet) *ConnectionTarget { + return NewReplicaConnectionTarget(replica, ServiceCredentials(primaryService())) } cases := []struct { @@ -292,7 +377,7 @@ func TestReplicaPoolerWarning(t *testing.T) { pooled bool wantWarning bool }{ - {"primary is never warned", &ConnectionTarget{ConnectionService: noPooler, CredentialService: noPooler}, true, false}, + {"primary is never warned", NewServiceConnectionTarget(primaryService()), true, false}, {"replica not pooled, no pooler", replica(noPooler), false, false}, {"replica not pooled, has pooler", replica(withPooler), false, false}, {"replica pooled, has pooler", replica(withPooler), true, false}, @@ -309,29 +394,13 @@ func TestReplicaPoolerWarning(t *testing.T) { } func TestResolveConnectionTargetByID_ReplicaParentFetchFails(t *testing.T) { - // The replica itself resolves, but its parent is absent from the server. - client := serviceTestClient(t, map[string]api.Service{"rep1234567": replicaService()}) + // The replica set itself resolves, but its parent is absent from the server. + replica := api.ReadReplicaSet{ID: "rep1234567", ServiceID: "svcprimary", Name: "reporting-replica"} + client := resolveTestClient(t, map[string]resolvedFixture{ + "rep1234567": {api.ResolveTypeREADREPLICASET, replica}, + }, nil) - if _, err := ResolveConnectionTargetByID(context.Background(), client, "proj1", "rep1234567"); err == nil { + if _, err := ResolveConnectionTargetByID(context.Background(), client, "proj1", "rep1234567", false); err == nil { t.Fatal("expected an error when the parent service can't be fetched, got nil") } } - -func TestResolveConnectionTargetByID_ReplicaWithoutParentID(t *testing.T) { - // A standby with no parent recorded has nowhere else to look for - // credentials, so it stands in as its own credential service. - replica := replicaService() - replica.ForkedFrom.ServiceID = nil - client := serviceTestClient(t, map[string]api.Service{"rep1234567": replica}) - - target, err := ResolveConnectionTargetByID(context.Background(), client, "proj1", "rep1234567") - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !target.IsReplica { - t.Error("expected a replica target") - } - if target.CredentialService.ServiceID != "rep1234567" { - t.Errorf("expected the replica as its own credential service, got %q", target.CredentialService.ServiceID) - } -} diff --git a/internal/common/connection_test.go b/internal/common/connection_test.go index 1e7e8669..ae127e50 100644 --- a/internal/common/connection_test.go +++ b/internal/common/connection_test.go @@ -181,7 +181,7 @@ func TestBuildConnectionString_Basic(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - result, err := GetConnectionDetails(testConfig(""), tc.service, tc.opts) + result, err := GetConnectionDetails(testConfig(""), NewServiceConnectionTarget(tc.service), tc.opts) if tc.expectError { if err == nil { @@ -230,13 +230,13 @@ func TestBuildConnectionString_WithPassword_KeyringStorage(t *testing.T) { testPassword := "test-password-keyring-123" role := "tsdbadmin" storage := GetPasswordStorage(cfg) - err := storage.Save(service, testPassword, role) + err := storage.Save(ServicePasswordTarget(service), testPassword, role) if err != nil { t.Fatalf("Failed to save test password: %v", err) } - defer storage.Remove(service, role) // Clean up after test + defer storage.Remove(ServicePasswordTarget(service), role) // Clean up after test - details, err := GetConnectionDetails(cfg, service, ConnectionDetailsOptions{ + details, err := GetConnectionDetails(cfg, NewServiceConnectionTarget(service), ConnectionDetailsOptions{ Role: "tsdbadmin", WithPassword: true, }) @@ -279,13 +279,13 @@ func TestBuildConnectionString_WithPassword_PgpassStorage(t *testing.T) { testPassword := "test-password-pgpass-456" role := "tsdbadmin" storage := GetPasswordStorage(cfg) - err := storage.Save(service, testPassword, role) + err := storage.Save(ServicePasswordTarget(service), testPassword, role) if err != nil { t.Fatalf("Failed to save test password: %v", err) } - defer storage.Remove(service, role) // Clean up after test + defer storage.Remove(ServicePasswordTarget(service), role) // Clean up after test - details, err := GetConnectionDetails(cfg, service, ConnectionDetailsOptions{ + details, err := GetConnectionDetails(cfg, NewServiceConnectionTarget(service), ConnectionDetailsOptions{ Role: "tsdbadmin", WithPassword: true, }) @@ -348,7 +348,7 @@ func TestBuildConnectionString_WithPassword_NoStorage(t *testing.T) { }, } - result, err := GetConnectionDetails(cfg, service, ConnectionDetailsOptions{ + result, err := GetConnectionDetails(cfg, NewServiceConnectionTarget(service), ConnectionDetailsOptions{ Role: "tsdbadmin", WithPassword: true, }) @@ -386,7 +386,7 @@ func TestBuildConnectionString_WithPassword_NoPasswordAvailable(t *testing.T) { }, } - result, err := GetConnectionDetails(cfg, service, ConnectionDetailsOptions{ + result, err := GetConnectionDetails(cfg, NewServiceConnectionTarget(service), ConnectionDetailsOptions{ Role: "tsdbadmin", WithPassword: true, }) @@ -425,12 +425,12 @@ func TestBuildConnectionString_ReadOnly_WithPassword(t *testing.T) { testPassword := "test-password-readonly-789" role := "tsdbadmin" storage := GetPasswordStorage(cfg) - if err := storage.Save(service, testPassword, role); err != nil { + if err := storage.Save(ServicePasswordTarget(service), testPassword, role); err != nil { t.Fatalf("Failed to save test password: %v", err) } - defer storage.Remove(service, role) + defer storage.Remove(ServicePasswordTarget(service), role) - details, err := GetConnectionDetails(cfg, service, ConnectionDetailsOptions{ + details, err := GetConnectionDetails(cfg, NewServiceConnectionTarget(service), ConnectionDetailsOptions{ Role: role, WithPassword: true, ReadOnly: true, @@ -463,7 +463,7 @@ func TestBuildConnectionString_WithPassword_InvalidServiceEndpoint(t *testing.T) Endpoint: nil, // Invalid - no endpoint } - _, err := GetConnectionDetails(cfg, service, ConnectionDetailsOptions{ + _, err := GetConnectionDetails(cfg, NewServiceConnectionTarget(service), ConnectionDetailsOptions{ Role: "tsdbadmin", WithPassword: true, }) diff --git a/internal/common/password_storage.go b/internal/common/password_storage.go index 3349233b..abfec543 100644 --- a/internal/common/password_storage.go +++ b/internal/common/password_storage.go @@ -13,19 +13,48 @@ import ( "github.com/timescale/tiger-cli/internal/config" ) -// buildPasswordKeyringUsername creates a unique keyring username for service passwords -func buildPasswordKeyringUsername(service api.Service, role string) (string, error) { - if service.ServiceID == "" { +// PasswordTarget is what a stored password belongs to: a service or a branch. +// The keyring keys a password by project and ID, and ~/.pgpass by the +// endpoint's host and port. +type PasswordTarget struct { + ProjectID string + ID string + Endpoint *api.Endpoint +} + +// ServicePasswordTarget returns the PasswordTarget of a service's own +// passwords. +func ServicePasswordTarget(service api.Service) PasswordTarget { + return PasswordTarget{ + ProjectID: service.ProjectID, + ID: service.ServiceID, + Endpoint: service.Endpoint, + } +} + +// BranchPasswordTarget returns the PasswordTarget of a branch's own +// passwords. +func BranchPasswordTarget(branch api.Branch) PasswordTarget { + return PasswordTarget{ + ProjectID: branch.ProjectID, + ID: branch.ID, + Endpoint: branch.Endpoint, + } +} + +// buildPasswordKeyringUsername creates a unique keyring username for a password +func buildPasswordKeyringUsername(target PasswordTarget, role string) (string, error) { + if target.ID == "" { return "", fmt.Errorf("service ID is required") } - if service.ProjectID == "" { + if target.ProjectID == "" { return "", fmt.Errorf("project ID is required") } if role == "" { return "", fmt.Errorf("role is required") } - return fmt.Sprintf("password-%s-%s-%s", service.ProjectID, service.ServiceID, role), nil + return fmt.Sprintf("password-%s-%s-%s", target.ProjectID, target.ID, role), nil } // sanitizeErrorMessage removes sensitive information from error messages @@ -50,17 +79,17 @@ type PasswordStorageResult struct { // PasswordStorage defines the interface for password storage implementations type PasswordStorage interface { - Save(service api.Service, password string, role string) error - Get(service api.Service, role string) (string, error) - Remove(service api.Service, role string) error + Save(target PasswordTarget, password string, role string) error + Get(target PasswordTarget, role string) (string, error) + Remove(target PasswordTarget, role string) error GetStorageResult(err error, password string) PasswordStorageResult } // KeyringStorage implements password storage using system keyring type KeyringStorage struct{} -func (k *KeyringStorage) Save(service api.Service, password string, role string) error { - username, err := buildPasswordKeyringUsername(service, role) +func (k *KeyringStorage) Save(target PasswordTarget, password string, role string) error { + username, err := buildPasswordKeyringUsername(target, role) if err != nil { return err } @@ -68,8 +97,8 @@ func (k *KeyringStorage) Save(service api.Service, password string, role string) return keyring.Set(config.KeyringServiceName, username, password) } -func (k *KeyringStorage) Get(service api.Service, role string) (string, error) { - username, err := buildPasswordKeyringUsername(service, role) +func (k *KeyringStorage) Get(target PasswordTarget, role string) (string, error) { + username, err := buildPasswordKeyringUsername(target, role) if err != nil { return "", err } @@ -77,8 +106,8 @@ func (k *KeyringStorage) Get(service api.Service, role string) (string, error) { return keyring.Get(config.KeyringServiceName, username) } -func (k *KeyringStorage) Remove(service api.Service, role string) error { - username, err := buildPasswordKeyringUsername(service, role) +func (k *KeyringStorage) Remove(target PasswordTarget, role string) error { + username, err := buildPasswordKeyringUsername(target, role) if err != nil { return err } @@ -105,8 +134,8 @@ func (k *KeyringStorage) GetStorageResult(err error, password string) PasswordSt // PgpassStorage implements password storage using ~/.pgpass file type PgpassStorage struct{} -func (p *PgpassStorage) Save(service api.Service, password string, role string) error { - if service.Endpoint == nil || service.Endpoint.Host == nil { +func (p *PgpassStorage) Save(target PasswordTarget, password string, role string) error { + if target.Endpoint == nil || target.Endpoint.Host == nil || target.Endpoint.Port == nil { return fmt.Errorf("service endpoint not available") } if role == "" { @@ -119,11 +148,8 @@ func (p *PgpassStorage) Save(service api.Service, password string, role string) } pgpassPath := filepath.Join(homeDir, ".pgpass") - host := *service.Endpoint.Host - port := "5432" // default PostgreSQL port - if service.Endpoint.Port != nil { - port = fmt.Sprintf("%d", *service.Endpoint.Port) - } + host := *target.Endpoint.Host + port := fmt.Sprintf("%d", *target.Endpoint.Port) database := "tsdb" // TimescaleDB database name username := role // Use the provided role as username @@ -149,8 +175,8 @@ func (p *PgpassStorage) Save(service api.Service, password string, role string) return nil } -func (p *PgpassStorage) Get(service api.Service, role string) (string, error) { - if service.Endpoint == nil || service.Endpoint.Host == nil { +func (p *PgpassStorage) Get(target PasswordTarget, role string) (string, error) { + if target.Endpoint == nil || target.Endpoint.Host == nil || target.Endpoint.Port == nil { return "", fmt.Errorf("service endpoint not available") } if role == "" { @@ -169,11 +195,8 @@ func (p *PgpassStorage) Get(service api.Service, role string) (string, error) { return "", fmt.Errorf("no .pgpass file found") } - host := *service.Endpoint.Host - port := "5432" - if service.Endpoint.Port != nil { - port = fmt.Sprintf("%d", *service.Endpoint.Port) - } + host := *target.Endpoint.Host + port := fmt.Sprintf("%d", *target.Endpoint.Port) username := role // Read and parse .pgpass file @@ -203,8 +226,8 @@ func (p *PgpassStorage) Get(service api.Service, role string) (string, error) { return "", fmt.Errorf("no matching entry found in .pgpass file") } -func (p *PgpassStorage) Remove(service api.Service, role string) error { - if service.Endpoint == nil || service.Endpoint.Host == nil { +func (p *PgpassStorage) Remove(target PasswordTarget, role string) error { + if target.Endpoint == nil || target.Endpoint.Host == nil || target.Endpoint.Port == nil { return fmt.Errorf("service endpoint not available") } if role == "" { @@ -217,11 +240,8 @@ func (p *PgpassStorage) Remove(service api.Service, role string) error { } pgpassPath := filepath.Join(homeDir, ".pgpass") - host := *service.Endpoint.Host - port := "5432" - if service.Endpoint.Port != nil { - port = fmt.Sprintf("%d", *service.Endpoint.Port) - } + host := *target.Endpoint.Host + port := fmt.Sprintf("%d", *target.Endpoint.Port) username := role return p.removeEntry(pgpassPath, host, port, username) @@ -304,15 +324,15 @@ func (p *PgpassStorage) GetStorageResult(err error, password string) PasswordSto // NoStorage implements no password storage (passwords are not saved) type NoStorage struct{} -func (n *NoStorage) Save(service api.Service, password string, role string) error { +func (n *NoStorage) Save(target PasswordTarget, password string, role string) error { return nil // Do nothing } -func (n *NoStorage) Get(service api.Service, role string) (string, error) { +func (n *NoStorage) Get(target PasswordTarget, role string) (string, error) { return "", fmt.Errorf("password storage disabled") } -func (n *NoStorage) Remove(service api.Service, role string) error { +func (n *NoStorage) Remove(target PasswordTarget, role string) error { return nil // Do nothing } @@ -339,7 +359,7 @@ func GetPasswordStorage(cfg *config.Config) PasswordStorage { } // SavePasswordWithResult handles saving a password and returns both error and result info -func SavePasswordWithResult(cfg *config.Config, service api.Service, password string, role string) (PasswordStorageResult, error) { +func SavePasswordWithResult(cfg *config.Config, target PasswordTarget, password string, role string) (PasswordStorageResult, error) { if password == "" { return PasswordStorageResult{ Success: false, @@ -356,7 +376,7 @@ func SavePasswordWithResult(cfg *config.Config, service api.Service, password st } storage := GetPasswordStorage(cfg) - err := storage.Save(service, password, role) + err := storage.Save(target, password, role) result := storage.GetStorageResult(err, password) return result, err diff --git a/internal/common/password_storage_test.go b/internal/common/password_storage_test.go index 9ffc68e3..7c2b88ca 100644 --- a/internal/common/password_storage_test.go +++ b/internal/common/password_storage_test.go @@ -30,7 +30,7 @@ func TestNoStorage_Save(t *testing.T) { storage := &NoStorage{} service := createTestService("test-service-123") - err := storage.Save(service, "test-password", "tsdbadmin") + err := storage.Save(ServicePasswordTarget(service), "test-password", "tsdbadmin") if err != nil { t.Errorf("NoStorage.Save() should never return an error, got: %v", err) } @@ -40,7 +40,7 @@ func TestNoStorage_Get(t *testing.T) { storage := &NoStorage{} service := createTestService("test-service-123") - password, err := storage.Get(service, "tsdbadmin") + password, err := storage.Get(ServicePasswordTarget(service), "tsdbadmin") if err == nil { t.Error("NoStorage.Get() should return an error") } @@ -56,7 +56,7 @@ func TestNoStorage_Remove(t *testing.T) { storage := &NoStorage{} service := createTestService("test-service-123") - err := storage.Remove(service, "tsdbadmin") + err := storage.Remove(ServicePasswordTarget(service), "tsdbadmin") if err != nil { t.Errorf("NoStorage.Remove() should never return an error, got: %v", err) } @@ -66,7 +66,7 @@ func TestKeyringStorage_Save_NoServiceId(t *testing.T) { storage := &KeyringStorage{} service := api.Service{} // No ServiceID - err := storage.Save(service, "test-password", "tsdbadmin") + err := storage.Save(ServicePasswordTarget(service), "test-password", "tsdbadmin") if err == nil { t.Error("KeyringStorage.Save() should return error when ServiceID is nil") } @@ -83,7 +83,7 @@ func TestKeyringStorage_Save_NoProjectId(t *testing.T) { // No ProjectID } - err := storage.Save(service, "test-password", "tsdbadmin") + err := storage.Save(ServicePasswordTarget(service), "test-password", "tsdbadmin") if err == nil { t.Error("KeyringStorage.Save() should return error when ProjectID is nil") } @@ -96,7 +96,7 @@ func TestKeyringStorage_Save_NoRole(t *testing.T) { storage := &KeyringStorage{} service := createTestService("test-service-123") - err := storage.Save(service, "test-password", "") + err := storage.Save(ServicePasswordTarget(service), "test-password", "") if err == nil { t.Error("KeyringStorage.Save() should return error when role is empty") } @@ -109,7 +109,7 @@ func TestKeyringStorage_Get_NoServiceId(t *testing.T) { storage := &KeyringStorage{} service := api.Service{} // No ServiceID - password, err := storage.Get(service, "tsdbadmin") + password, err := storage.Get(ServicePasswordTarget(service), "tsdbadmin") if err == nil { t.Error("KeyringStorage.Get() should return error when ServiceID is nil") } @@ -129,7 +129,7 @@ func TestKeyringStorage_Get_NoProjectId(t *testing.T) { // No ProjectID } - password, err := storage.Get(service, "tsdbadmin") + password, err := storage.Get(ServicePasswordTarget(service), "tsdbadmin") if err == nil { t.Error("KeyringStorage.Get() should return error when ProjectID is nil") } @@ -145,7 +145,7 @@ func TestKeyringStorage_Get_NoRole(t *testing.T) { storage := &KeyringStorage{} service := createTestService("test-service-123") - password, err := storage.Get(service, "") + password, err := storage.Get(ServicePasswordTarget(service), "") if err == nil { t.Error("KeyringStorage.Get() should return error when role is empty") } @@ -161,7 +161,7 @@ func TestKeyringStorage_Remove_NoServiceId(t *testing.T) { storage := &KeyringStorage{} service := api.Service{} // No ServiceID - err := storage.Remove(service, "tsdbadmin") + err := storage.Remove(ServicePasswordTarget(service), "tsdbadmin") if err == nil { t.Error("KeyringStorage.Remove() should return error when ServiceID is nil") } @@ -178,7 +178,7 @@ func TestKeyringStorage_Remove_NoProjectId(t *testing.T) { // No ProjectID } - err := storage.Remove(service, "tsdbadmin") + err := storage.Remove(ServicePasswordTarget(service), "tsdbadmin") if err == nil { t.Error("KeyringStorage.Remove() should return error when ProjectID is nil") } @@ -191,7 +191,7 @@ func TestKeyringStorage_Remove_NoRole(t *testing.T) { storage := &KeyringStorage{} service := createTestService("test-service-123") - err := storage.Remove(service, "") + err := storage.Remove(ServicePasswordTarget(service), "") if err == nil { t.Error("KeyringStorage.Remove() should return error when role is empty") } @@ -207,7 +207,7 @@ func TestPgpassStorage_Remove_NoEndpoint(t *testing.T) { // No Endpoint } - err := storage.Remove(service, "tsdbadmin") + err := storage.Remove(ServicePasswordTarget(service), "tsdbadmin") if err == nil { t.Error("PgpassStorage.Remove() should return error when endpoint is nil") } @@ -216,11 +216,29 @@ func TestPgpassStorage_Remove_NoEndpoint(t *testing.T) { } } +// An entry is keyed by host and port, and no port is guessed, so an endpoint +// without one can't be saved, looked up, or removed. +func TestPgpassStorage_NoPort(t *testing.T) { + storage := &PgpassStorage{} + service := createTestService("test-service-123") + service.Endpoint.Port = nil + + if err := storage.Save(ServicePasswordTarget(service), "secret", "tsdbadmin"); err == nil || err.Error() != "service endpoint not available" { + t.Errorf("PgpassStorage.Save() error = %v, want service endpoint not available", err) + } + if _, err := storage.Get(ServicePasswordTarget(service), "tsdbadmin"); err == nil || err.Error() != "service endpoint not available" { + t.Errorf("PgpassStorage.Get() error = %v, want service endpoint not available", err) + } + if err := storage.Remove(ServicePasswordTarget(service), "tsdbadmin"); err == nil || err.Error() != "service endpoint not available" { + t.Errorf("PgpassStorage.Remove() error = %v, want service endpoint not available", err) + } +} + func TestPgpassStorage_Remove_NoRole(t *testing.T) { storage := &PgpassStorage{} service := createTestService("test-service-123") - err := storage.Remove(service, "") + err := storage.Remove(ServicePasswordTarget(service), "") if err == nil { t.Error("PgpassStorage.Remove() should return error when role is empty") } @@ -236,7 +254,7 @@ func TestPgpassStorage_Get_NoEndpoint(t *testing.T) { // No Endpoint } - password, err := storage.Get(service, "tsdbadmin") + password, err := storage.Get(ServicePasswordTarget(service), "tsdbadmin") if err == nil { t.Error("PgpassStorage.Get() should return error when endpoint is nil") } @@ -252,7 +270,7 @@ func TestPgpassStorage_Get_NoRole(t *testing.T) { storage := &PgpassStorage{} service := createTestService("test-service-123") - password, err := storage.Get(service, "") + password, err := storage.Get(ServicePasswordTarget(service), "") if err == nil { t.Error("PgpassStorage.Get() should return error when role is empty") } @@ -274,7 +292,7 @@ func TestPgpassStorage_Get_NoFile(t *testing.T) { storage := &PgpassStorage{} service := createTestService("test-service-123") - password, err := storage.Get(service, "tsdbadmin") + password, err := storage.Get(ServicePasswordTarget(service), "tsdbadmin") if err == nil { t.Error("PgpassStorage.Get() should return error when .pgpass file doesn't exist") } @@ -334,7 +352,7 @@ func TestPasswordStorage_DifferentRoles(t *testing.T) { cleanup: func(t *testing.T, service api.Service, roles []string) { storage := &KeyringStorage{} for _, role := range roles { - storage.Remove(service, role) + storage.Remove(ServicePasswordTarget(service), role) } }, }, @@ -380,7 +398,7 @@ func TestPasswordStorage_DifferentRoles(t *testing.T) { // Save passwords for all roles for _, r := range roles { - err := tt.storage.Save(service, r.password, r.role) + err := tt.storage.Save(ServicePasswordTarget(service), r.password, r.role) if _, ok := tt.storage.(*NoStorage); ok { continue } @@ -391,7 +409,7 @@ func TestPasswordStorage_DifferentRoles(t *testing.T) { // Verify each role has its own password stored separately for _, r := range roles { - retrieved, err := tt.storage.Get(service, r.role) + retrieved, err := tt.storage.Get(ServicePasswordTarget(service), r.role) if _, ok := tt.storage.(*NoStorage); ok { if err == nil { t.Error("NoStorage.Get() should always return error") @@ -408,20 +426,20 @@ func TestPasswordStorage_DifferentRoles(t *testing.T) { // Verify removing one role doesn't affect others if _, ok := tt.storage.(*NoStorage); !ok { - err := tt.storage.Remove(service, roles[0].role) + err := tt.storage.Remove(ServicePasswordTarget(service), roles[0].role) if err != nil { t.Fatalf("Remove(%s) failed: %v", roles[0].role, err) } // First role should be gone - _, err = tt.storage.Get(service, roles[0].role) + _, err = tt.storage.Get(ServicePasswordTarget(service), roles[0].role) if err == nil { t.Errorf("Get(%s) should fail after removal", roles[0].role) } // Other roles should still exist for _, r := range roles[1:] { - retrieved, err := tt.storage.Get(service, r.role) + retrieved, err := tt.storage.Get(ServicePasswordTarget(service), r.role) if err != nil { t.Errorf("Get(%s) should still work after removing %s: %v", r.role, roles[0].role, err) } @@ -450,7 +468,7 @@ func TestPgpassStorage_Integration(t *testing.T) { role := "tsdbadmin" // Test Save - err := storage.Save(service, password, role) + err := storage.Save(ServicePasswordTarget(service), password, role) if err != nil { t.Fatalf("PgpassStorage.Save() failed: %v", err) } @@ -473,7 +491,7 @@ func TestPgpassStorage_Integration(t *testing.T) { } // Test Get - retrievedPassword, err := storage.Get(service, role) + retrievedPassword, err := storage.Get(ServicePasswordTarget(service), role) if err != nil { t.Fatalf("PgpassStorage.Get() failed: %v", err) } @@ -482,7 +500,7 @@ func TestPgpassStorage_Integration(t *testing.T) { } // Test Remove - err = storage.Remove(service, role) + err = storage.Remove(ServicePasswordTarget(service), role) if err != nil { t.Fatalf("PgpassStorage.Remove() failed: %v", err) } @@ -620,7 +638,7 @@ func TestPgpassStorage_GetStorageResult_Error(t *testing.T) { func TestSavePasswordWithResult_EmptyPassword(t *testing.T) { service := createTestService("test-service-123") - result, err := SavePasswordWithResult(testConfig(""), service, "", "tsdbadmin") + result, err := SavePasswordWithResult(testConfig(""), ServicePasswordTarget(service), "", "tsdbadmin") if err != nil { t.Errorf("SavePasswordWithResult() with empty password should not return error, got: %v", err) } @@ -639,7 +657,7 @@ func TestSavePasswordWithResult_WithPassword(t *testing.T) { service := createTestService("test-service-123") // Use NoStorage for predictable behavior - result, err := SavePasswordWithResult(testConfig("none"), service, "test-password", "tsdbadmin") + result, err := SavePasswordWithResult(testConfig("none"), ServicePasswordTarget(service), "test-password", "tsdbadmin") if err != nil { t.Errorf("SavePasswordWithResult() should not return error with NoStorage, got: %v", err) } @@ -769,7 +787,7 @@ func TestBuildPasswordKeyringUsername(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := buildPasswordKeyringUsername(tt.service, tt.role) + result, err := buildPasswordKeyringUsername(ServicePasswordTarget(tt.service), tt.role) if tt.expectError && err == nil { t.Errorf("buildPasswordKeyringUsername() expected error but got none") @@ -801,7 +819,7 @@ func TestPasswordStorage_OverwritePreviousValue(t *testing.T) { }, cleanup: func(t *testing.T, service api.Service) { storage := &KeyringStorage{} - storage.Remove(service, "tsdbadmin") // Ignore errors in cleanup + storage.Remove(ServicePasswordTarget(service), "tsdbadmin") // Ignore errors in cleanup }, }, { @@ -839,10 +857,10 @@ func TestPasswordStorage_OverwritePreviousValue(t *testing.T) { role := "tsdbadmin" // Save original password - err1 := tt.storage.Save(service, originalPassword, role) + err1 := tt.storage.Save(ServicePasswordTarget(service), originalPassword, role) // Save new password (should overwrite) - err2 := tt.storage.Save(service, newPassword, role) + err2 := tt.storage.Save(ServicePasswordTarget(service), newPassword, role) // Handle NoStorage specially (always succeeds but doesn't store) if _, ok := tt.storage.(*NoStorage); ok { @@ -850,7 +868,7 @@ func TestPasswordStorage_OverwritePreviousValue(t *testing.T) { t.Errorf("NoStorage.Save() should not return error, got first: %v, second: %v", err1, err2) } // NoStorage.Get() always returns error, so we can't test retrieval - _, err := tt.storage.Get(service, role) + _, err := tt.storage.Get(ServicePasswordTarget(service), role) if err == nil { t.Error("NoStorage.Get() should always return error") } @@ -870,7 +888,7 @@ func TestPasswordStorage_OverwritePreviousValue(t *testing.T) { } // Get the stored password - should be the new one - retrieved, err := tt.storage.Get(service, role) + retrieved, err := tt.storage.Get(ServicePasswordTarget(service), role) if err != nil { t.Fatalf("Get() failed: %v", err) } diff --git a/internal/common/query.go b/internal/common/query.go index f746a64c..b99f5a28 100644 --- a/internal/common/query.go +++ b/internal/common/query.go @@ -69,7 +69,7 @@ type ExecuteQueryArgs struct { var ExecuteQuery = func(ctx context.Context, cfg *config.Config, target *ConnectionTarget, args ExecuteQueryArgs) (*QueryResult, error) { // Fail with "service is paused"/"service is not ready" rather than an // opaque connection error. - if err := CheckServiceReady(target.ConnectionService); err != nil { + if err := target.CheckReady(); err != nil { return nil, err } diff --git a/internal/common/ready.go b/internal/common/ready.go index fdf9f534..f9b132b4 100644 --- a/internal/common/ready.go +++ b/internal/common/ready.go @@ -15,10 +15,10 @@ var ( ErrNotReady = errors.New("service is not ready") ) -// CheckServiceReady returns nil only when the service is READY, ErrPaused for +// CheckReady returns nil only for a READY status, ErrPaused for // PAUSED/PAUSING, and ErrNotReady for every other (or unknown) status. -func CheckServiceReady(service api.Service) error { - switch service.Status { +func CheckReady(status api.DeployStatus) error { + switch status { case api.DeployStatusREADY: return nil case api.DeployStatusPAUSED, api.DeployStatusPAUSING: diff --git a/internal/common/ready_test.go b/internal/common/ready_test.go index e743c37c..ff0f942e 100644 --- a/internal/common/ready_test.go +++ b/internal/common/ready_test.go @@ -7,7 +7,7 @@ import ( "github.com/timescale/tiger-cli/internal/api" ) -func TestCheckServiceReady(t *testing.T) { +func TestCheckReady(t *testing.T) { tests := []struct { name string status api.DeployStatus @@ -30,9 +30,9 @@ func TestCheckServiceReady(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := CheckServiceReady(api.Service{Status: tt.status}) + err := CheckReady(tt.status) if !errors.Is(err, tt.wantErr) { - t.Errorf("CheckServiceReady() = %v, want %v", err, tt.wantErr) + t.Errorf("CheckReady() = %v, want %v", err, tt.wantErr) } }) } diff --git a/internal/common/replica.go b/internal/common/replica.go index 10cccb1e..b3f24501 100644 --- a/internal/common/replica.go +++ b/internal/common/replica.go @@ -1,108 +1,12 @@ package common import ( - "context" - "fmt" - "github.com/timescale/tiger-cli/internal/api" - "github.com/timescale/tiger-cli/internal/config" "github.com/timescale/tiger-cli/internal/util" ) -// ConnectionTarget is the service to connect to plus the service whose -// credentials to use. They're the same for a primary; for a read replica the -// CredentialService is the parent primary, whose credentials it shares. -type ConnectionTarget struct { - // ConnectionService alone decides a session's read-only verdict, so a replica - // is judged on its own environment tag rather than its primary's. - ConnectionService api.Service - CredentialService api.Service - IsReplica bool -} - -// Details builds the target's connection details. A requested-but-unavailable -// pooler is a hard error for a primary but silently falls back to direct for a -// replica (see ReplicaPoolerWarning). -func (t *ConnectionTarget) Details(cfg *config.Config, opts ConnectionDetailsOptions) (*ConnectionDetails, error) { - details, err := getConnectionDetails(cfg, t.ConnectionService, t.CredentialService, opts) - if err != nil { - return nil, fmt.Errorf("failed to build connection string: %w", err) - } - if opts.Pooled && !details.IsPooler && !t.IsReplica { - return nil, fmt.Errorf("connection pooler not available for this service") - } - return details, nil -} - // IsReadReplica reports whether the service is a standby read replica (which // shares its parent primary's credentials). func IsReadReplica(service api.Service) bool { return service.ForkedFrom != nil && util.Deref(service.ForkedFrom.IsStandby) } - -// ResolveConnectionTargetByID fetches the service named by id — which may be a -// primary service ID or a read replica set ID, both of which GetService -// resolves — and works out which service holds its credentials. Callers that -// already hold the service use NewConnectionTarget instead. -func ResolveConnectionTargetByID(ctx context.Context, client api.ClientWithResponsesInterface, projectID, id string) (*ConnectionTarget, error) { - service, err := GetService(ctx, client, projectID, id) - if err != nil { - return nil, err - } - return NewConnectionTarget(ctx, client, projectID, *service) -} - -// NewConnectionTarget works out which service holds the credentials for an -// already-fetched service. A standby read replica connects to its own endpoint -// but shares the parent primary's password, so the parent is fetched here. -func NewConnectionTarget(ctx context.Context, client api.ClientWithResponsesInterface, projectID string, service api.Service) (*ConnectionTarget, error) { - if !IsReadReplica(service) { - return &ConnectionTarget{ConnectionService: service, CredentialService: service}, nil - } - - // A replica with no parent recorded has nowhere else to look, so it stands - // in as its own credential service. - parentID := util.DerefStr(service.ForkedFrom.ServiceID) - if parentID == "" { - return &ConnectionTarget{ConnectionService: service, CredentialService: service, IsReplica: true}, nil - } - - parent, err := GetService(ctx, client, projectID, parentID) - if err != nil { - return nil, fmt.Errorf("failed to fetch parent service %q for read replica: %w", parentID, err) - } - return &ConnectionTarget{ConnectionService: service, CredentialService: *parent, IsReplica: true}, nil -} - -// NewReplicaConnectionTarget builds a ConnectionTarget for connecting to one of -// a service's read replica sets (as listed via the /replicaSets endpoint). The -// replica supplies the endpoint; the primary supplies the credentials. -func NewReplicaConnectionTarget(primary api.Service, replica api.ReadReplicaSet) *ConnectionTarget { - // Carry the replica's own tag over. ReplicaSetMetadata and ServiceMetadata are - // separate types holding the same field, so it's copied rather than assigned. - var metadata *api.ServiceMetadata - if replica.Metadata != nil { - metadata = &api.ServiceMetadata{Environment: replica.Metadata.Environment} - } - - // Replica sets carry their own status enum. Map only "active" onto READY so - // a readiness check on this target (CheckServiceReady) treats every other - // state — creating, resizing, error — as not ready. - status := api.DeployStatus("") - if replica.Status == api.ReadReplicaSetStatusActive { - status = api.DeployStatusREADY - } - - return &ConnectionTarget{ - ConnectionService: api.Service{ - ServiceID: replica.ID, - Name: replica.Name, - Status: status, - Endpoint: replica.Endpoint, - ConnectionPooler: replica.ConnectionPooler, - Metadata: metadata, - }, - CredentialService: primary, - IsReplica: true, - } -} diff --git a/internal/common/resolve.go b/internal/common/resolve.go new file mode 100644 index 00000000..54c51eef --- /dev/null +++ b/internal/common/resolve.go @@ -0,0 +1,119 @@ +package common + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/timescale/tiger-cli/internal/api" +) + +// Resolved is what a ref resolved to. Exactly one of its fields is set. +type Resolved struct { + Service *api.Service + Branch *api.Branch + ReadReplicaSet *api.ReadReplicaSet +} + +// ID returns the ID of whatever the ref resolved to. +func (r *Resolved) ID() string { + switch { + case r.Service != nil: + return r.Service.ServiceID + case r.Branch != nil: + return r.Branch.ID + case r.ReadReplicaSet != nil: + return r.ReadReplicaSet.ID + } + return "" +} + +// resolve resolves ref, an ID or a name, to the service, branch, or read +// replica set it names, considering only resources of the given type when it +// is non-nil. kind names what the caller is looking for, in errors. A ref that +// matches nothing is an ExitServiceNotFound error, and one that matches more +// than one resource is refused by the API with a 400. +func resolve(ctx context.Context, client api.ClientWithResponsesInterface, projectID, ref string, resolveType *api.ResolveType, kind string) (*Resolved, error) { + resp, err := client.ResolveRefWithResponse(ctx, projectID, &api.ResolveRefParams{Ref: ref, Type: resolveType}) + if err != nil { + return nil, fmt.Errorf("failed to resolve %s '%s': %w", kind, ref, err) + } + if resp.StatusCode() == http.StatusNotFound { + return nil, ExitWithCode(ExitServiceNotFound, fmt.Errorf("%s '%s' not found", kind, ref)) + } + if resp.StatusCode() != http.StatusOK { + return nil, ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON200 == nil { + return nil, fmt.Errorf("empty response from API") + } + + resolved, err := unpackResolveResult(*resp.JSON200) + if err != nil { + return nil, fmt.Errorf("failed to resolve %s '%s': %w", kind, ref, err) + } + + // The API only returns the requested type, so a mismatch means the filter + // was ignored, and acting on the result would act on the wrong thing. + if resolveType != nil && resolvedType(resolved) != *resolveType { + return nil, fmt.Errorf("expected a %s for '%s', got a %s", kind, ref, resolveKind(resolvedType(resolved))) + } + return resolved, nil +} + +// resolveKind names a resolve type, in messages. +func resolveKind(resolveType api.ResolveType) string { + switch resolveType { + case api.ResolveTypeBRANCH: + return "branch" + case api.ResolveTypeREADREPLICASET: + return "read replica set" + default: + return "service" + } +} + +func resolvedType(r *Resolved) api.ResolveType { + switch { + case r.Branch != nil: + return api.ResolveTypeBRANCH + case r.ReadReplicaSet != nil: + return api.ResolveTypeREADREPLICASET + default: + return api.ResolveTypeSERVICE + } +} + +// unpackResolveResult decodes the union by its discriminator. Each Resolved* +// type is its plain counterpart plus the discriminator, so the raw JSON is +// decoded straight into the plain type rather than copied field by field. +func unpackResolveResult(result api.ResolveResult) (*Resolved, error) { + discriminator, err := result.Discriminator() + if err != nil { + return nil, err + } + raw, err := result.MarshalJSON() + if err != nil { + return nil, err + } + + var resolved Resolved + switch api.ResolveType(discriminator) { + case api.ResolveTypeSERVICE: + resolved.Service = &api.Service{} + err = json.Unmarshal(raw, resolved.Service) + case api.ResolveTypeBRANCH: + resolved.Branch = &api.Branch{} + err = json.Unmarshal(raw, resolved.Branch) + case api.ResolveTypeREADREPLICASET: + resolved.ReadReplicaSet = &api.ReadReplicaSet{} + err = json.Unmarshal(raw, resolved.ReadReplicaSet) + default: + return nil, fmt.Errorf("unknown resolved type '%s'", discriminator) + } + if err != nil { + return nil, err + } + return &resolved, nil +} diff --git a/internal/common/schema.go b/internal/common/schema.go index b339f747..1366b2ba 100644 --- a/internal/common/schema.go +++ b/internal/common/schema.go @@ -1118,7 +1118,7 @@ ORDER BY n.nspname, c.relname`, // It is a var so tests can stub the connection out and reach the callers' // success paths, which are otherwise only reachable against a live database. var FetchServiceSchema = func(ctx context.Context, cfg *config.Config, target *ConnectionTarget, args FetchServiceSchemaArgs) (*DatabaseSchema, error) { - if err := CheckServiceReady(target.ConnectionService); err != nil { + if err := target.CheckReady(); err != nil { return nil, err } @@ -1146,8 +1146,8 @@ var FetchServiceSchema = func(ctx context.Context, cfg *config.Config, target *C defer conn.Close(context.Background()) ident := SchemaIdent{ - ID: target.ConnectionService.ServiceID, - Name: target.ConnectionService.Name, + ID: target.ID, + Name: target.Name, } return fetchSchemaFromConn(ctx, conn, ident, args) } diff --git a/internal/common/service.go b/internal/common/service.go index e71ed503..bca3e2cc 100644 --- a/internal/common/service.go +++ b/internal/common/service.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/timescale/tiger-cli/internal/api" - "github.com/timescale/tiger-cli/internal/util" ) // GenerateServiceName matches the front-end logic for generating a random @@ -26,11 +25,7 @@ func ServiceEnvironmentTag(service api.Service) api.EnvironmentTag { if service.Metadata == nil { return api.EnvironmentTagDEV } - - if strings.EqualFold(util.DerefStr(service.Metadata.Environment), string(api.EnvironmentTagPROD)) { - return api.EnvironmentTagPROD - } - return api.EnvironmentTagDEV + return environmentTag(service.Metadata.Environment) } // GetService fetches a single service by ID. The API resolves both primary diff --git a/internal/common/wait.go b/internal/common/wait.go index f0f4ee62..d87f9365 100644 --- a/internal/common/wait.go +++ b/internal/common/wait.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "net/http" "time" "github.com/timescale/tiger-cli/internal/api" @@ -36,22 +37,115 @@ type WaitForServiceArgs struct { // WaitForService polls a service until it reports TargetStatus, showing its // current status next to a spinner in the meantime. func WaitForService(ctx context.Context, args WaitForServiceArgs) error { - if args.Service.Status == args.TargetStatus { + return waitForStatus(ctx, waitArgs{ + kind: "service", + title: "Service", + status: &args.Service.Status, + targetStatus: args.TargetStatus, + input: args.Input, + output: args.Output, + timeout: args.Timeout, + timeoutMsg: args.TimeoutMsg, + poll: func(ctx context.Context) (int, string, *api.DeployStatus, error) { + resp, err := args.Client.GetServiceWithResponse(ctx, args.ProjectID, args.ServiceID) + if err != nil { + return 0, "", nil, err + } + if resp.JSON200 == nil { + return resp.StatusCode(), resp.Status(), nil, nil + } + return resp.StatusCode(), resp.Status(), &resp.JSON200.Status, nil + }, + }) +} + +type WaitForBranchArgs struct { + Client api.ClientWithResponsesInterface + ProjectID string + ServiceID string // the branch's parent + BranchID string + + // Branch is the branch as last returned by the API. Unlike + // WaitForServiceArgs.Service, the whole branch is replaced on every poll, + // so fields that only appear once the branch is provisioned (its endpoint) + // are there for the caller afterwards. + Branch *api.Branch + + // TargetStatus, Input, Output, Timeout, and TimeoutMsg work as in + // WaitForServiceArgs. + TargetStatus api.DeployStatus + Input io.Reader + Output io.Writer + Timeout time.Duration + TimeoutMsg string +} + +// WaitForBranch polls a branch until it reports TargetStatus, showing its +// current status next to a spinner in the meantime. +func WaitForBranch(ctx context.Context, args WaitForBranchArgs) error { + return waitForStatus(ctx, waitArgs{ + kind: "branch", + title: "Branch", + status: &args.Branch.Status, + targetStatus: args.TargetStatus, + input: args.Input, + output: args.Output, + timeout: args.Timeout, + timeoutMsg: args.TimeoutMsg, + poll: func(ctx context.Context) (int, string, *api.DeployStatus, error) { + resp, err := args.Client.GetBranchWithResponse(ctx, args.ProjectID, args.ServiceID, args.BranchID) + if err != nil { + return 0, "", nil, err + } + if resp.JSON200 == nil { + return resp.StatusCode(), resp.Status(), nil, nil + } + *args.Branch = *resp.JSON200 + return resp.StatusCode(), resp.Status(), &args.Branch.Status, nil + }, + }) +} + +// waitArgs is what waitForStatus needs to poll any resource with a +// DeployStatus. +type waitArgs struct { + // kind names the resource in error messages, and title in the spinner's + // status message. + kind string + title string + + // status is updated in place as polling proceeds. + status *api.DeployStatus + targetStatus api.DeployStatus + + // poll fetches the resource once, returning the HTTP status code and + // text, and the resource's status when the response carried a body. + poll func(ctx context.Context) (code int, httpStatus string, current *api.DeployStatus, err error) + + input io.Reader + output io.Writer + timeout time.Duration + timeoutMsg string +} + +// waitForStatus is the poll loop behind WaitForService and WaitForBranch. +func waitForStatus(ctx context.Context, args waitArgs) error { + if *args.status == args.targetStatus { return nil } // The spinner cancels this context on Ctrl+C, which the loop below reports // as a canceled wait. - ctx, cancel := context.WithTimeout(ctx, args.Timeout) + ctx, cancel := context.WithTimeout(ctx, args.timeout) defer cancel() ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() spinner := NewSpinner(SpinnerArgs{ - Input: args.Input, - Output: args.Output, - Message: statusMessage(args.Service), + Input: args.input, + Output: args.output, + Message: statusMessage(args.title, *args.status), Cancel: cancel, }) defer spinner.Stop() @@ -61,42 +155,42 @@ func WaitForService(ctx context.Context, args WaitForServiceArgs) error { case <-ctx.Done(): switch { case errors.Is(ctx.Err(), context.DeadlineExceeded): - return ExitWithCode(ExitTimeout, fmt.Errorf("wait timeout reached after %v - %s", args.Timeout, args.TimeoutMsg)) + return ExitWithCode(ExitTimeout, fmt.Errorf("wait timeout reached after %v - %s", args.timeout, args.timeoutMsg)) case errors.Is(ctx.Err(), context.Canceled): - return fmt.Errorf("canceled waiting - %s", args.TimeoutMsg) + return fmt.Errorf("canceled waiting - %s", args.timeoutMsg) default: - return fmt.Errorf("error waiting - %s: %w", args.TimeoutMsg, ctx.Err()) + return fmt.Errorf("error waiting - %s: %w", args.timeoutMsg, ctx.Err()) } case <-ticker.C: - resp, err := args.Client.GetServiceWithResponse(ctx, args.ProjectID, args.ServiceID) + code, httpStatus, current, err := args.poll(ctx) if err != nil { - spinner.Update(fmt.Sprintf("Error checking service status: %s", err)) + spinner.Update(fmt.Sprintf("Error checking %s status: %s", args.kind, err)) continue } - switch resp.StatusCode() { - case 200: - if resp.JSON200 == nil { + switch code { + case http.StatusOK: + if current == nil { return errors.New("no response body returned from API") } - args.Service.Status = resp.JSON200.Status - if args.Service.Status == args.TargetStatus { + *args.status = *current + if *args.status == args.targetStatus { return nil } - spinner.Update(statusMessage(args.Service)) - case 404: - // Can happen if user deletes service while it's still provisioning - return errors.New("service not found") - case 500: + spinner.Update(statusMessage(args.title, *args.status)) + case http.StatusNotFound: + // Can happen if user deletes the resource while it's still provisioning + return fmt.Errorf("%s not found", args.kind) + case http.StatusInternalServerError: // Assume 500s are temporary server-side issues, and that it's safe to keep polling - spinner.Update("Error checking service status: internal server error") + spinner.Update(fmt.Sprintf("Error checking %s status: internal server error", args.kind)) default: - return fmt.Errorf("received unexpected %s while checking service status", resp.Status()) + return fmt.Errorf("received unexpected %s while checking %s status", httpStatus, args.kind) } } } } -func statusMessage(service *api.Service) string { - return fmt.Sprintf("Service status: %s", service.Status) +func statusMessage(title string, status api.DeployStatus) string { + return fmt.Sprintf("%s status: %s", title, status) } diff --git a/internal/mcp/branch_create.go b/internal/mcp/branch_create.go new file mode 100644 index 00000000..92da2037 --- /dev/null +++ b/internal/mcp/branch_create.go @@ -0,0 +1,166 @@ +package mcp + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "net/http" + + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// BranchCreateInput represents input for branch_create +type BranchCreateInput struct { + ServiceID string `json:"service_id"` + Name string `json:"name,omitempty"` + CPUMemory string `json:"cpu_memory,omitempty"` + IOPS *int `json:"iops,omitempty"` + Wait bool `json:"wait,omitempty"` +} + +func (BranchCreateInput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchCreateInput](nil)) + + setServiceIDSchemaProperties(schema) + schema.Properties["service_id"].Description = "Unique identifier of the Fluid Service to branch (10-character alphanumeric string). Use service_list to find service IDs. Branching from a branch isn't supported." + + schema.Properties["name"].Description = "Name for the branch, unique within the parent service (auto-generated if not provided)" + schema.Properties["name"].Examples = []any{"ci-4821", "feature/user-auth"} + + setBranchCPUMemorySchemaProperties(schema, "CPU and memory allocation combination. If not specified, the branch gets 0.25 CPU/2 GB, whatever the parent service's size.") + setBranchIOPSSchemaProperties(schema, "If not specified, the branch gets the baseline IOPS for the user's plan and its CPU allocation.") + + schema.Properties["wait"].Description = "Whether to wait for the branch to be fully ready before returning. Default is false (recommended). Only set to true if your next steps require connecting to or querying the branch. When true, waits up to 10 minutes." + schema.Properties["wait"].Default = util.Must(json.Marshal(false)) + schema.Properties["wait"].Examples = []any{false, true} + + return schema +} + +// BranchCreateOutput represents output for branch_create +type BranchCreateOutput struct { + Branch BranchDetail `json:"branch"` + Message string `json:"message"` + PasswordStorage *common.PasswordStorageResult `json:"password_storage,omitempty"` +} + +func (BranchCreateOutput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchCreateOutput](nil)) + schema.Properties["branch"] = BranchDetail{}.Schema() + schema.Properties["message"].Description = "Human-readable outcome of the operation" + schema.Properties["password_storage"].Description = "Where the branch's new tsdbadmin password was saved locally, and whether saving it succeeded" + return schema +} + +func newBranchCreateTool() *mcp.Tool { + return &mcp.Tool{ + Name: toolBranchCreate, + Title: "Create Branch", + Description: `Create a branch of a Fluid Service. + +A branch is a lightweight, ephemeral copy of a service for development, testing, CI/CD, and agent workflows. It shares the parent's storage as of the moment it is created and gets its own compute. The tsdbadmin user gets a new password, which is saved to the configured password storage. Branches are always treated as DEV, so under read-only mode for production services a PROD service can still be branched. + +By default: +- The name is auto-generated +- The branch gets 0.25 CPU and 2 GB of memory, and the baseline IOPS for that size + +WARNING: Creates billable resources.`, + InputSchema: BranchCreateInput{}.Schema(), + OutputSchema: BranchCreateOutput{}.Schema(), + Annotations: &mcp.ToolAnnotations{ + ReadOnlyHint: false, + DestructiveHint: new(false), // Creates resources but doesn't modify existing + IdempotentHint: false, // Branching the same service multiple times creates multiple branches + OpenWorldHint: new(false), + Title: "Create Branch", + }, + } +} + +// handleBranchCreate handles the branch_create MCP tool +func (s *Server) handleBranchCreate(ctx context.Context, req *mcp.CallToolRequest, input BranchCreateInput) (*mcp.CallToolResult, BranchCreateOutput, error) { + cfg, client, projectID, err := s.app.GetAll() + if err != nil { + return nil, BranchCreateOutput{}, err + } + + // Gate on the branch's own tag, which is always DEV: under prod mode, + // branching a PROD service is allowed. + if err := common.CheckReadOnly(cfg, common.BranchEnvironmentTag); err != nil { + return nil, BranchCreateOutput{}, err + } + + var createReq api.BranchCreate + if input.Name != "" { + createReq.Name = &input.Name + } + if input.CPUMemory != "" { + cpuMemoryCfg, err := parseBranchCPUMemory(input.CPUMemory) + if err != nil { + return nil, BranchCreateOutput{}, err + } + createReq.CPUMillis = &cpuMemoryCfg.CPUMillis + createReq.MemoryGbs = &cpuMemoryCfg.MemoryGBs + } + createReq.Iops = input.IOPS + + s.logger.Info("MCP: Creating branch", + slog.String("project_id", projectID), + slog.String("service_id", input.ServiceID), + slog.String("name", input.Name), + slog.String("cpu_memory", input.CPUMemory), + slog.Any("iops", input.IOPS), + ) + + resp, err := client.CreateBranchWithResponse(ctx, projectID, input.ServiceID, createReq) + if err != nil { + return nil, BranchCreateOutput{}, fmt.Errorf("failed to create branch: %w", err) + } + if resp.StatusCode() != http.StatusAccepted { + return nil, BranchCreateOutput{}, common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON202 == nil { + return nil, BranchCreateOutput{}, fmt.Errorf("empty response from API") + } + branch := *resp.JSON202 + + // Saved before any waiting, so the password is stored even if the wait + // fails or is interrupted. + var passwordStorage *common.PasswordStorageResult + if branch.InitialPassword != nil { + result, err := common.SavePasswordWithResult(cfg, common.BranchPasswordTarget(branch), *branch.InitialPassword, "tsdbadmin") + passwordStorage = &result + if err != nil { + s.logger.Warn("MCP: Password storage failed", slog.Any("error", err)) + } + } + + message := "Branch creation request accepted. The branch may still be provisioning." + if input.Wait { + if err := common.WaitForBranch(ctx, common.WaitForBranchArgs{ + Client: client, + ProjectID: projectID, + ServiceID: input.ServiceID, + BranchID: branch.ID, + Branch: &branch, + TargetStatus: api.DeployStatusREADY, + Timeout: waitTimeout, + TimeoutMsg: "branch may still be provisioning", + }); err != nil { + message = fmt.Sprintf("Error: %s", err.Error()) + } else { + message = "Branch is ready." + } + } + return nil, BranchCreateOutput{ + Branch: s.convertToBranchDetail(cfg, branch), + Message: message, + PasswordStorage: passwordStorage, + }, nil +} diff --git a/internal/mcp/branch_create_test.go b/internal/mcp/branch_create_test.go new file mode 100644 index 00000000..c85112d8 --- /dev/null +++ b/internal/mcp/branch_create_test.go @@ -0,0 +1,207 @@ +package mcp + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" +) + +func TestBranchCreateTool(t *testing.T) { + experimental := withExperimental() + args := map[string]any{"service_id": "e6ue9697jf"} + baseReq := api.BranchCreate{} + + // The create response carries the branch's new password, once. + created := sampleBranch(branchStatus(api.DeployStatusQUEUED), func(b *api.Branch) { + b.InitialPassword = new("branch-pw") + }) + + // expectCreate registers creating a branch of the sample service with req, + // answered with branch. + expectCreate := func(req api.BranchCreate, branch api.Branch) func(m *mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + m.EXPECT().CreateBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", req). + Return(&api.CreateBranchResponse{ + HTTPResponse: httpResponse(http.StatusAccepted), + JSON202: &branch, + }, nil) + } + } + + acceptedMsg := "Branch creation request accepted. The branch may still be provisioning." + storedPassword := map[string]any{ + "success": true, + "method": "keyring", + "message": "Password saved to system keyring", + } + + runToolTests(t, []toolTest{ + { + name: "not registered without the experimental gate", + tool: toolBranchCreate, + args: args, + wantCallErr: `calling "tools/call": unknown tool "branch_create"`, + }, + { + name: "not logged in", + tool: toolBranchCreate, + args: args, + opts: []runOption{experimental, withNotLoggedIn()}, + wantErr: notLoggedInMsg, + }, + { + name: "rejects a combination a branch can't have", + tool: toolBranchCreate, + args: map[string]any{"service_id": "e6ue9697jf", "cpu_memory": "1 CPU/4 GB"}, + opts: []runOption{experimental}, + wantErr: `validating "arguments": validating root: validating /properties/cpu_memory: enum: 1 CPU/4 GB does not equal any of: [0.25 CPU/2 GB 0.5 CPU/4 GB 1 CPU/8 GB 2 CPU/16 GB 4 CPU/32 GB 8 CPU/64 GB 16 CPU/128 GB 32 CPU/256 GB]`, + }, + { + name: "read-only all refuses without an API call", + tool: toolBranchCreate, + args: args, + opts: []runOption{experimental, withConfigAfterStart(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + // A branch always counts as DEV, so prod allows branching even a + // PROD service, without looking up the service's tag. + name: "read-only prod allows branching", + tool: toolBranchCreate, + args: args, + opts: []runOption{experimental, withConfig(map[string]any{"read_only": "prod"})}, + mock: expectCreate(baseReq, created), + wantOutput: map[string]any{ + "branch": sampleBranchDetail(api.DeployStatusQUEUED), + "message": acceptedMsg, + "password_storage": storedPassword, + }, + }, + { + name: "network error", + tool: toolBranchCreate, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + m.EXPECT().CreateBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", baseReq). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to create branch: connection refused", + }, + { + name: "API error", + tool: toolBranchCreate, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + m.EXPECT().CreateBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", baseReq). + Return(&api.CreateBranchResponse{ + HTTPResponse: httpResponse(http.StatusBadRequest), + JSON4XX: &api.ClientError{Message: new("service is not a Fluid Service")}, + }, nil) + }, + wantErr: "service is not a Fluid Service", + }, + { + name: "nil response body", + tool: toolBranchCreate, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + m.EXPECT().CreateBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", baseReq). + Return(&api.CreateBranchResponse{HTTPResponse: httpResponse(http.StatusAccepted)}, nil) + }, + wantErr: "empty response from API", + }, + { + // Only service IDs go in the default, so a branch never becomes it. + name: "saves the password and leaves the default alone", + tool: toolBranchCreate, + args: args, + opts: []runOption{experimental}, + mock: expectCreate(baseReq, created), + wantOutput: map[string]any{ + "branch": sampleBranchDetail(api.DeployStatusQUEUED), + "message": acceptedMsg, + "password_storage": storedPassword, + }, + checks: []checkFunc{checkStoredPassword("br4nch1d9x", "branch-pw"), checkDefaultService("")}, + }, + { + // A storage failure isn't fatal: the branch exists either way, so + // the tool reports the failure rather than erroring. + name: "reports a password storage failure", + tool: toolBranchCreate, + args: args, + opts: []runOption{experimental, withKeyringError(errors.New("keyring is locked"))}, + mock: expectCreate(baseReq, created), + wantOutput: map[string]any{ + "branch": sampleBranchDetail(api.DeployStatusQUEUED), + "message": acceptedMsg, + "password_storage": map[string]any{ + "success": false, + "method": "keyring", + "message": "Failed to save password to keyring: keyring is locked", + }, + }, + }, + { + name: "no initial password", + tool: toolBranchCreate, + args: args, + opts: []runOption{experimental}, + mock: expectCreate(baseReq, sampleBranch(branchStatus(api.DeployStatusQUEUED))), + wantOutput: map[string]any{ + "branch": sampleBranchDetail(api.DeployStatusQUEUED), + "message": acceptedMsg, + }, + }, + { + name: "custom name and compute", + tool: toolBranchCreate, + args: map[string]any{"service_id": "e6ue9697jf", "name": "ci-4821", "cpu_memory": "2 CPU/16 GB"}, + opts: []runOption{experimental}, + mock: expectCreate(api.BranchCreate{ + Name: new("ci-4821"), + CPUMillis: new(2000), + MemoryGbs: new(16), + }, created), + wantOutput: map[string]any{ + "branch": sampleBranchDetail(api.DeployStatusQUEUED), + "message": acceptedMsg, + "password_storage": storedPassword, + }, + }, + { + name: "iops", + tool: toolBranchCreate, + args: map[string]any{"service_id": "e6ue9697jf", "iops": 16000}, + opts: []runOption{experimental}, + mock: expectCreate(api.BranchCreate{Iops: new(16000)}, created), + wantOutput: map[string]any{ + "branch": sampleBranchDetail(api.DeployStatusQUEUED), + "message": acceptedMsg, + "password_storage": storedPassword, + }, + }, + { + name: "wait polls until ready", + synctest: true, + tool: toolBranchCreate, + args: map[string]any{"service_id": "e6ue9697jf", "wait": true}, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectCreate(baseReq, created)(m) + expectGetBranch(m, sampleBranch()) + }, + wantOutput: map[string]any{ + "branch": sampleBranchDetail(api.DeployStatusREADY), + "message": "Branch is ready.", + "password_storage": storedPassword, + }, + }, + }) +} diff --git a/internal/mcp/branch_delete.go b/internal/mcp/branch_delete.go new file mode 100644 index 00000000..fb63b8d3 --- /dev/null +++ b/internal/mcp/branch_delete.go @@ -0,0 +1,91 @@ +package mcp + +import ( + "context" + "fmt" + "log/slog" + "net/http" + + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// BranchDeleteInput represents input for branch_delete +type BranchDeleteInput struct { + BranchID string `json:"branch_id"` +} + +func (BranchDeleteInput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchDeleteInput](nil)) + setBranchIDSchemaProperties(schema) + return schema +} + +// BranchDeleteOutput represents output for branch_delete +type BranchDeleteOutput struct { + BranchID string `json:"branch_id"` + Message string `json:"message"` +} + +func (BranchDeleteOutput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchDeleteOutput](nil)) + schema.Properties["branch_id"].Description = "Identifier of the deleted branch" + schema.Properties["message"].Description = "Human-readable outcome of the operation" + return schema +} + +func newBranchDeleteTool() *mcp.Tool { + return &mcp.Tool{ + Name: toolBranchDelete, + Title: "Delete Branch", + Description: `Delete a branch permanently, along with any changes made on it. The parent service is not affected. + +This operation is irreversible. Branches are always treated as DEV, so the tool does not prompt the user for confirmation the way deleting a PROD service does.`, + InputSchema: BranchDeleteInput{}.Schema(), + OutputSchema: BranchDeleteOutput{}.Schema(), + Annotations: &mcp.ToolAnnotations{ + ReadOnlyHint: false, + DestructiveHint: new(true), // Permanently destroys the branch and the changes made on it + IdempotentHint: true, // Deleting an already-deleted branch has no further effect (but returns an error) + OpenWorldHint: new(false), + Title: "Delete Branch", + }, + } +} + +// handleBranchDelete handles the branch_delete MCP tool +func (s *Server) handleBranchDelete(ctx context.Context, req *mcp.CallToolRequest, input BranchDeleteInput) (*mcp.CallToolResult, BranchDeleteOutput, error) { + cfg, client, projectID, err := s.app.GetAll() + if err != nil { + return nil, BranchDeleteOutput{}, err + } + + if err := common.CheckReadOnly(cfg, common.BranchEnvironmentTag); err != nil { + return nil, BranchDeleteOutput{}, err + } + + resolved, err := resolveBranchByID(ctx, client, projectID, input.BranchID) + if err != nil { + return nil, BranchDeleteOutput{}, err + } + + s.logger.Info("MCP: Deleting branch", + slog.String("project_id", projectID), + slog.String("branch_id", input.BranchID)) + + resp, err := client.DeleteBranchWithResponse(ctx, projectID, resolved.ServiceID, resolved.ID) + if err != nil { + return nil, BranchDeleteOutput{}, fmt.Errorf("failed to delete branch: %w", err) + } + if resp.StatusCode() != http.StatusAccepted { + return nil, BranchDeleteOutput{}, common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + + return nil, BranchDeleteOutput{ + BranchID: input.BranchID, + Message: fmt.Sprintf("Branch %q has been deleted.", input.BranchID), + }, nil +} diff --git a/internal/mcp/branch_delete_test.go b/internal/mcp/branch_delete_test.go new file mode 100644 index 00000000..eae9f61e --- /dev/null +++ b/internal/mcp/branch_delete_test.go @@ -0,0 +1,92 @@ +package mcp + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" +) + +func TestBranchDeleteTool(t *testing.T) { + experimental := withExperimental() + args := map[string]any{"branch_id": "br4nch1d9x"} + expectDelete := func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().DeleteBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x"). + Return(&api.DeleteBranchResponse{HTTPResponse: httpResponse(http.StatusAccepted)}, nil) + } + deleted := map[string]any{ + "branch_id": "br4nch1d9x", + "message": `Branch "br4nch1d9x" has been deleted.`, + } + + runToolTests(t, []toolTest{ + { + name: "not registered without the experimental gate", + tool: toolBranchDelete, + args: args, + wantCallErr: `calling "tools/call": unknown tool "branch_delete"`, + }, + { + name: "not logged in", + tool: toolBranchDelete, + args: args, + opts: []runOption{experimental, withNotLoggedIn()}, + wantErr: notLoggedInMsg, + }, + { + name: "read-only all refuses without an API call", + tool: toolBranchDelete, + args: args, + opts: []runOption{experimental, withConfigAfterStart(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + // A branch always counts as DEV, so prod neither refuses nor + // prompts. + name: "read-only prod deletes without a prompt", + tool: toolBranchDelete, + args: args, + opts: []runOption{experimental, withConfig(map[string]any{"read_only": "prod"})}, + mock: expectDelete, + wantOutput: deleted, + }, + { + name: "network error", + tool: toolBranchDelete, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().DeleteBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x"). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to delete branch: connection refused", + }, + { + name: "API error", + tool: toolBranchDelete, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().DeleteBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x"). + Return(&api.DeleteBranchResponse{ + HTTPResponse: httpResponse(http.StatusNotFound), + JSON4XX: &api.ClientError{Message: new("branch not found")}, + }, nil) + }, + wantErr: "branch not found", + }, + { + name: "success", + tool: toolBranchDelete, + args: args, + opts: []runOption{experimental}, + mock: expectDelete, + wantOutput: deleted, + }, + }) +} diff --git a/internal/mcp/branch_get.go b/internal/mcp/branch_get.go new file mode 100644 index 00000000..f804580a --- /dev/null +++ b/internal/mcp/branch_get.go @@ -0,0 +1,79 @@ +package mcp + +import ( + "context" + "log/slog" + + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// BranchGetInput represents input for branch_get +type BranchGetInput struct { + BranchID string `json:"branch_id"` +} + +func (BranchGetInput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchGetInput](nil)) + setBranchIDSchemaProperties(schema) + return schema +} + +// BranchGetOutput represents output for branch_get +type BranchGetOutput struct { + Branch BranchDetail `json:"branch"` +} + +func (BranchGetOutput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchGetOutput](nil)) + schema.Properties["branch"] = BranchDetail{}.Schema() + return schema +} + +func newBranchGetTool() *mcp.Tool { + return &mcp.Tool{ + Name: toolBranchGet, + Title: "Get Branch Details", + Description: "Get detailed information for a specific branch of a database service. " + + "Returns its parent service, status, resource allocation, creation time, and connection details.", + InputSchema: BranchGetInput{}.Schema(), + OutputSchema: BranchGetOutput{}.Schema(), + Annotations: &mcp.ToolAnnotations{ + ReadOnlyHint: true, + OpenWorldHint: new(false), + Title: "Get Branch Details", + }, + } +} + +// handleBranchGet handles the branch_get MCP tool +func (s *Server) handleBranchGet(ctx context.Context, req *mcp.CallToolRequest, input BranchGetInput) (*mcp.CallToolResult, BranchGetOutput, error) { + cfg, client, projectID, err := s.app.GetAll() + if err != nil { + return nil, BranchGetOutput{}, err + } + + s.logger.Info("MCP: Getting branch details", + slog.String("project_id", projectID), + slog.String("branch_id", input.BranchID)) + + branch, err := resolveBranchByID(ctx, client, projectID, input.BranchID) + if err != nil { + return nil, BranchGetOutput{}, err + } + + detail := s.convertToBranchDetail(cfg, *branch) + + // The branch carries only its parent's ID, so the name takes a second + // fetch. The rest of the output doesn't depend on it. + if parent, err := common.GetService(ctx, client, projectID, branch.ServiceID); err != nil { + s.logger.Error("MCP: Failed to get parent service", slog.Any("error", err)) + } else { + detail.ServiceName = parent.Name + } + + return nil, BranchGetOutput{Branch: detail}, nil +} diff --git a/internal/mcp/branch_get_test.go b/internal/mcp/branch_get_test.go new file mode 100644 index 00000000..575e2775 --- /dev/null +++ b/internal/mcp/branch_get_test.go @@ -0,0 +1,181 @@ +package mcp + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" +) + +func TestBranchGetTool(t *testing.T) { + experimental := withExperimental() + args := map[string]any{"branch_id": "br4nch1d9x"} + + // getBranch registers resolving branch and then fetching its parent, for + // the parent's name. + getBranch := func(branch api.Branch) func(m *mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, branch) + expectGetService(m, "e6ue9697jf", sampleService()) + } + } + withServiceName := func(detail map[string]any) map[string]any { + detail["service_name"] = "test-service" + return detail + } + + runToolTests(t, []toolTest{ + { + name: "not registered without the experimental gate", + tool: toolBranchGet, + args: args, + wantCallErr: `calling "tools/call": unknown tool "branch_get"`, + }, + { + name: "not logged in", + tool: toolBranchGet, + args: args, + opts: []runOption{experimental, withNotLoggedIn()}, + wantErr: notLoggedInMsg, + }, + { + name: "rejects a malformed branch ID", + tool: toolBranchGet, + args: map[string]any{"branch_id": "nope"}, + opts: []runOption{experimental}, + wantErr: `validating "arguments": validating root: validating /properties/branch_id: pattern: "nope" does not match regular expression "^[a-z0-9]{10}$"`, + }, + { + name: "network error", + tool: toolBranchGet, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + m.EXPECT().ResolveRefWithResponse(validCtx, testProjectID, &api.ResolveRefParams{Ref: "br4nch1d9x", Type: branchType}). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to resolve branch 'br4nch1d9x': connection refused", + }, + { + name: "branch not found", + tool: toolBranchGet, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveStatus(m, "br4nch1d9x", branchType, http.StatusNotFound, &api.ClientError{Message: new("resource not found")}) + }, + wantErr: "branch 'br4nch1d9x' not found", + }, + { + name: "nil response body", + tool: toolBranchGet, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveStatus(m, "br4nch1d9x", branchType, http.StatusOK, nil) + }, + wantErr: "empty response from API", + }, + { + // The tools are IDs-only, but the resolve endpoint also matches + // names, so a name shaped like an ID resolves to some other + // branch. That match is refused. + name: "refuses a match by name", + tool: toolBranchGet, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolve(m, "br4nch1d9x", branchType, sampleBranch(func(b *api.Branch) { + b.ID = "0th3rbr4nc" + b.Name = "br4nch1d9x" + })) + }, + wantErr: "branch 'br4nch1d9x' not found", + }, + { + name: "success", + tool: toolBranchGet, + args: args, + opts: []runOption{experimental}, + mock: getBranch(sampleBranch()), + wantOutput: map[string]any{"branch": withServiceName(sampleBranchDetail(api.DeployStatusREADY))}, + }, + { + // The IOPS go in resources, as they do for services. + name: "returns the IOPS in resources", + tool: toolBranchGet, + args: args, + opts: []runOption{experimental}, + mock: getBranch(sampleBranch(func(b *api.Branch) { b.Iops = new(16000) })), + wantOutput: map[string]any{"branch": func() map[string]any { + detail := withServiceName(sampleBranchDetail(api.DeployStatusREADY)) + detail["resources"] = map[string]any{"cpu": "0.25 cores", "memory": "2 GB", "iops": float64(16000)} + return detail + }()}, + }, + { + // The parent's name is the only thing the second fetch adds, so + // without it the branch is still returned. + name: "parent fetch fails", + tool: toolBranchGet, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "e6ue9697jf"). + Return(nil, errors.New("connection refused")) + }, + wantOutput: map[string]any{"branch": sampleBranchDetail(api.DeployStatusREADY)}, + }, + { + // A branch counts as DEV, so only read_only=all makes its + // connection string read-only. + name: "read-only all makes the connection string read-only", + tool: toolBranchGet, + args: args, + opts: []runOption{experimental, withConfig(map[string]any{"read_only": "all"})}, + mock: getBranch(sampleBranch()), + wantOutput: map[string]any{"branch": func() map[string]any { + detail := withServiceName(sampleBranchDetail(api.DeployStatusREADY)) + detail["connection_string"] = "postgresql://tsdbadmin@br4nch1d9x.test-project-123.branch.tsdb.cloud.timescale.com:36956/tsdb?sslmode=require&options=-c%20tsdb_admin.read_only_connection%3Dtrue" + return detail + }()}, + }, + { + // No port is guessed, so without one there's no endpoint to show + // or connect to. + name: "endpoint without a port", + tool: toolBranchGet, + args: args, + opts: []runOption{experimental}, + mock: getBranch(sampleBranch(func(b *api.Branch) { b.Endpoint.Port = nil })), + wantOutput: map[string]any{"branch": map[string]any{ + "id": "br4nch1d9x", + "name": "test-branch", + "service_id": "e6ue9697jf", + "service_name": "test-service", + "status": "READY", + "created": "2025-01-16T09:00:00Z", + "resources": map[string]any{"cpu": "0.25 cores", "memory": "2 GB"}, + }}, + }, + { + name: "provisioning branch without an endpoint", + tool: toolBranchGet, + args: args, + opts: []runOption{experimental}, + mock: getBranch(sampleBranch(branchStatus(api.DeployStatusQUEUED), func(b *api.Branch) { b.Endpoint = nil })), + wantOutput: map[string]any{"branch": map[string]any{ + "id": "br4nch1d9x", + "name": "test-branch", + "service_id": "e6ue9697jf", + "service_name": "test-service", + "status": "QUEUED", + "created": "2025-01-16T09:00:00Z", + "resources": map[string]any{"cpu": "0.25 cores", "memory": "2 GB"}, + }}, + }, + }) +} diff --git a/internal/mcp/branch_list.go b/internal/mcp/branch_list.go new file mode 100644 index 00000000..0fca778b --- /dev/null +++ b/internal/mcp/branch_list.go @@ -0,0 +1,83 @@ +package mcp + +import ( + "context" + "fmt" + "log/slog" + "net/http" + + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// BranchListInput represents input for branch_list +type BranchListInput struct { + ServiceID string `json:"service_id"` +} + +func (BranchListInput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchListInput](nil)) + setServiceIDSchemaProperties(schema) + schema.Properties["service_id"].Description = "Unique identifier of the service whose branches to list (10-character alphanumeric string). Use service_list to find service IDs." + return schema +} + +// BranchListOutput represents output for branch_list +type BranchListOutput struct { + Branches []BranchDetail `json:"branches"` +} + +func (BranchListOutput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchListOutput](nil)) + schema.Properties["branches"].Items = BranchDetail{}.Schema() + return schema +} + +func newBranchListTool() *mcp.Tool { + return &mcp.Tool{ + Name: toolBranchList, + Title: "List Branches", + Description: "List the branches of a database service. " + + "Returns each branch's status, resource allocation, and connection details.", + InputSchema: BranchListInput{}.Schema(), + OutputSchema: BranchListOutput{}.Schema(), + Annotations: &mcp.ToolAnnotations{ + ReadOnlyHint: true, + OpenWorldHint: new(false), + Title: "List Branches", + }, + } +} + +// handleBranchList handles the branch_list MCP tool +func (s *Server) handleBranchList(ctx context.Context, req *mcp.CallToolRequest, input BranchListInput) (*mcp.CallToolResult, BranchListOutput, error) { + cfg, client, projectID, err := s.app.GetAll() + if err != nil { + return nil, BranchListOutput{}, err + } + + s.logger.Info("MCP: Listing branches", + slog.String("project_id", projectID), + slog.String("service_id", input.ServiceID)) + + resp, err := client.GetBranchesWithResponse(ctx, projectID, input.ServiceID, nil) + if err != nil { + return nil, BranchListOutput{}, fmt.Errorf("failed to list branches: %w", err) + } + if resp.StatusCode() != http.StatusOK { + return nil, BranchListOutput{}, common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON200 == nil { + return nil, BranchListOutput{}, fmt.Errorf("empty response from API") + } + + branches := make([]BranchDetail, len(*resp.JSON200)) + for i, branch := range *resp.JSON200 { + branches[i] = s.convertToBranchDetail(cfg, branch) + } + + return nil, BranchListOutput{Branches: branches}, nil +} diff --git a/internal/mcp/branch_list_test.go b/internal/mcp/branch_list_test.go new file mode 100644 index 00000000..4e386ed0 --- /dev/null +++ b/internal/mcp/branch_list_test.go @@ -0,0 +1,99 @@ +package mcp + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" +) + +func TestBranchListTool(t *testing.T) { + experimental := withExperimental() + args := map[string]any{"service_id": "e6ue9697jf"} + expectList := func(branches ...api.Branch) func(m *mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "e6ue9697jf", nil). + Return(&api.GetBranchesResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &branches, + }, nil) + } + } + + runToolTests(t, []toolTest{ + { + name: "not registered without the experimental gate", + tool: toolBranchList, + args: args, + wantCallErr: `calling "tools/call": unknown tool "branch_list"`, + }, + { + name: "not logged in", + tool: toolBranchList, + args: args, + opts: []runOption{experimental, withNotLoggedIn()}, + wantErr: notLoggedInMsg, + }, + { + name: "rejects a malformed service ID", + tool: toolBranchList, + args: map[string]any{"service_id": "nope"}, + opts: []runOption{experimental}, + wantErr: `validating "arguments": validating root: validating /properties/service_id: pattern: "nope" does not match regular expression "^[a-z0-9]{10}$"`, + }, + { + name: "network error", + tool: toolBranchList, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "e6ue9697jf", nil). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to list branches: connection refused", + }, + { + name: "API error", + tool: toolBranchList, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "e6ue9697jf", nil). + Return(&api.GetBranchesResponse{ + HTTPResponse: httpResponse(http.StatusForbidden), + JSON4XX: &api.ClientError{Message: new("forbidden")}, + }, nil) + }, + wantErr: "forbidden", + }, + { + name: "nil response body", + tool: toolBranchList, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "e6ue9697jf", nil). + Return(&api.GetBranchesResponse{HTTPResponse: httpResponse(http.StatusOK)}, nil) + }, + wantErr: "empty response from API", + }, + { + name: "no branches", + tool: toolBranchList, + args: args, + opts: []runOption{experimental}, + mock: expectList(), + wantOutput: map[string]any{"branches": []any{}}, + }, + { + name: "lists the service's branches", + tool: toolBranchList, + args: args, + opts: []runOption{experimental}, + mock: expectList(sampleBranch()), + wantOutput: map[string]any{"branches": []any{sampleBranchDetail(api.DeployStatusREADY)}}, + }, + }) +} diff --git a/internal/mcp/branch_rename.go b/internal/mcp/branch_rename.go new file mode 100644 index 00000000..70236dd1 --- /dev/null +++ b/internal/mcp/branch_rename.go @@ -0,0 +1,111 @@ +package mcp + +import ( + "context" + "errors" + "fmt" + "log/slog" + "net/http" + "strings" + + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// BranchRenameInput represents input for branch_rename +type BranchRenameInput struct { + BranchID string `json:"branch_id"` + Name string `json:"name"` +} + +func (BranchRenameInput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchRenameInput](nil)) + + setBranchIDSchemaProperties(schema) + + schema.Properties["name"].Description = "The new name for the branch, unique within its parent service." + schema.Properties["name"].Examples = []any{"feature/rbac", "ci-4821"} + + return schema +} + +// BranchRenameOutput represents output for branch_rename +type BranchRenameOutput struct { + Message string `json:"message"` + BranchID string `json:"branch_id"` + Name string `json:"name"` +} + +func (BranchRenameOutput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchRenameOutput](nil)) + schema.Properties["message"].Description = "Human-readable result of the rename operation" + schema.Properties["branch_id"].Description = "Unique identifier of the renamed branch, unchanged by the rename" + schema.Properties["name"].Description = "Name of the branch as stored after the rename" + return schema +} + +func newBranchRenameTool() *mcp.Tool { + return &mcp.Tool{ + Name: toolBranchRename, + Title: "Rename Branch", + Description: "Rename a branch. " + + "Only the name changes: the branch ID, endpoint, and data are untouched, so existing connections and connection strings keep working.", + InputSchema: BranchRenameInput{}.Schema(), + OutputSchema: BranchRenameOutput{}.Schema(), + Annotations: &mcp.ToolAnnotations{ + ReadOnlyHint: false, + DestructiveHint: new(false), // A rename loses nothing and can be reversed by renaming back + IdempotentHint: true, // Renaming to the name a branch already has changes nothing + OpenWorldHint: new(false), + Title: "Rename Branch", + }, + } +} + +// handleBranchRename handles the branch_rename MCP tool +func (s *Server) handleBranchRename(ctx context.Context, req *mcp.CallToolRequest, input BranchRenameInput) (*mcp.CallToolResult, BranchRenameOutput, error) { + cfg, client, projectID, err := s.app.GetAll() + if err != nil { + return nil, BranchRenameOutput{}, err + } + + if err := common.CheckReadOnly(cfg, common.BranchEnvironmentTag); err != nil { + return nil, BranchRenameOutput{}, err + } + + name := strings.TrimSpace(input.Name) + if name == "" { + return nil, BranchRenameOutput{}, errors.New("new name cannot be empty") + } + + resolved, err := resolveBranchByID(ctx, client, projectID, input.BranchID) + if err != nil { + return nil, BranchRenameOutput{}, err + } + + s.logger.Info("MCP: Renaming branch", + slog.String("project_id", projectID), + slog.String("branch_id", input.BranchID)) + + resp, err := client.RenameBranchWithResponse(ctx, projectID, resolved.ServiceID, resolved.ID, api.BranchRename{Name: name}) + if err != nil { + return nil, BranchRenameOutput{}, fmt.Errorf("failed to rename branch: %w", err) + } + if resp.StatusCode() != http.StatusOK { + return nil, BranchRenameOutput{}, common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON200 == nil { + return nil, BranchRenameOutput{}, errors.New("empty response from API") + } + renamed := *resp.JSON200 + + return nil, BranchRenameOutput{ + Message: fmt.Sprintf("Branch %q renamed to %q.", renamed.ID, renamed.Name), + BranchID: renamed.ID, + Name: renamed.Name, + }, nil +} diff --git a/internal/mcp/branch_rename_test.go b/internal/mcp/branch_rename_test.go new file mode 100644 index 00000000..fd283f7c --- /dev/null +++ b/internal/mcp/branch_rename_test.go @@ -0,0 +1,92 @@ +package mcp + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" +) + +func TestBranchRenameTool(t *testing.T) { + experimental := withExperimental() + args := map[string]any{"branch_id": "br4nch1d9x", "name": "feature/rbac"} + expectRename := func(m *mocks.MockClientWithResponsesInterface) { + renamed := sampleBranch(func(b *api.Branch) { b.Name = "feature/rbac" }) + expectResolveBranch(m, sampleBranch()) + m.EXPECT().RenameBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x", api.BranchRename{Name: "feature/rbac"}). + Return(&api.RenameBranchResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &renamed, + }, nil) + } + renamed := map[string]any{ + "message": `Branch "br4nch1d9x" renamed to "feature/rbac".`, + "branch_id": "br4nch1d9x", + "name": "feature/rbac", + } + + runToolTests(t, []toolTest{ + { + name: "not registered without the experimental gate", + tool: toolBranchRename, + args: args, + wantCallErr: `calling "tools/call": unknown tool "branch_rename"`, + }, + { + name: "read-only all refuses without an API call", + tool: toolBranchRename, + args: args, + opts: []runOption{experimental, withConfigAfterStart(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + name: "read-only prod allows", + tool: toolBranchRename, + args: args, + opts: []runOption{experimental, withConfig(map[string]any{"read_only": "prod"})}, + mock: expectRename, + wantOutput: renamed, + }, + { + name: "empty name", + tool: toolBranchRename, + args: map[string]any{"branch_id": "br4nch1d9x", "name": " "}, + opts: []runOption{experimental}, + wantErr: "new name cannot be empty", + }, + { + name: "network error", + tool: toolBranchRename, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().RenameBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x", api.BranchRename{Name: "feature/rbac"}). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to rename branch: connection refused", + }, + { + name: "nil response body", + tool: toolBranchRename, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().RenameBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x", api.BranchRename{Name: "feature/rbac"}). + Return(&api.RenameBranchResponse{HTTPResponse: httpResponse(http.StatusOK)}, nil) + }, + wantErr: "empty response from API", + }, + { + name: "success", + tool: toolBranchRename, + args: args, + opts: []runOption{experimental}, + mock: expectRename, + wantOutput: renamed, + }, + }) +} diff --git a/internal/mcp/branch_resize.go b/internal/mcp/branch_resize.go new file mode 100644 index 00000000..17575812 --- /dev/null +++ b/internal/mcp/branch_resize.go @@ -0,0 +1,156 @@ +package mcp + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log/slog" + "net/http" + + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// BranchResizeInput represents input for branch_resize +type BranchResizeInput struct { + BranchID string `json:"branch_id"` + CPUMemory string `json:"cpu_memory,omitempty"` + IOPS *int `json:"iops,omitempty"` + Wait bool `json:"wait,omitempty"` +} + +// Schema leaves both cpu_memory and iops optional; the handler requires at +// least one of the two. +func (BranchResizeInput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchResizeInput](nil)) + + setBranchIDSchemaProperties(schema) + setBranchCPUMemorySchemaProperties(schema, "CPU and memory allocation combination. If not specified, the branch keeps its current CPU and memory, and at least one of cpu_memory or iops is required.") + setBranchIOPSSchemaProperties(schema, "") + + schema.Properties["wait"].Description = "Whether to wait for the branch to be done resizing before returning. Default is false (recommended). Only set to true if your next steps require connecting to or querying the branch. When true, waits up to 10 minutes." + schema.Properties["wait"].Default = util.Must(json.Marshal(false)) + schema.Properties["wait"].Examples = []any{false, true} + + return schema +} + +// BranchResizeOutput represents output for branch_resize +type BranchResizeOutput struct { + Status string `json:"status"` + Resources *ResourceInfo `json:"resources,omitempty"` + Message string `json:"message"` +} + +func (BranchResizeOutput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchResizeOutput](nil)) + schema.Properties["status"].Description = "Current branch status after resize operation" + // The branch tools only exist behind the experimental gate, so the IOPS + // are always included. + setResourceInfoSchemaProperties(schema.Properties["resources"], true) + schema.Properties["message"].Description = "Human-readable outcome of the operation" + return schema +} + +func newBranchResizeTool() *mcp.Tool { + return &mcp.Tool{ + Name: toolBranchResize, + Title: "Resize Branch", + Description: `Resize a branch by changing its CPU and memory allocation or its provisioned IOPS. + +A branch's storage is shared with its parent service and isn't resized. The branch may be temporarily unavailable during the resize operation. + +WARNING: Creates billable resource changes. Increasing resources will increase costs.`, + InputSchema: BranchResizeInput{}.Schema(), + OutputSchema: BranchResizeOutput{}.Schema(), + Annotations: &mcp.ToolAnnotations{ + ReadOnlyHint: false, + DestructiveHint: new(false), // Not destructive, just modifies resources + IdempotentHint: true, // Can resize to same size multiple times + OpenWorldHint: new(false), + Title: "Resize Branch", + }, + } +} + +// handleBranchResize handles the branch_resize MCP tool +func (s *Server) handleBranchResize(ctx context.Context, req *mcp.CallToolRequest, input BranchResizeInput) (*mcp.CallToolResult, BranchResizeOutput, error) { + cfg, client, projectID, err := s.app.GetAll() + if err != nil { + return nil, BranchResizeOutput{}, err + } + + if err := common.CheckReadOnly(cfg, common.BranchEnvironmentTag); err != nil { + return nil, BranchResizeOutput{}, err + } + + if input.CPUMemory == "" && input.IOPS == nil { + return nil, BranchResizeOutput{}, errors.New("at least one of cpu_memory or iops is required") + } + + resolved, err := resolveBranchByID(ctx, client, projectID, input.BranchID) + if err != nil { + return nil, BranchResizeOutput{}, err + } + + // An IOPS-only resize omits CPU and memory, which the API leaves + // unchanged. + resizeReq := api.BranchResizeInput{Iops: input.IOPS} + if input.CPUMemory != "" { + cpuMemoryCfg, err := parseBranchCPUMemory(input.CPUMemory) + if err != nil { + return nil, BranchResizeOutput{}, err + } + resizeReq.CPUMillis = cpuMemoryCfg.CPUMillisString() + resizeReq.MemoryGbs = cpuMemoryCfg.MemoryGBsString() + } + + s.logger.Info("MCP: Resizing branch", + slog.String("project_id", projectID), + slog.String("branch_id", input.BranchID), + slog.String("cpu_memory", input.CPUMemory), + slog.Any("iops", input.IOPS), + ) + + resp, err := client.ResizeBranchWithResponse(ctx, projectID, resolved.ServiceID, resolved.ID, resizeReq) + if err != nil { + return nil, BranchResizeOutput{}, fmt.Errorf("failed to resize branch: %w", err) + } + if resp.StatusCode() != http.StatusAccepted { + return nil, BranchResizeOutput{}, common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON202 == nil { + return nil, BranchResizeOutput{}, fmt.Errorf("empty response from API") + } + branch := *resp.JSON202 + + message := "Resize request accepted. The branch may still be resizing." + if input.Wait { + if err := common.WaitForBranch(ctx, common.WaitForBranchArgs{ + Client: client, + ProjectID: projectID, + ServiceID: resolved.ServiceID, + BranchID: resolved.ID, + Branch: &branch, + TargetStatus: api.DeployStatusREADY, + Timeout: waitTimeout, + TimeoutMsg: "branch may still be resizing", + }); err != nil { + message = fmt.Sprintf("Error: %s", err.Error()) + } else { + message = "Branch resized." + } + } + + detail := s.convertToBranchDetail(cfg, branch) + return nil, BranchResizeOutput{ + Status: detail.Status, + Resources: detail.Resources, + Message: message, + }, nil +} diff --git a/internal/mcp/branch_resize_test.go b/internal/mcp/branch_resize_test.go new file mode 100644 index 00000000..13206558 --- /dev/null +++ b/internal/mcp/branch_resize_test.go @@ -0,0 +1,129 @@ +package mcp + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" +) + +func TestBranchResizeTool(t *testing.T) { + experimental := withExperimental() + args := map[string]any{"branch_id": "br4nch1d9x", "cpu_memory": "2 CPU/16 GB"} + req := api.BranchResizeInput{CPUMillis: new("2000"), MemoryGbs: new("16")} + expectResizeWith := func(req api.BranchResizeInput, status api.DeployStatus) func(*mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + branch := sampleBranch(branchStatus(status)) + expectResolveBranch(m, sampleBranch()) + m.EXPECT().ResizeBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x", req). + Return(&api.ResizeBranchResponse{ + HTTPResponse: httpResponse(http.StatusAccepted), + JSON202: &branch, + }, nil) + } + } + expectResize := func(status api.DeployStatus) func(*mocks.MockClientWithResponsesInterface) { + return expectResizeWith(req, status) + } + // The response's resources are the sample's, since the resize hasn't + // landed yet. + resources := map[string]any{"cpu": "0.25 cores", "memory": "2 GB"} + accepted := map[string]any{ + "status": "CONFIGURING", + "resources": resources, + "message": "Resize request accepted. The branch may still be resizing.", + } + + runToolTests(t, []toolTest{ + { + name: "not registered without the experimental gate", + tool: toolBranchResize, + args: args, + wantCallErr: `calling "tools/call": unknown tool "branch_resize"`, + }, + { + name: "missing cpu_memory and iops", + tool: toolBranchResize, + args: map[string]any{"branch_id": "br4nch1d9x"}, + opts: []runOption{experimental}, + wantErr: "at least one of cpu_memory or iops is required", + }, + { + name: "read-only all refuses without an API call", + tool: toolBranchResize, + args: args, + opts: []runOption{experimental, withConfigAfterStart(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + name: "read-only prod allows", + tool: toolBranchResize, + args: args, + opts: []runOption{experimental, withConfig(map[string]any{"read_only": "prod"})}, + mock: expectResize(api.DeployStatusCONFIGURING), + wantOutput: accepted, + }, + { + name: "network error", + tool: toolBranchResize, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().ResizeBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x", req). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to resize branch: connection refused", + }, + { + name: "nil response body", + tool: toolBranchResize, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().ResizeBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x", req). + Return(&api.ResizeBranchResponse{HTTPResponse: httpResponse(http.StatusAccepted)}, nil) + }, + wantErr: "empty response from API", + }, + { + // CPU and memory are left out, so the API keeps the current ones. + name: "iops only omits cpu and memory", + tool: toolBranchResize, + args: map[string]any{"branch_id": "br4nch1d9x", "iops": 16000}, + opts: []runOption{experimental}, + mock: expectResizeWith(api.BranchResizeInput{Iops: new(16000)}, api.DeployStatusCONFIGURING), + wantOutput: accepted, + }, + { + name: "resizes without waiting by default", + tool: toolBranchResize, + args: args, + opts: []runOption{experimental}, + mock: expectResize(api.DeployStatusCONFIGURING), + wantOutput: accepted, + }, + { + name: "wait polls until ready", + synctest: true, + tool: toolBranchResize, + args: map[string]any{"branch_id": "br4nch1d9x", "cpu_memory": "2 CPU/16 GB", "wait": true}, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResize(api.DeployStatusCONFIGURING)(m) + expectGetBranch(m, sampleBranch(func(b *api.Branch) { + b.CPUMillis = 2000 + b.MemoryGbs = 16 + })) + }, + wantOutput: map[string]any{ + "status": "READY", + "resources": map[string]any{"cpu": "2 cores", "memory": "16 GB"}, + "message": "Branch resized.", + }, + }, + }) +} diff --git a/internal/mcp/branch_start.go b/internal/mcp/branch_start.go new file mode 100644 index 00000000..4afd91ff --- /dev/null +++ b/internal/mcp/branch_start.go @@ -0,0 +1,122 @@ +package mcp + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "net/http" + + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// BranchStartInput represents input for branch_start +type BranchStartInput struct { + BranchID string `json:"branch_id"` + Wait bool `json:"wait,omitempty"` +} + +func (BranchStartInput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchStartInput](nil)) + + setBranchIDSchemaProperties(schema) + + schema.Properties["wait"].Description = "Whether to wait for the branch to be fully started before returning. Default is false (recommended). Only set to true if your next steps require connecting to or querying the branch. When true, waits up to 10 minutes." + schema.Properties["wait"].Default = util.Must(json.Marshal(false)) + schema.Properties["wait"].Examples = []any{false, true} + + return schema +} + +// BranchStartOutput represents output for branch_start +type BranchStartOutput struct { + Status string `json:"status"` + Message string `json:"message"` +} + +func (BranchStartOutput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchStartOutput](nil)) + schema.Properties["status"].Description = "Current branch status after start operation" + schema.Properties["message"].Description = "Human-readable outcome of the operation" + return schema +} + +func newBranchStartTool() *mcp.Tool { + return &mcp.Tool{ + Name: toolBranchStart, + Title: "Start Branch", + Description: `Start a stopped branch. + +Starting a branch resumes charging for its compute.`, + InputSchema: BranchStartInput{}.Schema(), + OutputSchema: BranchStartOutput{}.Schema(), + Annotations: &mcp.ToolAnnotations{ + ReadOnlyHint: false, + DestructiveHint: new(false), // Starting a branch doesn't modify or destroy anything + IdempotentHint: true, // Starting an already-started branch is safe (but returns an error) + OpenWorldHint: new(false), + Title: "Start Branch", + }, + } +} + +// handleBranchStart handles the branch_start MCP tool +func (s *Server) handleBranchStart(ctx context.Context, req *mcp.CallToolRequest, input BranchStartInput) (*mcp.CallToolResult, BranchStartOutput, error) { + cfg, client, projectID, err := s.app.GetAll() + if err != nil { + return nil, BranchStartOutput{}, err + } + + if err := common.CheckReadOnly(cfg, common.BranchEnvironmentTag); err != nil { + return nil, BranchStartOutput{}, err + } + + resolved, err := resolveBranchByID(ctx, client, projectID, input.BranchID) + if err != nil { + return nil, BranchStartOutput{}, err + } + + s.logger.Info("MCP: Starting branch", + slog.String("project_id", projectID), + slog.String("branch_id", input.BranchID)) + + resp, err := client.StartBranchWithResponse(ctx, projectID, resolved.ServiceID, resolved.ID) + if err != nil { + return nil, BranchStartOutput{}, fmt.Errorf("failed to start branch: %w", err) + } + if resp.StatusCode() != http.StatusAccepted { + return nil, BranchStartOutput{}, common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON202 == nil { + return nil, BranchStartOutput{}, fmt.Errorf("empty response from API") + } + branch := *resp.JSON202 + + message := "Branch start request accepted. The branch may still be starting." + if input.Wait { + if err := common.WaitForBranch(ctx, common.WaitForBranchArgs{ + Client: client, + ProjectID: projectID, + ServiceID: resolved.ServiceID, + BranchID: resolved.ID, + Branch: &branch, + TargetStatus: api.DeployStatusREADY, + Timeout: waitTimeout, + TimeoutMsg: "branch may still be starting", + }); err != nil { + message = fmt.Sprintf("Error: %s", err.Error()) + } else { + message = "Branch started." + } + } + + return nil, BranchStartOutput{ + Status: string(branch.Status), + Message: message, + }, nil +} diff --git a/internal/mcp/branch_start_test.go b/internal/mcp/branch_start_test.go new file mode 100644 index 00000000..1dc3e676 --- /dev/null +++ b/internal/mcp/branch_start_test.go @@ -0,0 +1,86 @@ +package mcp + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" +) + +func TestBranchStartTool(t *testing.T) { + experimental := withExperimental() + args := map[string]any{"branch_id": "br4nch1d9x"} + expectStart := func(status api.DeployStatus) func(*mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + branch := sampleBranch(branchStatus(status)) + expectResolveBranch(m, sampleBranch()) + m.EXPECT().StartBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x"). + Return(&api.StartBranchResponse{ + HTTPResponse: httpResponse(http.StatusAccepted), + JSON202: &branch, + }, nil) + } + } + accepted := map[string]any{ + "status": "RESUMING", + "message": "Branch start request accepted. The branch may still be starting.", + } + + runToolTests(t, []toolTest{ + { + name: "not registered without the experimental gate", + tool: toolBranchStart, + args: args, + wantCallErr: `calling "tools/call": unknown tool "branch_start"`, + }, + { + name: "read-only all refuses without an API call", + tool: toolBranchStart, + args: args, + opts: []runOption{experimental, withConfigAfterStart(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + name: "read-only prod allows", + tool: toolBranchStart, + args: args, + opts: []runOption{experimental, withConfig(map[string]any{"read_only": "prod"})}, + mock: expectStart(api.DeployStatusRESUMING), + wantOutput: accepted, + }, + { + name: "network error", + tool: toolBranchStart, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().StartBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x"). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to start branch: connection refused", + }, + { + name: "starts without waiting by default", + tool: toolBranchStart, + args: args, + opts: []runOption{experimental}, + mock: expectStart(api.DeployStatusRESUMING), + wantOutput: accepted, + }, + { + name: "wait polls until ready", + synctest: true, + tool: toolBranchStart, + args: map[string]any{"branch_id": "br4nch1d9x", "wait": true}, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectStart(api.DeployStatusRESUMING)(m) + expectGetBranch(m, sampleBranch()) + }, + wantOutput: map[string]any{"status": "READY", "message": "Branch started."}, + }, + }) +} diff --git a/internal/mcp/branch_stop.go b/internal/mcp/branch_stop.go new file mode 100644 index 00000000..b6b86214 --- /dev/null +++ b/internal/mcp/branch_stop.go @@ -0,0 +1,122 @@ +package mcp + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "net/http" + + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// BranchStopInput represents input for branch_stop +type BranchStopInput struct { + BranchID string `json:"branch_id"` + Wait bool `json:"wait,omitempty"` +} + +func (BranchStopInput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchStopInput](nil)) + + setBranchIDSchemaProperties(schema) + + schema.Properties["wait"].Description = "Whether to wait for the branch to be fully stopped before returning. Default is false (recommended). Only set to true if your next steps require confirmation that the branch is stopped. When true, waits up to 10 minutes." + schema.Properties["wait"].Default = util.Must(json.Marshal(false)) + schema.Properties["wait"].Examples = []any{false, true} + + return schema +} + +// BranchStopOutput represents output for branch_stop +type BranchStopOutput struct { + Status string `json:"status"` + Message string `json:"message"` +} + +func (BranchStopOutput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchStopOutput](nil)) + schema.Properties["status"].Description = "Current branch status after stop operation" + schema.Properties["message"].Description = "Human-readable outcome of the operation" + return schema +} + +func newBranchStopTool() *mcp.Tool { + return &mcp.Tool{ + Name: toolBranchStop, + Title: "Stop Branch", + Description: `Stop a running branch. + +A stopped branch no longer accepts connections and stops charging for compute, but keeps the changes made on it, whose storage is still billed.`, + InputSchema: BranchStopInput{}.Schema(), + OutputSchema: BranchStopOutput{}.Schema(), + Annotations: &mcp.ToolAnnotations{ + ReadOnlyHint: false, + DestructiveHint: new(true), // Stopping a branch breaks existing connections + IdempotentHint: true, // Stopping an already-stopped branch is safe (but returns an error) + OpenWorldHint: new(false), + Title: "Stop Branch", + }, + } +} + +// handleBranchStop handles the branch_stop MCP tool +func (s *Server) handleBranchStop(ctx context.Context, req *mcp.CallToolRequest, input BranchStopInput) (*mcp.CallToolResult, BranchStopOutput, error) { + cfg, client, projectID, err := s.app.GetAll() + if err != nil { + return nil, BranchStopOutput{}, err + } + + if err := common.CheckReadOnly(cfg, common.BranchEnvironmentTag); err != nil { + return nil, BranchStopOutput{}, err + } + + resolved, err := resolveBranchByID(ctx, client, projectID, input.BranchID) + if err != nil { + return nil, BranchStopOutput{}, err + } + + s.logger.Info("MCP: Stopping branch", + slog.String("project_id", projectID), + slog.String("branch_id", input.BranchID)) + + resp, err := client.StopBranchWithResponse(ctx, projectID, resolved.ServiceID, resolved.ID) + if err != nil { + return nil, BranchStopOutput{}, fmt.Errorf("failed to stop branch: %w", err) + } + if resp.StatusCode() != http.StatusAccepted { + return nil, BranchStopOutput{}, common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + if resp.JSON202 == nil { + return nil, BranchStopOutput{}, fmt.Errorf("empty response from API") + } + branch := *resp.JSON202 + + message := "Branch stop request accepted. The branch may still be stopping." + if input.Wait { + if err := common.WaitForBranch(ctx, common.WaitForBranchArgs{ + Client: client, + ProjectID: projectID, + ServiceID: resolved.ServiceID, + BranchID: resolved.ID, + Branch: &branch, + TargetStatus: api.DeployStatusPAUSED, + Timeout: waitTimeout, + TimeoutMsg: "branch may still be stopping", + }); err != nil { + message = fmt.Sprintf("Error: %s", err.Error()) + } else { + message = "Branch stopped." + } + } + + return nil, BranchStopOutput{ + Status: string(branch.Status), + Message: message, + }, nil +} diff --git a/internal/mcp/branch_stop_test.go b/internal/mcp/branch_stop_test.go new file mode 100644 index 00000000..eb6f2351 --- /dev/null +++ b/internal/mcp/branch_stop_test.go @@ -0,0 +1,105 @@ +package mcp + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" +) + +func TestBranchStopTool(t *testing.T) { + experimental := withExperimental() + args := map[string]any{"branch_id": "br4nch1d9x"} + expectStop := func(status api.DeployStatus) func(*mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + branch := sampleBranch(branchStatus(status)) + expectResolveBranch(m, sampleBranch()) + m.EXPECT().StopBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x"). + Return(&api.StopBranchResponse{ + HTTPResponse: httpResponse(http.StatusAccepted), + JSON202: &branch, + }, nil) + } + } + accepted := map[string]any{ + "status": "PAUSING", + "message": "Branch stop request accepted. The branch may still be stopping.", + } + + runToolTests(t, []toolTest{ + { + name: "not registered without the experimental gate", + tool: toolBranchStop, + args: args, + wantCallErr: `calling "tools/call": unknown tool "branch_stop"`, + }, + { + name: "not logged in", + tool: toolBranchStop, + args: args, + opts: []runOption{experimental, withNotLoggedIn()}, + wantErr: notLoggedInMsg, + }, + { + name: "read-only all refuses without an API call", + tool: toolBranchStop, + args: args, + opts: []runOption{experimental, withConfigAfterStart(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + name: "read-only prod allows", + tool: toolBranchStop, + args: args, + opts: []runOption{experimental, withConfig(map[string]any{"read_only": "prod"})}, + mock: expectStop(api.DeployStatusPAUSING), + wantOutput: accepted, + }, + { + name: "network error", + tool: toolBranchStop, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().StopBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x"). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to stop branch: connection refused", + }, + { + name: "nil response body", + tool: toolBranchStop, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().StopBranchWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x"). + Return(&api.StopBranchResponse{HTTPResponse: httpResponse(http.StatusAccepted)}, nil) + }, + wantErr: "empty response from API", + }, + { + name: "stops without waiting by default", + tool: toolBranchStop, + args: args, + opts: []runOption{experimental}, + mock: expectStop(api.DeployStatusPAUSING), + wantOutput: accepted, + }, + { + name: "wait polls until paused", + synctest: true, + tool: toolBranchStop, + args: map[string]any{"branch_id": "br4nch1d9x", "wait": true}, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectStop(api.DeployStatusPAUSING)(m) + expectGetBranch(m, sampleBranch(branchStatus(api.DeployStatusPAUSED))) + }, + wantOutput: map[string]any{"status": "PAUSED", "message": "Branch stopped."}, + }, + }) +} diff --git a/internal/mcp/branch_update_password.go b/internal/mcp/branch_update_password.go new file mode 100644 index 00000000..47f16314 --- /dev/null +++ b/internal/mcp/branch_update_password.go @@ -0,0 +1,124 @@ +package mcp + +import ( + "context" + "fmt" + "log/slog" + "net/http" + + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/common" + "github.com/timescale/tiger-cli/internal/util" +) + +// BranchUpdatePasswordInput represents input for branch_update_password. Like +// service_update_password, the tool takes no password and always generates +// one (see ServiceUpdatePasswordInput for why). +type BranchUpdatePasswordInput struct { + BranchID string `json:"branch_id"` + WithPassword bool `json:"with_password,omitempty"` +} + +func (BranchUpdatePasswordInput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchUpdatePasswordInput](nil)) + + setBranchIDSchemaProperties(schema) + setWithPasswordSchemaProperties(schema) + + return schema +} + +// BranchUpdatePasswordOutput represents output for branch_update_password +type BranchUpdatePasswordOutput struct { + Message string `json:"message"` + Password string `json:"password,omitempty"` + PasswordStorage *common.PasswordStorageResult `json:"password_storage,omitempty"` +} + +func (BranchUpdatePasswordOutput) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchUpdatePasswordOutput](nil)) + + schema.Properties["message"].Description = "Human-readable outcome of the operation" + schema.Properties["password"].Description = "The new password for the tsdbadmin user (only included if with_password=true)" + schema.Properties["password_storage"].Description = "Where the new password was saved locally, and whether saving it succeeded" + + return schema +} + +func newBranchUpdatePasswordTool() *mcp.Tool { + return &mcp.Tool{ + Name: toolBranchUpdatePassword, + Title: "Update Branch Password", + Description: `Update the master password for the 'tsdbadmin' user of a branch. + +Changing it affects only the branch, not its parent service. The tool generates a secure random password itself; it does not accept one. The change takes effect immediately and may terminate existing connections to the branch. The new password is saved to the configured password storage and is included in the result only if with_password is true. + +If the user wants to set a specific password, tell them to run 'tiger branch update-password' from the CLI instead, which keeps the password out of this conversation.`, + InputSchema: BranchUpdatePasswordInput{}.Schema(), + OutputSchema: BranchUpdatePasswordOutput{}.Schema(), + Annotations: &mcp.ToolAnnotations{ + ReadOnlyHint: false, + DestructiveHint: new(true), // Replaces the credentials existing clients use + IdempotentHint: false, // Each call generates a different password + OpenWorldHint: new(false), + Title: "Update Branch Password", + }, + } +} + +// handleBranchUpdatePassword handles the branch_update_password MCP tool +func (s *Server) handleBranchUpdatePassword(ctx context.Context, req *mcp.CallToolRequest, input BranchUpdatePasswordInput) (*mcp.CallToolResult, BranchUpdatePasswordOutput, error) { + cfg, client, projectID, err := s.app.GetAll() + if err != nil { + return nil, BranchUpdatePasswordOutput{}, err + } + + if err := common.CheckReadOnly(cfg, common.BranchEnvironmentTag); err != nil { + return nil, BranchUpdatePasswordOutput{}, err + } + + // The branch is needed to save the new password under it, and its parent + // for the API call. + branch, err := resolveBranchByID(ctx, client, projectID, input.BranchID) + if err != nil { + return nil, BranchUpdatePasswordOutput{}, err + } + + password, err := common.GenerateSecurePassword(32) + if err != nil { + return nil, BranchUpdatePasswordOutput{}, err + } + + s.logger.Info("MCP: Updating branch password", + slog.String("project_id", projectID), + slog.String("branch_id", input.BranchID)) + + resp, err := client.UpdateBranchPasswordWithResponse(ctx, projectID, branch.ServiceID, branch.ID, api.UpdatePasswordInput{Password: password}) + if err != nil { + return nil, BranchUpdatePasswordOutput{}, fmt.Errorf("failed to update branch password: %w", err) + } + if resp.StatusCode() != http.StatusNoContent { + return nil, BranchUpdatePasswordOutput{}, common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) + } + + storage, err := common.SavePasswordWithResult(cfg, common.BranchPasswordTarget(*branch), password, "tsdbadmin") + if err != nil { + s.logger.Warn("MCP: Password storage failed", slog.Any("error", err)) + } + + message := "Password updated for tsdbadmin." + if !storage.Success && !input.WithPassword { + message += " Warning: the new password was not saved, and was not returned because with_password was false." + } + output := BranchUpdatePasswordOutput{ + Message: message, + PasswordStorage: &storage, + } + if input.WithPassword { + output.Password = password + } + return nil, output, nil +} diff --git a/internal/mcp/branch_update_password_test.go b/internal/mcp/branch_update_password_test.go new file mode 100644 index 00000000..226898e2 --- /dev/null +++ b/internal/mcp/branch_update_password_test.go @@ -0,0 +1,139 @@ +package mcp + +import ( + "errors" + "net/http" + "testing" + + "github.com/timescale/tiger-cli/internal/api" + "github.com/timescale/tiger-cli/internal/api/mocks" +) + +func TestBranchUpdatePasswordTool(t *testing.T) { + experimental := withExperimental() + args := map[string]any{"branch_id": "br4nch1d9x"} + generated := withGenerateSecurePassword(32, "generated-pw", nil) + expectUpdate := func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().UpdateBranchPasswordWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x", api.UpdatePasswordInput{Password: "generated-pw"}). + Return(&api.UpdateBranchPasswordResponse{HTTPResponse: httpResponse(http.StatusNoContent)}, nil) + } + storedPassword := map[string]any{ + "success": true, + "method": "keyring", + "message": "Password saved to system keyring", + } + + runToolTests(t, []toolTest{ + { + name: "not registered without the experimental gate", + tool: toolBranchUpdatePassword, + args: args, + wantCallErr: `calling "tools/call": unknown tool "branch_update_password"`, + }, + { + name: "read-only all refuses without an API call", + tool: toolBranchUpdatePassword, + args: args, + opts: []runOption{experimental, withConfigAfterStart(map[string]any{"read_only": "all"})}, + wantErr: "this operation is not allowed in read-only mode", + }, + { + // A branch always counts as DEV, so prod neither refuses nor + // prompts. + name: "read-only prod allows", + tool: toolBranchUpdatePassword, + args: args, + opts: []runOption{experimental, generated, withConfig(map[string]any{"read_only": "prod"})}, + mock: expectUpdate, + wantOutput: map[string]any{ + "message": "Password updated for tsdbadmin.", + "password_storage": storedPassword, + }, + }, + { + name: "branch not found", + tool: toolBranchUpdatePassword, + args: args, + opts: []runOption{experimental}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveStatus(m, "br4nch1d9x", branchType, http.StatusNotFound, &api.ClientError{Message: new("resource not found")}) + }, + wantErr: "branch 'br4nch1d9x' not found", + }, + { + name: "network error", + tool: toolBranchUpdatePassword, + args: args, + opts: []runOption{experimental, generated}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().UpdateBranchPasswordWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x", api.UpdatePasswordInput{Password: "generated-pw"}). + Return(nil, errors.New("connection refused")) + }, + wantErr: "failed to update branch password: connection refused", + }, + { + name: "password generation fails", + tool: toolBranchUpdatePassword, + args: args, + opts: []runOption{experimental, withGenerateSecurePassword(32, "", errors.New("failed to generate random password: entropy unavailable"))}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + }, + wantErr: "failed to generate random password: entropy unavailable", + }, + { + // The endpoint answers success with a 204 and nothing else. + name: "unexpected 200 is an error", + tool: toolBranchUpdatePassword, + args: args, + opts: []runOption{experimental, generated}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolveBranch(m, sampleBranch()) + m.EXPECT().UpdateBranchPasswordWithResponse(validCtx, testProjectID, "e6ue9697jf", "br4nch1d9x", api.UpdatePasswordInput{Password: "generated-pw"}). + Return(&api.UpdateBranchPasswordResponse{HTTPResponse: httpResponse(http.StatusOK)}, nil) + }, + wantErr: "unknown error", + }, + { + name: "saves the generated password", + tool: toolBranchUpdatePassword, + args: args, + opts: []runOption{experimental, generated}, + mock: expectUpdate, + wantOutput: map[string]any{ + "message": "Password updated for tsdbadmin.", + "password_storage": storedPassword, + }, + checks: []checkFunc{checkStoredPassword("br4nch1d9x", "generated-pw")}, + }, + { + name: "with_password returns the password", + tool: toolBranchUpdatePassword, + args: map[string]any{"branch_id": "br4nch1d9x", "with_password": true}, + opts: []runOption{experimental, generated}, + mock: expectUpdate, + wantOutput: map[string]any{ + "message": "Password updated for tsdbadmin.", + "password": "generated-pw", + "password_storage": storedPassword, + }, + }, + { + name: "warns when the password was neither saved nor returned", + tool: toolBranchUpdatePassword, + args: args, + opts: []runOption{experimental, generated, withConfig(map[string]any{"password_storage": "none"})}, + mock: expectUpdate, + wantOutput: map[string]any{ + "message": "Password updated for tsdbadmin. Warning: the new password was not saved, and was not returned because with_password was false.", + "password_storage": map[string]any{ + "success": false, + "method": "none", + "message": "Password not saved (--password-storage=none). Make sure to store it securely.", + }, + }, + }, + }) +} diff --git a/internal/mcp/db_query.go b/internal/mcp/db_query.go index 160ec3bd..a587dfde 100644 --- a/internal/mcp/db_query.go +++ b/internal/mcp/db_query.go @@ -34,10 +34,10 @@ type DBQueryInput struct { Pooled bool `json:"pooled,omitempty"` } -func (DBQueryInput) Schema() *jsonschema.Schema { +func (DBQueryInput) Schema(experimental bool) *jsonschema.Schema { schema := util.Must(jsonschema.For[DBQueryInput](nil)) - schema.Properties["service_id"].Description = "Unique identifier of the service (10-character alphanumeric string). Use service_list to find service IDs. A read replica set ID is also accepted here — passing one runs the query against that read replica (which is read-only) instead of the primary service." + schema.Properties["service_id"].Description = "Unique identifier of the service (10-character alphanumeric string). Use service_list to find service IDs. A read replica set ID is also accepted here — passing one runs the query against that read replica (which is read-only) instead of the primary service." + branchIDHelp(experimental, "runs the query against that branch") schema.Properties["service_id"].Examples = []any{"e6ue9697jf", "u8me885b93"} schema.Properties["service_id"].Pattern = "^[a-z0-9]{10}$" @@ -112,7 +112,7 @@ func (DBQueryOutput) Schema() *jsonschema.Schema { return schema } -func newDBQueryTool() *mcp.Tool { +func newDBQueryTool(experimental bool) *mcp.Tool { return &mcp.Tool{ Name: toolDBQuery, Title: "Execute SQL Query", @@ -125,7 +125,7 @@ Multi-statement queries (semicolon-separated) are supported when no parameters a Process data in the database, not in your context: aggregate, filter, sort/limit, and join in SQL rather than fetching raw rows. WARNING: Can execute any SQL statement including INSERT, UPDATE, DELETE, and DDL commands. Always review queries before execution.`, - InputSchema: DBQueryInput{}.Schema(), + InputSchema: DBQueryInput{}.Schema(experimental), OutputSchema: DBQueryOutput{}.Schema(), Annotations: &mcp.ToolAnnotations{ ReadOnlyHint: false, @@ -162,14 +162,15 @@ func (s *Server) handleDBQuery(ctx context.Context, req *mcp.CallToolRequest, in slog.String("read_only", string(cfg.ReadOnly)), ) - // service_id may name a service or one of its read replicas. - target, err := common.ResolveConnectionTargetByID(ctx, client, projectID, input.ServiceID) + // service_id may name a service, one of its read replicas, or (behind the + // experimental gate) a branch. + target, err := common.ResolveConnectionTargetByID(ctx, client, projectID, input.ServiceID, s.app.Experimental) if err != nil { return nil, DBQueryOutput{}, err } // Under prod mode the resolved target's tag decides. - readOnlySession := common.CheckReadOnly(cfg, common.ServiceEnvironmentTag(target.ConnectionService)) != nil + readOnlySession := common.CheckReadOnly(cfg, target.Environment) != nil // A replica without a pooler connects directly; surface that as a warning. poolerWarning := common.ReplicaPoolerWarning(target, input.Pooled) @@ -192,7 +193,7 @@ func (s *Server) handleDBQuery(ctx context.Context, req *mcp.CallToolRequest, in MaxBytes: mcpMaxResponseBytes, }) if err != nil { - return nil, DBQueryOutput{}, handleDatabaseError(err) + return nil, DBQueryOutput{}, handleDatabaseError(err, target) } output := DBQueryOutput{ diff --git a/internal/mcp/db_query_test.go b/internal/mcp/db_query_test.go index 083c93b7..a2575515 100644 --- a/internal/mcp/db_query_test.go +++ b/internal/mcp/db_query_test.go @@ -41,7 +41,7 @@ func TestDBQueryTool(t *testing.T) { setupGetWithStatus := func(status api.DeployStatus) func(m *mocks.MockClientWithResponsesInterface) { return func(m *mocks.MockClientWithResponsesInterface) { - expectGetService(m, "e6ue9697jf", sampleService(func(s *api.Service) { s.Status = status })) + expectResolve(m, "e6ue9697jf", nil, sampleService(func(s *api.Service) { s.Status = status })) } } @@ -146,52 +146,79 @@ func TestDBQueryTool(t *testing.T) { tool: toolDBQuery, args: args, mock: func(m *mocks.MockClientWithResponsesInterface) { - m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "e6ue9697jf"). + m.EXPECT().ResolveRefWithResponse(validCtx, testProjectID, &api.ResolveRefParams{Ref: "e6ue9697jf"}). Return(nil, errors.New("connection refused")) }, - wantErr: "failed to fetch service details: connection refused", + wantErr: "failed to resolve service or read replica set 'e6ue9697jf': connection refused", }, { name: "service lookup API error", tool: toolDBQuery, args: args, mock: func(m *mocks.MockClientWithResponsesInterface) { - m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "e6ue9697jf"). - Return(&api.GetServiceResponse{ - HTTPResponse: httpResponse(http.StatusNotFound), - JSON4XX: &api.ClientError{Message: new("service not found")}, - }, nil) + expectResolveStatus(m, "e6ue9697jf", nil, http.StatusNotFound, &api.ClientError{Message: new("resource not found")}) }, - wantErr: "service not found", + wantErr: "service or read replica set 'e6ue9697jf' not found", }, { name: "nil response body", tool: toolDBQuery, args: args, mock: func(m *mocks.MockClientWithResponsesInterface) { - m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "e6ue9697jf"). - Return(&api.GetServiceResponse{HTTPResponse: httpResponse(http.StatusOK)}, nil) + expectResolveStatus(m, "e6ue9697jf", nil, http.StatusOK, nil) }, wantErr: "empty response from API", }, { - // Both services are fetched: the replica for its endpoint, the - // parent for its credentials. The replica's own status then stops it. + // The parent is fetched for its credentials, then the replica's own + // status stops it: a replica set that isn't active isn't ready. name: "read replica resolves its parent", tool: toolDBQuery, args: map[string]any{"service_id": "u8me885b93", "query": "SELECT 1"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectGetService(m, "u8me885b93", sampleReplica()) + expectResolve(m, "u8me885b93", nil, sampleReplica()) expectGetService(m, "e6ue9697jf", sampleService()) }, - wantErr: pausedMsg, + wantErr: notReadyMsg, + }, + { + // Branches are a preview, so without the gate a branch ID is + // reported as not found, without mentioning branches. + name: "branch not found without the experimental gate", + tool: toolDBQuery, + args: map[string]any{"service_id": "br4nch1d9x", "query": "SELECT 1"}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolve(m, "br4nch1d9x", nil, sampleBranch()) + }, + wantErr: "service or read replica set 'br4nch1d9x' not found", + }, + { + // A branch is started with branch_start, not service_start. + name: "paused branch points at branch_start", + tool: toolDBQuery, + args: map[string]any{"service_id": "br4nch1d9x", "query": "SELECT 1"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolve(m, "br4nch1d9x", nil, sampleBranch(branchStatus(api.DeployStatusPAUSED))) + }, + wantErr: "branch is paused — start it with the branch_start tool", + }, + { + name: "provisioning branch points at branch_get", + tool: toolDBQuery, + args: map[string]any{"service_id": "br4nch1d9x", "query": "SELECT 1"}, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectResolve(m, "br4nch1d9x", nil, sampleBranch(branchStatus(api.DeployStatusQUEUED))) + }, + wantErr: "branch is not ready — check its status with branch_get and try again", }, { name: "read replica parent lookup fails", tool: toolDBQuery, args: map[string]any{"service_id": "u8me885b93", "query": "SELECT 1"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectGetService(m, "u8me885b93", sampleReplica()) + expectResolve(m, "u8me885b93", nil, sampleReplica()) m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "e6ue9697jf"). Return(&api.GetServiceResponse{ HTTPResponse: httpResponse(http.StatusNotFound), @@ -226,7 +253,7 @@ func TestDBQueryTool(t *testing.T) { tool: toolDBQuery, args: map[string]any{"service_id": "e6ue9697jf", "query": "SELECT 1", "pooled": true}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectGetService(m, "e6ue9697jf", sampleService(func(s *api.Service) { + expectResolve(m, "e6ue9697jf", nil, sampleService(func(s *api.Service) { s.Endpoint = &api.Endpoint{ Host: new("e6ue9697jf.project.tsdb.cloud.timescale.com"), Port: new(5432), @@ -272,7 +299,7 @@ func TestDBQueryTool(t *testing.T) { MaxBytes: mcpMaxResponseBytes, }, result, nil)}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectGetService(m, "e6ue9697jf", sampleService(func(s *api.Service) { + expectResolve(m, "e6ue9697jf", nil, sampleService(func(s *api.Service) { s.ConnectionPooler = &api.ConnectionPooler{ Endpoint: &api.Endpoint{ Host: new("e6ue9697jf.project.tsdb.cloud.timescale.com"), @@ -392,7 +419,7 @@ func TestDBQueryTool(t *testing.T) { MaxBytes: mcpMaxResponseBytes, }, result, nil)}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectGetService(m, "u8me885b93", sampleReplica(func(s *api.Service) { s.Status = api.DeployStatusREADY })) + expectResolve(m, "u8me885b93", nil, sampleReplica(func(r *api.ReadReplicaSet) { r.Status = api.ReadReplicaSetStatusActive })) expectGetService(m, "e6ue9697jf", sampleService()) }, wantOutput: map[string]any{ diff --git a/internal/mcp/db_schema.go b/internal/mcp/db_schema.go index 9d12944b..39141b9d 100644 --- a/internal/mcp/db_schema.go +++ b/internal/mcp/db_schema.go @@ -23,10 +23,10 @@ type DBSchemaInput struct { Pooled bool `json:"pooled,omitempty"` } -func (DBSchemaInput) Schema() *jsonschema.Schema { +func (DBSchemaInput) Schema(experimental bool) *jsonschema.Schema { schema := util.Must(jsonschema.For[DBSchemaInput](nil)) - schema.Properties["service_id"].Description = "Unique identifier of the service (10-character alphanumeric string). Use service_list to find service IDs. A read replica set ID is also accepted here — passing one introspects that read replica instead of the primary service." + schema.Properties["service_id"].Description = "Unique identifier of the service (10-character alphanumeric string). Use service_list to find service IDs. A read replica set ID is also accepted here — passing one introspects that read replica instead of the primary service." + branchIDHelp(experimental, "introspects that branch") schema.Properties["service_id"].Examples = []any{"e6ue9697jf", "u8me885b93"} schema.Properties["service_id"].Pattern = "^[a-z0-9]{10}$" @@ -69,7 +69,7 @@ func (DBSchemaOutput) Schema() *jsonschema.Schema { return schema } -func newDBSchemaTool() *mcp.Tool { +func newDBSchemaTool(experimental bool) *mcp.Tool { return &mcp.Tool{ Name: toolDBSchema, Title: "Show Database Schema", @@ -78,7 +78,7 @@ func newDBSchemaTool() *mcp.Tool { Connects to a PostgreSQL/TimescaleDB service in Tiger Cloud and returns its schema as readable text: tables (regular, partitioned, and foreign), views, materialized views, enum types, functions, procedures, indexes, triggers, and TimescaleDB hypertable and continuous aggregate metadata. Only objects the connecting role can access are returned. By default only user-facing schemas and objects are shown; view/routine definitions and object comments are omitted unless requested. The connection is opened in immutable read-only mode.`, - InputSchema: DBSchemaInput{}.Schema(), + InputSchema: DBSchemaInput{}.Schema(experimental), OutputSchema: DBSchemaOutput{}.Schema(), Annotations: &mcp.ToolAnnotations{ ReadOnlyHint: true, @@ -106,8 +106,9 @@ func (s *Server) handleDBSchema(ctx context.Context, req *mcp.CallToolRequest, i slog.Bool("pooled", input.Pooled), ) - // service_id may name a service or one of its read replicas. - target, err := common.ResolveConnectionTargetByID(ctx, client, projectID, input.ServiceID) + // service_id may name a service, one of its read replicas, or (behind the + // experimental gate) a branch. + target, err := common.ResolveConnectionTargetByID(ctx, client, projectID, input.ServiceID, s.app.Experimental) if err != nil { return nil, DBSchemaOutput{}, err } @@ -124,7 +125,7 @@ func (s *Server) handleDBSchema(ctx context.Context, req *mcp.CallToolRequest, i IncludeComments: input.Comments, }) if err != nil { - return nil, DBSchemaOutput{}, handleDatabaseError(err) + return nil, DBSchemaOutput{}, handleDatabaseError(err, target) } return nil, DBSchemaOutput{SchemaText: common.FormatSchema(schema), Warning: warning}, nil diff --git a/internal/mcp/db_schema_test.go b/internal/mcp/db_schema_test.go index db06c13c..b1a0b6d1 100644 --- a/internal/mcp/db_schema_test.go +++ b/internal/mcp/db_schema_test.go @@ -38,7 +38,7 @@ func TestDBSchemaTool(t *testing.T) { setupGetWithStatus := func(status api.DeployStatus) func(m *mocks.MockClientWithResponsesInterface) { return func(m *mocks.MockClientWithResponsesInterface) { - expectGetService(m, "e6ue9697jf", sampleService(func(s *api.Service) { s.Status = status })) + expectResolve(m, "e6ue9697jf", nil, sampleService(func(s *api.Service) { s.Status = status })) } } @@ -78,31 +78,26 @@ func TestDBSchemaTool(t *testing.T) { tool: toolDBSchema, args: args, mock: func(m *mocks.MockClientWithResponsesInterface) { - m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "e6ue9697jf"). + m.EXPECT().ResolveRefWithResponse(validCtx, testProjectID, &api.ResolveRefParams{Ref: "e6ue9697jf"}). Return(nil, errors.New("connection refused")) }, - wantErr: "failed to fetch service details: connection refused", + wantErr: "failed to resolve service or read replica set 'e6ue9697jf': connection refused", }, { name: "service lookup API error", tool: toolDBSchema, args: args, mock: func(m *mocks.MockClientWithResponsesInterface) { - m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "e6ue9697jf"). - Return(&api.GetServiceResponse{ - HTTPResponse: httpResponse(http.StatusNotFound), - JSON4XX: &api.ClientError{Message: new("service not found")}, - }, nil) + expectResolveStatus(m, "e6ue9697jf", nil, http.StatusNotFound, &api.ClientError{Message: new("resource not found")}) }, - wantErr: "service not found", + wantErr: "service or read replica set 'e6ue9697jf' not found", }, { name: "nil response body", tool: toolDBSchema, args: args, mock: func(m *mocks.MockClientWithResponsesInterface) { - m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "e6ue9697jf"). - Return(&api.GetServiceResponse{HTTPResponse: httpResponse(http.StatusOK)}, nil) + expectResolveStatus(m, "e6ue9697jf", nil, http.StatusOK, nil) }, wantErr: "empty response from API", }, @@ -128,23 +123,23 @@ func TestDBSchemaTool(t *testing.T) { wantErr: notReadyMsg, }, { - // Both services are fetched: the replica for its endpoint, the - // parent for its credentials. The replica's own status then stops it. + // The parent is fetched for its credentials, then the replica's own + // status stops it: a replica set that isn't active isn't ready. name: "read replica resolves its parent", tool: toolDBSchema, args: map[string]any{"service_id": "u8me885b93"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectGetService(m, "u8me885b93", sampleReplica()) + expectResolve(m, "u8me885b93", nil, sampleReplica()) expectGetService(m, "e6ue9697jf", sampleService()) }, - wantErr: pausedMsg, + wantErr: notReadyMsg, }, { name: "read replica parent lookup fails", tool: toolDBSchema, args: map[string]any{"service_id": "u8me885b93"}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectGetService(m, "u8me885b93", sampleReplica()) + expectResolve(m, "u8me885b93", nil, sampleReplica()) m.EXPECT().GetServiceWithResponse(validCtx, testProjectID, "e6ue9697jf"). Return(&api.GetServiceResponse{ HTTPResponse: httpResponse(http.StatusNotFound), @@ -165,7 +160,7 @@ func TestDBSchemaTool(t *testing.T) { tool: toolDBSchema, args: map[string]any{"service_id": "e6ue9697jf", "pooled": true}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectGetService(m, "e6ue9697jf", sampleService(func(s *api.Service) { + expectResolve(m, "e6ue9697jf", nil, sampleService(func(s *api.Service) { s.Endpoint = &api.Endpoint{ Host: new("e6ue9697jf.project.tsdb.cloud.timescale.com"), Port: new(5432), @@ -221,7 +216,7 @@ func TestDBSchemaTool(t *testing.T) { args: map[string]any{"service_id": "u8me885b93", "pooled": true}, opts: []runOption{withFetchServiceSchema(common.FetchServiceSchemaArgs{Role: "tsdbadmin", Pooled: true}, schema, nil)}, mock: func(m *mocks.MockClientWithResponsesInterface) { - expectGetService(m, "u8me885b93", sampleReplica(func(s *api.Service) { s.Status = api.DeployStatusREADY })) + expectResolve(m, "u8me885b93", nil, sampleReplica(func(r *api.ReadReplicaSet) { r.Status = api.ReadReplicaSetStatusActive })) expectGetService(m, "e6ue9697jf", sampleService()) }, wantOutput: map[string]any{ diff --git a/internal/mcp/errors.go b/internal/mcp/errors.go index 7458ff24..8154a3e6 100644 --- a/internal/mcp/errors.go +++ b/internal/mcp/errors.go @@ -8,13 +8,21 @@ import ( ) // handleDatabaseError turns the readiness sentinels into guidance naming the -// tool that resolves them. Every other error passes through unchanged. -func handleDatabaseError(err error) error { +// tool that resolves them: a branch tool for a branch, a service tool for +// everything else. Every other error passes through unchanged. +func handleDatabaseError(err error, target *common.ConnectionTarget) error { + // The group also names the target, since the sentinels always say + // "service". + group := "service" + if target.Kind == common.TargetBranch { + group = "branch" + } + switch { case errors.Is(err, common.ErrPaused): - return fmt.Errorf("%w — start it with the service_start tool", common.ErrPaused) + return fmt.Errorf("%s is paused — start it with the %s_start tool", group, group) case errors.Is(err, common.ErrNotReady): - return fmt.Errorf("%w — check its status with service_get and try again", common.ErrNotReady) + return fmt.Errorf("%s is not ready — check its status with %s_get and try again", group, group) } return err } diff --git a/internal/mcp/main_test.go b/internal/mcp/main_test.go index ca1313f4..ad98ed25 100644 --- a/internal/mcp/main_test.go +++ b/internal/mcp/main_test.go @@ -2,6 +2,7 @@ package mcp import ( "context" + "encoding/json" "fmt" "maps" "net/http" @@ -11,6 +12,8 @@ import ( "testing/synctest" "time" + "github.com/timescale/tiger-cli/internal/util" + "github.com/google/go-cmp/cmp" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/zalando/go-keyring" @@ -466,25 +469,22 @@ func sampleService(overrides ...func(*api.Service)) api.Service { return svc } -// sampleReplica returns a paused standby read replica of sampleService, shaped -// the way GetService returns it when given a read replica set ID. A replica -// connects to its own endpoint but borrows the parent primary's credentials, -// so resolving one fetches both services. -func sampleReplica(overrides ...func(*api.Service)) api.Service { - svc := sampleService(func(s *api.Service) { - s.ServiceID = "u8me885b93" - s.Name = "replica-service" - s.Status = api.DeployStatusPAUSED - s.ForkedFrom = &api.ForkSpec{ - IsStandby: new(true), - ProjectID: new(testProjectID), - ServiceID: new("e6ue9697jf"), - } - }) +// sampleReplica returns a still-creating read replica set of sampleService, as +// the resolve endpoint returns it. A replica connects to its own endpoint but +// borrows the parent primary's credentials, so resolving one fetches the +// parent too. +func sampleReplica(overrides ...func(*api.ReadReplicaSet)) api.ReadReplicaSet { + replica := api.ReadReplicaSet{ + ID: "u8me885b93", + ServiceID: "e6ue9697jf", + Name: "replica-service", + Status: api.ReadReplicaSetStatusCreating, + Nodes: 1, + } for _, o := range overrides { - o(&svc) + o(&replica) } - return svc + return replica } // The readiness errors handleDatabaseError returns before a tool connects, @@ -515,6 +515,134 @@ func expectTaggedService(tag string, calls int) func(*mocks.MockClientWithRespon } } +// sampleBranch returns a branch of sampleService with reasonable defaults. +// Use overrides to customize specific fields. +func sampleBranch(overrides ...func(*api.Branch)) api.Branch { + branch := api.Branch{ + ID: "br4nch1d9x", + ProjectID: testProjectID, + ServiceID: "e6ue9697jf", + Name: "test-branch", + Status: api.DeployStatusREADY, + CPUMillis: 250, + MemoryGbs: 2, + Created: time.Date(2025, 1, 16, 9, 0, 0, 0, time.UTC), + Endpoint: &api.Endpoint{ + Host: new("br4nch1d9x.test-project-123.branch.tsdb.cloud.timescale.com"), + Port: new(36956), + }, + } + for _, o := range overrides { + o(&branch) + } + return branch +} + +// sampleBranchDetail is the BranchDetail output of sampleBranch, with the +// given status. +func sampleBranchDetail(status api.DeployStatus) map[string]any { + return map[string]any{ + "id": "br4nch1d9x", + "name": "test-branch", + "service_id": "e6ue9697jf", + "status": string(status), + "created": "2025-01-16T09:00:00Z", + "resources": map[string]any{"cpu": "0.25 cores", "memory": "2 GB"}, + "direct_endpoint": "br4nch1d9x.test-project-123.branch.tsdb.cloud.timescale.com:36956", + "connection_string": "postgresql://tsdbadmin@br4nch1d9x.test-project-123.branch.tsdb.cloud.timescale.com:36956/tsdb?sslmode=require", + } +} + +// branchStatus returns a sampleBranch override setting the branch's status. +func branchStatus(status api.DeployStatus) func(*api.Branch) { + return func(b *api.Branch) { b.Status = status } +} + +// expectResolve expects resolving ref through the resolve endpoint, +// restricted to resolveType (nil for none), answered with v: an api.Service, +// api.Branch, or api.ReadReplicaSet, whose Go type picks the discriminator. +// The call is returned so a caller can adjust its cardinality. +func expectResolve(m *mocks.MockClientWithResponsesInterface, ref string, resolveType *api.ResolveType, v any) *gomock.Call { + return m.EXPECT().ResolveRefWithResponse(validCtx, testProjectID, &api.ResolveRefParams{Ref: ref, Type: resolveType}). + Return(&api.ResolveRefResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: resolveResult(v), + }, nil) +} + +// expectResolveStatus expects a resolve answered with status. A nil apiErr +// stands in for a response with no body. +func expectResolveStatus(m *mocks.MockClientWithResponsesInterface, ref string, resolveType *api.ResolveType, status int, apiErr *api.ClientError) { + m.EXPECT().ResolveRefWithResponse(validCtx, testProjectID, &api.ResolveRefParams{Ref: ref, Type: resolveType}). + Return(&api.ResolveRefResponse{ + HTTPResponse: httpResponse(status), + JSON4XX: apiErr, + }, nil) +} + +// resolveResult encodes v as the resolve endpoint's union, discriminated by +// v's Go type. Each member is its plain type plus the discriminator. +func resolveResult(v any) *api.ResolveResult { + var resolveType api.ResolveType + switch v.(type) { + case api.Service: + resolveType = api.ResolveTypeSERVICE + case api.Branch: + resolveType = api.ResolveTypeBRANCH + case api.ReadReplicaSet: + resolveType = api.ResolveTypeREADREPLICASET + default: + panic(fmt.Sprintf("resolveResult: unsupported type %T", v)) + } + + var body map[string]any + if err := json.Unmarshal(util.Must(json.Marshal(v)), &body); err != nil { + panic(err) + } + body["type"] = resolveType + + var result api.ResolveResult + if err := result.UnmarshalJSON(util.Must(json.Marshal(body))); err != nil { + panic(err) + } + return &result +} + +// branchType is the resolve type every branch tool restricts its lookup to. +var branchType = new(api.ResolveTypeBRANCH) + +// expectResolveBranch expects resolving branch's ID as a branch, which every +// branch tool but branch_create and branch_list does first. +func expectResolveBranch(m *mocks.MockClientWithResponsesInterface, branch api.Branch) { + expectResolve(m, branch.ID, branchType, branch) +} + +// expectGetBranch expects one GetBranch call for branch, through its parent, +// returning branch. The call is returned so a caller can adjust its +// cardinality (a wait loop's poll). +func expectGetBranch(m *mocks.MockClientWithResponsesInterface, branch api.Branch) *gomock.Call { + return m.EXPECT().GetBranchWithResponse(validCtx, testProjectID, branch.ServiceID, branch.ID). + Return(&api.GetBranchResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &branch, + }, nil) +} + +// checkStoredPassword returns a check asserting the password stored in the +// mock keyring for the given service or branch ID. +func checkStoredPassword(id, want string) checkFunc { + return func(t *testing.T, result toolResult) { + t.Helper() + got, err := (&common.KeyringStorage{}).Get(common.PasswordTarget{ID: id, ProjectID: testProjectID}, "tsdbadmin") + if err != nil { + t.Fatalf("failed to read stored password: %v", err) + } + if got != want { + t.Errorf("stored password = %q, want %q", got, want) + } + } +} + // validCtx is a gomock matcher that verifies a context.Context parameter is // non-nil. Use this instead of gomock.Any() for context parameters. var validCtx = gomock.Cond(func(x any) bool { diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 532fb77c..45f7f74e 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -44,6 +44,15 @@ const ( toolServiceBackupRegionList = "service_backup_region_list" toolServiceBackupRegionAdd = "service_backup_region_add" toolServiceBackupRegionRemove = "service_backup_region_remove" + toolBranchCreate = "branch_create" + toolBranchList = "branch_list" + toolBranchGet = "branch_get" + toolBranchDelete = "branch_delete" + toolBranchStart = "branch_start" + toolBranchStop = "branch_stop" + toolBranchResize = "branch_resize" + toolBranchRename = "branch_rename" + toolBranchUpdatePassword = "branch_update_password" toolDBQuery = "db_query" toolDBSchema = "db_schema" toolFeedback = "feedback" @@ -75,6 +84,13 @@ var readOnlyGatedTools = []string{ toolServiceBackupRegionAdd, toolServiceBackupRegionRemove, toolServiceDelete, + toolBranchCreate, + toolBranchDelete, + toolBranchStart, + toolBranchStop, + toolBranchResize, + toolBranchRename, + toolBranchUpdatePassword, } // addTool registers an MCP tool, skipping readOnlyGatedTools under read_only=all. @@ -189,8 +205,11 @@ func (s *Server) registerTools(ctx context.Context, mode config.ReadOnlyMode, ex // Service management tools s.registerServiceTools(mode, experimental) + // Branch management tools + s.registerBranchTools(mode, experimental) + // Database operation tools - s.registerDatabaseTools(mode) + s.registerDatabaseTools(mode, experimental) // Submitting feedback mutates no service, so the tool is absent from // readOnlyGatedTools and stays registered in every read-only mode. @@ -213,7 +232,7 @@ func (s *Server) registerServiceTools(mode config.ReadOnlyMode, experimental boo addTool(s, mode, newServiceStopTool(), s.handleServiceStop) addTool(s, mode, newServiceResizeTool(experimental), s.handleServiceResize) addTool(s, mode, newServiceRenameTool(), s.handleServiceRename) - addTool(s, mode, newServiceDeleteTool(), s.handleServiceDelete) + addTool(s, mode, newServiceDeleteTool(experimental), s.handleServiceDelete) addTool(s, mode, newServiceLogsTool(), s.handleServiceLogs) addTool(s, mode, newServiceMetricsAvailableTool(), s.handleServiceMetricsAvailable) addTool(s, mode, newServiceMetricsDetailsTool(), s.handleServiceMetricsDetails) @@ -229,11 +248,29 @@ func (s *Server) registerServiceTools(mode config.ReadOnlyMode, experimental boo } } +// registerBranchTools registers branch management tools. Branches target +// gateway endpoints marked `x-tigerdata-preview: true`, so the tools are +// registered only when the experimental gate is on at server startup. +func (s *Server) registerBranchTools(mode config.ReadOnlyMode, experimental bool) { + if !experimental { + return + } + addTool(s, mode, newBranchCreateTool(), s.handleBranchCreate) + addTool(s, mode, newBranchListTool(), s.handleBranchList) + addTool(s, mode, newBranchGetTool(), s.handleBranchGet) + addTool(s, mode, newBranchDeleteTool(), s.handleBranchDelete) + addTool(s, mode, newBranchStartTool(), s.handleBranchStart) + addTool(s, mode, newBranchStopTool(), s.handleBranchStop) + addTool(s, mode, newBranchResizeTool(), s.handleBranchResize) + addTool(s, mode, newBranchRenameTool(), s.handleBranchRename) + addTool(s, mode, newBranchUpdatePasswordTool(), s.handleBranchUpdatePassword) +} + // registerDatabaseTools registers database operation tools with comprehensive schemas and descriptions -func (s *Server) registerDatabaseTools(mode config.ReadOnlyMode) { - addTool(s, mode, newDBQueryTool(), s.handleDBQuery) +func (s *Server) registerDatabaseTools(mode config.ReadOnlyMode, experimental bool) { + addTool(s, mode, newDBQueryTool(experimental), s.handleDBQuery) - mcp.AddTool(s.mcpServer, newDBSchemaTool(), s.handleDBSchema) + mcp.AddTool(s.mcpServer, newDBSchemaTool(experimental), s.handleDBSchema) } // analyticsMiddleware tracks analytics for all MCP requests diff --git a/internal/mcp/server_test.go b/internal/mcp/server_test.go index 1095d4bd..d1c7edbe 100644 --- a/internal/mcp/server_test.go +++ b/internal/mcp/server_test.go @@ -39,7 +39,8 @@ func registeredToolNames(t *testing.T, readOnly config.ReadOnlyMode) []string { logger: ensureLogger(nil), } s.registerServiceTools(readOnly, true) - s.registerDatabaseTools(readOnly) + s.registerBranchTools(readOnly, true) + s.registerDatabaseTools(readOnly, true) clientTransport, serverTransport := mcp.NewInMemoryTransports() diff --git a/internal/mcp/service_create.go b/internal/mcp/service_create.go index aa658631..cf477edf 100644 --- a/internal/mcp/service_create.go +++ b/internal/mcp/service_create.go @@ -222,7 +222,7 @@ func (s *Server) handleServiceCreate(ctx context.Context, req *mcp.CallToolReque // This ensures the password is stored even if the wait fails or is interrupted var passwordStorage *common.PasswordStorageResult if service.InitialPassword != nil { - result, err := common.SavePasswordWithResult(cfg, api.Service(service), *service.InitialPassword, "tsdbadmin") + result, err := common.SavePasswordWithResult(cfg, common.ServicePasswordTarget(service), *service.InitialPassword, "tsdbadmin") passwordStorage = &result if err != nil { s.logger.Warn("MCP: Password storage failed", slog.Any("error", err)) diff --git a/internal/mcp/service_delete.go b/internal/mcp/service_delete.go index 9b5db845..a57b8cdf 100644 --- a/internal/mcp/service_delete.go +++ b/internal/mcp/service_delete.go @@ -5,6 +5,7 @@ import ( "fmt" "log/slog" "net/http" + "strings" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" @@ -48,13 +49,20 @@ func (ServiceDeleteOutput) Schema() *jsonschema.Schema { return schema } -func newServiceDeleteTool() *mcp.Tool { - return &mcp.Tool{ - Name: toolServiceDelete, - Title: "Delete Database Service", - Description: `Delete a database service permanently. +func newServiceDeleteTool(experimental bool) *mcp.Tool { + description := "Delete a database service permanently.\n\n" + + "This operation is irreversible: the service and all of its data are destroyed. " + // Branches are a preview, so only the experimental description mentions + // them. The PROD prompt lists them regardless: they're deleted either way. + if experimental { + description += "Deleting a service also deletes all of its branches. " + } + description += `Deleting a service tagged PROD automatically prompts the user, via an elicitation request through the MCP client, to confirm the deletion before it proceeds, so agents don't need to ask the user for confirmation themselves; if the client cannot prompt, the deletion is refused and the user must run 'tiger service delete' from the CLI instead.` -This operation is irreversible: the service and all of its data are destroyed. Deleting a service tagged PROD automatically prompts the user, via an elicitation request through the MCP client, to confirm the deletion before it proceeds, so agents don't need to ask the user for confirmation themselves; if the client cannot prompt, the deletion is refused and the user must run 'tiger service delete' from the CLI instead.`, + return &mcp.Tool{ + Name: toolServiceDelete, + Title: "Delete Database Service", + Description: description, InputSchema: ServiceDeleteInput{}.Schema(), OutputSchema: ServiceDeleteOutput{}.Schema(), Annotations: &mcp.ToolAnnotations{ @@ -98,10 +106,14 @@ func (s *Server) handleServiceDelete(ctx context.Context, req *mcp.CallToolReque return nil, ServiceDeleteOutput{}, err } + // Listed on every run, PROD's prompting run included, so both the prompt + // and the result can name them. + branches, branchesErr := s.listBranchesToDelete(ctx, client, projectID, *service) + if tag == api.EnvironmentTagPROD { answer, answered := req.Params.InputResponses[deleteConfirmationKey] if !answered { - result, err := promptProdDelete(req, *service) + result, err := promptProdDelete(req, *service, branchesToDeleteMessage(branches, branchesErr)) return result, ServiceDeleteOutput{}, err } if !serviceIDConfirmed(answer, input.ServiceID) { @@ -125,24 +137,64 @@ func (s *Server) handleServiceDelete(ctx context.Context, req *mcp.CallToolReque return nil, ServiceDeleteOutput{}, common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) } + message := fmt.Sprintf("Service %q has been deleted.", input.ServiceID) + if len(branches) > 0 { + message = fmt.Sprintf("Service %q has been deleted, along with its %s: %s.", input.ServiceID, common.FormatBranchCount(len(branches)), branchLabels(branches)) + } return nil, ServiceDeleteOutput{ ServiceID: input.ServiceID, Deleted: true, - Message: fmt.Sprintf("Service %q has been deleted.", input.ServiceID), + Message: message, }, nil } // promptProdDelete returns the input-required result that asks the user to -// confirm deleting a PROD service. It refuses outright when the client can't -// show the prompt: silently deleting a production service without a human in -// the loop is the one outcome this tool must never produce. -func promptProdDelete(req *mcp.CallToolRequest, service api.Service) (*mcp.CallToolResult, error) { +// confirm deleting a PROD service, with branchesNote (from +// branchesToDeleteMessage) appended to the question. It refuses outright when +// the client can't show the prompt: silently deleting a production service +// without a human in the loop is the one outcome this tool must never produce. +func promptProdDelete(req *mcp.CallToolRequest, service api.Service, branchesNote string) (*mcp.CallToolResult, error) { if !clientSupportsFormElicitation(req) { return nil, fmt.Errorf("deleting service %s requires the user's confirmation because it is tagged PROD, but this MCP client does not support elicitation; ask the user to run 'tiger service delete %s' instead", service.ServiceID, service.ServiceID) } - return serviceIDConfirmationRequest( - deleteConfirmationKey, - fmt.Sprintf("Delete PRODUCTION service %q (%s)? This permanently destroys the service and all of its data, and cannot be undone.", service.Name, service.ServiceID), - service.ServiceID, - ), nil + message := fmt.Sprintf("Delete PRODUCTION service %q (%s)? This permanently destroys the service and all of its data, and cannot be undone.", service.Name, service.ServiceID) + return serviceIDConfirmationRequest(deleteConfirmationKey, message+branchesNote, service.ServiceID), nil +} + +// listBranchesToDelete returns the branches that deleting service takes with +// it. The API deletes them without asking, so this isn't gated on the +// experimental env var: whoever has branches gets told. A failed listing is +// logged and returned rather than failing the call, so the delete can still go +// ahead. +func (s *Server) listBranchesToDelete(ctx context.Context, client api.ClientWithResponsesInterface, projectID string, service api.Service) ([]api.Branch, error) { + if !common.CanHaveBranches(service) { + return nil, nil + } + branches, err := common.ListBranches(ctx, client, projectID, service.ServiceID) + if err != nil { + s.logger.Error("MCP: Failed to list branches", slog.Any("error", err)) + } + return branches, err +} + +// branchesToDeleteMessage is the sentence the PROD prompt appends about the +// branches deleted with the service: their names, a warning in their place if +// listing them failed, or "" if there are none. +func branchesToDeleteMessage(branches []api.Branch, err error) string { + if err != nil { + return " Any branches of the service will also be deleted." + } + if len(branches) == 0 { + return "" + } + return fmt.Sprintf(" Its %s will also be deleted: %s.", common.FormatBranchCount(len(branches)), branchLabels(branches)) +} + +// branchLabels lists branches by name and ID, e.g. `"a" (id1), "b" (id2)`. +func branchLabels(branches []api.Branch) string { + labels := make([]string, len(branches)) + for i, branch := range branches { + labels[i] = fmt.Sprintf("%q (%s)", branch.Name, branch.ID) + } + return strings.Join(labels, ", ") } diff --git a/internal/mcp/service_delete_test.go b/internal/mcp/service_delete_test.go index 299da832..09e22020 100644 --- a/internal/mcp/service_delete_test.go +++ b/internal/mcp/service_delete_test.go @@ -1,7 +1,9 @@ package mcp import ( + "errors" "net/http" + "strings" "testing" "github.com/modelcontextprotocol/go-sdk/mcp" @@ -54,6 +56,45 @@ func TestServiceDeleteTool(t *testing.T) { "required": []any{"service_id"}, }, } + // promptWith is prompt with extra appended to its message, for a fluid + // service whose branches the prompt names. + promptWith := func(extra string) *mcp.ElicitParams { + p := *prompt + p.Message += extra + return &p + } + // expectFluidService registers fetching a fluid service tagged tag, the + // kind that can have branches, on each of calls handler runs. + expectFluidService := func(tag string, calls int) func(*mocks.MockClientWithResponsesInterface) { + return func(m *mocks.MockClientWithResponsesInterface) { + expectGetService(m, "e6ue9697jf", sampleService(func(s *api.Service) { + s.Metadata = &api.ServiceMetadata{Environment: &tag} + s.Platform = new(api.ServicePlatformFLUID) + })).Times(calls) + } + } + // expectBranches registers listing the service's branches on each of + // calls handler runs. + expectBranches := func(m *mocks.MockClientWithResponsesInterface, calls int, branches ...api.Branch) { + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "e6ue9697jf", nil). + Return(&api.GetBranchesResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &branches, + }, nil).Times(calls) + } + otherBranch := sampleBranch(func(b *api.Branch) { + b.ID = "x7k2m9p4qa" + b.Name = "other-branch" + }) + // deletedWith is a successful deletion's result, with the given message. + deletedWith := func(message string) map[string]any { + return map[string]any{ + "service_id": "e6ue9697jf", + "deleted": true, + "message": message, + } + } + // confirm answers the prompt by typing id back. confirm := func(id string) runOption { return withElicitation(&mcp.ElicitResult{Action: "accept", Content: map[string]any{"service_id": id}}) @@ -129,6 +170,77 @@ func TestServiceDeleteTool(t *testing.T) { wantPrompt: prompt, wantOutput: deleted, }, + { + // The API deletes a service's branches along with it, so the + // prompt and the result name them even without the experimental + // gate. Both handler runs list them. + name: "PROD prompt and result name the branches deleted with the service", + tool: toolServiceDelete, + args: args, + opts: []runOption{confirm("e6ue9697jf")}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectFluidService("PROD", 2)(m) + expectBranches(m, 2, sampleBranch(), otherBranch) + expectDelete(m) + }, + wantPrompt: promptWith(` Its 2 branches will also be deleted: "test-branch" (br4nch1d9x), "other-branch" (x7k2m9p4qa).`), + wantOutput: deletedWith(`Service "e6ue9697jf" has been deleted, along with its 2 branches: "test-branch" (br4nch1d9x), "other-branch" (x7k2m9p4qa).`), + }, + { + name: "PROD prompt names a single branch", + tool: toolServiceDelete, + args: args, + opts: []runOption{confirm("e6ue9697jf")}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectFluidService("PROD", 2)(m) + expectBranches(m, 2, sampleBranch()) + expectDelete(m) + }, + wantPrompt: promptWith(` Its 1 branch will also be deleted: "test-branch" (br4nch1d9x).`), + wantOutput: deletedWith(`Service "e6ue9697jf" has been deleted, along with its 1 branch: "test-branch" (br4nch1d9x).`), + }, + { + name: "PROD prompt for a fluid service without branches names none", + tool: toolServiceDelete, + args: args, + opts: []runOption{confirm("e6ue9697jf")}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectFluidService("PROD", 2)(m) + expectBranches(m, 2) + expectDelete(m) + }, + wantPrompt: prompt, + wantOutput: deleted, + }, + { + // The listing only informs the decision, so a failure still + // prompts, with a warning in place of the names. + name: "PROD prompt when the branch listing fails", + tool: toolServiceDelete, + args: args, + opts: []runOption{confirm("e6ue9697jf")}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectFluidService("PROD", 2)(m) + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "e6ue9697jf", nil). + Return(nil, errors.New("connection refused")).Times(2) + expectDelete(m) + }, + wantPrompt: promptWith(" Any branches of the service will also be deleted."), + wantOutput: deleted, + }, + { + // DEV deletions don't prompt, but the result still names the + // branches deleted along with the service. + name: "deletes fluid DEV service and names its branches", + tool: toolServiceDelete, + args: args, + mock: func(m *mocks.MockClientWithResponsesInterface) { + expectFluidService("DEV", 1)(m) + expectBranches(m, 1, sampleBranch(), otherBranch) + expectDelete(m) + }, + wantOutput: deletedWith(`Service "e6ue9697jf" has been deleted, along with its 2 branches: "test-branch" (br4nch1d9x), "other-branch" (x7k2m9p4qa).`), + }, { name: "PROD deletion cancelled when the typed ID does not match", tool: toolServiceDelete, @@ -199,3 +311,16 @@ func TestServiceDeleteTool(t *testing.T) { }, }) } + +// TestServiceDeleteToolDescription covers the description's experimental +// gate at the helper level: the harness calls tools but never lists them, so +// a description isn't reachable through runTool. +func TestServiceDeleteToolDescription(t *testing.T) { + const branchNote = "Deleting a service also deletes all of its branches." + if got := newServiceDeleteTool(false).Description; strings.Contains(got, "branch") { + t.Errorf("description must not mention branches without the gate, got:\n%s", got) + } + if got := newServiceDeleteTool(true).Description; !strings.Contains(got, branchNote) { + t.Errorf("description must contain %q behind the gate, got:\n%s", branchNote, got) + } +} diff --git a/internal/mcp/service_fork.go b/internal/mcp/service_fork.go index e7031b34..35102d2d 100644 --- a/internal/mcp/service_fork.go +++ b/internal/mcp/service_fork.go @@ -209,7 +209,7 @@ func (s *Server) handleServiceFork(ctx context.Context, req *mcp.CallToolRequest // This ensures the password is stored even if the wait fails or is interrupted var passwordStorage *common.PasswordStorageResult if service.InitialPassword != nil { - result, err := common.SavePasswordWithResult(cfg, api.Service(service), *service.InitialPassword, "tsdbadmin") + result, err := common.SavePasswordWithResult(cfg, common.ServicePasswordTarget(service), *service.InitialPassword, "tsdbadmin") passwordStorage = &result if err != nil { s.logger.Warn("MCP: Password storage failed", slog.Any("error", err)) diff --git a/internal/mcp/service_get.go b/internal/mcp/service_get.go index 06a6c1de..6aa6d328 100644 --- a/internal/mcp/service_get.go +++ b/internal/mcp/service_get.go @@ -84,6 +84,16 @@ func (s *Server) handleServiceGet(ctx context.Context, req *mcp.CallToolRequest, Service: s.convertToServiceDetail(cfg, *resp.JSON200, input.WithPassword), } + // Branches are a preview, so they're counted only behind the gate. The + // rest of the output doesn't depend on the count. + if s.app.Experimental && common.CanHaveBranches(*resp.JSON200) { + if count, err := common.CountBranches(ctx, client, projectID, input.ServiceID); err != nil { + s.logger.Error("MCP: Failed to count branches", slog.Any("error", err)) + } else { + output.Service.BranchCount = &count + } + } + // Check if password was requested but not available if input.WithPassword && output.Service.Password == "" { return nil, ServiceGetOutput{}, fmt.Errorf("requested password but password not available") diff --git a/internal/mcp/service_get_test.go b/internal/mcp/service_get_test.go index 28919f5b..3541974f 100644 --- a/internal/mcp/service_get_test.go +++ b/internal/mcp/service_get_test.go @@ -47,6 +47,11 @@ func TestServiceGetTool(t *testing.T) { s.Created = created s.Endpoint = endpoint }) + fluidService := sampleService(func(s *api.Service) { + s.Created = created + s.Endpoint = endpoint + s.Platform = new(api.ServicePlatformFLUID) + }) setupGet := func(svc api.Service) func(m *mocks.MockClientWithResponsesInterface) { return func(m *mocks.MockClientWithResponsesInterface) { @@ -143,18 +148,54 @@ func TestServiceGetTool(t *testing.T) { wantOutput: full, }, { - // The platform and IOPS are preview fields: the case above carries - // them and drops them, while this one returns them. - name: "experimental returns the platform and IOPS", + // No port is guessed, for the direct endpoint or the pooler, so + // an endpoint without one isn't returned or connected to. + name: "endpoints without a port", tool: toolServiceGet, args: args, - opts: []runOption{withExperimental()}, mock: setupGet(sampleService(func(s *api.Service) { s.Created = created - s.Endpoint = endpoint - s.Platform = new(api.ServicePlatformFLUID) - s.Resources = []api.Resource{{Spec: &api.ResourceSpec{CPUMillis: new(2000), MemoryGbs: new(16), Iops: new(12000)}}} + s.Endpoint = &api.Endpoint{Host: new("e6ue9697jf.project.tsdb.cloud.timescale.com")} + s.ConnectionPooler = &api.ConnectionPooler{ + Endpoint: &api.Endpoint{Host: new("e6ue9697jf.project.pooler.tsdb.cloud.timescale.com")}, + } })), + wantOutput: map[string]any{ + "service": map[string]any{ + "id": "e6ue9697jf", + "name": "test-service", + "status": "READY", + "type": "TIMESCALEDB", + "region": "us-east-1", + "created": "2025-01-15T09:30:00Z", + "environment": "DEV", + "replicas": float64(0), + "connection_string": "", + }, + }, + }, + { + // The platform, IOPS, and branch count are preview fields: the case + // above carries a platform and IOPS and drops them, while this one + // returns all three. + name: "experimental returns the platform, IOPS, and branch count", + tool: toolServiceGet, + args: args, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + setupGet(sampleService(func(s *api.Service) { + s.Created = created + s.Endpoint = endpoint + s.Platform = new(api.ServicePlatformFLUID) + s.Resources = []api.Resource{{Spec: &api.ResourceSpec{CPUMillis: new(2000), MemoryGbs: new(16), Iops: new(12000)}}} + }))(m) + branches := []api.Branch{sampleBranch(), sampleBranch()} + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "e6ue9697jf", nil). + Return(&api.GetBranchesResponse{ + HTTPResponse: httpResponse(http.StatusOK), + JSON200: &branches, + }, nil) + }, wantOutput: map[string]any{ "service": map[string]any{ "id": "e6ue9697jf", @@ -167,6 +208,59 @@ func TestServiceGetTool(t *testing.T) { "environment": "DEV", "resources": map[string]any{"cpu": "2 cores", "memory": "16 GB", "iops": float64(12000)}, "replicas": float64(0), + "branch_count": float64(2), + "direct_endpoint": "e6ue9697jf.project.tsdb.cloud.timescale.com:5432", + "connection_string": "postgresql://tsdbadmin@e6ue9697jf.project.tsdb.cloud.timescale.com:5432/tsdb?sslmode=require", + }, + }, + }, + { + // Only fluid services can be branched, so a standard one isn't + // counted. The mock fails the test on any branch listing. + name: "experimental standard service lists no branches", + tool: toolServiceGet, + args: args, + opts: []runOption{withExperimental()}, + mock: setupGet(noPasswordService), + wantOutput: map[string]any{ + "service": map[string]any{ + "id": "e6ue9697jf", + "name": "test-service", + "status": "READY", + "platform": "STANDARD", + "type": "TIMESCALEDB", + "region": "us-east-1", + "created": "2025-01-15T09:30:00Z", + "environment": "DEV", + "replicas": float64(0), + "direct_endpoint": "e6ue9697jf.project.tsdb.cloud.timescale.com:5432", + "connection_string": "postgresql://tsdbadmin@e6ue9697jf.project.tsdb.cloud.timescale.com:5432/tsdb?sslmode=require", + }, + }, + }, + { + // The count is the only thing the branch listing adds, so the + // service is still returned without it. + name: "branch count fails", + tool: toolServiceGet, + args: args, + opts: []runOption{withExperimental()}, + mock: func(m *mocks.MockClientWithResponsesInterface) { + setupGet(fluidService)(m) + m.EXPECT().GetBranchesWithResponse(validCtx, testProjectID, "e6ue9697jf", nil). + Return(nil, errors.New("connection refused")) + }, + wantOutput: map[string]any{ + "service": map[string]any{ + "id": "e6ue9697jf", + "name": "test-service", + "status": "READY", + "platform": "FLUID", + "type": "TIMESCALEDB", + "region": "us-east-1", + "created": "2025-01-15T09:30:00Z", + "environment": "DEV", + "replicas": float64(0), "direct_endpoint": "e6ue9697jf.project.tsdb.cloud.timescale.com:5432", "connection_string": "postgresql://tsdbadmin@e6ue9697jf.project.tsdb.cloud.timescale.com:5432/tsdb?sslmode=require", }, diff --git a/internal/mcp/service_update_password.go b/internal/mcp/service_update_password.go index a8ef3c49..14b0c0ec 100644 --- a/internal/mcp/service_update_password.go +++ b/internal/mcp/service_update_password.go @@ -147,11 +147,11 @@ func (s *Server) handleServiceUpdatePassword(ctx context.Context, req *mcp.CallT if err != nil { return nil, ServiceUpdatePasswordOutput{}, fmt.Errorf("failed to update service password: %w", err) } - if resp.StatusCode() != http.StatusOK && resp.StatusCode() != http.StatusNoContent { + if resp.StatusCode() != http.StatusNoContent { return nil, ServiceUpdatePasswordOutput{}, common.ExitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX) } - storage, err := common.SavePasswordWithResult(cfg, *service, password, "tsdbadmin") + storage, err := common.SavePasswordWithResult(cfg, common.ServicePasswordTarget(*service), password, "tsdbadmin") if err != nil { s.logger.Warn("MCP: Password storage failed", slog.Any("error", err)) } else { diff --git a/internal/mcp/service_update_password_test.go b/internal/mcp/service_update_password_test.go index aa1cfb42..55cfad22 100644 --- a/internal/mcp/service_update_password_test.go +++ b/internal/mcp/service_update_password_test.go @@ -50,7 +50,7 @@ func TestServiceUpdatePasswordTool(t *testing.T) { expectTaggedServiceAndUpdate := func(tag string, calls int) func(*mocks.MockClientWithResponsesInterface) { return func(m *mocks.MockClientWithResponsesInterface) { expectTaggedService(tag, calls)(m) - expectUpdate(http.StatusOK)(m) + expectUpdate(http.StatusNoContent)(m) } } @@ -201,7 +201,7 @@ func TestServiceUpdatePasswordTool(t *testing.T) { opts: []runOption{generated}, mock: expectGetServiceAndUpdate(sampleService(func(s *api.Service) { s.ForkedFrom = &api.ForkSpec{ServiceID: new("u8me885b93")} - }), http.StatusOK), + }), http.StatusNoContent), wantOutput: updated, }, { @@ -303,19 +303,20 @@ func TestServiceUpdatePasswordTool(t *testing.T) { wantErr: "unknown error", }, { - name: "updates password on a 204", - tool: toolServiceUpdatePassword, - args: args, - opts: []runOption{generated}, - mock: expectGetServiceAndUpdate(sampleService(), http.StatusNoContent), - wantOutput: updated, + // The endpoint answers success with a 204 and nothing else. + name: "unexpected 200 is an error", + tool: toolServiceUpdatePassword, + args: args, + opts: []runOption{generated}, + mock: expectGetServiceAndUpdate(sampleService(), http.StatusOK), + wantErr: "unknown error", }, { name: "with_password includes the generated password", tool: toolServiceUpdatePassword, args: withPasswordArgs, opts: []runOption{generated}, - mock: expectGetServiceAndUpdate(sampleService(), http.StatusOK), + mock: expectGetServiceAndUpdate(sampleService(), http.StatusNoContent), wantOutput: map[string]any{ "updated": true, "message": "Password updated for tsdbadmin.", @@ -328,7 +329,7 @@ func TestServiceUpdatePasswordTool(t *testing.T) { tool: toolServiceUpdatePassword, args: withPasswordArgs, opts: []runOption{withConfig(map[string]any{"password_storage": "none"}), generated}, - mock: expectGetServiceAndUpdate(sampleService(), http.StatusOK), + mock: expectGetServiceAndUpdate(sampleService(), http.StatusNoContent), wantOutput: map[string]any{ "updated": true, "message": "Password updated for tsdbadmin.", @@ -347,7 +348,7 @@ func TestServiceUpdatePasswordTool(t *testing.T) { tool: toolServiceUpdatePassword, args: args, opts: []runOption{withConfig(map[string]any{"password_storage": "none"}), generated}, - mock: expectGetServiceAndUpdate(sampleService(), http.StatusOK), + mock: expectGetServiceAndUpdate(sampleService(), http.StatusNoContent), wantOutput: map[string]any{ "updated": true, "message": "Password updated for tsdbadmin. Warning: the new password was not saved, and was not returned because with_password was false.", @@ -366,7 +367,7 @@ func TestServiceUpdatePasswordTool(t *testing.T) { tool: toolServiceUpdatePassword, args: args, opts: []runOption{withConfig(map[string]any{"password_storage": "pgpass"}), generated}, - mock: expectGetServiceAndUpdate(sampleService(), http.StatusOK), + mock: expectGetServiceAndUpdate(sampleService(), http.StatusNoContent), wantOutput: map[string]any{ "updated": true, "message": "Password updated for tsdbadmin. Warning: the new password was not saved, and was not returned because with_password was false.", diff --git a/internal/mcp/utils.go b/internal/mcp/utils.go index 79fd2a41..9b38c992 100644 --- a/internal/mcp/utils.go +++ b/internal/mcp/utils.go @@ -1,6 +1,7 @@ package mcp import ( + "context" "encoding/json" "fmt" "log/slog" @@ -93,6 +94,16 @@ func setPlatformSchemaProperties(schema *jsonschema.Schema, experimental bool) { schema.Properties["platform"].Enum = []any{api.ServicePlatformSTANDARD, api.ServicePlatformFLUID} } +// setBranchCountSchemaProperties describes a service's branch count, or +// removes it outside the experimental gate, since branches are a preview. +func setBranchCountSchemaProperties(schema *jsonschema.Schema, experimental bool) { + if !experimental { + delete(schema.Properties, "branch_count") + return + } + schema.Properties["branch_count"].Description = "Number of branches of the service (only returned by service_get, for fluid services)" +} + // setIOPSSchemaProperties sets a tool's iops input property. IOPS are a // preview parameter, so without the experimental gate the property is removed, // and the schema rejects it as an unknown property. defaultDescription, if @@ -180,6 +191,7 @@ type ServiceDetail struct { Environment string `json:"environment"` Resources *ResourceInfo `json:"resources,omitempty"` Replicas int `json:"replicas"` + BranchCount *int `json:"branch_count,omitempty"` DirectEndpoint string `json:"direct_endpoint,omitempty"` PoolerEndpoint string `json:"pooler_endpoint,omitempty"` Password string `json:"password,omitempty"` @@ -203,6 +215,7 @@ func (ServiceDetail) Schema(experimental bool) *jsonschema.Schema { setResourceInfoSchemaProperties(schema.Properties["resources"], experimental) schema.Properties["replicas"].Description = "Number of HA replicas (0=single node/no HA, 1+=HA enabled)" + setBranchCountSchemaProperties(schema, experimental) schema.Properties["direct_endpoint"].Description = "Direct database connection endpoint" schema.Properties["pooler_endpoint"].Description = "Connection pooler endpoint" schema.Properties["password"].Description = "Password for tsdbadmin user (only included if with_password=true)" @@ -237,21 +250,15 @@ func (s *Server) convertToServiceDetail(cfg *config.Config, service api.Service, } // Add endpoint information - if service.Endpoint != nil && service.Endpoint.Host != nil { - port := "5432" - if service.Endpoint.Port != nil { - port = fmt.Sprintf("%d", *service.Endpoint.Port) - } - detail.DirectEndpoint = fmt.Sprintf("%s:%s", *service.Endpoint.Host, port) + if service.Endpoint != nil && service.Endpoint.Host != nil && service.Endpoint.Port != nil { + detail.DirectEndpoint = fmt.Sprintf("%s:%d", *service.Endpoint.Host, *service.Endpoint.Port) } // Add connection pooler endpoint - if service.ConnectionPooler != nil && service.ConnectionPooler.Endpoint != nil && service.ConnectionPooler.Endpoint.Host != nil { - port := "6432" - if service.ConnectionPooler.Endpoint.Port != nil { - port = fmt.Sprintf("%d", *service.ConnectionPooler.Endpoint.Port) + if service.ConnectionPooler != nil && service.ConnectionPooler.Endpoint != nil { + if pooler := service.ConnectionPooler.Endpoint; pooler.Host != nil && pooler.Port != nil { + detail.PoolerEndpoint = fmt.Sprintf("%s:%d", *pooler.Host, *pooler.Port) } - detail.PoolerEndpoint = fmt.Sprintf("%s:%s", *service.ConnectionPooler.Endpoint.Host, port) } // Include password in ServiceDetail if requested. Setting it here ensures @@ -263,7 +270,7 @@ func (s *Server) convertToServiceDetail(cfg *config.Config, service api.Service, // Always include connection string in ServiceDetail // Password is embedded in connection string only if with_password=true - if details, err := common.GetConnectionDetails(cfg, service, common.ConnectionDetailsOptions{ + if details, err := common.GetConnectionDetails(cfg, common.NewServiceConnectionTarget(service), common.ConnectionDetailsOptions{ Role: "tsdbadmin", WithPassword: withPassword, InitialPassword: util.Deref(service.InitialPassword), @@ -280,3 +287,140 @@ func (s *Server) convertToServiceDetail(cfg *config.Config, service api.Service, return detail } + +// branchIDHelp is the sentence a db tool's service_id description gains for +// branches, saying what passing a branch ID does. Branches are a preview, so +// it is empty without the experimental gate. +func branchIDHelp(experimental bool, does string) string { + if !experimental { + return "" + } + return " A branch ID is also accepted — passing one " + does + " instead. Use branch_list to find branch IDs." +} + +// setBranchIDSchemaProperties sets common branch_id schema properties. Like +// service_id, branch_id is IDs-only (see setServiceIDSchemaProperties), which +// resolveBranchByID enforces. +// +// TODO: confirm the branch ID format with the API. The pattern assumes branch +// IDs look like service IDs, which branches are on the platform side. +func setBranchIDSchemaProperties(schema *jsonschema.Schema) { + schema.Properties["branch_id"].Description = "Unique identifier of the branch (10-character alphanumeric string). Use branch_list to find branch IDs." + schema.Properties["branch_id"].Examples = []any{"br4nch1d9x", "k2m9x2sd3a"} + schema.Properties["branch_id"].Pattern = "^[a-z0-9]{10}$" +} + +// setBranchCPUMemorySchemaProperties sets a branch tool's cpu_memory property, +// offering only the combinations a branch can have. +func setBranchCPUMemorySchemaProperties(schema *jsonschema.Schema, description string) { + schema.Properties["cpu_memory"].Description = description + schema.Properties["cpu_memory"].Enum = util.AnySlice(common.FluidCPUMemoryConfigs().Strings()) +} + +// resolveBranchByID looks up the branch with the given ID, through the API's +// resolve endpoint, which also matches names. A match by name is reported as +// not found, keeping the tools IDs-only. The branch carries its parent's ID, +// which every other branch endpoint needs. +func resolveBranchByID(ctx context.Context, client api.ClientWithResponsesInterface, projectID, id string) (*api.Branch, error) { + branch, err := common.ResolveBranch(ctx, client, projectID, id) + if err != nil { + return nil, err + } + if branch.ID != id { + return nil, common.ExitWithCode(common.ExitServiceNotFound, fmt.Errorf("branch '%s' not found", id)) + } + return branch, nil +} + +// setBranchIOPSSchemaProperties sets a branch tool's iops input property. +// defaultDescription, if any, says what happens when iops is omitted. As for +// services, the allowed values aren't listed: they depend on too much to +// describe, and would go stale. The API validates the value. +func setBranchIOPSSchemaProperties(schema *jsonschema.Schema, defaultDescription string) { + property := schema.Properties["iops"] + property.Description = strings.TrimSpace("Provisioned IOPS for the branch's storage. " + + "Check the Tiger Data documentation for the allowed values, which depend on the user's plan and the branch's CPU/memory allocation. " + + defaultDescription) + property.Minimum = new(0.0) + property.Examples = []any{16000, 32000} +} + +// BranchDetail represents detailed branch information +type BranchDetail struct { + BranchID string `json:"id"` + Name string `json:"name"` + ServiceID string `json:"service_id"` + ServiceName string `json:"service_name,omitempty"` + Status string `json:"status"` + Created string `json:"created,omitempty"` + Resources *ResourceInfo `json:"resources,omitempty"` + DirectEndpoint string `json:"direct_endpoint,omitempty"` + ConnectionString string `json:"connection_string,omitempty"` +} + +func (BranchDetail) Schema() *jsonschema.Schema { + schema := util.Must(jsonschema.For[BranchDetail](nil)) + + schema.Properties["id"].Description = "Branch identifier (10-character alphanumeric string)" + schema.Properties["name"].Description = "Branch name, unique within its parent service" + schema.Properties["service_id"].Description = "ID of the parent service the branch was created from" + schema.Properties["service_name"].Description = "Name of the parent service (only returned by branch_get)" + + schema.Properties["status"].Description = "Branch status" + schema.Properties["status"].Examples = []any{"READY", "PAUSED", "QUEUED", "CONFIGURING"} + + // The branch tools only exist behind the experimental gate, so the IOPS + // are always included. + setResourceInfoSchemaProperties(schema.Properties["resources"], true) + + schema.Properties["direct_endpoint"].Description = "Direct database connection endpoint (absent while the branch is still provisioning)" + schema.Properties["connection_string"].Description = "PostgreSQL connection string, without a password" + + return schema +} + +// convertToBranchDetail converts an API Branch to MCP BranchDetail +func (s *Server) convertToBranchDetail(cfg *config.Config, branch api.Branch) BranchDetail { + detail := BranchDetail{ + BranchID: branch.ID, + Name: branch.Name, + ServiceID: branch.ServiceID, + Status: string(branch.Status), + Created: branch.Created.Format("2006-01-02T15:04:05Z"), + Resources: &ResourceInfo{ + CPU: common.FormatCPUCores(branch.CPUMillis) + " cores", + Memory: fmt.Sprintf("%d GB", branch.MemoryGbs), + IOPS: util.Deref(branch.Iops), + }, + } + + // A branch that's still provisioning may have no endpoint yet. + if branch.Endpoint == nil { + return detail + } + if branch.Endpoint.Host != nil && branch.Endpoint.Port != nil { + detail.DirectEndpoint = fmt.Sprintf("%s:%d", *branch.Endpoint.Host, *branch.Endpoint.Port) + } + if details, err := common.GetConnectionDetails(cfg, common.NewBranchConnectionTarget(branch), common.ConnectionDetailsOptions{ + Role: "tsdbadmin", + ReadOnly: common.CheckReadOnly(cfg, common.BranchEnvironmentTag) != nil, + }); err != nil { + s.logger.Error("MCP: Failed to build connection string", slog.Any("error", err)) + } else { + detail.ConnectionString = details.String() + } + + return detail +} + +// parseBranchCPUMemory returns the branch configuration a cpu_memory value +// names. The schema's enum already rejects anything else, so an error here +// means the two have drifted apart. +func parseBranchCPUMemory(cpuMemory string) (common.CPUMemoryConfig, error) { + for _, c := range common.FluidCPUMemoryConfigs() { + if c.String() == cpuMemory { + return c, nil + } + } + return common.CPUMemoryConfig{}, fmt.Errorf("invalid CPU/Memory specification '%s'. Valid options: %s", cpuMemory, common.FluidCPUMemoryConfigs()) +} diff --git a/openapi.yaml b/openapi.yaml index c7332936..c60bfd99 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -36,6 +36,8 @@ tags: description: Manage services and their associated actions. - name: Read Replica Sets description: Manage read replica sets and their associated actions. + - name: Branches + description: Manage branches of a Fluid Service and their associated actions. - name: Backups description: View the backups taken for a service. - name: Exporters @@ -1467,6 +1469,334 @@ paths: '4XX': $ref: '#/components/responses/ClientError' + /projects/{project_id}/branches: + get: + operationId: getProjectBranches + x-tigerdata-owner: platform + x-tigerdata-preview: true + x-tigerdata-internal: true + tags: + - Branches + summary: List All Branches in a Project + description: | + **Preview - this endpoint is experimental and may change without notice.** + + Retrieves a list of all branches within a specific project, regardless + of parent service. + parameters: + - $ref: '#/components/parameters/ProjectId' + responses: + '200': + description: A list of branches. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Branch' + '4XX': + $ref: '#/components/responses/ClientError' + + /projects/{project_id}/services/{service_id}/branches: + get: + operationId: getBranches + x-tigerdata-owner: platform + x-tigerdata-preview: true + tags: + - Branches + summary: List All Branches + description: | + **Preview - this endpoint is experimental and may change without notice.** + + Retrieves a list of all branches created from a service within a project. + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/ServiceId' + - name: ref + in: query + required: false + description: | + Returns only the branch this reference matches: one whose ID + equals it, or whose name equals it, exactly and case-sensitively, + with no precedence between the two. A reference matching nothing + returns an empty list. + schema: + type: string + minLength: 1 + example: "my-production-db-pr-412" + responses: + '200': + description: A list of branches. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Branch' + '4XX': + $ref: '#/components/responses/ClientError' + post: + operationId: createBranch + x-tigerdata-owner: platform + x-tigerdata-preview: true + tags: + - Branches + summary: Create a Branch + description: | + **Preview - this endpoint is experimental and may change without notice.** + + Creates a new branch from the service identified by `service_id`. A + branch shares its parent's data at the moment of creation and gets + its own, independently-sized compute. This is an asynchronous + operation. + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/ServiceId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BranchCreate' + responses: + '202': + description: Branch creation request has been accepted. + content: + application/json: + schema: + $ref: '#/components/schemas/Branch' + '4XX': + $ref: '#/components/responses/ClientError' + + /projects/{project_id}/services/{service_id}/branches/{branch_id}: + get: + operationId: getBranch + x-tigerdata-owner: platform + x-tigerdata-preview: true + tags: + - Branches + summary: Get a Branch + description: | + **Preview - this endpoint is experimental and may change without notice.** + + Retrieves the details of a specific branch by its ID. + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/ServiceId' + - $ref: '#/components/parameters/BranchId' + responses: + '200': + description: Branch details. + content: + application/json: + schema: + $ref: '#/components/schemas/Branch' + '4XX': + $ref: '#/components/responses/ClientError' + delete: + operationId: deleteBranch + x-tigerdata-owner: platform + x-tigerdata-preview: true + tags: + - Branches + summary: Delete a Branch + description: | + **Preview - this endpoint is experimental and may change without notice.** + + Deletes a specific branch. This is an asynchronous operation. + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/ServiceId' + - $ref: '#/components/parameters/BranchId' + responses: + '202': + description: Deletion request has been accepted. + '4XX': + $ref: '#/components/responses/ClientError' + + /projects/{project_id}/services/{service_id}/branches/{branch_id}/start: + post: + operationId: startBranch + x-tigerdata-owner: platform + x-tigerdata-preview: true + tags: + - Branches + summary: Start a Branch + description: | + **Preview - this endpoint is experimental and may change without notice.** + + Starts a stopped branch. This is an asynchronous operation. + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/ServiceId' + - $ref: '#/components/parameters/BranchId' + responses: + '202': + description: Branch start request has been accepted. + content: + application/json: + schema: + $ref: '#/components/schemas/Branch' + '4XX': + $ref: '#/components/responses/ClientError' + + /projects/{project_id}/services/{service_id}/branches/{branch_id}/stop: + post: + operationId: stopBranch + x-tigerdata-owner: platform + x-tigerdata-preview: true + tags: + - Branches + summary: Stop a Branch + description: | + **Preview - this endpoint is experimental and may change without notice.** + + Stops a running branch. This is an asynchronous operation. + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/ServiceId' + - $ref: '#/components/parameters/BranchId' + responses: + '202': + description: Branch stop request has been accepted. + content: + application/json: + schema: + $ref: '#/components/schemas/Branch' + '4XX': + $ref: '#/components/responses/ClientError' + + /projects/{project_id}/services/{service_id}/branches/{branch_id}/resize: + post: + operationId: resizeBranch + x-tigerdata-owner: platform + x-tigerdata-preview: true + tags: + - Branches + summary: Resize a Branch + description: | + **Preview - this endpoint is experimental and may change without notice.** + + Changes the compute allocation for a specific branch. + Storage size is shared with the parent and is not resized through + this operation. + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/ServiceId' + - $ref: '#/components/parameters/BranchId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BranchResizeInput' + responses: + '202': + description: Resize request has been accepted and is in progress. + content: + application/json: + schema: + $ref: '#/components/schemas/Branch' + '4XX': + $ref: '#/components/responses/ClientError' + + /projects/{project_id}/services/{service_id}/branches/{branch_id}/updatePassword: + post: + operationId: updateBranchPassword + x-tigerdata-owner: platform + x-tigerdata-preview: true + tags: + - Branches + summary: Update Branch Password + description: | + **Preview - this endpoint is experimental and may change without notice.** + + Sets a new master password for the branch. + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/ServiceId' + - $ref: '#/components/parameters/BranchId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePasswordInput' + responses: + '204': + description: Password updated successfully. No content returned. + '4XX': + $ref: '#/components/responses/ClientError' + + /projects/{project_id}/services/{service_id}/branches/{branch_id}/rename: + post: + operationId: renameBranch + x-tigerdata-owner: platform + x-tigerdata-preview: true + tags: + - Branches + summary: Rename a Branch + description: | + **Preview - this endpoint is experimental and may change without notice.** + + Updates the name of a specific branch. + parameters: + - $ref: '#/components/parameters/ProjectId' + - $ref: '#/components/parameters/ServiceId' + - $ref: '#/components/parameters/BranchId' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BranchRename' + responses: + '200': + description: Branch renamed successfully. + content: + application/json: + schema: + $ref: '#/components/schemas/Branch' + '4XX': + $ref: '#/components/responses/ClientError' + + /projects/{project_id}/resolve: + get: + operationId: resolveRef + x-tigerdata-owner: platform + x-tigerdata-internal: true + tags: + - Services + summary: Resolve a Service, Branch, or Read Replica Set + description: Resolves `ref` to the service, branch, or read replica set it names. + parameters: + - $ref: '#/components/parameters/ProjectId' + - name: ref + in: query + required: true + description: | + The ID or name to resolve, exactly and case-sensitively. A ref + matching more than one resource is an error. + schema: + type: string + minLength: 1 + example: "my-production-db" + - name: type + in: query + required: false + description: Only match resources of this kind. + schema: + $ref: '#/components/schemas/ResolveType' + responses: + '200': + description: The resolved service, branch, or read replica set. + content: + application/json: + schema: + $ref: '#/components/schemas/ResolveResult' + '4XX': + $ref: '#/components/responses/ClientError' + /projects/{project_id}/exporters: get: operationId: getExporters @@ -1778,6 +2108,14 @@ components: type: string minLength: 3 example: "alb8jicdpr" + BranchId: + name: branch_id + in: path + required: true + description: The unique identifier of the branch. + schema: + type: string + example: "br4nch1d9x" BackupRegionCode: name: region_code in: path @@ -2415,6 +2753,7 @@ components: description: A set of read replicas for a service. required: - id + - service_id - name - status - nodes @@ -2425,6 +2764,10 @@ components: type: string description: The unique identifier for the read replica set. example: "alb8jicdpr" + service_id: + type: string + description: The ID of the parent service this read replica set was created from. + example: "d1k5vk7hf2" name: type: string description: The name of the read replica set. @@ -2471,6 +2814,182 @@ components: type: string description: Environment tag for the read replica set. example: "PROD" + Branch: + x-tigerdata-preview: true + type: object + description: A branch of a Fluid Service. + required: + - id + - project_id + - service_id + - name + - created + - status + - cpu_millis + - memory_gbs + properties: + id: + type: string + description: The unique identifier for the branch. + example: "br4nch1d9x" + project_id: + type: string + description: The project this branch belongs to. + example: "rp1pz7uyae" + service_id: + type: string + description: The service this branch was created from. + example: "4rwj1h3fd0" + name: + type: string + description: The name of the branch. + example: "my-production-db-pr-412" + created: + type: string + format: date-time + description: Creation timestamp. + example: "2026-09-01T09:30:00Z" + initial_password: + type: string + description: The initial password for the branch. + format: password + example: "a-very-secure-initial-password" + status: + allOf: + - $ref: '#/components/schemas/DeployStatus' + description: Current status of the branch. + cpu_millis: + type: integer + description: CPU allocation in milli-cores. + example: 500 + memory_gbs: + type: integer + description: Memory allocation in gigabytes. + example: 4 + iops: + x-tigerdata-preview: true + type: integer + description: Provisioned IOPS for the storage volume. + example: 3000 + endpoint: + $ref: '#/components/schemas/Endpoint' + ResolveType: + x-tigerdata-internal: true + type: string + description: What kind of resource a resolved ref is, or should be restricted to. + enum: [SERVICE, BRANCH, READ_REPLICA_SET] + ResolveResult: + x-tigerdata-internal: true + description: A service, branch, or read replica set resolved by ref. `type` determines which. + oneOf: + - $ref: '#/components/schemas/ResolvedService' + - $ref: '#/components/schemas/ResolvedBranch' + - $ref: '#/components/schemas/ResolvedReadReplicaSet' + discriminator: + propertyName: type + mapping: + SERVICE: '#/components/schemas/ResolvedService' + BRANCH: '#/components/schemas/ResolvedBranch' + READ_REPLICA_SET: '#/components/schemas/ResolvedReadReplicaSet' + ResolvedService: + x-tigerdata-internal: true + description: A Service resolved by ref. + allOf: + - $ref: '#/components/schemas/Service' + - type: object + required: + - type + properties: + type: + type: string + description: Always "SERVICE" for a resolved Service. + enum: [SERVICE] + ResolvedBranch: + x-tigerdata-internal: true + description: A Branch resolved by ref. + allOf: + - $ref: '#/components/schemas/Branch' + - type: object + required: + - type + properties: + type: + type: string + description: Always "BRANCH" for a resolved Branch. + enum: [BRANCH] + ResolvedReadReplicaSet: + x-tigerdata-internal: true + description: A ReadReplicaSet resolved by ref. + allOf: + - $ref: '#/components/schemas/ReadReplicaSet' + - type: object + required: + - type + properties: + type: + type: string + description: Always "READ_REPLICA_SET" for a resolved ReadReplicaSet. + enum: [READ_REPLICA_SET] + BranchCreate: + x-tigerdata-preview: true + type: object + description: Parameters for creating a branch of the service identified by `service_id` in the path. + properties: + name: + type: string + minLength: 1 + maxLength: 128 + description: A human-readable name for the branch. If not provided, defaults to "branch-", with "-1", "-2", ... appended if that name is already in use in the project. + example: "my-production-db-pr-412" + cpu_millis: + type: integer + minimum: 1 + description: The initial CPU allocation in milli-cores. Defaults to 250 if not provided. + example: 250 + memory_gbs: + type: integer + minimum: 1 + description: The initial memory allocation in gigabytes. Defaults to 2 if not provided. + example: 2 + iops: + x-tigerdata-preview: true + type: integer + minimum: 0 + description: Requested provisioned IOPS for the branch's storage. + example: 4000 + BranchRename: + type: object + description: Parameters for renaming a branch. + required: + - name + properties: + name: + type: string + description: The new name for the branch. + example: "my-production-db-pr-412" + BranchResizeInput: + type: object + minProperties: 1 + properties: + cpu_millis: + type: string + pattern: "^[0-9]+$" + description: The new CPU allocation in milli-cores. + example: "1000" + memory_gbs: + type: string + pattern: "^[0-9]+$" + description: The new memory allocation in gigabytes. + example: "4" + iops: + x-tigerdata-preview: true + type: integer + minimum: 0 + description: The new provisioned IOPS for the branch's storage. + example: 4000 + description: >- + At least one of cpu_millis, memory_gbs, or iops must be provided. + cpu_millis and memory_gbs must be provided together. ServiceCreate: type: object description: Parameters for creating a service.