diff --git a/atlasaction/action.go b/atlasaction/action.go index 5a2ae779..eee32c00 100644 --- a/atlasaction/action.go +++ b/atlasaction/action.go @@ -219,6 +219,8 @@ func WithRuntimeAction() Option { c.action = NewCircleCIOrb(c.getenv, c.out) case c.getenv("GITLAB_CI") == "true": c.action = NewGitlabCI(c.getenv, c.out) + case c.getenv("BITBUCKET_PIPELINE_UUID") != "": + c.action = NewBitBucketPipe(c.getenv, c.out) } } } diff --git a/atlasaction/bitbucket.go b/atlasaction/bitbucket.go new file mode 100644 index 00000000..471c4a05 --- /dev/null +++ b/atlasaction/bitbucket.go @@ -0,0 +1,110 @@ +// Copyright 2021-present The Atlas Authors. All rights reserved. +// This source code is licensed under the Apache 2.0 license found +// in the LICENSE file in the root directory of this source tree. + +package atlasaction + +import ( + "errors" + "fmt" + "io" + "net/url" + "path/filepath" + "strconv" + "strings" + "testing" + + "ariga.io/atlas-go-sdk/atlasexec" + "github.com/fatih/color" +) + +type bbPipe struct { + *coloredLogger + getenv func(string) string +} + +// New returns a new Action for GitHub Actions. +func NewBitBucketPipe(getenv func(string) string, w io.Writer) Action { + // Disable color output for testing, + // but enable it for non-testing environments. + color.NoColor = testing.Testing() + return &bbPipe{getenv: getenv, coloredLogger: &coloredLogger{w: w}} +} + +// GetType implements Action. +func (a *bbPipe) GetType() atlasexec.TriggerType { + return atlasexec.TriggerTypeBitbucket +} + +// GetTriggerContext implements Action. +func (a *bbPipe) GetTriggerContext() (*TriggerContext, error) { + tc := &TriggerContext{ + Branch: a.getenv("BITBUCKET_BRANCH"), + Commit: a.getenv("BITBUCKET_COMMIT"), + Repo: a.getenv("BITBUCKET_REPO_FULL_NAME"), + RepoURL: a.getenv("BITBUCKET_GIT_HTTP_ORIGIN"), + SCM: SCM{ + Type: atlasexec.SCMTypeBitbucket, + APIURL: "https://api.bitbucket.org/2.0", + }, + } + if pr := a.getenv("BITBUCKET_PR_ID"); pr != "" { + var err error + tc.PullRequest = &PullRequest{ + Commit: a.getenv("BITBUCKET_COMMIT"), + } + tc.PullRequest.Number, err = strconv.Atoi(pr) + if err != nil { + return nil, err + } + // /pull-requests/ + tc.PullRequest.URL, err = url.JoinPath(tc.RepoURL, "pull-requests", pr) + if err != nil { + return nil, err + } + } + return tc, nil +} + +// GetInput implements the Action interface. +func (a *bbPipe) GetInput(name string) string { + return strings.TrimSpace(a.getenv("ATLAS_INPUT_" + toEnvVar(name))) +} + +// SetOutput implements Action. +func (a *bbPipe) SetOutput(name, value string) { + // Because Bitbucket Pipes does not support output variables, + // we write the output to a file. + // So the next step can read the outputs using the source command. + // e.g: + // ```shell + // source $BITBUCKET_PIPE_STORAGE_DIR/outputs.sh + // ``` + // https://support.atlassian.com/bitbucket-cloud/docs/advanced-techniques-for-writing-pipes/#Sharing-information-between-pipes + dir := a.getenv("BITBUCKET_PIPE_STORAGE_DIR") + if out := a.getenv("OUTPUT_DIR"); out != "" { + // The user can set the output directory using + // the OUTPUT_DIR environment variable. + // This is useful when the user wants to share the output + // with steps run outside the pipe. + dir = out + } + if dir == "" { + return + } + cmd := a.getenv("ATLAS_ACTION_COMMAND") + err := writeBashEnv(filepath.Join(dir, "outputs.sh"), toEnvVar( + fmt.Sprintf("ATLAS_OUTPUT_%s_%s", cmd, name)), value) + if err != nil { + a.Errorf("failed to write output to file %s: %v", dir, err) + } +} + +func (a *bbPipe) AddStepSummary(string) {} + +// SCM implements Action. +func (a *bbPipe) SCM() (SCMClient, error) { + return nil, errors.New("not implemented") +} + +var _ Action = (*bbPipe)(nil) diff --git a/atlasaction/bitbucket_test.go b/atlasaction/bitbucket_test.go new file mode 100644 index 00000000..de69a5a1 --- /dev/null +++ b/atlasaction/bitbucket_test.go @@ -0,0 +1,56 @@ +package atlasaction_test + +import ( + "os" + "path/filepath" + "testing" + + "ariga.io/atlas-go-sdk/atlasexec" + "github.com/rogpeppe/go-internal/testscript" +) + +func TestBitbucketPipe(t *testing.T) { + var ( + actions = "actions" + outputs = filepath.Join(actions, "outputs.sh") + ) + testscript.Run(t, testscript.Params{ + Dir: filepath.Join("testdata", "bitbucket"), + Setup: func(e *testscript.Env) (err error) { + dir := filepath.Join(e.WorkDir, actions) + if err := os.Mkdir(dir, 0700); err != nil { + return err + } + e.Setenv("BITBUCKET_PIPELINE_UUID", "fbfb4205-c666-42ed-983a-d27f47f2aad2") + e.Setenv("BITBUCKET_PIPE_STORAGE_DIR", dir) + c, err := atlasexec.NewClient(e.WorkDir, "atlas") + if err != nil { + return err + } + // Create a new actions for each test. + e.Values[atlasKey{}] = &atlasClient{c} + return nil + }, + Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ + "atlas-action": atlasAction, + "mock-atlas": mockAtlasOutput, + "output": func(ts *testscript.TestScript, neg bool, args []string) { + if len(args) == 0 { + _, err := os.Stat(ts.MkAbs(outputs)) + if neg { + if !os.IsNotExist(err) { + ts.Fatalf("expected no output, but got some") + } + return + } + if err != nil { + ts.Fatalf("expected output, but got none") + return + } + return + } + cmpFiles(ts, neg, args[0], outputs) + }, + }, + }) +} diff --git a/atlasaction/gh_action.go b/atlasaction/gh_action.go index 8f6ad659..b402a4e8 100644 --- a/atlasaction/gh_action.go +++ b/atlasaction/gh_action.go @@ -6,12 +6,13 @@ package atlasaction import ( "fmt" - "golang.org/x/oauth2" "io" "net/http" "os" "time" + "golang.org/x/oauth2" + "ariga.io/atlas-go-sdk/atlasexec" "github.com/mitchellh/mapstructure" "github.com/sethvargo/go-githubactions" diff --git a/atlasaction/testdata/bitbucket/schema-push.txtar b/atlasaction/testdata/bitbucket/schema-push.txtar new file mode 100644 index 00000000..0bbfa069 --- /dev/null +++ b/atlasaction/testdata/bitbucket/schema-push.txtar @@ -0,0 +1,60 @@ +# Mock atlas command outputs +mock-atlas $WORK/schema-push +# Setup the action input variables +env ATLAS_INPUT_ENV=test +env ATLAS_INPUT_LATEST=true + +# Run the action without a tag +atlas-action schema/push + +# Run the action with a tag +env ATLAS_INPUT_TAG=98765 +atlas-action schema/push + +output outputs.sh +-- outputs.sh -- +export ATLAS_OUTPUT_SCHEMA_PUSH_LINK="https://test.atlas.ariga/schemas/12345" +export ATLAS_OUTPUT_SCHEMA_PUSH_SLUG="test-repo" +export ATLAS_OUTPUT_SCHEMA_PUSH_URL="atlas://schema/12345" +export ATLAS_OUTPUT_SCHEMA_PUSH_LINK="https://test.atlas.ariga/schemas/12345" +export ATLAS_OUTPUT_SCHEMA_PUSH_SLUG="test-repo" +export ATLAS_OUTPUT_SCHEMA_PUSH_URL="atlas://schema/12345" +-- schema-push/1/args -- +schema push --format {{ json . }} --env test --context {"scmType":"BITBUCKET"} --tag latest + +-- schema-push/1/stdout -- +{ + "link": "https://test.atlas.ariga/schemas/12345", + "url": "atlas://schema/12345", + "slug": "test-repo" +} + +-- schema-push/2/args -- +schema push --format {{ json . }} --env test --context {"scmType":"BITBUCKET"} + +-- schema-push/2/stdout -- +{ + "link": "https://test.atlas.ariga/schemas/12345", + "url": "atlas://schema/12345", + "slug": "test-repo" +} + +-- schema-push/3/args -- +schema push --format {{ json . }} --env test --context {"scmType":"BITBUCKET"} --tag latest + +-- schema-push/3/stdout -- +{ + "link": "https://test.atlas.ariga/schemas/12345", + "url": "atlas://schema/12345", + "slug": "test-repo" +} + +-- schema-push/4/args -- +schema push --format {{ json . }} --env test --context {"scmType":"BITBUCKET"} --tag 98765 + +-- schema-push/4/stdout -- +{ + "link": "https://test.atlas.ariga/schemas/12345", + "url": "atlas://schema/12345", + "slug": "test-repo" +} diff --git a/go.mod b/go.mod index d04a127c..8b3eab52 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23 require ( ariga.io/atlas v0.21.2-0.20240418081819-02b3f6239b04 - ariga.io/atlas-go-sdk v0.6.5-0.20241016165749-25a157fbe224 + ariga.io/atlas-go-sdk v0.6.5 github.com/alecthomas/kong v0.8.0 github.com/fatih/color v1.17.0 github.com/gorilla/mux v1.8.1 diff --git a/go.sum b/go.sum index 96fdcf9e..5c8b95a9 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ ariga.io/atlas v0.21.2-0.20240418081819-02b3f6239b04 h1:YF3qiqtnhn+y4tfhZKTfZKfizpjqHYt7rWPUb+eA4ZA= ariga.io/atlas v0.21.2-0.20240418081819-02b3f6239b04/go.mod h1:VPlcXdd4w2KqKnH54yEZcry79UAhpaWaxEsmn5JRNoE= -ariga.io/atlas-go-sdk v0.6.5-0.20241016165749-25a157fbe224 h1:V/n4TgCKyhcKMaUEszoUs8UmwMOFMDfbBMQbiDocE2o= -ariga.io/atlas-go-sdk v0.6.5-0.20241016165749-25a157fbe224/go.mod h1:9Q+/04PVyJHUse1lEE9Kp6E18xj/6mIzaUTcWYSjSnQ= +ariga.io/atlas-go-sdk v0.6.5 h1:tl0L3ObGtHjitP9N/56njjDHUrj5jJTQBjftMNwJBcM= +ariga.io/atlas-go-sdk v0.6.5/go.mod h1:9Q+/04PVyJHUse1lEE9Kp6E18xj/6mIzaUTcWYSjSnQ= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=