Skip to content

Commit 09899c9

Browse files
authored
Merge pull request #19 from eunomie/warn-flags
Warn user based on flags
2 parents 02117a9 + 873edfc commit 09899c9

File tree

10 files changed

+118
-11
lines changed

10 files changed

+118
-11
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ This is useful to configure some actions for a specific project for instance.
172172

173173
```yaml
174174
# Optional.
175+
# If set to true, the user will not be prompted to check some security risks.
176+
# If not set, the user will be prompted for confirmation based on flags like volume, mounts, privileged, etc.
177+
accept-the-risk: true|false
178+
# Optional.
175179
# It allows to define a default reference to an image if none is provided by the user.
176180
# with the ref set to IMAGE a `docker runx` is equivalent to `docker runx IMAGE`
177181
ref: IMAGE

docs/index.markdown

+4
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ This is useful to configure some actions for a specific project for instance.
165165

166166
```yaml
167167
# Optional.
168+
# If set to true, the user will not be prompted to check some security risks.
169+
# If not set, the user will be prompted for confirmation based on flags like volume, mounts, privileged, etc.
170+
accept-the-risk: true|false
171+
# Optional.
168172
# It allows to define a default reference to an image if none is provided by the user.
169173
# with the ref set to IMAGE a `docker runx` is equivalent to `docker runx IMAGE`
170174
ref: IMAGE

docs/reference/docker_runx.yaml

+11
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ options:
5757
experimentalcli: false
5858
kubernetes: false
5959
swarm: false
60+
- option: "yes"
61+
shorthand: "y"
62+
value_type: bool
63+
default_value: "false"
64+
description: Do not check flags before running the command
65+
deprecated: false
66+
hidden: false
67+
experimental: false
68+
experimentalcli: false
69+
kubernetes: false
70+
swarm: false
6071
deprecated: false
6172
hidden: false
6273
experimental: false

docs/reference/runx.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Docker Run, better
2121
| `-d`, `--docs` | `bool` | | Print the documentation of the image |
2222
| `-l`, `--list` | `bool` | | List available actions |
2323
| `--opt` | `stringArray` | | Set an option value |
24+
| `-y`, `--yes` | `bool` | | Do not check flags before running the command |
2425

2526

2627
<!---MARKER_GEN_END-->

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/dustin/go-humanize v1.0.1
1313
github.com/gertd/go-pluralize v0.2.1
1414
github.com/google/go-containerregistry v0.20.2
15+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
1516
github.com/mattn/go-isatty v0.0.20
1617
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a
1718
github.com/spf13/cobra v1.8.1

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
172172
github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo=
173173
github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8=
174174
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
175+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
176+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
175177
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
176178
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
177179
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=

internal/commands/root/root.go

+40-9
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package root
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"io"
78
"os"
89
"strings"
910

