1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package app
4
5import (
6 "fmt"
7 "strings"
8
9 "connectrpc.com/connect"
10 "github.com/spf13/cobra"
11
12 "bnema.dev/ermite-api/gen/ermite"
13)
14
15// `ermite workflow run`: start a workflow that has a workflow_dispatch trigger,
16// like `gh workflow run`.
17
18func newWorkflowCmd(env *cliEnv) *cobra.Command {
19 wf := &cobra.Command{
20 Use: "workflow",
21 Short: "Start workflows by hand",
22 }
23 wf.AddCommand(newWorkflowRunCmd(env))
24 return wf
25}
26
27// parseInputFlags reads the -f key=value flags. The first = separates the name
28// from the value, so a value may hold =; a repeated name takes the last value.
29func parseInputFlags(flags []string) (map[string]string, error) {
30 if len(flags) == 0 {
31 return nil, nil
32 }
33 out := make(map[string]string, len(flags))
34 for _, f := range flags {
35 k, v, ok := strings.Cut(f, "=")
36 if !ok || k == "" {
37 return nil, fmt.Errorf("invalid input %q: use -f name=value", f)
38 }
39 out[k] = v
40 }
41 return out, nil
42}
43
44func newWorkflowRunCmd(env *cliEnv) *cobra.Command {
45 var repo, ref string
46 var fields []string
47 cmd := &cobra.Command{
48 Use: "run <workflow file> --repo <repo>",
49 Short: "Run a workflow that has a workflow_dispatch trigger",
50 Long: "Start a run of a workflow file on a branch or a tag (the default branch without --ref). The workflow is read at\n" +
51 "that ref and must have a workflow_dispatch trigger; -f name=value gives the inputs it declares (checked by type,\n" +
52 "required inputs and choices). The workflow file is its path or its file name. Needs the ci:write scope." + jsonRunHelp,
53 Example: " ermite workflow run release.yml --repo app -f version=1.2.3\n" +
54 " ermite workflow run .github/workflows/ci.yml --repo app --ref feature/x",
55 Args: cobra.ExactArgs(1),
56 ValidArgsFunction: cobra.NoFileCompletions,
57 }
58 addRepoFlag(cmd, env, &repo)
59 cmd.Flags().StringVar(&ref, "ref", "", "the branch or tag to run on (default: the default branch)")
60 cmd.Flags().StringArrayVarP(&fields, "field", "f", nil, "an input of the workflow as name=value (repeatable)")
61 asJSON := addJSONFlag(cmd)
62 cmd.RunE = func(cmd *cobra.Command, args []string) error {
63 inputs, err := parseInputFlags(fields)
64 if err != nil {
65 return err
66 }
67 c, err := env.client(cmd)
68 if err != nil {
69 return err
70 }
71 res, err := c.Runs().DispatchWorkflow(env.ctx(cmd), connect.NewRequest(&ermite.DispatchWorkflowRequest{
72 Repo: repo, WorkflowPath: args[0], Ref: ref, Inputs: inputs,
73 }))
74 if err != nil {
75 return err
76 }
77 run := res.Msg.GetRun()
78 if *asJSON {
79 return writeJSON(cmd.OutOrStdout(), toJSONRun(run))
80 }
81 noticef(cmd, "run %d of %s started on %s", run.GetId(), run.GetWorkflowName(), refText(run.GetRef()))
82 return nil
83 }
84 return cmd
85}