From 9af7e6dfd88e2dfd5b5145e560b3820e940c3d5d Mon Sep 17 00:00:00 2001 From: Kyle Thompson Date: Thu, 2 May 2024 15:39:16 -0400 Subject: [PATCH] Support Mint (#70) --- internal/providers/mint_provider.go | 235 +++++++++++++++++++++++ internal/providers/mint_provider_test.go | 216 +++++++++++++++++++++ internal/providers/provider.go | 3 + test/.env.mint | 17 ++ test/helpers/helpers.go | 2 +- test/integration_suite_test.go | 8 + 6 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 internal/providers/mint_provider.go create mode 100644 internal/providers/mint_provider_test.go create mode 100644 test/.env.mint diff --git a/internal/providers/mint_provider.go b/internal/providers/mint_provider.go new file mode 100644 index 0000000..aaed643 --- /dev/null +++ b/internal/providers/mint_provider.go @@ -0,0 +1,235 @@ +package providers + +import ( + "strconv" + + "github.com/rwx-research/captain-cli/internal/config" + "github.com/rwx-research/captain-cli/internal/errors" +) + +type MintEnv struct { + Detected bool `env:"MINT"` + + ParallelIndex *string `env:"MINT_PARALLEL_INDEX"` + ParallelTotal *string `env:"MINT_PARALLEL_TOTAL"` + Actor string `env:"MINT_ACTOR"` + ActorID *string `env:"MINT_ACTOR_ID"` + RunURL string `env:"MINT_RUN_URL"` + TaskURL string `env:"MINT_TASK_URL"` + RunID string `env:"MINT_RUN_ID"` + TaskID string `env:"MINT_TASK_ID"` + TaskAttemptNumber string `env:"MINT_TASK_ATTEMPT_NUMBER"` + RunTitle string `env:"MINT_RUN_TITLE"` + GitRepositoryURL string `env:"MINT_GIT_REPOSITORY_URL"` + GitRepositoryName string `env:"MINT_GIT_REPOSITORY_NAME"` + GitCommitMessage string `env:"MINT_GIT_COMMIT_MESSAGE"` + GitCommitSha string `env:"MINT_GIT_COMMIT_SHA"` + GitRef string `env:"MINT_GIT_REF"` + GitRefName string `env:"MINT_GIT_REF_NAME"` +} + +func (cfg MintEnv) makeProvider() (Provider, error) { + tags, validationError := mintTags(cfg) + + if validationError != nil { + return Provider{}, validationError + } + + index := -1 + if cfg.ParallelIndex != nil { + var err error + index, err = strconv.Atoi(*cfg.ParallelIndex) + if err != nil { + index = -1 + } + } + + total := -1 + if cfg.ParallelTotal != nil { + var err error + total, err = strconv.Atoi(*cfg.ParallelTotal) + if err != nil { + total = -1 + } + } + + provider := Provider{ + AttemptedBy: cfg.Actor, + BranchName: cfg.GitRefName, + CommitMessage: cfg.GitCommitMessage, + CommitSha: cfg.GitCommitSha, + JobTags: tags, + ProviderName: "mint", + Title: cfg.RunTitle, + PartitionNodes: config.PartitionNodes{ + Index: index, + Total: total, + }, + } + + return provider, nil +} + +func mintTags(cfg MintEnv) (map[string]any, error) { + err := func() error { + // don't validate + // ActorID -- expected to be sometimes unset + // ParallelIndex -- only present if parallelization enabled + // ParallelTotal -- only present if parallelization enabled + + if cfg.Actor == "" { + return errors.NewConfigurationError( + "Missing actor", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your actor.", + "Make sure you are running on Mint. If not, set `MINT` to `false`.", + ) + } + + if cfg.RunURL == "" { + return errors.NewConfigurationError( + "Missing run url", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your run url.", + "Make sure you are running on Mint. If not, set `MINT` to `false`.", + ) + } + + if cfg.TaskURL == "" { + return errors.NewConfigurationError( + "Missing task url", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your task url.", + "Make sure you are running on Mint. If not, set `MINT` to `false`.", + ) + } + + if cfg.RunID == "" { + return errors.NewConfigurationError( + "Missing run id", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your run id.", + "Make sure you are running on Mint. If not, set `MINT` to `false`.", + ) + } + + if cfg.TaskID == "" { + return errors.NewConfigurationError( + "Missing task id", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your task id.", + "Make sure you are running on Mint. If not, set `MINT` to `false`.", + ) + } + + if cfg.TaskAttemptNumber == "" { + return errors.NewConfigurationError( + "Missing task attempt number", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your task attempt number.", + "Make sure you are running on Mint. If not, set `MINT` to `false`.", + ) + } + + if cfg.RunTitle == "" { + return errors.NewConfigurationError( + "Missing run title", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your run title.", + "Make sure you are running on Mint. If not, set `MINT` to `false`.", + ) + } + + if cfg.GitRepositoryURL == "" { + return errors.NewConfigurationError( + "Missing git repository url", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your git repository url.", + "Ensure that you've run the `mint/git-clone` leaf to set the `MINT_GIT_*` metadata.", + ) + } + + if cfg.GitRepositoryName == "" { + return errors.NewConfigurationError( + "Missing git repository name", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your git repository name.", + "Ensure that you've run the `mint/git-clone` leaf to set the `MINT_GIT_*` metadata.", + ) + } + + if cfg.GitCommitMessage == "" { + return errors.NewConfigurationError( + "Missing git commit message", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your git commit message.", + "Ensure that you've run the `mint/git-clone` leaf to set the `MINT_GIT_*` metadata.", + ) + } + + if cfg.GitCommitSha == "" { + return errors.NewConfigurationError( + "Missing git commit sha", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your git commit sha.", + "Ensure that you've run the `mint/git-clone` leaf to set the `MINT_GIT_*` metadata.", + ) + } + + if cfg.GitRef == "" { + return errors.NewConfigurationError( + "Missing git ref", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your git ref.", + "Ensure that you've run the `mint/git-clone` leaf to set the `MINT_GIT_*` metadata.", + ) + } + + if cfg.GitRefName == "" { + return errors.NewConfigurationError( + "Missing git ref name", + "It appears that you are running on Mint (`MINT` is set to `true`),"+ + " however Captain is unable to determine your git ref name.", + "Ensure that you've run the `mint/git-clone` leaf to set the `MINT_GIT_*` metadata.", + ) + } + + return nil + }() + + tags := map[string]any{ + "mint_actor": cfg.Actor, + "mint_run_url": cfg.RunURL, + "mint_task_url": cfg.TaskURL, + "mint_run_id": cfg.RunID, + "mint_task_id": cfg.TaskID, + "mint_task_attempt_number": cfg.TaskAttemptNumber, + "mint_run_title": cfg.RunTitle, + "mint_git_repository_url": cfg.GitRepositoryURL, + "mint_git_repository_name": cfg.GitRepositoryName, + "mint_git_commit_message": cfg.GitCommitMessage, + "mint_git_commit_sha": cfg.GitCommitSha, + "mint_git_ref": cfg.GitRef, + "mint_git_ref_name": cfg.GitRefName, + } + + if cfg.ParallelIndex != nil { + tags["mint_parallel_index"] = *cfg.ParallelIndex + } else { + tags["mint_parallel_index"] = nil + } + + if cfg.ParallelTotal != nil { + tags["mint_parallel_total"] = *cfg.ParallelTotal + } else { + tags["mint_parallel_total"] = nil + } + + if cfg.ActorID != nil { + tags["mint_actor_id"] = *cfg.ActorID + } else { + tags["mint_actor_id"] = nil + } + + return tags, err +} diff --git a/internal/providers/mint_provider_test.go b/internal/providers/mint_provider_test.go new file mode 100644 index 0000000..fe01937 --- /dev/null +++ b/internal/providers/mint_provider_test.go @@ -0,0 +1,216 @@ +package providers_test + +import ( + "github.com/rwx-research/captain-cli/internal/config" + "github.com/rwx-research/captain-cli/internal/providers" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("MintEnv.MakeProvider", func() { + var env providers.Env + + BeforeEach(func() { + partitionIndex := "0" + partitionTotal := "2" + actorID := "some-actor-id" + env = providers.Env{Mint: providers.MintEnv{ + Detected: true, + ParallelIndex: &partitionIndex, + ParallelTotal: &partitionTotal, + ActorID: &actorID, + Actor: "some-actor", + RunURL: "https://cloud.rwx.com/mint/some-org/runs/some-run-id", + TaskURL: "https://cloud.rwx.com/mint/some-org/tasks/some-task-id", + RunID: "some-run-id", + TaskID: "some-task-id", + TaskAttemptNumber: "1", + RunTitle: "Some title", + GitRepositoryURL: "git@github.com:rwx-research/captain.git", + GitRepositoryName: "rwx-research/captain", + GitCommitMessage: "Some commit message", + GitCommitSha: "some-commit-sha", + GitRef: "refs/heads/some-ref", + GitRefName: "some-ref", + }} + }) + + It("is valid", func() { + provider, err := env.MakeProvider() + Expect(err).To(BeNil()) + Expect(provider.AttemptedBy).To(Equal("some-actor")) + Expect(provider.BranchName).To(Equal("some-ref")) + Expect(provider.CommitSha).To(Equal("some-commit-sha")) + Expect(provider.CommitMessage).To(Equal("Some commit message")) + Expect(provider.ProviderName).To(Equal("mint")) + Expect(provider.Title).To(Equal("Some title")) + Expect(provider.PartitionNodes).To(Equal(config.PartitionNodes{Index: 0, Total: 2})) + }) + + It("requires Actor", func() { + env.Mint.Actor = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing actor")) + }) + It("requires RunURL", func() { + env.Mint.RunURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing run url")) + }) + It("requires TaskURL", func() { + env.Mint.TaskURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing task url")) + }) + It("requires RunID", func() { + env.Mint.RunID = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing run id")) + }) + It("requires TaskID", func() { + env.Mint.TaskID = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing task id")) + }) + It("requires TaskAttemptNumber", func() { + env.Mint.TaskAttemptNumber = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing task attempt number")) + }) + It("requires RunTitle", func() { + env.Mint.RunTitle = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing run title")) + }) + It("requires GitRepositoryURL", func() { + env.Mint.GitRepositoryURL = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git repository url")) + }) + It("requires GitRepositoryName", func() { + env.Mint.GitRepositoryName = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git repository name")) + }) + It("requires GitCommitMessage", func() { + env.Mint.GitCommitMessage = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git commit message")) + }) + It("requires GitCommitSha", func() { + env.Mint.GitCommitSha = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git commit sha")) + }) + It("requires GitRef", func() { + env.Mint.GitRef = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git ref")) + }) + It("requires GitRefName", func() { + env.Mint.GitRefName = "" + _, err := env.MakeProvider() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Missing git ref name")) + }) +}) + +var _ = Describe("MintEnv.JobTags", func() { + It("constructs job tags with optional attributes", func() { + partitionIndex := "0" + partitionTotal := "2" + actorID := "some-actor-id" + provider, _ := providers.Env{Mint: providers.MintEnv{ + Detected: true, + ParallelIndex: &partitionIndex, + ParallelTotal: &partitionTotal, + ActorID: &actorID, + Actor: "some-actor", + RunURL: "https://cloud.rwx.com/mint/some-org/runs/some-run-id", + TaskURL: "https://cloud.rwx.com/mint/some-org/tasks/some-task-id", + RunID: "some-run-id", + TaskID: "some-task-id", + TaskAttemptNumber: "1", + RunTitle: "Some title", + GitRepositoryURL: "git@github.com:rwx-research/captain.git", + GitRepositoryName: "rwx-research/captain", + GitCommitMessage: "Some commit message", + GitCommitSha: "some-commit-sha", + GitRef: "refs/heads/some-ref", + GitRefName: "some-ref", + }}.MakeProvider() + + Expect(provider.JobTags).To(Equal(map[string]any{ + "mint_actor": "some-actor", + "mint_run_url": "https://cloud.rwx.com/mint/some-org/runs/some-run-id", + "mint_task_url": "https://cloud.rwx.com/mint/some-org/tasks/some-task-id", + "mint_run_id": "some-run-id", + "mint_task_id": "some-task-id", + "mint_task_attempt_number": "1", + "mint_run_title": "Some title", + "mint_git_repository_url": "git@github.com:rwx-research/captain.git", + "mint_git_repository_name": "rwx-research/captain", + "mint_git_commit_message": "Some commit message", + "mint_git_commit_sha": "some-commit-sha", + "mint_git_ref": "refs/heads/some-ref", + "mint_git_ref_name": "some-ref", + "mint_parallel_index": partitionIndex, + "mint_parallel_total": partitionTotal, + "mint_actor_id": actorID, + })) + }) + + It("constructs job tags without optional attributes", func() { + provider, _ := providers.Env{Mint: providers.MintEnv{ + Detected: true, + ParallelIndex: nil, + ParallelTotal: nil, + ActorID: nil, + Actor: "some-actor", + RunURL: "https://cloud.rwx.com/mint/some-org/runs/some-run-id", + TaskURL: "https://cloud.rwx.com/mint/some-org/tasks/some-task-id", + RunID: "some-run-id", + TaskID: "some-task-id", + TaskAttemptNumber: "1", + RunTitle: "Some title", + GitRepositoryURL: "git@github.com:rwx-research/captain.git", + GitRepositoryName: "rwx-research/captain", + GitCommitMessage: "Some commit message", + GitCommitSha: "some-commit-sha", + GitRef: "refs/heads/some-ref", + GitRefName: "some-ref", + }}.MakeProvider() + + Expect(provider.JobTags).To(Equal(map[string]any{ + "mint_actor": "some-actor", + "mint_run_url": "https://cloud.rwx.com/mint/some-org/runs/some-run-id", + "mint_task_url": "https://cloud.rwx.com/mint/some-org/tasks/some-task-id", + "mint_run_id": "some-run-id", + "mint_task_id": "some-task-id", + "mint_task_attempt_number": "1", + "mint_run_title": "Some title", + "mint_git_repository_url": "git@github.com:rwx-research/captain.git", + "mint_git_repository_name": "rwx-research/captain", + "mint_git_commit_message": "Some commit message", + "mint_git_commit_sha": "some-commit-sha", + "mint_git_ref": "refs/heads/some-ref", + "mint_git_ref_name": "some-ref", + "mint_parallel_index": nil, + "mint_parallel_total": nil, + "mint_actor_id": nil, + })) + }) +}) diff --git a/internal/providers/provider.go b/internal/providers/provider.go index 81f62dd..822225c 100644 --- a/internal/providers/provider.go +++ b/internal/providers/provider.go @@ -13,6 +13,7 @@ type Env struct { Generic GenericEnv GitHub GitHubEnv GitLab GitLabEnv + Mint MintEnv } type Provider struct { @@ -125,6 +126,8 @@ func (env Env) MakeProvider() (Provider, error) { return wrapError(env.CircleCI.makeProvider()) case env.GitLab.Detected: return wrapError(env.GitLab.makeProvider()) + case env.Mint.Detected: + return wrapError(env.Mint.makeProvider()) } return Provider{}, nil }() diff --git a/test/.env.mint b/test/.env.mint new file mode 100644 index 0000000..cfb0527 --- /dev/null +++ b/test/.env.mint @@ -0,0 +1,17 @@ +export MINT=true +export MINT_PARALLEL_INDEX=0 +export MINT_PARALLEL_TOTAL=2 +export MINT_ACTOR=some-actor +export MINT_ACTOR_ID=some-actor-id +export MINT_RUN_URL=https://cloud.rwx.com/mint/some-org/runs/some-run-id +export MINT_TASK_URL=https://cloud.rwx.com/mint/some-org/tasks/some-task-id +export MINT_RUN_ID=some-run-id +export MINT_TASK_ID=some-task-id +export MINT_TASK_ATTEMPT_NUMBER=1 +export MINT_RUN_TITLE=Some title +export MINT_GIT_REPOSITORY_URL=git@github.com:rwx-research/captain.git +export MINT_GIT_REPOSITORY_NAME=rwx-research/captain +export MINT_GIT_COMMIT_MESSAGE=Some commit message +export MINT_GIT_COMMIT_SHA=some-commit-sha +export MINT_GIT_REF=refs/heads/some-ref +export MINT_GIT_REF_NAME=some-ref diff --git a/test/helpers/helpers.go b/test/helpers/helpers.go index deffb03..479ce6f 100644 --- a/test/helpers/helpers.go +++ b/test/helpers/helpers.go @@ -44,7 +44,7 @@ func SetEnv(env map[string]string) { } func UnsetCIEnv() { - envPrefixes := []string{"GITHUB", "BUILDKITE", "CIRCLE", "GITLAB", "CI", "RWX", "CAPTAIN"} + envPrefixes := []string{"MINT", "GITHUB", "BUILDKITE", "CIRCLE", "GITLAB", "CI", "RWX", "CAPTAIN"} for _, env := range os.Environ() { for _, prefix := range envPrefixes { if strings.HasPrefix(env, prefix) { diff --git a/test/integration_suite_test.go b/test/integration_suite_test.go index 685f038..33e4405 100644 --- a/test/integration_suite_test.go +++ b/test/integration_suite_test.go @@ -156,6 +156,14 @@ func withAndWithoutInheritedEnv(sharedTests sharedTestGen) { }) if os.Getenv("FAST_INTEGRATION") == "" { + Describe("using the Mint provider", func() { + env := helpers.ReadEnvFromFile(".env.mint") + env["MINT_GIT_COMMIT_SHA"] = randomGitSha + sharedTests(func() map[string]string { + return copyMap(env) + }, "mint") + }) + Describe("using the GitHub Actions provider", func() { env := helpers.ReadEnvFromFile(".env.github.actions") env["GITHUB_SHA"] = randomGitSha