Skip to content

Commit fc48333

Browse files
authored
atlasaction: add schema/lint command (#356)
1 parent 2c07a14 commit fc48333

18 files changed

+574
-12
lines changed

.github/workflows/ci-go.yaml

+31
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,37 @@ jobs:
357357
env://url
358358
env:
359359
GITHUB_TOKEN: ${{ github.token }}
360+
schema-lint:
361+
runs-on: ubuntu-latest
362+
strategy:
363+
matrix:
364+
test:
365+
- url: sqlite://schemarule.db
366+
- url: file://ok.lt.hcl
367+
- url: file://schema.lt.hcl
368+
env:
369+
ATLAS_ACTION_LOCAL: 1
370+
steps:
371+
- uses: ariga/setup-atlas@v0
372+
with:
373+
cloud-token: ${{ secrets.ATLAS_TOKEN }}
374+
- uses: actions/checkout@v3
375+
- uses: actions/setup-go@v4
376+
with:
377+
go-version-file: go.mod
378+
- run: go install ./cmd/atlas-action
379+
env:
380+
CGO_ENABLED: 0
381+
- id: schema_lint_test
382+
uses: ./schema/lint
383+
with:
384+
working-directory: atlasaction/testdata/schema-lint
385+
config: file://atlas.hcl
386+
dev-url: sqlite://file?mode=memory
387+
url: ${{ matrix.test.url }}
388+
vars: '{"rulefile":"schema.rule.hcl"}'
389+
env:
390+
GITHUB_TOKEN: ${{ github.token }}
360391
schema-apply:
361392
runs-on: ubuntu-latest
362393
env:

README.md

+20
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ To learn more about the recommended way to build workflows, read our guide on
1919
| [ariga/atlas-action/migrate/test](#arigaatlas-actionmigratetest) | Test migrations on a database |
2020
| [ariga/atlas-action/migrate/autorebase](#arigaatlas-actionmigrateautorebase) | Fix `atlas.sum` conflicts in migration directory |
2121
| [ariga/atlas-action/schema/test](#arigaatlas-actionschematest) | Test schema on a database |
22+
| [ariga/atlas-action/schema/lint](#arigaatlas-actionschemalint) | Lint database schema with Atlas |
2223
| [ariga/atlas-action/schema/push](#arigaatlas-actionschemapush) | Push a schema to [Atlas Registry](https://atlasgo.io/registry) |
2324
| [ariga/atlas-action/schema/plan](#arigaatlas-actionschemaplan) | Plan a declarative migration for a schema transition |
2425
| [ariga/atlas-action/schema/plan/approve](#arigaatlas-actionschemaplanapprove) | Approve a declarative migration plan |
@@ -439,6 +440,25 @@ Apply a declarative migrations to a database.
439440

440441
* `error` - The error message if the action fails.
441442

443+
### `ariga/atlas-action/schema/lint`
444+
445+
Lint database schema with Atlas.
446+
447+
#### Inputs
448+
449+
* `url` - (Required) Schema URL(s) to lint. For example: `file://schema.hcl`.
450+
Read more about [Atlas URLs](https://atlasgo.io/concepts/url).
451+
* `dev-url` - (Required) The URL of the dev-database to use for analysis. For example: `mysql://root:pass@localhost:3306/dev`.
452+
Read more about [dev-databases](https://atlasgo.io/concepts/dev-database).
453+
* `schema` - (Optional) The database schema(s) to include. For example: `public`.
454+
* `config` - (Optional) The path to the Atlas configuration file. By default, Atlas will look for a file
455+
named `atlas.hcl` in the current directory. For example, `file://config/atlas.hcl`.
456+
Learn more about [Atlas configuration files](https://atlasgo.io/atlas-schema/projects).
457+
* `env` - (Optional) The environment to use from the Atlas configuration file. For example, `dev`.
458+
* `vars` - (Optional) Stringify JSON object containing variables to be used inside the Atlas configuration file.
459+
For example: `'{"var1": "value1", "var2": "value2"}'`.
460+
* `working-directory` - (Optional) The working directory to run from. Defaults to project root.
461+
442462
### `ariga/atlas-action/schema/push`
443463

444464
Push a schema to [Atlas Registry](https://atlasgo.io/registry) with an optional tag.

atlasaction/action.go

+77
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"ariga.io/atlas-action/atlasaction/cloud"
3131
"ariga.io/atlas-go-sdk/atlasexec"
3232
"ariga.io/atlas/sql/migrate"
33+
"ariga.io/atlas/sql/sqlclient"
3334
"github.com/fatih/color"
3435
)
3536

@@ -66,13 +67,16 @@ type (
6667
MigrateLint(context.Context, *atlasexec.SummaryReport)
6768
SchemaPlan(context.Context, *atlasexec.SchemaPlan)
6869
SchemaApply(context.Context, *atlasexec.SchemaApply)
70+
SchemaLint(context.Context, *SchemaLintReport)
6971
}
7072
// SCMClient contains methods for interacting with SCM platforms (GitHub, Gitlab etc...).
7173
SCMClient interface {
7274
// CommentLint comments on the pull request with the lint report.
7375
CommentLint(context.Context, *TriggerContext, *atlasexec.SummaryReport) error
7476
// CommentPlan comments on the pull request with the schema plan.
7577
CommentPlan(context.Context, *TriggerContext, *atlasexec.SchemaPlan) error
78+
// CommentSchemaLint comments on the pull request with the schema lint report.
79+
CommentSchemaLint(context.Context, *TriggerContext, *SchemaLintReport) error
7680
}
7781
Logger interface {
7882
// Infof logs an info message.
@@ -115,6 +119,8 @@ type (
115119
SchemaTest(context.Context, *atlasexec.SchemaTestParams) (string, error)
116120
// SchemaPlan runs the `schema plan` command.
117121
SchemaPlan(context.Context, *atlasexec.SchemaPlanParams) (*atlasexec.SchemaPlan, error)
122+
// SchemaLint runs the `schema lint` command.
123+
SchemaLint(context.Context, *atlasexec.SchemaLintParams) (*atlasexec.SchemaLintReport, error)
118124
// SchemaPlanList runs the `schema plan list` command.
119125
SchemaPlanList(context.Context, *atlasexec.SchemaPlanListParams) ([]atlasexec.SchemaPlanFile, error)
120126
// SchemaPlanLint runs the `schema plan lint` command.
@@ -162,6 +168,10 @@ type (
162168
Commit string // Latest commit SHA.
163169
Body string // Body (description) of the pull request.
164170
}
171+
SchemaLintReport struct {
172+
URL []string `json:"URL,omitempty"` // Redacted schema URLs
173+
*atlasexec.SchemaLintReport
174+
}
165175
)
166176

167177
// AtlasDirectives returns any directives that are
@@ -297,6 +307,7 @@ const (
297307
CmdMigrateAutoRebase = "migrate/autorebase"
298308
// Declarative workflow Commands
299309
CmdSchemaPush = "schema/push"
310+
CmdSchemaLint = "schema/lint"
300311
CmdSchemaTest = "schema/test"
301312
CmdSchemaPlan = "schema/plan"
302313
CmdSchemaPlanApprove = "schema/plan/approve"
@@ -328,6 +339,8 @@ func (a *Actions) Run(ctx context.Context, act string) error {
328339
return a.MigrateAutoRebase(ctx)
329340
case CmdSchemaPush:
330341
return a.SchemaPush(ctx)
342+
case CmdSchemaLint:
343+
return a.SchemaLint(ctx)
331344
case CmdSchemaTest:
332345
return a.SchemaTest(ctx)
333346
case CmdSchemaPlan:
@@ -725,6 +738,62 @@ func (a *Actions) SchemaPush(ctx context.Context) error {
725738
return nil
726739
}
727740

741+
// SchemaLint runs the GitHub Action for "ariga/atlas-action/schema/lint"
742+
func (a *Actions) SchemaLint(ctx context.Context) error {
743+
tc, err := a.GetTriggerContext(ctx)
744+
switch {
745+
case err != nil:
746+
return fmt.Errorf("unable to get the trigger context: %w", err)
747+
}
748+
params := &atlasexec.SchemaLintParams{
749+
ConfigURL: a.GetInput("config"),
750+
Vars: a.GetVarsInput("vars"),
751+
Env: a.GetInput("env"),
752+
URL: a.GetArrayInput("url"),
753+
Schema: a.GetArrayInput("schema"),
754+
DevURL: a.GetInput("dev-url"),
755+
}
756+
report, err := a.Atlas.SchemaLint(ctx, params)
757+
if err != nil {
758+
a.SetOutput("error", err.Error())
759+
return fmt.Errorf("`atlas schema lint` completed with errors:\n%s", err)
760+
}
761+
if len(report.Steps) == 0 {
762+
a.Infof("`atlas schema lint` completed successfully, no issues found")
763+
return nil
764+
}
765+
redactedURLs := make([]string, 0, len(params.URL))
766+
for _, u := range params.URL {
767+
redacted, err := redactedURL(u)
768+
if err != nil {
769+
a.Errorf("failed to redact URL: %v", err)
770+
} else {
771+
redactedURLs = append(redactedURLs, redacted)
772+
}
773+
}
774+
rp := &SchemaLintReport{
775+
URL: redactedURLs,
776+
SchemaLintReport: report,
777+
}
778+
if r, ok := a.Action.(Reporter); ok {
779+
r.SchemaLint(ctx, rp)
780+
}
781+
if tc.PullRequest != nil {
782+
switch c, err := tc.SCMClient(); {
783+
case errors.Is(err, ErrNoSCM):
784+
a.Infof("No SCM client available, skipping PR comment")
785+
case err != nil:
786+
a.Errorf("failed to get SCM client: %v", err)
787+
default:
788+
if err = c.CommentSchemaLint(ctx, tc, rp); err != nil {
789+
a.Errorf("failed to comment on the pull request: %v", err)
790+
}
791+
}
792+
}
793+
a.Infof("`atlas schema lint` completed successfully, no issues found")
794+
return nil
795+
}
796+
728797
// SchemaTest runs the GitHub Action for "ariga/atlas-action/schema/test"
729798
func (a *Actions) SchemaTest(ctx context.Context) error {
730799
result, err := a.Atlas.SchemaTest(ctx, &atlasexec.SchemaTestParams{
@@ -1655,6 +1724,14 @@ func fprintln(name string, val ...any) error {
16551724
return err
16561725
}
16571726

1727+
func redactedURL(s string) (string, error) {
1728+
u, err := sqlclient.ParseURL(s)
1729+
if err != nil {
1730+
return "", err
1731+
}
1732+
return u.Redacted(), nil
1733+
}
1734+
16581735
// commentMarker creates a hidden marker to identify the comment as one created by this action.
16591736
func commentMarker(id string) string {
16601737
return fmt.Sprintf(`<!-- generated by ariga/atlas-action for %v -->`, id)

0 commit comments

Comments
 (0)