Skip to content

Commit 2030ea2

Browse files
authored
Merge pull request #28 from semaphoreci/env
Substitute env vars in Docker Images and change_in expressions.
2 parents df23a75 + 6e7219e commit 2030ea2

File tree

8 files changed

+335
-7
lines changed

8 files changed

+335
-7
lines changed

.semaphore/semaphore.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ blocks:
5858
- test/e2e/change_in_excluded_paths.rb
5959
- test/e2e/change_in_glob.rb
6060
- test/e2e/change_in_invalid_when.rb
61+
- test/e2e/change_in_java_vs_javascript_clash.rb
62+
- test/e2e/change_in_large_commit_diff.rb
63+
- test/e2e/change_in_large_commit_diff_on_default_branch.rb
6164
- test/e2e/change_in_missing_branch.rb
65+
- test/e2e/change_in_multiple_functions_in_one_when.rb
6266
- test/e2e/change_in_multiple_paths.rb
6367
- test/e2e/change_in_on_forked_prs.rb
6468
- test/e2e/change_in_on_prs.rb
@@ -68,9 +72,8 @@ blocks:
6872
- test/e2e/change_in_relative_paths.rb
6973
- test/e2e/change_in_simple.rb
7074
- test/e2e/change_in_with_default_branch.rb
71-
- test/e2e/change_in_java_vs_javascript_clash.rb
72-
- test/e2e/change_in_large_commit_diff.rb
73-
- test/e2e/change_in_large_commit_diff_on_default_branch.rb
75+
- test/e2e/env_simple.rb
76+
- test/e2e/env_vars_in_docker_images.rb
7477
- test/e2e/when_conditions_without_change_in.rb
7578

7679
commands:

