Skip to content

Commit f9fbbe3

Browse files
Merge pull request #220 from carolynvs/wrap-step-in-action
Wrap steps in the containing action
2 parents e0be913 + 6a2e795 commit f9fbbe3

File tree

11 files changed

+173
-36
lines changed

11 files changed

+173
-36
lines changed

cmd/exec/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func buildRootCommand(in io.Reader) *cobra.Command {
2828
m.Out = cmd.OutOrStdout()
2929
m.Err = cmd.OutOrStderr()
3030
},
31+
SilenceUsage: true,
3132
}
3233

3334
cmd.PersistentFlags().BoolVar(&m.Debug, "debug", false, "Enable debug logging")

pkg/exec/exec.go

+58-13
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import (
88
"io"
99
"io/ioutil"
1010

11-
"github.com/deislabs/porter/pkg/config"
11+
"github.com/pkg/errors"
12+
1213
"github.com/deislabs/porter/pkg/context"
1314
"github.com/gobuffalo/packr/v2"
1415
yaml "gopkg.in/yaml.v2"
@@ -18,22 +19,52 @@ import (
1819
type Mixin struct {
1920
*context.Context
2021

21-
Step Step
22+
Action Action
2223

2324
schemas *packr.Box
2425
}
2526

27+
type Action struct {
28+
Steps []Step // using UnmarshalYAML so that we don't need a custom type per action
29+
}
30+
31+
// UnmarshalYAML takes any yaml in this form
32+
// ACTION:
33+
// - exec: ...
34+
// and puts the steps into the Action.Steps field
35+
func (a *Action) UnmarshalYAML(unmarshal func(interface{}) error) error {
36+
actionMap := map[interface{}][]interface{}{}
37+
err := unmarshal(&actionMap)
38+
if err != nil {
39+
return errors.Wrap(err, "could not unmarshal yaml into an action map of exec steps")
40+
}
41+
42+
for _, stepMaps := range actionMap {
43+
b, err := yaml.Marshal(stepMaps)
44+
if err != nil {
45+
return err
46+
}
47+
48+
var steps []Step
49+
err = yaml.Unmarshal(b, &steps)
50+
if err != nil {
51+
return err
52+
}
53+
54+
a.Steps = append(a.Steps, steps...)
55+
}
56+
57+
return nil
58+
}
59+
2660
type Step struct {
27-
Description string `yaml:"description"`
28-
Outputs []config.StepOutput `yaml:"outputs"`
29-
Instruction Instruction `yaml:"exec"`
61+
Instruction `yaml:"exec"`
3062
}
3163

3264
type Instruction struct {
33-
Name string `yaml:"name"`
34-
Command string `yaml:"command"`
35-
Arguments []string `yaml:"arguments"`
36-
Parameters map[string]string `yaml:"parameters"`
65+
Description string `yaml:"description"`
66+
Command string `yaml:"command"`
67+
Arguments []string `yaml:"arguments"`
3768
}
3869

3970
// New exec mixin client, initialized with useful defaults.
@@ -48,16 +79,30 @@ func NewSchemaBox() *packr.Box {
4879
return packr.New("github.com/deislabs/porter/pkg/exec/schema", "./schema")
4980
}
5081

51-
func (m *Mixin) LoadInstruction(commandFile string) error {
82+
func (m *Mixin) loadAction(commandFile string) error {
5283
contents, err := m.getCommandFile(commandFile, m.Out)
5384
if err != nil {
54-
return fmt.Errorf("there was an error getting commands: %s", err)
85+
source := "STDIN"
86+
if commandFile == "" {
87+
source = commandFile
88+
}
89+
return errors.Wrapf(err, "could not load input from %s", source)
5590
}
56-
return yaml.Unmarshal(contents, &m.Step)
91+
92+
err = yaml.Unmarshal(contents, &m.Action)
93+
if m.Debug {
94+
fmt.Fprintf(m.Err, "DEBUG Parsed Input:\n%#v\n", m.Action)
95+
}
96+
return errors.Wrapf(err, "could unmarshal input:\n %s", string(contents))
5797
}
5898

5999
func (m *Mixin) Execute() error {
60-
cmd := m.NewCommand(m.Step.Instruction.Command, m.Step.Instruction.Arguments...)
100+
if len(m.Action.Steps) != 1 {
101+
return errors.Errorf("expected a single step, but got %d", len(m.Action.Steps))
102+
}
103+
step := m.Action.Steps[0]
104+
105+
cmd := m.NewCommand(step.Command, step.Arguments...)
61106
cmd.Stdout = m.Out
62107
cmd.Stderr = m.Err
63108

pkg/exec/exec_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package exec
2+
3+
import (
4+
"io/ioutil"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
yaml "gopkg.in/yaml.v2"
10+
)
11+
12+
func TestAction_UnmarshalYAML(t *testing.T) {
13+
b, err := ioutil.ReadFile("testdata/exec_input.yaml")
14+
require.NoError(t, err)
15+
16+
action := Action{}
17+
err = yaml.Unmarshal(b, &action)
18+
require.NoError(t, err)
19+
20+
assert.Len(t, action.Steps, 1)
21+
step := action.Steps[0]
22+
assert.Equal(t, "bash", step.Command)
23+
assert.Equal(t, "Install Hello World", step.Description)
24+
assert.Len(t, step.Arguments, 2)
25+
assert.Equal(t, "-c", step.Arguments[0])
26+
assert.Equal(t, "echo Hello World", step.Arguments[1])
27+
}

pkg/exec/install.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package exec
22

33
func (m *Mixin) Install(commandFile string) error {
4-
err := m.LoadInstruction(commandFile)
4+
err := m.loadAction(commandFile)
55
if err != nil {
66
return err
77
}

pkg/exec/install_test.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ func TestMixin_Install(t *testing.T) {
2222
Arguments: []string{"-c", "echo Hello World"},
2323
},
2424
}
25-
b, _ := yaml.Marshal(step)
25+
action := Action{
26+
Steps: []Step{step},
27+
}
28+
b, _ := yaml.Marshal(action)
2629

2730
h := NewTestMixin(t)
2831
h.In = bytes.NewReader(b)
@@ -36,8 +39,10 @@ func TestMixin_LoadInstructionFromFile(t *testing.T) {
3639
h := NewTestMixin(t)
3740
h.TestContext.AddTestDirectory("testdata", "testdata")
3841

39-
err := h.LoadInstruction("testdata/exec_input.yaml")
42+
err := h.loadAction("testdata/exec_input.yaml")
4043
require.NoError(t, err)
4144

42-
assert.Equal(t, "bash", h.Mixin.Step.Instruction.Command)
45+
assert.Len(t, h.Mixin.Action.Steps, 1)
46+
step := h.Mixin.Action.Steps[0]
47+
assert.Equal(t, "bash", step.Instruction.Command)
4348
}

pkg/exec/testdata/exec_input.yaml

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
exec:
2-
description: "Install Hello World"
3-
command: bash
4-
arguments:
5-
- -c
6-
- echo Hello World
1+
install:
2+
- exec:
3+
description: "Install Hello World"
4+
command: bash
5+
arguments:
6+
- -c
7+
- echo Hello World

pkg/mixin/runner.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type Runner struct {
1818
Mixin string
1919
Runtime bool
2020
Command string
21-
Step string
21+
Input string
2222
File string
2323
}
2424

@@ -53,7 +53,7 @@ func (r *Runner) Run() error {
5353
fmt.Fprintf(r.Err, "DEBUG mixin: %s\n", r.Mixin)
5454
fmt.Fprintf(r.Err, "DEBUG mixinDir: %s\n", r.mixinDir)
5555
fmt.Fprintf(r.Err, "DEBUG file: %s\n", r.File)
56-
fmt.Fprintf(r.Err, "DEBUG stdin:\n%s\n", r.Step)
56+
fmt.Fprintf(r.Err, "DEBUG stdin:\n%s\n", r.Input)
5757
}
5858

5959
mixinPath := r.getMixinPath()
@@ -71,14 +71,14 @@ func (r *Runner) Run() error {
7171
cmd.Args = append(cmd.Args, "--debug")
7272
}
7373

74-
if r.Step != "" {
74+
if r.Input != "" {
7575
stdin, err := cmd.StdinPipe()
7676
if err != nil {
7777
return err
7878
}
7979
go func() {
8080
defer stdin.Close()
81-
io.WriteString(stdin, r.Step)
81+
io.WriteString(stdin, r.Input)
8282
}()
8383
}
8484

pkg/mixin/testdata/exec_input.yaml

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
exec:
2-
description: "Say Hello"
3-
command: bash
4-
arguments:
5-
- -c
6-
- echo Hello World
1+
install:
2+
- exec:
3+
description: "Say Hello"
4+
command: bash
5+
arguments:
6+
- -c
7+
- echo Hello World

pkg/porter/build.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ func (p *Porter) buildMixinsSection() ([]string, error) {
121121

122122
r := mixin.NewRunner(m, mixinDir, false)
123123
r.Command = "build"
124-
r.Step = "" // TODO: let the mixin know about which steps will be executed so that it can be more selective about copying into the invocation image
124+
r.Input = "" // TODO: let the mixin know about which steps will be executed so that it can be more selective about copying into the invocation image
125125

126126
// Copy the existing context and tweak to pipe the output differently
127127
mixinStdout := &bytes.Buffer{}

pkg/porter/run.go

+36-2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,36 @@ func (p *Porter) Run(file string, action config.Action) error {
7070
return nil
7171
}
7272

73+
type ActionInput struct {
74+
action config.Action
75+
Steps []*config.Step `yaml:"steps"`
76+
}
77+
78+
// MarshalYAML marshals the step nested under the action
79+
// install:
80+
// - helm:
81+
// ...
82+
// Solution from https://stackoverflow.com/a/42547226
83+
func (a *ActionInput) MarshalYAML() (interface{}, error) {
84+
// encode the original
85+
b, err := yaml.Marshal(a.Steps)
86+
if err != nil {
87+
return nil, err
88+
}
89+
90+
// decode it back to get a map
91+
var tmp interface{}
92+
err = yaml.Unmarshal(b, &tmp)
93+
if err != nil {
94+
return nil, err
95+
}
96+
stepMap := tmp.([]interface{})
97+
actionMap := map[string]interface{}{
98+
string(a.action): stepMap,
99+
}
100+
return actionMap, nil
101+
}
102+
73103
func (p *Porter) loadRunner(s *config.Step, action config.Action, mixinsDir string) *mixin.Runner {
74104
name := s.GetMixinName()
75105
mixinDir := filepath.Join(mixinsDir, name)
@@ -78,8 +108,12 @@ func (p *Porter) loadRunner(s *config.Step, action config.Action, mixinsDir stri
78108
r.Command = string(action)
79109
r.Context = p.Context
80110

81-
stepBytes, _ := yaml.Marshal(s)
82-
r.Step = string(stepBytes)
111+
input := &ActionInput{
112+
action: action,
113+
Steps: []*config.Step{s},
114+
}
115+
inputBytes, _ := yaml.Marshal(input)
116+
r.Input = string(inputBytes)
83117

84118
return r
85119
}

pkg/porter/run_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"path/filepath"
55
"testing"
66

7+
"github.com/deislabs/porter/pkg/config"
8+
yaml "gopkg.in/yaml.v2"
9+
710
"github.com/deislabs/porter/pkg/mixin"
811
"github.com/stretchr/testify/assert"
912
"github.com/stretchr/testify/require"
@@ -24,3 +27,23 @@ func TestPorter_readOutputs(t *testing.T) {
2427
}
2528
assert.Equal(t, wantOutputs, gotOutputs)
2629
}
30+
31+
func TestActionInput_MarshalYAML(t *testing.T) {
32+
s := &config.Step{
33+
Data: map[string]interface{}{
34+
"exec": map[string]interface{}{
35+
"command": "echo hi",
36+
},
37+
},
38+
}
39+
40+
input := &ActionInput{
41+
action: config.ActionInstall,
42+
Steps: []*config.Step{s},
43+
}
44+
45+
b, err := yaml.Marshal(input)
46+
require.NoError(t, err)
47+
wantYaml := "install:\n- exec:\n command: echo hi\n"
48+
assert.Equal(t, wantYaml, string(b))
49+
}

0 commit comments

Comments
 (0)