Skip to content

Commit 34097e0

Browse files
authored
Merge pull request #774 from ZhuGongpu/feature-226
Support generating Cloud Build pipelines
2 parents 94d97d1 + d8a61a2 commit 34097e0

File tree

11 files changed

+439
-18
lines changed

11 files changed

+439
-18
lines changed

internal/cmdexport/cmdexport.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package cmdexport
1818
import (
1919
"fmt"
2020
"os"
21+
"path"
22+
"strings"
2123

2224
"github.com/GoogleContainerTools/kpt/internal/cmdexport/orchestrators"
2325
"github.com/GoogleContainerTools/kpt/internal/cmdexport/types"
@@ -30,7 +32,7 @@ func ExportCommand() *cobra.Command {
3032
return GetExportRunner().Command
3133
}
3234

33-
// Create a ExportRunner instance and wire it to the corresponding Command.
35+
// GetExportRunner creates a ExportRunner instance and wires it to the corresponding Command.
3436
func GetExportRunner() *ExportRunner {
3537
r := &ExportRunner{PipelineConfig: &types.PipelineConfig{}}
3638
c := &cobra.Command{
@@ -51,6 +53,10 @@ func GetExportRunner() *ExportRunner {
5153
{
5254
r.Pipeline = new(orchestrators.GitHubActions)
5355
}
56+
case "cloud-build":
57+
{
58+
r.Pipeline = new(orchestrators.CloudBuild)
59+
}
5460
default:
5561
return fmt.Errorf("unsupported orchestrator %v", r.Orchestrator)
5662
}
@@ -81,9 +87,17 @@ type ExportRunner struct {
8187
Pipeline orchestrators.Pipeline
8288
}
8389

84-
// Generate the pipeline and write it into a file or stdout.
90+
// runE generates the pipeline and writes it into a file or stdout.
8591
func (r *ExportRunner) runE(c *cobra.Command, args []string) error {
86-
pipeline := r.Pipeline.Init(r.PipelineConfig).Generate()
92+
if err := r.checkFnPaths(); err != nil {
93+
return err
94+
}
95+
96+
pipeline, e := r.Pipeline.Init(r.PipelineConfig).Generate()
97+
98+
if e != nil {
99+
return e
100+
}
87101

88102
if r.OutputFilePath != "" {
89103
fo, err := os.Create(r.OutputFilePath)
@@ -99,3 +113,35 @@ func (r *ExportRunner) runE(c *cobra.Command, args []string) error {
99113

100114
return err
101115
}
116+
117+
// checkPaths checks if fnPaths exist within the current directory.
118+
func (r *ExportRunner) checkFnPaths() error {
119+
cwd, err := os.Getwd()
120+
if err != nil {
121+
return err
122+
}
123+
cwd = fmt.Sprintf("%s%s", cwd, string(os.PathSeparator))
124+
125+
for _, fnPath := range r.FnPaths {
126+
p := fnPath
127+
if !path.IsAbs(p) {
128+
p = path.Join(cwd, p)
129+
}
130+
131+
if !strings.HasPrefix(p, cwd) {
132+
return fmt.Errorf(
133+
"function path (%s) is not within the current working directory",
134+
fnPath,
135+
)
136+
}
137+
138+
if _, err := os.Stat(p); os.IsNotExist(err) {
139+
return fmt.Errorf(
140+
`function path (%s) does not exist`,
141+
fnPath,
142+
)
143+
}
144+
}
145+
146+
return nil
147+
}

internal/cmdexport/cmdexport_test.go

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,23 @@ import (
2525
"gotest.tools/assert"
2626
)
2727

28-
var tempDir, _ = ioutil.TempDir("", "kpt-fn-export-test")
28+
// Use file path as key, and content as value.
29+
type files map[string]string
2930

3031
type TestCase struct {
3132
description string
3233
params []string
3334
expected string
3435
err string
36+
files files
3537
}
3638

3739
var testCases = []TestCase{
40+
{
41+
description: "fails on not providing enough args",
42+
params: []string{"github-actions"},
43+
err: "accepts 2 args, received 1",
44+
},
3845
{
3946
description: "fails on an unsupported orchestrator",
4047
params: []string{"random-orchestrator", "."},
@@ -65,7 +72,7 @@ jobs:
6572
"github-actions",
6673
".",
6774
"--output",
68-
filepath.Join(tempDir, "main.yaml"),
75+
"main.yaml",
6976
},
7077
expected: `
7178
name: kpt
@@ -85,7 +92,10 @@ jobs:
8592
},
8693
{
8794
description: "exports a GitHub Actions pipeline with --fn-path",
88-
params: []string{"github-actions", ".", "--fn-path", "functions/"},
95+
files: map[string]string{
96+
"function.yaml": "",
97+
},
98+
params: []string{"github-actions", ".", "--fn-path", "function.yaml"},
8999
expected: `
90100
name: kpt
91101
on:
@@ -99,14 +109,96 @@ jobs:
99109
- name: Run all kpt functions
100110
uses: docker://gongpu/kpt:latest
101111
with:
102-
args: fn run . --fn-path functions/
112+
args: fn run . --fn-path function.yaml
103113
`,
104114
},
115+
{
116+
description: "exports a Cloud Build pipeline with --fn-path",
117+
files: map[string]string{
118+
"functions/function.yaml": "",
119+
},
120+
params: []string{
121+
"cloud-build",
122+
".",
123+
"--fn-path",
124+
"functions/",
125+
"--output",
126+
"cloudbuild.yaml",
127+
},
128+
expected: `
129+
steps:
130+
- name: gongpu/kpt:latest
131+
args:
132+
- fn
133+
- run
134+
- .
135+
- --fn-path
136+
- functions/
137+
`,
138+
},
139+
{
140+
description: "fails to export a Cloud Build pipeline with non-exist function path",
141+
params: []string{
142+
"cloud-build",
143+
".",
144+
"--fn-path",
145+
"functions.yaml",
146+
"--output",
147+
"cloudbuild.yaml",
148+
},
149+
err: "function path (functions.yaml) does not exist",
150+
},
151+
{
152+
description: "fails to export a Cloud Build pipeline with outside function path",
153+
params: []string{
154+
"cloud-build",
155+
".",
156+
"--fn-path",
157+
"../functions/functions.yaml",
158+
"--output",
159+
"cloudbuild.yaml",
160+
},
161+
err: "function path (../functions/functions.yaml) is not within the current working directory",
162+
},
163+
}
164+
165+
func setupTempDir(files files) (dir string, err error) {
166+
tempDir, err := ioutil.TempDir("", "kpt-fn-export-test")
167+
if err != nil {
168+
return "", err
169+
}
170+
171+
for p, content := range files {
172+
p = filepath.Join(tempDir, p)
173+
174+
err = os.MkdirAll(
175+
filepath.Dir(p),
176+
0755, // drwxr-xr-x
177+
)
178+
if err != nil {
179+
return "", nil
180+
}
181+
182+
err = ioutil.WriteFile(
183+
p,
184+
[]byte(content),
185+
0644, // -rw-r--r--
186+
)
187+
if err != nil {
188+
return "", err
189+
}
190+
}
191+
192+
return tempDir, nil
105193
}
106194

107195
func TestCmdExport(t *testing.T) {
108196
for i := range testCases {
109197
testCase := testCases[i]
198+
tempDir, err := setupTempDir(testCase.files)
199+
assert.NilError(t, err)
200+
err = os.Chdir(tempDir)
201+
assert.NilError(t, err)
110202

111203
t.Run(testCase.description, func(t *testing.T) {
112204
r := GetExportRunner()
@@ -136,7 +228,7 @@ func TestCmdExport(t *testing.T) {
136228
assert.Equal(t, expected, actual)
137229
}
138230
})
139-
}
140231

141-
_ = os.RemoveAll(tempDir)
232+
_ = os.RemoveAll(tempDir)
233+
}
142234
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package orchestrators
16+
17+
import (
18+
"github.com/GoogleContainerTools/kpt/internal/cmdexport/types"
19+
"sigs.k8s.io/kustomize/kyaml/yaml"
20+
)
21+
22+
// CloudBuild is a simplified representation of Cloud Build config.
23+
// @see https://cloud.google.com/cloud-build/docs/build-config
24+
type CloudBuild struct {
25+
Steps []CloudBuildStep `yaml:",omitempty"`
26+
}
27+
28+
type CloudBuildStep struct {
29+
Name string `yaml:",omitempty"`
30+
Args []string `yaml:",omitempty"`
31+
}
32+
33+
func (p *CloudBuild) Init(config *types.PipelineConfig) Pipeline {
34+
step := CloudBuildStep{}
35+
step.Name = KptImage
36+
step.Args = []string{
37+
"fn",
38+
"run",
39+
config.Dir,
40+
}
41+
42+
if fnPaths := config.FnPaths; len(fnPaths) > 0 {
43+
step.Args = append(
44+
step.Args,
45+
append(
46+
[]string{"--fn-path"},
47+
fnPaths...
48+
)...
49+
)
50+
}
51+
52+
p.Steps = []CloudBuildStep{step}
53+
54+
return p
55+
}
56+
57+
func (p *CloudBuild) Generate() (out []byte, err error) {
58+
return yaml.Marshal(p)
59+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package orchestrators
16+
17+
import "github.com/GoogleContainerTools/kpt/internal/cmdexport/types"
18+
19+
var cloudBuildTestCases = []testCase{
20+
{
21+
description: "generates a Cloud Build pipeline",
22+
config: &types.PipelineConfig{
23+
Dir: ".",
24+
FnPaths: nil,
25+
},
26+
expected: `
27+
steps:
28+
- name: gongpu/kpt:latest
29+
args:
30+
- fn
31+
- run
32+
- .
33+
`,
34+
},
35+
{
36+
description: "generates a Cloud Build pipeline with --fn-path",
37+
config: &types.PipelineConfig{
38+
Dir: "resources",
39+
FnPaths: []string{"config/function.yaml"},
40+
},
41+
expected: `
42+
steps:
43+
- name: gongpu/kpt:latest
44+
args:
45+
- fn
46+
- run
47+
- resources
48+
- --fn-path
49+
- config/function.yaml
50+
`,
51+
},
52+
{
53+
description: "generates a Cloud Build pipeline with multiple --fn-path",
54+
config: &types.PipelineConfig{
55+
Dir: "resources",
56+
FnPaths: []string{"config/function1.yaml", "config/function2.yaml"},
57+
},
58+
expected: `
59+
steps:
60+
- name: gongpu/kpt:latest
61+
args:
62+
- fn
63+
- run
64+
- resources
65+
- --fn-path
66+
- config/function1.yaml
67+
- config/function2.yaml
68+
`,
69+
},
70+
}
71+
72+
var cloudBuildTestSuite = testSuite{
73+
pipeline: &CloudBuild{},
74+
testCases: cloudBuildTestCases,
75+
}

internal/cmdexport/orchestrators/githubactions.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
"sigs.k8s.io/kustomize/kyaml/yaml"
2323
)
2424

25-
// Represent a GitHub Actions workflow.
25+
// GitHubActions represents a GitHub Actions workflow.
2626
// @see https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions
2727
type GitHubActions struct {
2828
Name string `yaml:",omitempty"`
@@ -82,8 +82,6 @@ func (p *GitHubActions) Init(config *types.PipelineConfig) Pipeline {
8282
return p
8383
}
8484

85-
func (p *GitHubActions) Generate() []byte {
86-
data, _ := yaml.Marshal(p)
87-
88-
return data
85+
func (p *GitHubActions) Generate() (out []byte, err error) {
86+
return yaml.Marshal(p)
8987
}

0 commit comments

Comments
 (0)