Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type appImpl struct {
mFS []ManagedFS
}

const versionFlag = "--version"

// getPluginByType returns specific plugins from the app.
func getPluginByType[T Plugin](app *appImpl) []T {
plugins := app.pluginMngr.All()
Expand Down Expand Up @@ -143,7 +145,7 @@ func (app *appImpl) init() error {
// Skip discover actions if we check version.
args := os.Args[1:]
for i := 0; i < len(args); i++ {
if args[i] == "--version" {
if args[i] == versionFlag {
return nil
}
}
Expand Down Expand Up @@ -181,7 +183,7 @@ func (app *appImpl) exec() error {
var skipActions bool // skipActions to skip loading if not requested.
var reqCmd string // reqCmd to search for the requested cobra command.
for i := 0; i < len(args); i++ {
if args[i] == "--version" {
if args[i] == versionFlag {
rootCmd.SetVersionTemplate(Version().Full())
skipActions = true
}
Expand Down
2 changes: 2 additions & 0 deletions docs/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ Global Flags:

### Container environment flags

* `--entrypoint` Entrypoint: Overwrite the default ENTRYPOINT of the image
* `--exec` Exec: Overwrite CMD definition of the container
* `--no-cache` No cache: Send command to build container without cache
* `--remove-image` Remove Image: Remove an image after execution of action
* `--use-volume-wd` Use volume as a WD: Copy the working directory to a container volume and not bind local paths. Usually used with remote environments.
Expand Down
25 changes: 19 additions & 6 deletions pkg/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ type Action struct {

// Input is a container for action input arguments and options.
type Input struct {
Args TypeArgs
Opts TypeOpts
IO cli.Streams // @todo should it be in Input?
Args TypeArgs
Opts TypeOpts
IO cli.Streams // @todo should it be in Input?
ArgsRaw []string
}

type (
Expand Down Expand Up @@ -145,7 +146,7 @@ func (a *Action) SetInput(input Input) (err error) {
return err
}
// @todo disabled for now until fully tested.
//if err = a.ValidateInput(input); err != nil {
//if err = a.validateJSONSchema(input); err != nil {
// return err
//}

Expand All @@ -165,6 +166,7 @@ func (a *Action) SetInput(input Input) (err error) {
return a.EnsureLoaded()
}

// validateJSONSchema validates arguments and options according to
func (a *Action) processOptions(opts TypeOpts) error {
for _, optDef := range a.ActionDef().Options {
if _, ok := opts[optDef.Name]; !ok {
Expand Down Expand Up @@ -233,10 +235,10 @@ func (a *Action) processValue(value interface{}, valueType jsonschema.Type, toAp
return newValue, nil
}

// ValidateInput validates arguments and options according to
// validateJSONSchema validates arguments and options according to
// a specified json schema in action definition.
// @todo move to jsonschema
func (a *Action) ValidateInput(inp Input) error {
func (a *Action) validateJSONSchema(inp Input) error {
jsch := a.JSONSchema()
// @todo cache jsonschema and resources.
b, err := json.Marshal(jsch)
Expand Down Expand Up @@ -264,6 +266,17 @@ func (a *Action) ValidateInput(inp Input) error {
return nil
}

// ValidateInput validates input arguments in action definition.
func (a *Action) ValidateInput(args TypeArgs) error {
argsInitNum := len(a.ActionDef().Arguments)
argsInputNum := len(args)
if argsInitNum != argsInputNum {
return fmt.Errorf("expected (%d) arg(s), provided (%d) arg(s)", argsInitNum, argsInputNum)
}

return nil
}

// Execute runs action in the specified environment.
func (a *Action) Execute(ctx context.Context) error {
// @todo maybe it shouldn't be here.
Expand Down
2 changes: 1 addition & 1 deletion pkg/action/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func Test_Action(t *testing.T) {
"optarr": []interface{}{"opt5.1val", "opt5.2val"},
"opt6": "unexpectedOpt",
}
err = act.SetInput(Input{inputArgs, inputOpts, nil})
err = act.SetInput(Input{inputArgs, inputOpts, nil, nil})
assert.NoError(err)
assert.Equal(inputArgs, act.input.Args)
assert.Equal(inputOpts, act.input.Opts)
Expand Down
36 changes: 28 additions & 8 deletions pkg/action/cobra.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,37 @@ func CobraImpl(a *Action, streams cli.Streams) (*cobra.Command, error) {
options := make(TypeOpts)
runOpts := make(TypeOpts)
cmd := &cobra.Command{
Use: use,
Args: cobra.ExactArgs(len(argsDef)),
Use: use,
// Using custom args validation in ValidateInput.
// @todo: maybe we need a long template for arguments description
Short: getDesc(actConf.Title, actConf.Description),
RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true // Don't show usage help on a runtime error.
// Pass to the run environment its flags.
if env, ok := a.env.(RunEnvironmentFlags); ok {
runOpts = filterFlags(cmd, runOpts)
err := env.UseFlags(derefOpts(runOpts))
if err != nil {
return err
}
}

// Set action input.
err := a.SetInput(Input{
Args: argsToMap(args, argsDef),
Opts: derefOpts(options),
IO: streams,
})
if err != nil {
input := Input{
Args: argsToMap(args, argsDef),
Opts: derefOpts(options),
IO: streams,
ArgsRaw: args,
}
if runEnv, ok := a.env.(RunEnvironmentFlags); ok {
if err := runEnv.ValidateInput(a, input.Args); err != nil {
return err
}
}
if err := a.SetInput(input); err != nil {
return err
}

// @todo can we use action manager here and Manager.Run()
return a.Execute(cmd.Context())
},
Expand All @@ -66,6 +75,17 @@ func CobraImpl(a *Action, streams cli.Streams) (*cobra.Command, error) {
return cmd, nil
}

func filterFlags(cmd *cobra.Command, opts TypeOpts) TypeOpts {
filtered := make(TypeOpts)
for name, flag := range opts {
// Filter options not set.
if opts[name] != nil && cmd.Flags().Changed(name) {
filtered[name] = flag
}
}
return filtered
}

func setCobraOptions(cmd *cobra.Command, defs OptionsList, opts TypeOpts) error {
for _, opt := range defs {
v, err := setFlag(cmd, opt)
Expand Down
55 changes: 52 additions & 3 deletions pkg/action/env.container.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const (
containerFlagUseVolumeWD = "use-volume-wd"
containerFlagRemoveImage = "remove-image"
containerFlagNoCache = "no-cache"
containerFlagEntrypoint = "entrypoint"
containerFlagExec = "exec"
)

type containerEnv struct {
Expand All @@ -44,9 +46,12 @@ type containerEnv struct {
nameprv ContainerNameProvider

// Runtime flags
useVolWD bool
removeImg bool
noCache bool
useVolWD bool
removeImg bool
noCache bool
entrypoint string
entrypointSet bool
exec bool
}

// ContainerNameProvider provides an ability to generate a random container name
Expand Down Expand Up @@ -99,6 +104,20 @@ func (c *containerEnv) FlagsDefinition() OptionsList {
Type: jsonschema.Boolean,
Default: false,
},
&Option{
Name: containerFlagEntrypoint,
Title: "Image Entrypoint",
Description: "Overwrite the default ENTRYPOINT of the image",
Type: jsonschema.String,
Default: "",
},
&Option{
Name: containerFlagExec,
Title: "Exec command",
Description: "Overwrite CMD definition of the container",
Type: jsonschema.Boolean,
Default: false,
},
}
}

Expand All @@ -115,9 +134,25 @@ func (c *containerEnv) UseFlags(flags TypeOpts) error {
c.noCache = nc.(bool)
}

if e, ok := flags[containerFlagEntrypoint]; ok {
c.entrypointSet = true
c.entrypoint = e.(string)
}

if ex, ok := flags[containerFlagExec]; ok {
c.exec = ex.(bool)
}

return nil
}
func (c *containerEnv) ValidateInput(a *Action, args TypeArgs) error {
if c.exec {
return nil
}

// Check arguments if no exec flag present.
return a.ValidateInput(args)
}
func (c *containerEnv) AddImageBuildResolver(r ImageBuildResolver) { c.imgres = append(c.imgres, r) }
func (c *containerEnv) SetImageBuildCacheResolver(s *ImageBuildCacheResolver) { c.imgccres = s }
func (c *containerEnv) SetContainerNameProvider(p ContainerNameProvider) { c.nameprv = p }
Expand Down Expand Up @@ -151,6 +186,12 @@ func (c *containerEnv) Execute(ctx context.Context, a *Action) (err error) {
autoRemove = false
}

// Add entrypoint command option.
var entrypoint []string
if c.entrypointSet {
entrypoint = []string{c.entrypoint}
}

// Create container.
runConfig := &types.ContainerCreateOptions{
ContainerName: name,
Expand All @@ -164,6 +205,7 @@ func (c *containerEnv) Execute(ctx context.Context, a *Action) (err error) {
Tty: streams.In().IsTerminal(),
Env: actConf.Env,
User: getCurrentUser(),
Entrypoint: entrypoint,
}
log.Debug("Creating a container for action %q", a.ID)
cid, err := c.containerCreate(ctx, a, runConfig)
Expand Down Expand Up @@ -428,6 +470,12 @@ func (c *containerEnv) containerCreate(ctx context.Context, a *Action, opts *typ

// Create a container
actConf := a.ActionDef()

// Override Cmd with exec command.
if c.exec {
actConf.Command = a.GetInput().ArgsRaw
}

createOpts := types.ContainerCreateOptions{
ContainerName: opts.ContainerName,
Image: actConf.Image,
Expand All @@ -444,6 +492,7 @@ func (c *containerEnv) containerCreate(ctx context.Context, a *Action, opts *typ
Tty: opts.Tty,
Env: opts.Env,
User: opts.User,
Entrypoint: opts.Entrypoint,
}

if c.useVolWD {
Expand Down
2 changes: 1 addition & 1 deletion pkg/action/env.container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ func Test_ContainerExec(t *testing.T) {
resCh, errCh := make(chan types.ContainerWaitResponse, 1), make(chan error, 1)
assert, ctrl, d, r := prepareContainerTestSuite(t)
a := act.Clone()
err := a.SetInput(Input{nil, nil, cli.NoopStreams()})
err := a.SetInput(Input{nil, nil, cli.NoopStreams(), nil})
assert.NoError(err)
defer ctrl.Finish()
defer r.Close()
Expand Down
2 changes: 2 additions & 0 deletions pkg/action/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type RunEnvironmentFlags interface {
FlagsDefinition() OptionsList
// UseFlags sets environment configuration.
UseFlags(flags TypeOpts) error
// ValidateInput validates input arguments in action definition.
ValidateInput(a *Action, args TypeArgs) error
}

// ContainerRunEnvironment is an interface for container run environments.
Expand Down
6 changes: 3 additions & 3 deletions pkg/action/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func Test_InputProcessor(t *testing.T) {
act := testLoaderAction()
ctx := LoadContext{Action: act}
proc := inputProcessor{}
err := act.SetInput(Input{TypeArgs{"arg1": "arg1"}, TypeOpts{"optStr": "optVal1", "opt-str": "opt-val2"}, nil})
err := act.SetInput(Input{TypeArgs{"arg1": "arg1"}, TypeOpts{"optStr": "optVal1", "opt-str": "opt-val2"}, nil, nil})
assert.NoError(t, err)

s := "{{ .arg1 }},{{ .optStr }},{{ .opt_str }}"
Expand All @@ -72,7 +72,7 @@ func Test_YamlTplCommentsProcessor(t *testing.T) {
escapeYamlTplCommentsProcessor{},
inputProcessor{},
)
err := act.SetInput(Input{TypeArgs{"arg1": "arg1"}, TypeOpts{"optStr": "optVal1"}, nil})
err := act.SetInput(Input{TypeArgs{"arg1": "arg1"}, TypeOpts{"optStr": "optVal1"}, nil, nil})
assert.NoError(t, err)
// Check the commented strings are not considered.
s := `
Expand Down Expand Up @@ -100,7 +100,7 @@ func Test_PipeProcessor(t *testing.T) {
)

_ = os.Setenv("TEST_ENV1", "VAL1")
err := act.SetInput(Input{TypeArgs{"arg1": "arg1"}, TypeOpts{"optStr": "optVal1"}, nil})
err := act.SetInput(Input{TypeArgs{"arg1": "arg1"}, TypeOpts{"optStr": "optVal1"}, nil, nil})
assert.NoError(t, err)
s := "$TEST_ENV1,{{ .arg1 }},{{ .optStr }}"
res, err := proc.Process(ctx, []byte(s))
Expand Down
1 change: 1 addition & 0 deletions pkg/driver/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func (d *dockerDriver) ContainerCreate(ctx context.Context, opts types.Container
Env: opts.Env,
User: opts.User,
Volumes: opts.Volumes,
Entrypoint: opts.Entrypoint,
},
hostCfg,
nil, nil, opts.ContainerName,
Expand Down
1 change: 1 addition & 0 deletions pkg/types/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ type ContainerCreateOptions struct {
Tty bool
Env []string
User string
Entrypoint []string
}

// ContainerStartOptions stores options for starting a container.
Expand Down