pkg/cli/evaluate.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ var evaluateChangeInCmd = &cobra.Command{
3434
ppl, err := pipelines.LoadFromFile(input)
3535
check(err)
3636

37+
err = ppl.SubstituteEnvVarsInDockerImages()
38+
check(err)
39+
3740
err = ppl.EvaluateChangeIns()
3841
check(err)
3942

pkg/pipelines/model.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package pipelines
22

33
import (
44
"encoding/json"
5+
"os"
6+
"strconv"
57
"time"
68

79
gabs "github.com/Jeffail/gabs/v2"
810
"github.com/ghodss/yaml"
11+
consolelogger "github.com/semaphoreci/spc/pkg/consolelogger"
912
)
1013

1114
type Pipeline struct {
@@ -17,16 +20,61 @@ func n() int64 {
1720
return time.Now().UnixNano() / int64(time.Millisecond)
1821
}
1922

20-
func (p *Pipeline) UpdateWhenExpression(path []string, value string) error {
23+
func (p *Pipeline) UpdateString(path []string, value string) error {
2124
_, err := p.raw.Set(value, path...)
2225

2326
return err
2427
}
2528

29+
func (p *Pipeline) GetStringValueFrom(path []string) (string, bool) {
30+
val, ok := p.raw.Search(path...).Data().(string)
31+
return val, ok
32+
}
33+
2634
func (p *Pipeline) EvaluateChangeIns() error {
2735
return newWhenEvaluator(p).Run()
2836
}
2937

38+
func (p *Pipeline) SubstituteEnvVarsInDockerImages() error {
39+
consolelogger.Info("Expanding environment variables in YAML file")
40+
consolelogger.EmptyLine()
41+
42+
containers := p.raw.Search("agent", "containers").Children()
43+
44+
for containerIndex := range containers {
45+
path := []string{"agent", "containers", strconv.Itoa(containerIndex), "image"}
46+
47+
p.expandEnvIfExists(path)
48+
}
49+
50+
for blockIndex := range p.Blocks() {
51+
path := []string{"blocks", strconv.Itoa(blockIndex), "agent", "containers"}
52+
53+
containers := p.raw.Search(path...).Children()
54+
55+
for containerIndex := range containers {
56+
path := append(path, []string{strconv.Itoa(containerIndex), "image"}...)
57+
58+
p.expandEnvIfExists(path)
59+
}
60+
}
61+
62+
return nil
63+
}
64+
65+
func (p *Pipeline) expandEnvIfExists(path []string) {
66+
if value, ok := p.GetStringValueFrom(path); ok {
67+
newValue := os.ExpandEnv(value)
68+
69+
consolelogger.Infof("Expanding env vars in %+v\n", path)
70+
consolelogger.Infof("Original: '%s'\n", value)
71+
consolelogger.Infof("Expanded: '%s'\n", newValue)
72+
consolelogger.EmptyLine()
73+
74+
p.UpdateString(path, newValue)
75+
}
76+
}
77+
3078
func (p *Pipeline) Blocks() []*gabs.Container {
3179
return p.raw.Search("blocks").Children()
3280
}

pkg/pipelines/when_evaluator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func (e *whenEvaluator) Run() error {
5555

5656
func (e *whenEvaluator) updatePipeline() error {
5757
for index := range e.results {
58-
err := e.pipeline.UpdateWhenExpression(e.list[index].Path, e.results[index])
58+
err := e.pipeline.UpdateString(e.list[index].Path, e.results[index])
5959

6060
if err != nil {
6161
return err

pkg/when/env/env.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package env
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
gabs "github.com/Jeffail/gabs/v2"
8+
consolelogger "github.com/semaphoreci/spc/pkg/consolelogger"
9+
)
10+
11+
type Function struct {
12+
VarName string
13+
}
14+
15+
func Parse(ast *gabs.Container) (*Function, error) {
16+
firstArg := ast.Search("params", "0")
17+
18+
if !firstArg.Exists() {
19+
return nil, fmt.Errorf("variable name not found in env function")
20+
}
21+
22+
varName, ok := firstArg.Data().(string)
23+
if !ok {
24+
return nil, fmt.Errorf("invalid variable name in env function")
25+
}
26+
27+
return &Function{VarName: varName}, nil
28+
}
29+
30+
func Eval(fun *Function) (string, error) {
31+
value := os.Getenv(fun.VarName)
32+
33+
consolelogger.Infof("Result: '%+v'\n", value)
34+
35+
return value, nil
36+
}

pkg/when/expression.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
gabs "github.com/Jeffail/gabs/v2"
55
consolelogger "github.com/semaphoreci/spc/pkg/consolelogger"
66
changein "github.com/semaphoreci/spc/pkg/when/changein"
7+
env "github.com/semaphoreci/spc/pkg/when/env"
78
whencli "github.com/semaphoreci/spc/pkg/when/whencli"
89
)
910

@@ -17,7 +18,22 @@ type WhenExpression struct {
1718

1819
func (w *WhenExpression) Eval() error {
1920
for _, requirment := range w.ListChangeInFunctions(w.Requirments) {
20-
result, err := w.EvalFunction(requirment)
21+
result, err := w.EvaluateChangeInFunction(requirment)
22+
if err != nil {
23+
return err
24+
}
25+
26+
input := map[string]interface{}{}
27+
input["name"] = w.functionName(requirment)
28+
input["params"] = w.functionParams(requirment)
29+
input["result"] = result
30+
31+
w.ReduceInputs.Keywords = map[string]interface{}{}
32+
w.ReduceInputs.Functions = append(w.ReduceInputs.Functions, input)
33+
}
34+
35+
for _, requirment := range w.ListEnvFunctions(w.Requirments) {
36+
result, err := w.EvaluateEnvFunction(requirment)
2137
if err != nil {
2238
return err
2339
}
@@ -46,6 +62,18 @@ func (w *WhenExpression) ListChangeInFunctions(requirments *gabs.Container) []*g
4662
return result
4763
}
4864

65+
func (w *WhenExpression) ListEnvFunctions(requirments *gabs.Container) []*gabs.Container {
66+
result := []*gabs.Container{}
67+
68+
for _, input := range requirments.Children() {
69+
if w.IsEnvFunction(input) {
70+
result = append(result, input)
71+
}
72+
}
73+
74+
return result
75+
}
76+
4977
func (w *WhenExpression) IsChangeInFunction(input *gabs.Container) bool {
5078
elType := input.Search("type").Data().(string)
5179
if elType != "fun" {
@@ -59,7 +87,20 @@ func (w *WhenExpression) IsChangeInFunction(input *gabs.Container) bool {
5987
return true
6088
}
6189

62-
func (w *WhenExpression) EvalFunction(input *gabs.Container) (bool, error) {
90+
func (w *WhenExpression) IsEnvFunction(input *gabs.Container) bool {
91+
elType := input.Search("type").Data().(string)
92+
if elType != "fun" {
93+
return false
94+
}
95+
96+
if w.functionName(input) != "env" {
97+
return false
98+
}
99+
100+
return true
101+
}
102+
103+
func (w *WhenExpression) EvaluateChangeInFunction(input *gabs.Container) (bool, error) {
63104
consolelogger.EmptyLine()
64105

65106
consolelogger.Infof("%s(%+v)\n", w.functionName(input), w.functionParams(input))
@@ -72,6 +113,19 @@ func (w *WhenExpression) EvalFunction(input *gabs.Container) (bool, error) {
72113
return changein.Eval(fun)
73114
}
74115

116+
func (w *WhenExpression) EvaluateEnvFunction(input *gabs.Container) (string, error) {
117+
consolelogger.EmptyLine()
118+
119+
consolelogger.Infof("%s(%+v)\n", w.functionName(input), w.functionParams(input))
120+
121+
fun, err := env.Parse(input)
122+
if err != nil {
123+
return "", err
124+
}
125+
126+
return env.Eval(fun)
127+
}
128+
75129
func (w *WhenExpression) functionName(input *gabs.Container) string {
76130
return input.Search("name").Data().(string)
77131
}

test/e2e/env_simple.rb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# rubocop:disable all
2+
3+
require_relative "../e2e"
4+
require 'yaml'
5+
6+
pipeline = %q(
7+
version: v1.0
8+
name: Test
9+
agent:
10+
machine:
11+
type: e1-standard-2
12+
13+
blocks:
14+
- name: Test1
15+
run:
16+
when: "branch = 'master' or env('SEMAPHORE_GIT_PR_NAME') =~ '^docs:'"
17+
task:
18+
jobs:
19+
- name: Hello
20+
commands:
21+
- echo "Hello World"
22+
23+
- name: Test2
24+
run:
25+
when: "branch = 'master' or env('SEMAPHORE_GIT_PR_NAME') =~ '^feature:'"
26+
task:
27+
jobs:
28+
- name: Hello
29+
commands:
30+
- echo "Hello World"
31+
32+
- name: Test3
33+
run:
34+
when: "branch = 'master' or env('NON_EXISTENT_ENV_VAR') =~ '^feature:'"
35+
task:
36+
jobs:
37+
- name: Hello
38+
commands:
39+
- echo "Hello World"
40+
)
41+
42+
origin = TestRepoForChangeIn.setup()
43+
44+
origin.add_file('.semaphore/semaphore.yml', pipeline)
45+
origin.commit!("Bootstrap")
46+
47+
repo = origin.clone_local_copy(branch: "master")
48+
repo.run(%{
49+
export SEMAPHORE_GIT_PR_NAME="docs: Change in deployment order"
50+
51+
#{spc} evaluate change-in --input .semaphore/semaphore.yml --output /tmp/output.yml --logs /tmp/logs.yml
52+
})
53+
54+
assert_eq(YAML.load_file('/tmp/output.yml'), YAML.load(%{
55+
version: v1.0
56+
name: Test
57+
agent:
58+
machine:
59+
type: e1-standard-2
60+
61+
blocks:
62+
- name: Test1
63+
run:
64+
when: "(branch = 'master') or true"
65+
task:
66+
jobs:
67+
- name: Hello
68+
commands:
69+
- echo "Hello World"
70+
71+
- name: Test2
72+
run:
73+
when: "(branch = 'master') or false"
74+
task:
75+
jobs:
76+
- name: Hello
77+
commands:
78+
- echo "Hello World"
79+
80+
- name: Test3
81+
run:
82+
when: "(branch = 'master') or false"
83+
task:
84+
jobs:
85+
- name: Hello
86+
commands:
87+
- echo "Hello World"
88+
}))

0 commit comments

Comments
 (0)