Skip to content

Commit 943a0e6

Browse files
authored
implement pre and post steps (#1089)
* feat: add post step to actions and add state command This commit includes requried changes for running post steps for local and remote actions. This allows general cleanup work to be done after executing an action. Communication is allowed between this steps, by using the action state. * feat: collect pre and post steps for composite actions * refactor: move composite action logic into own file * refactor: restructure composite handling * feat: run composite post steps during post step lifecycle * refactor: remove duplicate log output * feat: run all composite post actions in a step Since composite actions could have multiple pre/post steps inside, we need to run all of them in a single top-level pre/post step. This PR includes a test case for this and the correct order of steps to be executed. * refactor: remove unused lines of code * refactor: simplify test expression * fix: use composite job logger * fix: make step output more readable * fix: enforce running all post executor To make sure every post executor/step is executed, it is chained with it's own Finally executor. * fix: do not run post step if no step result is available Having no step result means we do not run any step (neither pre nor main) and we do not need to run post. * fix: setup defaults If no pre-if or post-if is given, it should default to 'always()'. This could be set even if there is no pre or post step. In fact this is required for composite actions and included post steps to run. * fix: output step related if expression * test: update expectation * feat: run pre step from actions (#1110) This PR implements running pre steps for remote actions. This includes remote actions using inside local composite actions. * fix: set correct expr default status checks For post-if conditions the default status check should be always(), while for all other if expression the default status check is success() References: https://docs.github.com/en/actions/learn-github-actions/expressions#status-check-functions https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runspost-if * fix: remove code added during rebase
1 parent ebb408f commit 943a0e6

37 files changed

+1551
-341
lines changed

pkg/exprparser/functions_test.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestFunctionContains(t *testing.T) {
3737

3838
for _, tt := range table {
3939
t.Run(tt.name, func(t *testing.T) {
40-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
40+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
4141
assert.Nil(t, err)
4242

4343
assert.Equal(t, tt.expected, output)
@@ -66,7 +66,7 @@ func TestFunctionStartsWith(t *testing.T) {
6666

6767
for _, tt := range table {
6868
t.Run(tt.name, func(t *testing.T) {
69-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
69+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
7070
assert.Nil(t, err)
7171

7272
assert.Equal(t, tt.expected, output)
@@ -95,7 +95,7 @@ func TestFunctionEndsWith(t *testing.T) {
9595

9696
for _, tt := range table {
9797
t.Run(tt.name, func(t *testing.T) {
98-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
98+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
9999
assert.Nil(t, err)
100100

101101
assert.Equal(t, tt.expected, output)
@@ -122,7 +122,7 @@ func TestFunctionJoin(t *testing.T) {
122122

123123
for _, tt := range table {
124124
t.Run(tt.name, func(t *testing.T) {
125-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
125+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
126126
assert.Nil(t, err)
127127

128128
assert.Equal(t, tt.expected, output)
@@ -148,7 +148,7 @@ func TestFunctionToJSON(t *testing.T) {
148148

149149
for _, tt := range table {
150150
t.Run(tt.name, func(t *testing.T) {
151-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
151+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
152152
assert.Nil(t, err)
153153

154154
assert.Equal(t, tt.expected, output)
@@ -171,7 +171,7 @@ func TestFunctionFromJSON(t *testing.T) {
171171

172172
for _, tt := range table {
173173
t.Run(tt.name, func(t *testing.T) {
174-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
174+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
175175
assert.Nil(t, err)
176176

177177
assert.Equal(t, tt.expected, output)
@@ -197,7 +197,7 @@ func TestFunctionHashFiles(t *testing.T) {
197197
t.Run(tt.name, func(t *testing.T) {
198198
workdir, err := filepath.Abs("testdata")
199199
assert.Nil(t, err)
200-
output, err := NewInterpeter(env, Config{WorkingDir: workdir}).Evaluate(tt.input, false)
200+
output, err := NewInterpeter(env, Config{WorkingDir: workdir}).Evaluate(tt.input, DefaultStatusCheckNone)
201201
assert.Nil(t, err)
202202

203203
assert.Equal(t, tt.expected, output)
@@ -234,7 +234,7 @@ func TestFunctionFormat(t *testing.T) {
234234

235235
for _, tt := range table {
236236
t.Run(tt.name, func(t *testing.T) {
237-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
237+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
238238
if tt.error != nil {
239239
assert.Equal(t, tt.error, err.Error())
240240
} else {

pkg/exprparser/interpreter.go

+30-6
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,32 @@ type Config struct {
3030
Context string
3131
}
3232

33+
type DefaultStatusCheck int
34+
35+
const (
36+
DefaultStatusCheckNone DefaultStatusCheck = iota
37+
DefaultStatusCheckSuccess
38+
DefaultStatusCheckAlways
39+
DefaultStatusCheckCanceled
40+
DefaultStatusCheckFailure
41+
)
42+
43+
func (dsc DefaultStatusCheck) String() string {
44+
switch dsc {
45+
case DefaultStatusCheckSuccess:
46+
return "success"
47+
case DefaultStatusCheckAlways:
48+
return "always"
49+
case DefaultStatusCheckCanceled:
50+
return "cancelled"
51+
case DefaultStatusCheckFailure:
52+
return "failure"
53+
}
54+
return ""
55+
}
56+
3357
type Interpreter interface {
34-
Evaluate(input string, isIfExpression bool) (interface{}, error)
58+
Evaluate(input string, defaultStatusCheck DefaultStatusCheck) (interface{}, error)
3559
}
3660

3761
type interperterImpl struct {
@@ -46,9 +70,9 @@ func NewInterpeter(env *EvaluationEnvironment, config Config) Interpreter {
4670
}
4771
}
4872

49-
func (impl *interperterImpl) Evaluate(input string, isIfExpression bool) (interface{}, error) {
73+
func (impl *interperterImpl) Evaluate(input string, defaultStatusCheck DefaultStatusCheck) (interface{}, error) {
5074
input = strings.TrimPrefix(input, "${{")
51-
if isIfExpression && input == "" {
75+
if defaultStatusCheck != DefaultStatusCheckNone && input == "" {
5276
input = "success()"
5377
}
5478
parser := actionlint.NewExprParser()
@@ -57,7 +81,7 @@ func (impl *interperterImpl) Evaluate(input string, isIfExpression bool) (interf
5781
return nil, fmt.Errorf("Failed to parse: %s", err.Message)
5882
}
5983

60-
if isIfExpression {
84+
if defaultStatusCheck != DefaultStatusCheckNone {
6185
hasStatusCheckFunction := false
6286
actionlint.VisitExprNode(exprNode, func(node, _ actionlint.ExprNode, entering bool) {
6387
if funcCallNode, ok := node.(*actionlint.FuncCallNode); entering && ok {
@@ -72,7 +96,7 @@ func (impl *interperterImpl) Evaluate(input string, isIfExpression bool) (interf
7296
exprNode = &actionlint.LogicalOpNode{
7397
Kind: actionlint.LogicalOpNodeKindAnd,
7498
Left: &actionlint.FuncCallNode{
75-
Callee: "success",
99+
Callee: defaultStatusCheck.String(),
76100
Args: []actionlint.ExprNode{},
77101
},
78102
Right: exprNode,
@@ -361,7 +385,7 @@ func (impl *interperterImpl) coerceToNumber(value reflect.Value) reflect.Value {
361385
}
362386

363387
// try to parse the string as a number
364-
evaluated, err := impl.Evaluate(value.String(), false)
388+
evaluated, err := impl.Evaluate(value.String(), DefaultStatusCheckNone)
365389
if err != nil {
366390
return reflect.ValueOf(math.NaN())
367391
}

pkg/exprparser/interpreter_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestLiterals(t *testing.T) {
2929

3030
for _, tt := range table {
3131
t.Run(tt.name, func(t *testing.T) {
32-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
32+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
3333
assert.Nil(t, err)
3434

3535
assert.Equal(t, tt.expected, output)
@@ -93,7 +93,7 @@ func TestOperators(t *testing.T) {
9393

9494
for _, tt := range table {
9595
t.Run(tt.name, func(t *testing.T) {
96-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
96+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
9797
if tt.error != "" {
9898
assert.NotNil(t, err)
9999
assert.Equal(t, tt.error, err.Error())
@@ -146,7 +146,7 @@ func TestOperatorsCompare(t *testing.T) {
146146

147147
for _, tt := range table {
148148
t.Run(tt.name, func(t *testing.T) {
149-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
149+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
150150
assert.Nil(t, err)
151151

152152
assert.Equal(t, tt.expected, output)
@@ -509,7 +509,7 @@ func TestOperatorsBooleanEvaluation(t *testing.T) {
509509

510510
for _, tt := range table {
511511
t.Run(tt.name, func(t *testing.T) {
512-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
512+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
513513
assert.Nil(t, err)
514514

515515
if expected, ok := tt.expected.(float64); ok && math.IsNaN(expected) {
@@ -607,7 +607,7 @@ func TestContexts(t *testing.T) {
607607

608608
for _, tt := range table {
609609
t.Run(tt.name, func(t *testing.T) {
610-
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, false)
610+
output, err := NewInterpeter(env, Config{}).Evaluate(tt.input, DefaultStatusCheckNone)
611611
assert.Nil(t, err)
612612

613613
assert.Equal(t, tt.expected, output)

pkg/model/action.go

+12
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ type ActionRuns struct {
4949
Using ActionRunsUsing `yaml:"using"`
5050
Env map[string]string `yaml:"env"`
5151
Main string `yaml:"main"`
52+
Pre string `yaml:"pre"`
53+
PreIf string `yaml:"pre-if"`
54+
Post string `yaml:"post"`
55+
PostIf string `yaml:"post-if"`
5256
Image string `yaml:"image"`
5357
Entrypoint string `yaml:"entrypoint"`
5458
Args []string `yaml:"args"`
@@ -90,5 +94,13 @@ func ReadAction(in io.Reader) (*Action, error) {
9094
return nil, err
9195
}
9296

97+
// set defaults
98+
if a.Runs.PreIf == "" {
99+
a.Runs.PreIf = "always()"
100+
}
101+
if a.Runs.PostIf == "" {
102+
a.Runs.PostIf = "always()"
103+
}
104+
93105
return a, nil
94106
}

pkg/model/step_result.go

+1
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ type StepResult struct {
4242
Outputs map[string]string `json:"outputs"`
4343
Conclusion stepStatus `json:"conclusion"`
4444
Outcome stepStatus `json:"outcome"`
45+
State map[string]string
4546
}

0 commit comments

Comments
 (0)