From 16c70fd3e0659f3379ee4a220003161c2cdcf7c1 Mon Sep 17 00:00:00 2001 From: "Giau. Tran Minh" <12751435+giautm@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:12:47 +0700 Subject: [PATCH] schem/apply: apply schema changes to the target database (#218) * schem/apply: apply schema changes to the target database * chore: test script for schema/apply action --- .github/workflows/ci-go.yaml | 48 ++++++- atlasaction/action.go | 52 ++++++- atlasaction/action_test.go | 6 + atlasaction/comments/schema-apply.tmpl | 71 +++++++++ .../testdata/github/schema-apply-envs.txtar | 136 ++++++++++++++++++ .../testdata/github/schema-apply-lint.txtar | 57 ++++++++ .../{plan => schema-apply/legacy}/atlas.hcl | 0 .../{plan => schema-apply/legacy}/local.db | 0 .../legacy}/schema-1.lt.hcl | 0 .../legacy}/schema-2.lt.hcl | 0 .../schema-apply/lint-review/atlas.hcl | 8 ++ .../schema-apply/lint-review/local.db | Bin 0 -> 24576 bytes .../schema-apply/lint-review/schema.lt.hcl | 22 +++ .../local-plan/20240910173744.plan.hcl | 8 ++ .../schema-apply/local-plan/atlas.hcl | 5 + .../testdata/schema-apply/local-plan/local.db | Bin 0 -> 24576 bytes .../schema-apply/local-plan/schema.lt.hcl | 32 +++++ .../multiple-envs/20240910173744.plan.hcl | 8 ++ .../schema-apply/multiple-envs/atlas.hcl | 7 + .../schema-apply/multiple-envs/local-bu.db | Bin 0 -> 24576 bytes .../schema-apply/multiple-envs/local-pi.db | Bin 0 -> 24576 bytes .../schema-apply/multiple-envs/local-su.db | Bin 0 -> 24576 bytes .../schema-apply/multiple-envs/schema.lt.hcl | 32 +++++ .../schema-apply/on-the-fly/atlas.hcl | 5 + .../testdata/schema-apply/on-the-fly/local.db | Bin 0 -> 24576 bytes .../schema-apply/on-the-fly/schema.lt.hcl | 32 +++++ .../schema-apply/remote-repo/atlas.hcl | 16 +++ .../schema-apply/remote-repo/local.db | Bin 0 -> 24576 bytes .../schema-apply/remote-repo/schema.lt.hcl | 32 +++++ schema/apply/action.yml | 41 ++++++ schema/apply/index.js | 1 + 31 files changed, 614 insertions(+), 5 deletions(-) create mode 100644 atlasaction/comments/schema-apply.tmpl create mode 100644 atlasaction/testdata/github/schema-apply-envs.txtar create mode 100644 atlasaction/testdata/github/schema-apply-lint.txtar rename atlasaction/testdata/{plan => schema-apply/legacy}/atlas.hcl (100%) rename atlasaction/testdata/{plan => schema-apply/legacy}/local.db (100%) rename atlasaction/testdata/{plan => schema-apply/legacy}/schema-1.lt.hcl (100%) rename atlasaction/testdata/{plan => schema-apply/legacy}/schema-2.lt.hcl (100%) create mode 100644 atlasaction/testdata/schema-apply/lint-review/atlas.hcl create mode 100644 atlasaction/testdata/schema-apply/lint-review/local.db create mode 100644 atlasaction/testdata/schema-apply/lint-review/schema.lt.hcl create mode 100644 atlasaction/testdata/schema-apply/local-plan/20240910173744.plan.hcl create mode 100644 atlasaction/testdata/schema-apply/local-plan/atlas.hcl create mode 100644 atlasaction/testdata/schema-apply/local-plan/local.db create mode 100644 atlasaction/testdata/schema-apply/local-plan/schema.lt.hcl create mode 100644 atlasaction/testdata/schema-apply/multiple-envs/20240910173744.plan.hcl create mode 100644 atlasaction/testdata/schema-apply/multiple-envs/atlas.hcl create mode 100644 atlasaction/testdata/schema-apply/multiple-envs/local-bu.db create mode 100644 atlasaction/testdata/schema-apply/multiple-envs/local-pi.db create mode 100644 atlasaction/testdata/schema-apply/multiple-envs/local-su.db create mode 100644 atlasaction/testdata/schema-apply/multiple-envs/schema.lt.hcl create mode 100644 atlasaction/testdata/schema-apply/on-the-fly/atlas.hcl create mode 100644 atlasaction/testdata/schema-apply/on-the-fly/local.db create mode 100644 atlasaction/testdata/schema-apply/on-the-fly/schema.lt.hcl create mode 100644 atlasaction/testdata/schema-apply/remote-repo/atlas.hcl create mode 100644 atlasaction/testdata/schema-apply/remote-repo/local.db create mode 100644 atlasaction/testdata/schema-apply/remote-repo/schema.lt.hcl create mode 100644 schema/apply/action.yml create mode 100644 schema/apply/index.js diff --git a/.github/workflows/ci-go.yaml b/.github/workflows/ci-go.yaml index 4c8e9bb0..1dc27015 100644 --- a/.github/workflows/ci-go.yaml +++ b/.github/workflows/ci-go.yaml @@ -298,7 +298,7 @@ jobs: - id: sanity uses: ./schema/push with: - working-directory: atlasaction/testdata/plan + working-directory: atlasaction/testdata/schema-apply/legacy env: test schema-plan: runs-on: ubuntu-latest @@ -318,9 +318,53 @@ jobs: - id: sanity uses: ./schema/plan with: - working-directory: atlasaction/testdata/plan + working-directory: atlasaction/testdata/schema-apply/legacy env: test from: |- env://url env: GITHUB_TOKEN: ${{ github.token }} + schema-apply: + runs-on: ubuntu-latest + env: + ATLAS_ACTION_LOCAL: 1 + strategy: + fail-fast: false + matrix: + test: + - directory: lint-review + - directory: on-the-fly + auto-approve: "true" + - directory: remote-repo + plan: "atlas://atlas-action/plans/20240910183610" + to: "atlas://atlas-action?tag=e2e" + - directory: local-plan + plan: "file://20240910173744.plan.hcl" + - directory: multiple-envs + plan: "file://20240910173744.plan.hcl" + - directory: legacy + atlas: 'v0.27.0' + auto-approve: "true" + steps: + - uses: ariga/setup-atlas@v0 + with: + cloud-token: ${{ secrets.ATLAS_TOKEN }} + version: ${{ matrix.test.atlas }} + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: go.mod + - run: go install ./cmd/atlas-action + env: + CGO_ENABLED: 0 + - name: Apply changes + continue-on-error: true + uses: ./schema/apply + env: + GITHUB_TOKEN: ${{ github.token }} + with: + working-directory: atlasaction/testdata/schema-apply/${{ matrix.test.directory }} + env: test + plan: ${{ matrix.test.plan }} + to: ${{ matrix.test.to }} + auto-approve: ${{ matrix.test.auto-approve }} diff --git a/atlasaction/action.go b/atlasaction/action.go index 62b89fc7..4974250b 100644 --- a/atlasaction/action.go +++ b/atlasaction/action.go @@ -89,6 +89,8 @@ type AtlasExec interface { SchemaPlanLint(context.Context, *atlasexec.SchemaPlanLintParams) (*atlasexec.SchemaPlan, error) // SchemaPlanApprove runs the `schema plan approve` command. SchemaPlanApprove(context.Context, *atlasexec.SchemaPlanApproveParams) (*atlasexec.SchemaPlanApprove, error) + // SchemaApplySlice runs the `schema apply` command. + SchemaApplySlice(context.Context, *atlasexec.SchemaApplyParams) ([]*atlasexec.SchemaApply, error) } // Context holds the context of the environment the action is running in. @@ -150,9 +152,10 @@ const ( CmdMigrateDown = "migrate/down" CmdMigrateTest = "migrate/test" // Declarative workflow Commands - CmdSchemaPush = "schema/push" - CmdSchemaTest = "schema/test" - CmdSchemaPlan = "schema/plan" + CmdSchemaPush = "schema/push" + CmdSchemaTest = "schema/test" + CmdSchemaPlan = "schema/plan" + CmdSchemaApply = "schema/apply" ) // Run runs the action based on the command name. @@ -180,6 +183,8 @@ func (a *Actions) Run(ctx context.Context, act string) error { return a.SchemaTest(ctx) case CmdSchemaPlan: return a.SchemaPlan(ctx) + case CmdSchemaApply: + return a.SchemaApply(ctx) default: return fmt.Errorf("unknown action: %s", act) } @@ -615,6 +620,47 @@ func (a *Actions) SchemaPlan(ctx context.Context) error { return nil } +// SchemaApply runs the GitHub Action for "ariga/atlas-action/schema/apply" +func (a *Actions) SchemaApply(ctx context.Context) error { + params := &atlasexec.SchemaApplyParams{ + ConfigURL: a.GetInput("config"), + Env: a.GetInput("env"), + Vars: a.GetVarsInput("vars"), + URL: a.GetInput("url"), + To: a.GetInput("to"), + DryRun: a.GetBoolInput("dry-run"), + AutoApprove: a.GetBoolInput("auto-approve"), + PlanURL: a.GetInput("plan"), + TxMode: a.GetInput("tx-mode"), // Hidden param. + } + results, err := a.Atlas.SchemaApplySlice(ctx, params) + // Any errors will print at the end of execution. + if mErr := (&atlasexec.SchemaApplyError{}); errors.As(err, &mErr) { + // If the error is a SchemaApplyError, we can still get the successful runs. + results = mErr.Result + } + for _, result := range results { + switch summary, err := RenderTemplate("schema-apply.tmpl", result); { + case err != nil: + a.Errorf("failed to create summary: %v", err) + default: + a.AddStepSummary(summary) + } + if result.Error != "" { + a.SetOutput("error", result.Error) + return errors.New(result.Error) + } + a.Infof(`"atlas schema apply" completed successfully on the target %q`, result.URL) + } + // We generate summary for the successful runs. + // Then fail the action if there is an error. + if err != nil { + a.SetOutput("error", err.Error()) + return err + } + return nil +} + // WorkingDir returns the working directory for the action. func (a *Actions) WorkingDir() string { return a.GetInput("working-directory") diff --git a/atlasaction/action_test.go b/atlasaction/action_test.go index 7c84c91c..8389f7ac 100644 --- a/atlasaction/action_test.go +++ b/atlasaction/action_test.go @@ -326,6 +326,12 @@ func (m *mockAtlas) SchemaPlanLint(ctx context.Context, p *atlasexec.SchemaPlanL return m.schemaPlanLint(ctx, p) } +// SchemaPlanStatus implements AtlasExec. +func (m *mockAtlas) SchemaApplySlice(ctx context.Context, params *atlasexec.SchemaApplyParams) ([]*atlasexec.SchemaApply, error) { + panic("unimplemented") +} + +// MigrateDown implements AtlasExec. func (m *mockAtlas) MigrateDown(ctx context.Context, params *atlasexec.MigrateDownParams) (*atlasexec.MigrateDown, error) { return m.migrateDown(ctx, params) } diff --git a/atlasaction/comments/schema-apply.tmpl b/atlasaction/comments/schema-apply.tmpl new file mode 100644 index 00000000..d5fe6e1c --- /dev/null +++ b/atlasaction/comments/schema-apply.tmpl @@ -0,0 +1,71 @@ +{{- template "migrate-header" . -}} +

atlas schema apply Summary:

+{{- with .Plan -}} + + + + + + {{- with .File -}} + + + + + + + + + + + + + {{- if .URL -}} + + + + + {{- end -}} + {{- with .Status -}} + + {{- end -}} + {{- end -}} + {{- with $.Error -}} + + {{- else -}} + + + + + {{- end -}} +
Database URL{{ $.URL }}
Plan Name{{ .Name }}
From Hash{{ .FromHash }}
To Hash{{ .ToHash }}
Plan URL + {{- $planURL := printf "%s" .URL -}} + {{- with .Link -}} + {{- . | link $planURL -}} + {{- else -}} + {{- $planURL -}} + {{- end -}} +
Plan Status{{ . }}
Error{{ . }}
Total Time{{ execTime $.Start $.End }}
+ {{- if $.Applied -}} + {{- template "applied-file" $.Applied -}} + {{- end -}} + {{- $kind := or (and .File.URL "Pre-planned SQL") "SQL" -}} + {{- .File.Migration | codeblock "sql" | details (printf "๐Ÿ“„ View %s Statements" $kind) -}} + {{- with .Lint -}} +

Atlas lint results

+ {{- template "lint-report" . -}} + {{- end -}} +{{/* Fallback to the old output */}} +{{- else with .Changes -}} + {{- with .Error -}} +The following SQL statement failed to execute: + {{- .Stmt | codeblock "sql" -}} +
Database returned the following error: + {{- .Text | codeblock "" -}} + {{- end -}} + {{- with .Applied -}} + {{- join . "\n" | codeblock "sql" | details "๐Ÿ“„ Succeeded SQL Applied" -}} + {{- end -}} + {{- with .Pending -}} + {{- join . "\n" | codeblock "sql" | details "๐Ÿ“„ Pending SQL statements" -}} + {{- end }} +{{- end -}} diff --git a/atlasaction/testdata/github/schema-apply-envs.txtar b/atlasaction/testdata/github/schema-apply-envs.txtar new file mode 100644 index 00000000..c270b92f --- /dev/null +++ b/atlasaction/testdata/github/schema-apply-envs.txtar @@ -0,0 +1,136 @@ +# Mock the atlas command outputs +mock-atlas $WORK/schema-apply +# Setup the action input variables +env INPUT_ENV=test +env INPUT_PLAN=file://20240910173744.plan.hcl +# Run the action +! atlas-action schema/apply +stdout '"atlas schema apply" completed successfully on the target "sqlite://local-bu.db"' +stdout '"atlas schema apply" completed successfully on the target "sqlite://local-pi.db"' +stdout '"atlas schema apply" completed successfully on the target "sqlite://local-su.db"' + +summary summary.html +output output.txt + +-- schema-apply/1/args -- +schema apply --format {{ json . }} --env test --plan file://20240910173744.plan.hcl +-- schema-apply/1/stderr -- +Abort: The plan "From" hash does not match the current state hash (passed with --from): + + - iHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE= (plan value) + + R1cGcSfo1oWYK4dz+7WvgCtE/QppFo9lKFEqEDzoS4o= (current hash) + +-- schema-apply/1/stdout -- +{"Driver":"sqlite3","URL":{"Scheme":"sqlite","Opaque":"","User":null,"Host":"local-bu.db","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":"","Schema":"main"},"Start":"2024-09-12T21:31:20.339663+07:00","End":"2024-09-12T21:31:20.351603+07:00","Applied":{"Name":"20240910173744.sql","Version":"20240910173744","Start":"2024-09-12T21:31:20.350607+07:00","End":"2024-09-12T21:31:20.351228+07:00","Applied":["ALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;"]},"Plan":{"Env":{"Driver":"sqlite3","URL":{"Scheme":"sqlite","Opaque":"","User":null,"Host":"local-bu.db","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":"","Schema":"main"}},"File":{"Name":"20240910173744","FromHash":"iHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE=","ToHash":"Cp8xCVYilZuwULkggsfJLqIQHaxYcg/IpU+kgjVUBA4=","Migration":"-- Add column \"c2\" to table: \"t4\"\nALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;\n","URL":"file://20240910173744.plan.hcl"}},"Changes":{"Applied":["ALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;"]}} +{"Driver":"sqlite3","URL":{"Scheme":"sqlite","Opaque":"","User":null,"Host":"local-pi.db","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":"","Schema":"main"},"Start":"2024-09-12T21:31:20.354074+07:00","End":"2024-09-12T21:31:20.35764+07:00","Applied":{"Name":"20240910173744.sql","Version":"20240910173744","Start":"2024-09-12T21:31:20.356221+07:00","End":"2024-09-12T21:31:20.356755+07:00","Applied":["ALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;"]},"Plan":{"Env":{"Driver":"sqlite3","URL":{"Scheme":"sqlite","Opaque":"","User":null,"Host":"local-pi.db","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":"","Schema":"main"}},"File":{"Name":"20240910173744","FromHash":"iHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE=","ToHash":"Cp8xCVYilZuwULkggsfJLqIQHaxYcg/IpU+kgjVUBA4=","Migration":"-- Add column \"c2\" to table: \"t4\"\nALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;\n","URL":"file://20240910173744.plan.hcl"}},"Changes":{"Applied":["ALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;"]}} +{"Driver":"sqlite3","URL":{"Scheme":"sqlite","Opaque":"","User":null,"Host":"local-su.db","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":"","Schema":"main"},"Start":"2024-09-12T21:31:20.360863+07:00","End":"2024-09-12T21:31:20.368395+07:00","Applied":{"Name":"20240910173744.sql","Version":"20240910173744","Start":"2024-09-12T21:31:20.364331+07:00","End":"2024-09-12T21:31:20.365086+07:00","Applied":["ALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;"]},"Plan":{"Env":{"Driver":"sqlite3","URL":{"Scheme":"sqlite","Opaque":"","User":null,"Host":"local-su.db","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":"","Schema":"main"}},"File":{"Name":"20240910173744","FromHash":"iHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE=","ToHash":"Cp8xCVYilZuwULkggsfJLqIQHaxYcg/IpU+kgjVUBA4=","Migration":"-- Add column \"c2\" to table: \"t4\"\nALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;\n","URL":"file://20240910173744.plan.hcl"}},"Changes":{"Applied":["ALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;"]}} +-- output.txt -- +error<<_GitHubActionsFileCommandDelimeter_ +Abort: The plan "From" hash does not match the current state hash (passed with --from): - iHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE= (plan value) + R1cGcSfo1oWYK4dz+7WvgCtE/QppFo9lKFEqEDzoS4o= (current hash) +_GitHubActionsFileCommandDelimeter_ +-- summary.html -- +

Migration Passed

atlas schema apply Summary:

+ + + + + + + + + + + + + + + + + + + + +
Database URLsqlite://local-bu.db
Plan Name20240910173744
From HashiHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE=
To HashCp8xCVYilZuwULkggsfJLqIQHaxYcg/IpU+kgjVUBA4=
Plan URLfile://20240910173744.plan.hcl
Total Time11.94ms

Version 20240910173744.sql:

+ + + + + + + + + + +
StatusExecuted StatementsExecution TimeErrorError Statement
1621ยตs--
๐Ÿ“„ View Pre-planned SQL Statements
-- Add column "c2" to table: "t4"
+ALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;
+
+

Migration Passed

atlas schema apply Summary:

+ + + + + + + + + + + + + + + + + + + + +
Database URLsqlite://local-pi.db
Plan Name20240910173744
From HashiHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE=
To HashCp8xCVYilZuwULkggsfJLqIQHaxYcg/IpU+kgjVUBA4=
Plan URLfile://20240910173744.plan.hcl
Total Time3.566ms

Version 20240910173744.sql:

+ + + + + + + + + + +
StatusExecuted StatementsExecution TimeErrorError Statement
1534ยตs--
๐Ÿ“„ View Pre-planned SQL Statements
-- Add column "c2" to table: "t4"
+ALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;
+
+

Migration Passed

atlas schema apply Summary:

+ + + + + + + + + + + + + + + + + + + + +
Database URLsqlite://local-su.db
Plan Name20240910173744
From HashiHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE=
To HashCp8xCVYilZuwULkggsfJLqIQHaxYcg/IpU+kgjVUBA4=
Plan URLfile://20240910173744.plan.hcl
Total Time7.532ms

Version 20240910173744.sql:

+ + + + + + + + + + +
StatusExecuted StatementsExecution TimeErrorError Statement
1755ยตs--
๐Ÿ“„ View Pre-planned SQL Statements
-- Add column "c2" to table: "t4"
+ALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL;
+
diff --git a/atlasaction/testdata/github/schema-apply-lint.txtar b/atlasaction/testdata/github/schema-apply-lint.txtar new file mode 100644 index 00000000..dd55fcdd --- /dev/null +++ b/atlasaction/testdata/github/schema-apply-lint.txtar @@ -0,0 +1,57 @@ +# Mock the atlas command outputs +mock-atlas $WORK/schema-apply +# Setup the action input variables +env INPUT_ENV=test +env INPUT_AUTO-APPROVE=true +# Run the action +! atlas-action schema/apply + +summary summary.html +output output.txt + +-- schema-apply/1/args -- +schema apply --format {{ json . }} --env test --auto-approve +-- schema-apply/1/stderr -- +Rejected by review policy: errors or warnings were found +-- schema-apply/1/stdout -- +{"Driver":"sqlite3","URL":{"Scheme":"sqlite","Opaque":"","User":null,"Host":"local.db","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":"","Schema":"main"},"Start":"2024-09-12T17:47:58.815235+07:00","End":"0001-01-01T00:00:00Z","Error":"Rejected by review policy: errors or warnings were found","Plan":{"Env":{"Driver":"sqlite3","URL":{"Scheme":"sqlite","Opaque":"","User":null,"Host":"local.db","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":"","Schema":"main"}},"Lint":{"Env":{"Driver":"sqlite3","URL":{"Scheme":"sqlite","Opaque":"","User":null,"Host":"file","Path":"","RawPath":"","OmitHost":false,"ForceQuery":false,"RawQuery":"mode=memory","Fragment":"","RawFragment":"","Schema":"main"}},"Schema":{},"Steps":[{"Name":"Analyze 20240912104758.sql","Text":"1 reports were found in analysis","Result":{"Name":"20240912104758.sql","Text":"-- Disable the enforcement of foreign-keys constraints\nPRAGMA foreign_keys = off;\n-- Drop \"t4\" table\nDROP TABLE `t4`;\n-- Enable back the enforcement of foreign-keys constraints\nPRAGMA foreign_keys = on;\n","Reports":[{"Text":"destructive changes detected","Diagnostics":[{"Pos":101,"Text":"Dropping table \"t4\"","Code":"DS102","SuggestedFixes":[{"Message":"Add a pre-migration check to ensure table \"t4\" is empty before dropping it"}]}]}],"Error":"destructive changes detected"}}],"Files":[{"Name":"20240912104758.sql","Text":"-- Disable the enforcement of foreign-keys constraints\nPRAGMA foreign_keys = off;\n-- Drop \"t4\" table\nDROP TABLE `t4`;\n-- Enable back the enforcement of foreign-keys constraints\nPRAGMA foreign_keys = on;\n","Reports":[{"Text":"destructive changes detected","Diagnostics":[{"Pos":101,"Text":"Dropping table \"t4\"","Code":"DS102","SuggestedFixes":[{"Message":"Add a pre-migration check to ensure table \"t4\" is empty before dropping it"}]}]}],"Error":"destructive changes detected"}]},"File":{"Name":"20240912104758","FromHash":"iHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE=","ToHash":"Rl4lBdMkvFoGQ4xu+3sYCeogTVnamJ7bmDoq9pMXcjw=","Migration":"-- Disable the enforcement of foreign-keys constraints\nPRAGMA foreign_keys = off;\n-- Drop \"t4\" table\nDROP TABLE `t4`;\n-- Enable back the enforcement of foreign-keys constraints\nPRAGMA foreign_keys = on;\n"}},"Changes":{}} +-- output.txt -- +error<<_GitHubActionsFileCommandDelimeter_ +Rejected by review policy: errors or warnings were found +_GitHubActionsFileCommandDelimeter_ +-- summary.html -- +

Migration Failed

atlas schema apply Summary:

+ + + + + + + + + + + + + + +
Database URLsqlite://local.db
Plan Name20240912104758
From HashiHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE=
To HashRl4lBdMkvFoGQ4xu+3sYCeogTVnamJ7bmDoq9pMXcjw=
ErrorRejected by review policy: errors or warnings were found
๐Ÿ“„ View SQL Statements
-- Disable the enforcement of foreign-keys constraints
+PRAGMA foreign_keys = off;
+-- Drop "t4" table
+DROP TABLE `t4`;
+-- Enable back the enforcement of foreign-keys constraints
+PRAGMA foreign_keys = on;
+

Atlas lint results

+ + + + + + + + + + + + +
StatusStepResult
1 new migration file detected20240912104758.sql
Analyze 20240912104758.sql
1 reports were found in analysis
Destructive changes detected
Dropping table "t4" (DS102)
diff --git a/atlasaction/testdata/plan/atlas.hcl b/atlasaction/testdata/schema-apply/legacy/atlas.hcl similarity index 100% rename from atlasaction/testdata/plan/atlas.hcl rename to atlasaction/testdata/schema-apply/legacy/atlas.hcl diff --git a/atlasaction/testdata/plan/local.db b/atlasaction/testdata/schema-apply/legacy/local.db similarity index 100% rename from atlasaction/testdata/plan/local.db rename to atlasaction/testdata/schema-apply/legacy/local.db diff --git a/atlasaction/testdata/plan/schema-1.lt.hcl b/atlasaction/testdata/schema-apply/legacy/schema-1.lt.hcl similarity index 100% rename from atlasaction/testdata/plan/schema-1.lt.hcl rename to atlasaction/testdata/schema-apply/legacy/schema-1.lt.hcl diff --git a/atlasaction/testdata/plan/schema-2.lt.hcl b/atlasaction/testdata/schema-apply/legacy/schema-2.lt.hcl similarity index 100% rename from atlasaction/testdata/plan/schema-2.lt.hcl rename to atlasaction/testdata/schema-apply/legacy/schema-2.lt.hcl diff --git a/atlasaction/testdata/schema-apply/lint-review/atlas.hcl b/atlasaction/testdata/schema-apply/lint-review/atlas.hcl new file mode 100644 index 00000000..88ba47da --- /dev/null +++ b/atlasaction/testdata/schema-apply/lint-review/atlas.hcl @@ -0,0 +1,8 @@ +env "test" { + url = "sqlite://local.db" + dev = "sqlite://file?mode=memory" + src = "file://schema.lt.hcl" +} +lint { + review = ERROR +} diff --git a/atlasaction/testdata/schema-apply/lint-review/local.db b/atlasaction/testdata/schema-apply/lint-review/local.db new file mode 100644 index 0000000000000000000000000000000000000000..83cf293315465d96c65a9094a13c5bfecd5eaaf2 GIT binary patch literal 24576 zcmeI(KTE?v9LDiWo2I3pv5O_!jk*Z=vlgr+Lm^buh@+_zOF>%*2hIw<58sOK%)7SI zP&D1+_u!Y|lIN29T$Ur`<|>)1Ts|#}m#LDWI1-j6&ZQJWbaXkoTDmM<$GU7?Kh5;* zYaMY`d>-hlY}XdJ22UySnzAb!UNej)!S1)8R!D%WU15$&;>>*`uGy`9kHhypWU2R8DS_q&;t@ zrJ`~BkD=}D&;#2snb{_yqMh5SsPdMBW;q_Jur#!JY86(;8|8SQg68(gR`H-ZZk6M{ z@*6vG%~auK_U3xiPeT9!1Q0*~0R#|0009ILKmdUn3$XvM@!~Qy1Q0*~0R#|0009IL zKmY**HUjMbc@+>q009ILKmY**5I_I{1Q7UF0_IDaYqp?F2LS{SKmY**5I_I{1Q0*~fnNl^0g%^lkN^Mx literal 0 HcmV?d00001 diff --git a/atlasaction/testdata/schema-apply/lint-review/schema.lt.hcl b/atlasaction/testdata/schema-apply/lint-review/schema.lt.hcl new file mode 100644 index 00000000..68233893 --- /dev/null +++ b/atlasaction/testdata/schema-apply/lint-review/schema.lt.hcl @@ -0,0 +1,22 @@ +schema "main" {} + +table "t1" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t2" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t3" { + schema = schema.main + column "c1" { + type = integer + } +} diff --git a/atlasaction/testdata/schema-apply/local-plan/20240910173744.plan.hcl b/atlasaction/testdata/schema-apply/local-plan/20240910173744.plan.hcl new file mode 100644 index 00000000..84fa42f4 --- /dev/null +++ b/atlasaction/testdata/schema-apply/local-plan/20240910173744.plan.hcl @@ -0,0 +1,8 @@ +plan "20240910173744" { + from = "iHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE=" + to = "Cp8xCVYilZuwULkggsfJLqIQHaxYcg/IpU+kgjVUBA4=" + migration = <<-SQL + -- Add column "c2" to table: "t4" + ALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL; + SQL +} diff --git a/atlasaction/testdata/schema-apply/local-plan/atlas.hcl b/atlasaction/testdata/schema-apply/local-plan/atlas.hcl new file mode 100644 index 00000000..263735b6 --- /dev/null +++ b/atlasaction/testdata/schema-apply/local-plan/atlas.hcl @@ -0,0 +1,5 @@ +env "test" { + url = "sqlite://local.db" + dev = "sqlite://file?mode=memory" + src = "file://schema.lt.hcl" +} diff --git a/atlasaction/testdata/schema-apply/local-plan/local.db b/atlasaction/testdata/schema-apply/local-plan/local.db new file mode 100644 index 0000000000000000000000000000000000000000..83cf293315465d96c65a9094a13c5bfecd5eaaf2 GIT binary patch literal 24576 zcmeI(KTE?v9LDiWo2I3pv5O_!jk*Z=vlgr+Lm^buh@+_zOF>%*2hIw<58sOK%)7SI zP&D1+_u!Y|lIN29T$Ur`<|>)1Ts|#}m#LDWI1-j6&ZQJWbaXkoTDmM<$GU7?Kh5;* zYaMY`d>-hlY}XdJ22UySnzAb!UNej)!S1)8R!D%WU15$&;>>*`uGy`9kHhypWU2R8DS_q&;t@ zrJ`~BkD=}D&;#2snb{_yqMh5SsPdMBW;q_Jur#!JY86(;8|8SQg68(gR`H-ZZk6M{ z@*6vG%~auK_U3xiPeT9!1Q0*~0R#|0009ILKmdUn3$XvM@!~Qy1Q0*~0R#|0009IL zKmY**HUjMbc@+>q009ILKmY**5I_I{1Q7UF0_IDaYqp?F2LS{SKmY**5I_I{1Q0*~fnNl^0g%^lkN^Mx literal 0 HcmV?d00001 diff --git a/atlasaction/testdata/schema-apply/local-plan/schema.lt.hcl b/atlasaction/testdata/schema-apply/local-plan/schema.lt.hcl new file mode 100644 index 00000000..8649a329 --- /dev/null +++ b/atlasaction/testdata/schema-apply/local-plan/schema.lt.hcl @@ -0,0 +1,32 @@ +schema "main" {} + +table "t1" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t2" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t3" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t4" { + schema = schema.main + column "c1" { + type = integer + } + column "c2" { + type = integer + } +} diff --git a/atlasaction/testdata/schema-apply/multiple-envs/20240910173744.plan.hcl b/atlasaction/testdata/schema-apply/multiple-envs/20240910173744.plan.hcl new file mode 100644 index 00000000..84fa42f4 --- /dev/null +++ b/atlasaction/testdata/schema-apply/multiple-envs/20240910173744.plan.hcl @@ -0,0 +1,8 @@ +plan "20240910173744" { + from = "iHZMQ1EoarAXt/KU0KQbBljbbGs8gVqX2ZBXefePSGE=" + to = "Cp8xCVYilZuwULkggsfJLqIQHaxYcg/IpU+kgjVUBA4=" + migration = <<-SQL + -- Add column "c2" to table: "t4" + ALTER TABLE `t4` ADD COLUMN `c2` integer NOT NULL; + SQL +} diff --git a/atlasaction/testdata/schema-apply/multiple-envs/atlas.hcl b/atlasaction/testdata/schema-apply/multiple-envs/atlas.hcl new file mode 100644 index 00000000..603b6a81 --- /dev/null +++ b/atlasaction/testdata/schema-apply/multiple-envs/atlas.hcl @@ -0,0 +1,7 @@ +env { + name = atlas.env + for_each = ["bu", "pi", "su", "bleh"] + url = format("sqlite://local-%s.db", each.value) + dev = "sqlite://file?mode=memory" + src = "file://schema.lt.hcl" +} diff --git a/atlasaction/testdata/schema-apply/multiple-envs/local-bu.db b/atlasaction/testdata/schema-apply/multiple-envs/local-bu.db new file mode 100644 index 0000000000000000000000000000000000000000..83cf293315465d96c65a9094a13c5bfecd5eaaf2 GIT binary patch literal 24576 zcmeI(KTE?v9LDiWo2I3pv5O_!jk*Z=vlgr+Lm^buh@+_zOF>%*2hIw<58sOK%)7SI zP&D1+_u!Y|lIN29T$Ur`<|>)1Ts|#}m#LDWI1-j6&ZQJWbaXkoTDmM<$GU7?Kh5;* zYaMY`d>-hlY}XdJ22UySnzAb!UNej)!S1)8R!D%WU15$&;>>*`uGy`9kHhypWU2R8DS_q&;t@ zrJ`~BkD=}D&;#2snb{_yqMh5SsPdMBW;q_Jur#!JY86(;8|8SQg68(gR`H-ZZk6M{ z@*6vG%~auK_U3xiPeT9!1Q0*~0R#|0009ILKmdUn3$XvM@!~Qy1Q0*~0R#|0009IL zKmY**HUjMbc@+>q009ILKmY**5I_I{1Q7UF0_IDaYqp?F2LS{SKmY**5I_I{1Q0*~fnNl^0g%^lkN^Mx literal 0 HcmV?d00001 diff --git a/atlasaction/testdata/schema-apply/multiple-envs/local-pi.db b/atlasaction/testdata/schema-apply/multiple-envs/local-pi.db new file mode 100644 index 0000000000000000000000000000000000000000..83cf293315465d96c65a9094a13c5bfecd5eaaf2 GIT binary patch literal 24576 zcmeI(KTE?v9LDiWo2I3pv5O_!jk*Z=vlgr+Lm^buh@+_zOF>%*2hIw<58sOK%)7SI zP&D1+_u!Y|lIN29T$Ur`<|>)1Ts|#}m#LDWI1-j6&ZQJWbaXkoTDmM<$GU7?Kh5;* zYaMY`d>-hlY}XdJ22UySnzAb!UNej)!S1)8R!D%WU15$&;>>*`uGy`9kHhypWU2R8DS_q&;t@ zrJ`~BkD=}D&;#2snb{_yqMh5SsPdMBW;q_Jur#!JY86(;8|8SQg68(gR`H-ZZk6M{ z@*6vG%~auK_U3xiPeT9!1Q0*~0R#|0009ILKmdUn3$XvM@!~Qy1Q0*~0R#|0009IL zKmY**HUjMbc@+>q009ILKmY**5I_I{1Q7UF0_IDaYqp?F2LS{SKmY**5I_I{1Q0*~fnNl^0g%^lkN^Mx literal 0 HcmV?d00001 diff --git a/atlasaction/testdata/schema-apply/multiple-envs/local-su.db b/atlasaction/testdata/schema-apply/multiple-envs/local-su.db new file mode 100644 index 0000000000000000000000000000000000000000..83cf293315465d96c65a9094a13c5bfecd5eaaf2 GIT binary patch literal 24576 zcmeI(KTE?v9LDiWo2I3pv5O_!jk*Z=vlgr+Lm^buh@+_zOF>%*2hIw<58sOK%)7SI zP&D1+_u!Y|lIN29T$Ur`<|>)1Ts|#}m#LDWI1-j6&ZQJWbaXkoTDmM<$GU7?Kh5;* zYaMY`d>-hlY}XdJ22UySnzAb!UNej)!S1)8R!D%WU15$&;>>*`uGy`9kHhypWU2R8DS_q&;t@ zrJ`~BkD=}D&;#2snb{_yqMh5SsPdMBW;q_Jur#!JY86(;8|8SQg68(gR`H-ZZk6M{ z@*6vG%~auK_U3xiPeT9!1Q0*~0R#|0009ILKmdUn3$XvM@!~Qy1Q0*~0R#|0009IL zKmY**HUjMbc@+>q009ILKmY**5I_I{1Q7UF0_IDaYqp?F2LS{SKmY**5I_I{1Q0*~fnNl^0g%^lkN^Mx literal 0 HcmV?d00001 diff --git a/atlasaction/testdata/schema-apply/multiple-envs/schema.lt.hcl b/atlasaction/testdata/schema-apply/multiple-envs/schema.lt.hcl new file mode 100644 index 00000000..8649a329 --- /dev/null +++ b/atlasaction/testdata/schema-apply/multiple-envs/schema.lt.hcl @@ -0,0 +1,32 @@ +schema "main" {} + +table "t1" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t2" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t3" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t4" { + schema = schema.main + column "c1" { + type = integer + } + column "c2" { + type = integer + } +} diff --git a/atlasaction/testdata/schema-apply/on-the-fly/atlas.hcl b/atlasaction/testdata/schema-apply/on-the-fly/atlas.hcl new file mode 100644 index 00000000..263735b6 --- /dev/null +++ b/atlasaction/testdata/schema-apply/on-the-fly/atlas.hcl @@ -0,0 +1,5 @@ +env "test" { + url = "sqlite://local.db" + dev = "sqlite://file?mode=memory" + src = "file://schema.lt.hcl" +} diff --git a/atlasaction/testdata/schema-apply/on-the-fly/local.db b/atlasaction/testdata/schema-apply/on-the-fly/local.db new file mode 100644 index 0000000000000000000000000000000000000000..83cf293315465d96c65a9094a13c5bfecd5eaaf2 GIT binary patch literal 24576 zcmeI(KTE?v9LDiWo2I3pv5O_!jk*Z=vlgr+Lm^buh@+_zOF>%*2hIw<58sOK%)7SI zP&D1+_u!Y|lIN29T$Ur`<|>)1Ts|#}m#LDWI1-j6&ZQJWbaXkoTDmM<$GU7?Kh5;* zYaMY`d>-hlY}XdJ22UySnzAb!UNej)!S1)8R!D%WU15$&;>>*`uGy`9kHhypWU2R8DS_q&;t@ zrJ`~BkD=}D&;#2snb{_yqMh5SsPdMBW;q_Jur#!JY86(;8|8SQg68(gR`H-ZZk6M{ z@*6vG%~auK_U3xiPeT9!1Q0*~0R#|0009ILKmdUn3$XvM@!~Qy1Q0*~0R#|0009IL zKmY**HUjMbc@+>q009ILKmY**5I_I{1Q7UF0_IDaYqp?F2LS{SKmY**5I_I{1Q0*~fnNl^0g%^lkN^Mx literal 0 HcmV?d00001 diff --git a/atlasaction/testdata/schema-apply/on-the-fly/schema.lt.hcl b/atlasaction/testdata/schema-apply/on-the-fly/schema.lt.hcl new file mode 100644 index 00000000..8649a329 --- /dev/null +++ b/atlasaction/testdata/schema-apply/on-the-fly/schema.lt.hcl @@ -0,0 +1,32 @@ +schema "main" {} + +table "t1" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t2" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t3" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t4" { + schema = schema.main + column "c1" { + type = integer + } + column "c2" { + type = integer + } +} diff --git a/atlasaction/testdata/schema-apply/remote-repo/atlas.hcl b/atlasaction/testdata/schema-apply/remote-repo/atlas.hcl new file mode 100644 index 00000000..6189d977 --- /dev/null +++ b/atlasaction/testdata/schema-apply/remote-repo/atlas.hcl @@ -0,0 +1,16 @@ +env "test" { + url = "sqlite://local.db" + dev = "sqlite://file?mode=memory" + schema { + src = "file://schema.lt.hcl" + repo { + name = "atlas-action" + } + } +} + +env "local" { + url = "sqlite://local.db" + dev = "sqlite://file?mode=memory" + src = "file://schema.lt.hcl" +} diff --git a/atlasaction/testdata/schema-apply/remote-repo/local.db b/atlasaction/testdata/schema-apply/remote-repo/local.db new file mode 100644 index 0000000000000000000000000000000000000000..83cf293315465d96c65a9094a13c5bfecd5eaaf2 GIT binary patch literal 24576 zcmeI(KTE?v9LDiWo2I3pv5O_!jk*Z=vlgr+Lm^buh@+_zOF>%*2hIw<58sOK%)7SI zP&D1+_u!Y|lIN29T$Ur`<|>)1Ts|#}m#LDWI1-j6&ZQJWbaXkoTDmM<$GU7?Kh5;* zYaMY`d>-hlY}XdJ22UySnzAb!UNej)!S1)8R!D%WU15$&;>>*`uGy`9kHhypWU2R8DS_q&;t@ zrJ`~BkD=}D&;#2snb{_yqMh5SsPdMBW;q_Jur#!JY86(;8|8SQg68(gR`H-ZZk6M{ z@*6vG%~auK_U3xiPeT9!1Q0*~0R#|0009ILKmdUn3$XvM@!~Qy1Q0*~0R#|0009IL zKmY**HUjMbc@+>q009ILKmY**5I_I{1Q7UF0_IDaYqp?F2LS{SKmY**5I_I{1Q0*~fnNl^0g%^lkN^Mx literal 0 HcmV?d00001 diff --git a/atlasaction/testdata/schema-apply/remote-repo/schema.lt.hcl b/atlasaction/testdata/schema-apply/remote-repo/schema.lt.hcl new file mode 100644 index 00000000..8649a329 --- /dev/null +++ b/atlasaction/testdata/schema-apply/remote-repo/schema.lt.hcl @@ -0,0 +1,32 @@ +schema "main" {} + +table "t1" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t2" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t3" { + schema = schema.main + column "c1" { + type = integer + } +} + +table "t4" { + schema = schema.main + column "c1" { + type = integer + } + column "c2" { + type = integer + } +} diff --git a/schema/apply/action.yml b/schema/apply/action.yml new file mode 100644 index 00000000..8878f0c1 --- /dev/null +++ b/schema/apply/action.yml @@ -0,0 +1,41 @@ +name: 'Schema Apply' +description: 'Applies schema changes to a target database' +branding: + icon: database +author: 'Ariga' +inputs: + working-directory: + description: Atlas working directory, default is project root + required: false + config: + description: | + The URL of the Atlas configuration file. By default, Atlas will look for a file + named `atlas.hcl` in the current directory. For example, `file://config/atlas.hcl`. + Learn more about [Atlas configuration files](https://atlasgo.io/atlas-schema/projects). + required: false + env: + description: The environment to use from the Atlas configuration file. For example, `dev`. + required: false + vars: + description: | + A JSON object containing variables to be used in the Atlas configuration file. + For example, `{"var1": "value1", "var2": "value2"}`. + required: false + to: + description: | + URL(s) of the desired schema state. + required: false + plan: + description: The plan to apply. (ig. `atlas:///plans/`) + required: false + dry-run: + description: Print SQL without executing it. Either "true" or "false". + required: false + default: "false" + auto-approve: + description: Automatically approve and apply changes. Either "true" or "false". + required: false + default: "false" +runs: + using: node20 + main: index.js diff --git a/schema/apply/index.js b/schema/apply/index.js new file mode 100644 index 00000000..41c3df0f --- /dev/null +++ b/schema/apply/index.js @@ -0,0 +1 @@ +require('../../shim/dist')('schema/apply')