Skip to content

Commit fc0188e

Browse files
author
Joel Longtine
committed
dagger do action options flags
Signed-off-by: Joel Longtine <[email protected]>
1 parent 552584a commit fc0188e

File tree

5 files changed

+260
-34
lines changed

5 files changed

+260
-34
lines changed

cmd/dagger/cmd/do.go

Lines changed: 169 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"cuelang.org/go/cue"
1313
"github.com/spf13/cobra"
14+
"github.com/spf13/pflag"
1415
"github.com/spf13/viper"
1516
"go.dagger.io/dagger/cmd/dagger/cmd/common"
1617
"go.dagger.io/dagger/cmd/dagger/logger"
@@ -21,27 +22,88 @@ import (
2122
)
2223

2324
var doCmd = &cobra.Command{
24-
Use: "do [OPTIONS] ACTION [SUBACTION...]",
25-
Short: "Execute a dagger action.",
25+
Use: "do ACTION [SUBACTION...]",
26+
// Short: "Execute a dagger action.",
2627
PreRun: func(cmd *cobra.Command, args []string) {
2728
// Fix Viper bug for duplicate flags:
2829
// https://github.com/spf13/viper/issues/233
2930
if err := viper.BindPFlags(cmd.Flags()); err != nil {
3031
panic(err)
3132
}
3233
},
34+
// Don't fail on unknown flags for the first parse
35+
FParseErrWhitelist: cobra.FParseErrWhitelist{
36+
UnknownFlags: true,
37+
},
38+
// We're going to take care of flag parsing ourselves
39+
DisableFlagParsing: true,
3340
Run: func(cmd *cobra.Command, args []string) {
34-
if len(args) < 1 {
35-
doHelpCmd(cmd, nil)
36-
return
37-
}
38-
3941
var (
4042
lg = logger.New()
4143
tty *logger.TTYOutput
4244
err error
4345
)
4446

47+
cmd.Flags().Parse(args)
48+
49+
targetPath := getTargetPath(cmd.Flags().Args())
50+
51+
daggerPlan, err := loadPlan(viper.GetString("plan"))
52+
if err != nil && !viper.GetBool("help") {
53+
err = fmt.Errorf("failed to load plan: %w", err)
54+
doHelpCmd(cmd, nil, nil, nil, targetPath, []string{err.Error()})
55+
os.Exit(1)
56+
} else if err != nil && viper.GetBool("help") {
57+
doHelpCmd(cmd, nil, nil, nil, targetPath, nil)
58+
os.Exit(0)
59+
}
60+
61+
action := daggerPlan.Action().FindByPath(targetPath)
62+
63+
if action == nil {
64+
selectorStrs := []string{}
65+
for _, selector := range targetPath.Selectors()[1:] {
66+
selectorStrs = append(selectorStrs, selector.String())
67+
}
68+
targetStr := strings.Join(selectorStrs, " ")
69+
70+
err = fmt.Errorf("failed to find action: %s", targetStr)
71+
l := len(targetPath.Selectors())
72+
for i := l - 1; i >= 0; i-- {
73+
tmpPath := cue.MakePath(targetPath.Selectors()[:i]...)
74+
tmpAction := daggerPlan.Action().FindByPath(tmpPath)
75+
if tmpAction != nil {
76+
action = tmpAction
77+
targetPath = tmpPath
78+
break
79+
}
80+
}
81+
doHelpCmd(cmd, daggerPlan, action, nil, targetPath, []string{err.Error()})
82+
os.Exit(1)
83+
}
84+
85+
actionFlags := getActionFlags(action)
86+
87+
cmd.Flags().AddFlagSet(actionFlags)
88+
89+
cmd.Flags().ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{
90+
UnknownFlags: false,
91+
}
92+
err = cmd.Flags().Parse(args)
93+
if err != nil {
94+
doHelpCmd(cmd, daggerPlan, action, actionFlags, targetPath, []string{err.Error()})
95+
os.Exit(1)
96+
}
97+
98+
if err := viper.BindPFlags(cmd.Flags()); err != nil {
99+
panic(err)
100+
}
101+
102+
if len(cmd.Flags().Args()) < 1 || viper.GetBool("help") {
103+
doHelpCmd(cmd, daggerPlan, action, actionFlags, targetPath, []string{})
104+
os.Exit(0)
105+
}
106+
45107
if f := viper.GetString("log-format"); f == "tty" || f == "auto" && term.IsTerminal(int(os.Stdout.Fd())) {
46108
tty, err = logger.NewTTYOutput(os.Stderr)
47109
if err != nil {
@@ -56,34 +118,36 @@ var doCmd = &cobra.Command{
56118
ctx := lg.WithContext(cmd.Context())
57119
cl := common.NewClient(ctx)
58120

59-
p, err := loadPlan()
60-
if err != nil {
61-
lg.Fatal().Err(err).Msg("failed to load plan")
62-
}
63-
target := getTargetPath(args)
121+
actionFlags.VisitAll(func(flag *pflag.Flag) {
122+
if cmd.Flags().Changed(flag.Name) {
123+
fmt.Printf("Changed: %s: %s\n", flag.Name, cmd.Flags().Lookup(flag.Name).Value.String())
124+
flagPath := []cue.Selector{}
125+
flagPath = append(flagPath, targetPath.Selectors()...)
126+
flagPath = append(flagPath, cue.Str(flag.Name))
127+
daggerPlan.Source().FillPath(cue.MakePath(flagPath...), viper.Get(flag.Name))
128+
}
129+
})
64130

65131
doneCh := common.TrackCommand(ctx, cmd, &telemetry.Property{
66132
Name: "action",
67-
Value: target.String(),
133+
Value: targetPath.String(),
68134
})
69135

70-
err = cl.Do(ctx, p.Context(), func(ctx context.Context, s solver.Solver) error {
71-
return p.Do(ctx, target, s)
136+
err = cl.Do(ctx, daggerPlan.Context(), func(ctx context.Context, s solver.Solver) error {
137+
return daggerPlan.Do(ctx, targetPath, s)
72138
})
73139

74140
<-doneCh
75141

76-
p.Context().TempDirs.Clean()
142+
daggerPlan.Context().TempDirs.Clean()
77143

78144
if err != nil {
79145
lg.Fatal().Err(err).Msg("failed to execute plan")
80146
}
81147
},
82148
}
83149

84-
func loadPlan() (*plan.Plan, error) {
85-
planPath := viper.GetString("plan")
86-
150+
func loadPlan(planPath string) (*plan.Plan, error) {
87151
// support only local filesystem paths
88152
// even though CUE supports loading module and package names
89153
absPlanPath, err := filepath.Abs(planPath)
@@ -110,27 +174,87 @@ func getTargetPath(args []string) cue.Path {
110174
return cue.MakePath(selectors...)
111175
}
112176

113-
func doHelpCmd(cmd *cobra.Command, _ []string) {
177+
func doHelpCmd(cmd *cobra.Command, daggerPlan *plan.Plan, action *plan.Action, actionFlags *pflag.FlagSet, target cue.Path, preamble []string) {
114178
lg := logger.New()
115179

116-
fmt.Println(cmd.Short)
180+
if len(preamble) > 0 {
181+
fmt.Println(strings.Join(preamble, "\n"))
182+
fmt.Println()
183+
}
117184

118-
err := printActions(os.Stdout, getTargetPath(cmd.Flags().Args()))
185+
target = cue.MakePath(target.Selectors()[1:]...)
186+
187+
// fmt.Println(cmd.Short)
188+
189+
if action != nil {
190+
selectorStrs := []string{}
191+
for _, selector := range target.Selectors() {
192+
selectorStrs = append(selectorStrs, selector.String())
193+
}
194+
targetStr := strings.Join(selectorStrs, " ")
195+
fmt.Printf("Usage: \n dagger do %s [flags]\n\n", targetStr)
196+
if actionFlags != nil {
197+
fmt.Println("Options")
198+
actionFlags.VisitAll(func(flag *pflag.Flag) {
199+
flag.Hidden = false
200+
})
201+
fmt.Println(actionFlags.FlagUsages())
202+
actionFlags.VisitAll(func(flag *pflag.Flag) {
203+
flag.Hidden = true
204+
})
205+
}
206+
} else {
207+
fmt.Println("Usage: \n dagger do [flags]")
208+
}
209+
210+
var err error
211+
if daggerPlan != nil {
212+
err = printActions(daggerPlan, action, os.Stdout, target)
213+
}
119214

120215
fmt.Printf("\n%s", cmd.UsageString())
121216

122-
if err != nil {
123-
lg.Fatal().Err(err).Msg("failed to load plan")
217+
if err == nil {
218+
lg.Fatal().Err(err)
124219
}
125220
}
126221

127-
func printActions(w io.Writer, target cue.Path) error {
128-
p, err := loadPlan()
129-
if err != nil {
130-
return err
222+
func getActionFlags(action *plan.Action) *pflag.FlagSet {
223+
flags := pflag.NewFlagSet("action inputs", pflag.ContinueOnError)
224+
flags.Usage = func() {}
225+
226+
if action == nil {
227+
panic("action is nil")
228+
}
229+
230+
if action.Inputs == nil {
231+
panic("action inputs is nil")
232+
}
233+
234+
for _, input := range action.Inputs {
235+
switch input.Type {
236+
case "string":
237+
flags.String(input.Name, "", input.Documentation)
238+
case "int":
239+
flags.Int(input.Name, 0, input.Documentation)
240+
case "bool":
241+
flags.Bool(input.Name, false, input.Documentation)
242+
case "float":
243+
flags.Float64(input.Name, 0, input.Documentation)
244+
case "number":
245+
flags.Float64(input.Name, 0, input.Documentation)
246+
default:
247+
}
248+
flags.MarkHidden(input.Name)
249+
}
250+
return flags
251+
}
252+
253+
func printActions(p *plan.Plan, action *plan.Action, w io.Writer, target cue.Path) error {
254+
if p == nil {
255+
return nil
131256
}
132257

133-
action := p.Action().FindByPath(target)
134258
if action == nil {
135259
return fmt.Errorf("action %s not found", target.String())
136260
}
@@ -162,9 +286,20 @@ func init() {
162286
doCmd.Flags().StringArray("cache-from", []string{},
163287
"External cache sources (eg. user/app:cache, type=local,src=path/to/dir)")
164288

165-
doCmd.SetHelpFunc(doHelpCmd)
166-
167-
if err := viper.BindPFlags(doCmd.Flags()); err != nil {
168-
panic(err)
169-
}
289+
doCmd.SetUsageTemplate(`{{if .HasAvailableSubCommands}}
290+
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
291+
Aliases:
292+
{{.NameAndAliases}}{{end}}{{if .HasExample}}
293+
Examples:
294+
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
295+
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
296+
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
297+
Flags:
298+
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
299+
Global Flags:
300+
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
301+
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
302+
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
303+
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
304+
`)
170305
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ require (
2626
github.com/rs/zerolog v1.26.1
2727
github.com/sergi/go-diff v1.2.0
2828
github.com/spf13/cobra v1.4.0
29+
github.com/spf13/pflag v1.0.5
2930
github.com/spf13/viper v1.10.0
3031
github.com/stretchr/testify v1.7.1
3132
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3

plan/action.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ type Action struct {
1010
Path cue.Path
1111
Documentation string
1212
Children []*Action
13+
Inputs []Input
14+
}
15+
16+
type Input struct {
17+
Name string
18+
Type string
19+
Documentation string
1320
}
1421

1522
func (a *Action) AddChild(c *Action) {

plan/plan.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ func (p *Plan) fillAction() {
201201
}
202202
p.action.Documentation = actions.DocSummary()
203203

204+
p.action.Inputs = getInputs(actions)
205+
204206
tasks := flow.Tasks()
205207

206208
for _, t := range tasks {
@@ -218,10 +220,33 @@ func (p *Plan) fillAction() {
218220
Path: path,
219221
Documentation: v.DocSummary(),
220222
Children: []*Action{},
223+
Inputs: getInputs(v),
221224
}
222225
prevAction.AddChild(a)
223226
}
224227
prevAction = a
225228
}
226229
}
227230
}
231+
232+
func getInputs(v *compiler.Value) []Input {
233+
cueVal := v.Cue()
234+
inputs := []Input{}
235+
for iter, _ := cueVal.Fields(cue.All()); iter.Next(); {
236+
val := iter.Value()
237+
cVal := compiler.Wrap(val)
238+
239+
_, refPath := val.ReferencePath()
240+
241+
ik := val.IncompleteKind()
242+
validKind := ik == cue.StringKind || ik == cue.NumberKind || ik == cue.BoolKind || ik == cue.IntKind || ik == cue.FloatKind
243+
if validKind && !val.IsConcrete() && len(refPath.Selectors()) == 0 {
244+
inputs = append(inputs, Input{
245+
Name: iter.Label(),
246+
Type: ik.String(),
247+
Documentation: cVal.DocSummary(),
248+
})
249+
}
250+
}
251+
return inputs
252+
}

0 commit comments

Comments
 (0)