Repository navigation
feat(databases): apply migrations in tracked runs #365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+3,116
−204
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
13784cc
feat(databases): apply migrations in tracked runs
swkeever 3283d11
fix(databases): address review of tracked migration runs
swkeever 61c6f2f
Merge remote-tracking branch 'origin/main' into skeever/database-migr…
swkeever 0803aee
refactor(databases): align migration run files with the grant path
swkeever 3440982
docs(databases): document the no-transaction migration marker
swkeever File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package api | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| "github.com/Kong/volcano-cli/internal/apiclient" | ||
| ) | ||
|
|
||
| // CapabilityAvailable reports whether the API lists capability id as | ||
| // available. The list is how a client learns what an environment offers, so a | ||
| // capability it leaves out, or reports with any status but available, is | ||
| // unavailable. | ||
| func (c *Client) CapabilityAvailable(ctx context.Context, id string) (bool, error) { | ||
| resp, err := c.client.ListCapabilitiesWithResponse(ctx) | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
| list, err := apiResult[apiclient.CapabilityList](resp.StatusCode(), resp.Body, resp.JSON200) | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
| for _, capability := range list.Data { | ||
| if capability.Id == id { | ||
| return capability.Status == apiclient.CapabilityStatusAvailable, nil | ||
| } | ||
| } | ||
| return false, nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| package api | ||
|
|
||
| import ( | ||
| "context" | ||
| "net/http" | ||
| "strings" | ||
|
|
||
| "github.com/google/uuid" | ||
|
|
||
| "github.com/Kong/volcano-cli/internal/apiclient" | ||
| ) | ||
|
|
||
| // MigrationRunInProgressCode is the 409 the API answers when the database | ||
| // already has a queued or running migration run. | ||
| const MigrationRunInProgressCode = "migration_run_in_progress" | ||
|
|
||
| // MigrationRunInProgressError is a migration request refused because the | ||
| // database already has a queued or running run, which RunID names. | ||
| type MigrationRunInProgressError struct { | ||
| Err *Error | ||
| RunID uuid.UUID | ||
| } | ||
|
|
||
| func (e *MigrationRunInProgressError) Error() string { return e.Err.Error() } | ||
|
|
||
| func (e *MigrationRunInProgressError) Unwrap() error { return e.Err } | ||
|
|
||
| // GetDatabaseMigrations returns the files a database's migration history | ||
| // records and the run that is queued or running, if any. | ||
| func (c *Client) GetDatabaseMigrations(ctx context.Context, projectID uuid.UUID, databaseName string) (*apiclient.DatabaseMigrations, error) { | ||
| resp, err := c.client.GetDatabaseMigrationsWithResponse(ctx, projectID, strings.TrimSpace(databaseName)) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return apiResult(resp.StatusCode(), resp.Body, resp.JSON200, resp.JSON404, resp.JSON409, resp.JSON503) | ||
| } | ||
|
|
||
| // CreateDatabaseMigrationRun starts a server-side migration run of files on | ||
| // databaseName. The server compares them with the database's history and | ||
| // applies the pending ones; nothing here connects to the database. | ||
| // idempotencyKey makes a retried request return the run the first one started, | ||
| // which the API answers with 200 instead of 202. | ||
| func (c *Client) CreateDatabaseMigrationRun( | ||
| ctx context.Context, projectID uuid.UUID, databaseName, idempotencyKey string, files []apiclient.DatabaseMigrationFile, | ||
| ) (*apiclient.DatabaseMigrationRun, error) { | ||
| params := &apiclient.CreateDatabaseMigrationRunParams{} | ||
| if key := strings.TrimSpace(idempotencyKey); key != "" { | ||
| params.IdempotencyKey = &key | ||
| } | ||
| resp, err := c.client.CreateDatabaseMigrationRunWithResponse(ctx, projectID, strings.TrimSpace(databaseName), params, | ||
| apiclient.CreateDatabaseMigrationRunJSONRequestBody{Files: files}) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| switch { | ||
| case resp.JSON202 != nil: | ||
| return resp.JSON202, nil | ||
| case resp.JSON200 != nil: | ||
| return resp.JSON200, nil | ||
| case resp.JSON409 != nil: | ||
| return nil, migrationConflictError(resp.JSON409) | ||
| } | ||
| // A run the parser did not accept quotes database errors, which can quote | ||
| // data, so only an error body's own fields reach the error. | ||
| return nil, apiErrorFromGeneratedErrors(resp.StatusCode(), errorFieldsOnly(resp.Body), resp.JSON400, resp.JSON404, resp.JSON413) | ||
| } | ||
|
|
||
| // BaselineDatabaseMigrations records files in databaseName's migration | ||
| // history as applied, without running them. The server skips files it already | ||
| // records with the same checksum, and refuses one recorded with another. | ||
| func (c *Client) BaselineDatabaseMigrations( | ||
| ctx context.Context, projectID uuid.UUID, databaseName string, files []apiclient.DatabaseMigrationFile, | ||
| ) (*apiclient.DatabaseMigrationBaseline, error) { | ||
| resp, err := c.client.BaselineDatabaseMigrationsWithResponse(ctx, projectID, strings.TrimSpace(databaseName), | ||
| apiclient.BaselineDatabaseMigrationsJSONRequestBody{Files: files}) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if resp.JSON200 != nil { | ||
| return resp.JSON200, nil | ||
| } | ||
| if resp.JSON409 != nil { | ||
| return nil, migrationConflictError(resp.JSON409) | ||
| } | ||
| return nil, apiErrorFromGeneratedErrors(resp.StatusCode(), resp.Body, resp.JSON400, resp.JSON404, resp.JSON413, resp.JSON503) | ||
| } | ||
|
|
||
| // migrationConflictError is the error for a migration 409, naming the run in | ||
| // progress when that is the reason. | ||
| func migrationConflictError(conflict *apiclient.DatabaseMigrationConflictError) error { | ||
| apiErr := &Error{StatusCode: http.StatusConflict, Message: strings.TrimSpace(conflict.Error), fromAPI: true} | ||
| if conflict.Code != nil { | ||
| apiErr.Code = *conflict.Code | ||
| } | ||
| if apiErr.Code == MigrationRunInProgressCode && conflict.RunId != nil { | ||
| return &MigrationRunInProgressError{Err: apiErr, RunID: *conflict.RunId} | ||
| } | ||
| return apiErr | ||
| } | ||
|
|
||
| // GetDatabaseMigrationRun returns one migration run with each file's outcome. | ||
| func (c *Client) GetDatabaseMigrationRun( | ||
| ctx context.Context, projectID uuid.UUID, databaseName string, runID uuid.UUID, | ||
| ) (*apiclient.DatabaseMigrationRun, error) { | ||
| resp, err := c.client.GetDatabaseMigrationRunWithResponse(ctx, projectID, strings.TrimSpace(databaseName), runID) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return apiResult(resp.StatusCode(), errorFieldsOnly(resp.Body), resp.JSON200, resp.JSON404) | ||
| } | ||
|
|
||
| // ListDatabaseMigrationRuns returns a database's 50 most recent migration | ||
| // runs, newest first, without their files. | ||
| func (c *Client) ListDatabaseMigrationRuns(ctx context.Context, projectID uuid.UUID, databaseName string) (*apiclient.DatabaseMigrationRunList, error) { | ||
| resp, err := c.client.ListDatabaseMigrationRunsWithResponse(ctx, projectID, strings.TrimSpace(databaseName)) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| // A run's error can quote data, as in GetDatabaseMigrationRun. | ||
| return apiResult(resp.StatusCode(), errorFieldsOnly(resp.Body), resp.JSON200, resp.JSON404) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚪ P3: CapabilityAvailable duplicates the existing ListCapabilities call
internal/api/lavaflow.go:23already hasClient.ListCapabilities, which calls the sameListCapabilitiesWithResponse, andsharing.CheckAvailablelooks up its capability on top of it. The newCapabilityAvailablemakes a second wrapper for the same route. Building it onListCapabilities, or doing the lookup indatabase.Service.RequireMigrationRunsthe way sharing does, keeps one place that reads/capabilities.Found by claude-opus-5-5