From 1e9016ac2032bdfb9b4c96a4a8bf628bffd3ee92 Mon Sep 17 00:00:00 2001 From: Frantjc Date: Sat, 2 Nov 2024 10:52:43 -0600 Subject: [PATCH] docs, remove unused code, better errors --- azuredevops/task_reference.go | 4 +- cloudbuild/workspace.go | 2 +- command/attach.go | 2 +- forgeactions/env_files.go | 2 +- githubactions/download_action.go | 12 +++--- githubactions/errors.go | 16 -------- githubactions/metadata.go | 5 +-- githubactions/uses.go | 12 +++++- githubactions/uses_test.go | 48 ++++++++++++++++++++++++ githubactions/workflow_command.go | 3 ++ githubactions/workflow_command_writer.go | 2 +- githubactions/workflow_commands.go | 20 +++++++--- 12 files changed, 89 insertions(+), 39 deletions(-) delete mode 100644 githubactions/errors.go create mode 100644 githubactions/uses_test.go diff --git a/azuredevops/task_reference.go b/azuredevops/task_reference.go index 04a2f415..923e9e63 100644 --- a/azuredevops/task_reference.go +++ b/azuredevops/task_reference.go @@ -24,12 +24,12 @@ func (r *TaskReference) IsRemote() bool { func (r *TaskReference) String() string { ref := r.Path if v := r.Version; v != "" { - ref = ref + "@" + v + ref = fmt.Sprintf("%s@%s", ref, v) } return ref } -// TODO regexp. +// Parse parses a reference to a Azure DevOps Task. func Parse(ref string) (*TaskReference, error) { r := &TaskReference{} diff --git a/cloudbuild/workspace.go b/cloudbuild/workspace.go index 51116681..6ead2a5e 100644 --- a/cloudbuild/workspace.go +++ b/cloudbuild/workspace.go @@ -1,3 +1,3 @@ package cloudbuild -var WorkspacePath = "/workspace" +const WorkspacePath = "/workspace" diff --git a/command/attach.go b/command/attach.go index 2b1a6951..b38ff405 100644 --- a/command/attach.go +++ b/command/attach.go @@ -12,7 +12,7 @@ func hookAttach(cmd *cobra.Command, workingDir string, stdoutUsed ...bool) func( return func(ctx context.Context, c forge.Container) { var ( streams = commandStreams(cmd, stdoutUsed...) - _, _ = fmt.Fprintln(streams.Out, "detach with "+forge.DefaultDetachKeys) + _, _ = fmt.Fprintln(streams.Out, "detach with", forge.DefaultDetachKeys) ) streams, restore, err := forge.TerminalStreams(streams.In, streams.Out, streams.Err) diff --git a/forgeactions/env_files.go b/forgeactions/env_files.go index 88ab1781..2f6492c2 100644 --- a/forgeactions/env_files.go +++ b/forgeactions/env_files.go @@ -62,7 +62,7 @@ func (m *Mapping) SetGlobalContextFromEnvFiles(ctx context.Context, globalContex } for k, v := range outputs { - globalContext.EnvContext["STATE_"+k] = v + globalContext.EnvContext[fmt.Sprintf("STATE_%s", k)] = v } case strings.HasSuffix(m.GitHubEnvPath, header.Name): env, err := githubactions.ParseEnvFile(r) diff --git a/githubactions/download_action.go b/githubactions/download_action.go index 7b2eae50..e73ea3f3 100644 --- a/githubactions/download_action.go +++ b/githubactions/download_action.go @@ -47,10 +47,10 @@ func DownloadAction(ctx context.Context, u *Uses) (*Metadata, io.ReadCloser, err go func() { defer close(shaC) - if ref, _, err := client.Git.GetRef(ctx, u.GetOwner(), u.GetRepository(), "tags/"+u.Version); err == nil { + if ref, _, err := client.Git.GetRef(ctx, u.GetOwner(), u.GetRepository(), fmt.Sprintf("tags/%s", u.Version)); err == nil { shaC <- ref.GetObject().GetSHA() } else { - if ref, _, err := client.Git.GetRef(ctx, u.GetOwner(), u.GetRepository(), "heads/"+u.Version); err == nil { + if ref, _, err := client.Git.GetRef(ctx, u.GetOwner(), u.GetRepository(), fmt.Sprintf("heads/%s", u.Version)); err == nil { shaC <- ref.GetObject().GetSHA() } else { shaC <- u.Version @@ -59,7 +59,7 @@ func DownloadAction(ctx context.Context, u *Uses) (*Metadata, io.ReadCloser, err }() for _, filename := range ActionYAMLFilenames { - rc, _, err := client.Repositories.DownloadContents(ctx, u.GetOwner(), u.GetRepository(), u.GetActionPath()+"/"+filename, &github.RepositoryContentGetOptions{ + rc, _, err := client.Repositories.DownloadContents(ctx, u.GetOwner(), u.GetRepository(), fmt.Sprintf("%s/%s", u.GetActionPath(), filename), &github.RepositoryContentGetOptions{ Ref: u.Version, }) if err != nil { @@ -78,7 +78,7 @@ func DownloadAction(ctx context.Context, u *Uses) (*Metadata, io.ReadCloser, err } if metadata == nil { - return nil, nil, ErrNotAnAction + return nil, nil, fmt.Errorf("action.yaml/action.yml not found at %s", u) } link, _, err := client.Repositories.GetArchiveLink( @@ -108,7 +108,7 @@ func DownloadAction(ctx context.Context, u *Uses) (*Metadata, io.ReadCloser, err if matched, err := regexp.MatchString("[0-9a-f]{40}", sha); err != nil { return nil, nil, err } else if !matched { - return nil, nil, fmt.Errorf("get action sha") + return nil, nil, fmt.Errorf("get action sha returned something that does not look like a sha: %s", sha) } r, err := gzip.NewReader(res.Body) @@ -117,5 +117,5 @@ func DownloadAction(ctx context.Context, u *Uses) (*Metadata, io.ReadCloser, err } // sha is guaranteed to be a 40 character string by the above regexp. - return metadata, xtar.Subdir(tar.NewReader(r), u.GetOwner()+"-"+u.GetRepository()+"-"+sha[0:7]+"/"), nil + return metadata, xtar.Subdir(tar.NewReader(r), fmt.Sprintf("%s-%s-%s/", u.GetOwner(), u.GetRepository(), sha[0:7])), nil } diff --git a/githubactions/errors.go b/githubactions/errors.go deleted file mode 100644 index f6126de9..00000000 --- a/githubactions/errors.go +++ /dev/null @@ -1,16 +0,0 @@ -package githubactions - -import "errors" - -var ( - ErrNotAnAction = errors.New("action.yaml/action.yml not found") - ErrNotAWorkflowCommand = errors.New("not a workflow command") -) - -func IsErrNotAnAction(err error) bool { - return errors.Is(err, ErrNotAnAction) -} - -func IsErrNotAWorkflowCommand(err error) bool { - return errors.Is(err, ErrNotAWorkflowCommand) -} diff --git a/githubactions/metadata.go b/githubactions/metadata.go index 8d918c4b..abc6bb14 100644 --- a/githubactions/metadata.go +++ b/githubactions/metadata.go @@ -1,7 +1,6 @@ package githubactions import ( - "errors" "fmt" "io" "strings" @@ -18,8 +17,6 @@ const ( RunsUsingNode20 = "node20" ) -var ErrMissingRequiredInput = errors.New("required input missing") - func NewMetadataFromReader(r io.Reader) (*Metadata, error) { m := &Metadata{} return m, yaml.NewDecoder(r).Decode(m) @@ -40,7 +37,7 @@ func (m *Metadata) InputsFromWith(with map[string]string) (map[string]string, er case input.Default != "": inputs[name] = fmt.Sprint(input.Default) case input.Required: - return nil, ErrMissingRequiredInput + return nil, fmt.Errorf("required input %s is missing", name) } } return inputs, nil diff --git a/githubactions/uses.go b/githubactions/uses.go index 094849e1..ec3b2048 100644 --- a/githubactions/uses.go +++ b/githubactions/uses.go @@ -28,7 +28,7 @@ func (u *Uses) IsRemote() bool { func (u *Uses) String() string { uses := u.Path if v := u.Version; v != "" { - uses = uses + "@" + v + uses = fmt.Sprintf("%s@%s", uses, v) } return uses } @@ -64,7 +64,15 @@ func (u *Uses) GoString() string { return "&Uses{" + u.String() + "}" } -// TODO regexp. +// Parse parses a reference to a GitHub Action that would appear as the value +// of `uses` in a GitHub Actions Workflow Step, such as: +// +// steps: +// - uses: frantjc/forge@v0 +// - uses: ./ +// - uses: ./my/local/action +// +// Also supports the special case ".". func Parse(uses string) (*Uses, error) { r := &Uses{} diff --git a/githubactions/uses_test.go b/githubactions/uses_test.go new file mode 100644 index 00000000..ac8e1fa4 --- /dev/null +++ b/githubactions/uses_test.go @@ -0,0 +1,48 @@ +package githubactions_test + +import ( + "testing" + + "github.com/frantjc/forge/githubactions" +) + +func TestParse(t *testing.T) { + for _, s := range []struct { + uses string + expectedPath string + expectedVersion string + expectedLocal bool + }{ + { + uses: "./frantjc/forge", + expectedPath: "./frantjc/forge", + expectedVersion: "", + expectedLocal: true, + }, + { + uses: ".", + expectedPath: ".", + expectedVersion: "", + expectedLocal: true, + }, + { + uses: "frantjc/forge@v0", + expectedPath: "frantjc/forge", + expectedVersion: "v0", + }, + } { + if actual, err := githubactions.Parse(s.uses); err != nil { + t.Error(err) + t.FailNow() + } else if actual.Path != s.expectedPath { + t.Error("was", actual.Path, "but expected", s.expectedPath) + t.FailNow() + } else if actual.Version != s.expectedVersion { + t.Error("was", actual.Version, "but expected", s.expectedVersion) + t.FailNow() + } else if local := actual.IsLocal(); local != s.expectedLocal { + t.Error("was", local, "but expected", s.expectedLocal) + t.FailNow() + } + } +} diff --git a/githubactions/workflow_command.go b/githubactions/workflow_command.go index 28ee8911..41f4bd7a 100644 --- a/githubactions/workflow_command.go +++ b/githubactions/workflow_command.go @@ -34,6 +34,9 @@ func (c *WorkflowCommand) GoString() string { return "&WorkflowCommand{" + c.String() + "}" } +// GetName returns the value of the name parameter from the workflow command. +// Useful for set-env workflow commands as they require it to specify the name +// of the environment variable. func (c *WorkflowCommand) GetName() string { if c.Parameters != nil { if name, ok := c.Parameters["name"]; ok { diff --git a/githubactions/workflow_command_writer.go b/githubactions/workflow_command_writer.go index eac37737..f260ed14 100644 --- a/githubactions/workflow_command_writer.go +++ b/githubactions/workflow_command_writer.go @@ -63,7 +63,7 @@ func (w *WorkflowCommandWriter) handleCommand(wc *WorkflowCommand) []byte { case CommandStopCommands: w.StopCommandsTokens[wc.Value] = true case CommandSaveState: - w.GlobalContext.EnvContext["STATE_"+wc.GetName()] = wc.Value + w.GlobalContext.EnvContext[fmt.Sprintf("STATE_%s", wc.GetName())] = wc.Value if !w.saveStateDeprecationWarned { return []byte("[" + CommandWarning + "] The `" + wc.Command + "` command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/") diff --git a/githubactions/workflow_commands.go b/githubactions/workflow_commands.go index 3ac1cec2..62fc2129 100644 --- a/githubactions/workflow_commands.go +++ b/githubactions/workflow_commands.go @@ -1,6 +1,9 @@ package githubactions -import "strings" +import ( + "fmt" + "strings" +) const ( CommandDebug = "debug" @@ -17,21 +20,26 @@ const ( CommandStopCommands = "stop-commands" ) -// TODO regexp. +// ParseWorkflowCommandString parses a workflow command from a string such as: +// +// ::set-env name=HELLO::there +// +// This supports a deprecated GitHub Actions function that used such strings written +// to stdout to send commands from a GitHub Action up to GitHub Actions. func ParseWorkflowCommandString(workflowCommand string) (*WorkflowCommand, error) { if !strings.HasPrefix(workflowCommand, "::") { - return nil, ErrNotAWorkflowCommand + return nil, fmt.Errorf("not a workflow command: %s", workflowCommand) } a := strings.Split(workflowCommand, "::") if len(a) < 2 { - return nil, ErrNotAWorkflowCommand + return nil, fmt.Errorf("not a workflow command: %s", workflowCommand) } cmdAndParams := a[1] b := strings.Split(cmdAndParams, " ") if len(b) < 1 { - return nil, ErrNotAWorkflowCommand + return nil, fmt.Errorf("not a workflow command: %s", workflowCommand) } cmd := b[0] @@ -57,6 +65,8 @@ func ParseWorkflowCommandString(workflowCommand string) (*WorkflowCommand, error }, nil } +// ParseWorkflowCommand parses a workflow command from bytes. +// See ParseWorkflowCommandString for more details. func ParseWorkflowCommand(b []byte) (*WorkflowCommand, error) { return ParseWorkflowCommandString(string(b)) }