From 2cd0dcdeb38c88bef1721d18c217e43931984c32 Mon Sep 17 00:00:00 2001 From: Ronen Lubin Date: Thu, 9 Jan 2025 14:26:59 +0200 Subject: [PATCH] support using atlas hcl file in monitor action --- .github/workflows/ci-go.yaml | 8 ++++++- README.md | 5 ++++- atlasaction/action.go | 43 +++++++++++++++++++++++++----------- atlasaction/action_test.go | 29 +++++++++++++++++++++--- monitor/schema/action.yml | 12 ++++++++-- monitor/schema/atlas.hcl | 4 ++++ 6 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 monitor/schema/atlas.hcl diff --git a/.github/workflows/ci-go.yaml b/.github/workflows/ci-go.yaml index 1b8395d0..b576c42f 100644 --- a/.github/workflows/ci-go.yaml +++ b/.github/workflows/ci-go.yaml @@ -444,9 +444,15 @@ jobs: - run: go install ./cmd/atlas-action env: CGO_ENABLED: 0 - - id: sanity + - id: sanity using url uses: ./monitor/schema with: cloud-token: ${{ secrets.ATLAS_AGENT_TOKEN }} url: 'mysql://root:pass@localhost:3306/dev' slug: 'github-action' + - id: sanity using config + uses: ./monitor/schema + with: + cloud-token: ${{ secrets.ATLAS_AGENT_TOKEN }} + config: 'file://monitor/schema/atlas.hcl' + env: 'dev' diff --git a/README.md b/README.md index 03288217..852a82d1 100644 --- a/README.md +++ b/README.md @@ -459,7 +459,10 @@ Can be used periodically to [monitor](https://atlasgo.io/monitoring) changes in * `cloud-token` - (required) The Atlas Cloud token to use for authentication. To create a cloud token see the [docs](https://atlasgo.io/cloud/bots). -* `url` - (required) The URL of the database to monitor. For example: `mysql://root:pass@localhost:3306/prod`. +* `url` - (optional) The URL of the database to monitor. For example: `mysql://root:pass@localhost:3306/prod` (mutually exclusive with `config` and `env`). +* `config` - (optional) 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` (mutually exclusive with `url`). +* `env` - (optional) The environment to use from the Atlas configuration file. For example, `dev` (mutually exclusive with `url`). * `slug` - (optional) Unique identifier for the database server. * `schemas` - (optional) List of database schemas to include (by default includes all schemas). see: https://atlasgo.io/declarative/inspect#inspect-multiple-schemas. * `exclude` - (optional) List of exclude patterns from inspection. see: https://atlasgo.io/declarative/inspect#exclude-schemas. diff --git a/atlasaction/action.go b/atlasaction/action.go index 873eff97..d96d8c90 100644 --- a/atlasaction/action.go +++ b/atlasaction/action.go @@ -820,6 +820,13 @@ func (a *Actions) MonitorSchema(ctx context.Context) error { if err != nil { return err } + var ( + config = a.GetInput("config") + env = a.GetInput("env") + ) + if (config != "" || env != "") && db.String() != "" { + return errors.New("only one of the inputs 'config' or 'url' should be provided") + } var ( id = cloud.ScopeIdent{ URL: db.Redacted(), @@ -833,15 +840,30 @@ func (a *Actions) MonitorSchema(ctx context.Context) error { }); err != nil { return fmt.Errorf("failed to login to Atlas Cloud: %w", err) } - res, err := a.Atlas.SchemaInspect(ctx, &atlasexec.SchemaInspectParams{ - URL: db.String(), + params := &atlasexec.SchemaInspectParams{ Schema: id.Schemas, Exclude: id.Schemas, - Format: `{{ printf "# %s\n%s" .Hash .MarshalHCL }}`, - }) + Format: `{{ printf "# %s\n%s\n%s" .Hash .MarshalHCL .RedactedURL }}`, + } + if db.String() != "" { + params.URL = db.String() + } + if config != "" { + params.ConfigURL = config + params.Env = env + } + res, err := a.Atlas.SchemaInspect(ctx, params) if err != nil { return fmt.Errorf("failed to inspect the schema: %w", err) } + var ( + parts = strings.SplitN(res, "\n", 3) + hash = strings.TrimPrefix(parts[0], "# ") + hcl = parts[1] + ) + if id.URL == "" { + id.URL = parts[2] // Get the URL from the inspect output. + } cc, err := a.cloudClient(ctx, "cloud-token") if err != nil { return err @@ -850,15 +872,10 @@ func (a *Actions) MonitorSchema(ctx context.Context) error { if err != nil { return fmt.Errorf("failed to get the schema snapshot hash: %w", err) } - var ( - parts = strings.SplitN(res, "\n", 2) - hash = strings.TrimPrefix(parts[0], "# ") - hcl = parts[1] - input = &cloud.PushSnapshotInput{ - ScopeIdent: id, - HashMatch: strings.HasPrefix(h, "h1:") && OldAgentHash(hcl) == h || hash == h, - } - ) + input := &cloud.PushSnapshotInput{ + ScopeIdent: id, + HashMatch: strings.HasPrefix(h, "h1:") && OldAgentHash(hcl) == h || hash == h, + } if !input.HashMatch { input.Snapshot = &cloud.SnapshotInput{Hash: hash, HCL: hcl} } diff --git a/atlasaction/action_test.go b/atlasaction/action_test.go index 6a63a5c1..d9edc0f5 100644 --- a/atlasaction/action_test.go +++ b/atlasaction/action_test.go @@ -555,11 +555,12 @@ func TestMonitorSchema(t *testing.T) { ctx = context.Background() ) for _, tt := range []struct { - name, url, slug string + name, url, slug, config string schemas, exclude []string latestHash, newHash, hcl string exSnapshot *cloud.SnapshotInput exMatch bool + wantErr bool }{ { name: "no latest hash", @@ -628,6 +629,22 @@ func TestMonitorSchema(t *testing.T) { exclude: []string{"foo.*", "bar.*.*"}, exMatch: true, }, + { + name: "url and config should rerurn error", + url: u, + config: "config", + wantErr: true, + }, + { + name: "hash match old hash func, using config", + config: "file:/atlas.hcl", + latestHash: atlasaction.OldAgentHash("hcl"), + newHash: "hash", + hcl: "hcl", + schemas: []string{}, + exclude: []string{}, + exMatch: true, + }, } { t.Run(tt.name, func(t *testing.T) { var ( @@ -637,6 +654,7 @@ func TestMonitorSchema(t *testing.T) { "cloud-token": "token", "url": tt.url, "slug": tt.slug, + "config": tt.config, "schemas": strings.Join(tt.schemas, "\n"), "exclude": strings.Join(tt.exclude, "\n"), }, @@ -647,7 +665,7 @@ func TestMonitorSchema(t *testing.T) { return nil }, schemaInspect: func(_ context.Context, p *atlasexec.SchemaInspectParams) (string, error) { - return fmt.Sprintf("# %s\n%s", tt.newHash, tt.hcl), nil + return fmt.Sprintf("# %s\n%s\n%s", tt.newHash, tt.hcl, ""), nil }, } cc = &mockCloudClient{hash: tt.latestHash} @@ -660,7 +678,12 @@ func TestMonitorSchema(t *testing.T) { ) ) require.NoError(t, err) - require.NoError(t, as.MonitorSchema(ctx)) + err = as.MonitorSchema(ctx) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) require.Equal(t, &cloud.PushSnapshotInput{ ScopeIdent: cloud.ScopeIdent{ URL: must(url.Parse(tt.url)).Redacted(), diff --git a/monitor/schema/action.yml b/monitor/schema/action.yml index 51475bf0..dee21728 100644 --- a/monitor/schema/action.yml +++ b/monitor/schema/action.yml @@ -8,8 +8,16 @@ inputs: description: 'The token that is used to connect to Atlas Cloud (should be passed as a secret).' required: true url: - description: 'URL of the database to sync.' - required: true + description: 'URL of the database to sync (mutually exclusive with `config` and `env`).' + required: false + config: + description: 'The URL of the Atlas configuration file (mutually exclusive with `url`). + 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` + (mutually exclusive with `url`).' + required: false slug: description: 'Optional unique identifier for the database server.' required: false diff --git a/monitor/schema/atlas.hcl b/monitor/schema/atlas.hcl new file mode 100644 index 00000000..c27d5709 --- /dev/null +++ b/monitor/schema/atlas.hcl @@ -0,0 +1,4 @@ +# used in monitor github action for integration tests +env "dev" { + url = "mysql://root:pass@localhost:3306/dev" +}