11+
"github.com/charmbracelet/huh"
1012
"github.com/charmbracelet/huh/spinner"
1113
"github.com/gertd/go-pluralize"
1214
"github.com/spf13/cobra"
@@ -19,6 +21,7 @@ import (
1921
"github.com/eunomie/docker-runx/internal/commands/help"
2022
"github.com/eunomie/docker-runx/internal/commands/version"
2123
"github.com/eunomie/docker-runx/internal/constants"
24+
"github.com/eunomie/docker-runx/internal/pizza"
2225
"github.com/eunomie/docker-runx/internal/prompt"
2326
"github.com/eunomie/docker-runx/internal/registry"
2427
"github.com/eunomie/docker-runx/internal/sugar"
@@ -27,10 +30,11 @@ import (
2730
)
2831

2932
var (
30-
docs bool
31-
list bool
32-
ask bool
33-
opts []string
33+
docs bool
34+
list bool
35+
ask bool
36+
opts []string
37+
noFlagCheck bool
3438
)
3539

3640
func NewCmd(dockerCli command.Cli, isPlugin bool) *cobra.Command {
@@ -118,7 +122,7 @@ func NewCmd(dockerCli command.Cli, isPlugin bool) *cobra.Command {
118122
if tui.IsATTY(dockerCli.In().FD()) && len(rk.Config.Actions) > 0 {
119123
selectedAction := prompt.SelectAction(rk.Config.Actions)
120124
if selectedAction != "" {
121-
return run(cmd.Context(), dockerCli.Err(), src, rk, selectedAction)
125+
return run(cmd.Context(), dockerCli.Err(), src, rk, selectedAction, lc)
122126
}
123127
} else {
124128
_, _ = fmt.Fprintln(dockerCli.Out(), tui.Markdown(mdActions(rk)))
@@ -127,7 +131,7 @@ func NewCmd(dockerCli command.Cli, isPlugin bool) *cobra.Command {
127131
}
128132

129133
if action != "" {
130-
return run(cmd.Context(), dockerCli.Err(), src, rk, action)
134+
return run(cmd.Context(), dockerCli.Err(), src, rk, action, lc)
131135
}
132136

133137
return cmd.Help()
@@ -168,6 +172,7 @@ func NewCmd(dockerCli command.Cli, isPlugin bool) *cobra.Command {
168172
f.BoolVarP(&list, "list", "l", false, "List available actions")
169173
f.BoolVar(&ask, "ask", false, "Do not read local configuration option values and always ask them")
170174
f.StringArrayVar(&opts, "opt", nil, "Set an option value")
175+
f.BoolVarP(&noFlagCheck, "yes", "y", false, "Do not check flags before running the command")
171176

172177
return cmd
173178
}
@@ -194,7 +199,7 @@ func getValuesLocal(src, action string) map[string]string {
194199
return localOpts
195200
}
196201

197-
func run(ctx context.Context, out io.Writer, src string, rk *runkit.RunKit, action string) error {
202+
func run(ctx context.Context, out io.Writer, src string, rk *runkit.RunKit, action string, lc *runkit.LocalConfig) error {
198203
runnable, cleanup, err := rk.GetRunnable(action)
199204
defer cleanup()
200205
if err != nil {
@@ -224,13 +229,39 @@ func run(ctx context.Context, out io.Writer, src string, rk *runkit.RunKit, acti
224229
return err
225230
}
226231

227-
_, _ = fmt.Fprintln(out, tui.Markdown(fmt.Sprintf(`
232+
mdCommand := fmt.Sprintf(`
228233
> **Running the following command:**
229234
230235
%s
231236
232237
---
233-
`, runnable.Command)))
238+
`, runnable.Command)
239+
240+
var flags []string
241+
if !noFlagCheck && !lc.AcceptTheRisk {
242+
flags, err = runnable.CheckFlags()
243+
}
244+
if err != nil {
245+
return err
246+
} else if len(flags) > 0 {
247+
_, _ = fmt.Fprintln(out, tui.Markdown(mdCommand+fmt.Sprintf(`
248+
> **Some flags require your attention:**
249+
250+
%s
251+
`, strings.Join(pizza.Map(flags, func(flag string) string {
252+
return fmt.Sprintf("- `%s`", flag)
253+
}), "\n"))))
254+
var cont bool
255+
err = huh.NewConfirm().Title("Continue?").Value(&cont).Run()
256+
if err != nil {
257+
return err
258+
}
259+
if !cont {
260+
return errors.New("aborted")
261+
}
262+
} else {
263+
_, _ = fmt.Fprintln(out, tui.Markdown(mdCommand))
264+
}
234265

235266
return runnable.Run(ctx)
236267
}

runkit/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ func getLocalConfig() (LocalConfig, error) {
6262
}
6363

6464
func merge(a, b LocalConfig) LocalConfig {
65+
a.AcceptTheRisk = cmp.Or(b.AcceptTheRisk, a.AcceptTheRisk)
6566
a.Ref = cmp.Or(b.Ref, a.Ref)
6667
if a.Images == nil {
6768
a.Images = b.Images

runkit/run.go

+51
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"strings"
99
"text/template"
1010

11+
"github.com/google/shlex"
12+
"github.com/spf13/pflag"
1113
"mvdan.cc/sh/v3/expand"
1214
"mvdan.cc/sh/v3/interp"
1315
"mvdan.cc/sh/v3/syntax"
@@ -149,6 +151,19 @@ func (r *Runnable) compute() error {
149151
return nil
150152
}
151153

154+
func flagSet() *pflag.FlagSet {
155+
f := pflag.NewFlagSet("", pflag.ContinueOnError)
156+
f.ParseErrorsWhitelist.UnknownFlags = true
157+
f.StringArrayP("volume", "v", nil, "")
158+
f.StringArray("mount", nil, "")
159+
f.StringArrayP("publish", "p", nil, "")
160+
f.StringP("publish-all", "P", "", "")
161+
f.String("pid", "", "")
162+
f.Bool("privileged", false, "")
163+
f.String("network", "", "")
164+
return f
165+
}
166+
152167
func (r *Runnable) SetOptionValues(opts map[string]string) error {
153168
for _, opt := range r.Action.Options {
154169
if opt.Required && opts[opt.Name] == "" {
@@ -163,9 +178,45 @@ func (r *Runnable) SetOptionValues(opts map[string]string) error {
163178
}
164179

165180
r.Command = fmt.Sprintf("%s %s", r.command, r.args)
181+
166182
return nil
167183
}
168184

185+
func (r *Runnable) CheckFlags() ([]string, error) {
186+
if r.Action.Type != ActionTypeRun {
187+
return nil, nil
188+
}
189+
tokens, err := shlex.Split(r.args)
190+
if err != nil {
191+
return nil, err
192+
}
193+
194+
f := flagSet()
195+
if err = f.Parse(tokens); err != nil {
196+
return nil, err
197+
}
198+
if f.NArg() > 0 {
199+
args, _, _ := strings.Cut(r.args, f.Arg(0))
200+
tokens, err = shlex.Split(args)
201+
if err != nil {
202+
return nil, err
203+
}
204+
f = flagSet()
205+
if err = f.Parse(tokens); err != nil {
206+
return nil, err
207+
}
208+
}
209+
210+
var flagsSet []string
211+
f.Visit(func(flag *pflag.Flag) {
212+
if flag.Changed {
213+
flagsSet = append(flagsSet, flag.Name)
214+
}
215+
})
216+
217+
return flagsSet, nil
218+
}
219+
169220
func (r *Runnable) Run(ctx context.Context) error {
170221
if r.Command == "" {
171222
return fmt.Errorf("command not set")

runkit/types.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ type (
4343
OptType string
4444

4545
LocalConfig struct {
46-
Ref string `yaml:"ref,omitempty" json:"ref,omitempty"`
47-
Images map[string]ConfigImage `yaml:"images,omitempty" json:"images,omitempty"`
46+
AcceptTheRisk bool `yaml:"accept-the-risk,omitempty" json:"accept-the-risk,omitempty"`
47+
Ref string `yaml:"ref,omitempty" json:"ref,omitempty"`
48+
Images map[string]ConfigImage `yaml:"images,omitempty" json:"images,omitempty"`
4849
}
4950

5051
ConfigImage struct {

0 commit comments

Comments
 (0)