Skip to content

Commit 67fe089

Browse files
committed
[v3] Add validator. Write the skaffold validation rule to Kptfile pipeline.
1 parent acab5fd commit 67fe089

File tree

4 files changed

+246
-19
lines changed

4 files changed

+246
-19
lines changed

pkg/skaffold/render/renderer/renderer.go

+44-7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"context"
2020
"fmt"
2121
"io"
22+
"io/ioutil"
2223
"os"
2324
"os/exec"
2425
"path/filepath"
@@ -30,6 +31,7 @@ import (
3031
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/manifest"
3132
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/generate"
3233
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/kptfile"
34+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/validate"
3335
latestV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v2"
3436
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
3537
)
@@ -44,17 +46,37 @@ type Renderer interface {
4446
}
4547

4648
// NewSkaffoldRenderer creates a new Renderer object from the latestV2 API schema.
47-
func NewSkaffoldRenderer(config *latestV2.RenderConfig, workingDir string) Renderer {
49+
func NewSkaffoldRenderer(config *latestV2.RenderConfig, workingDir string) (Renderer, error) {
4850
// TODO(yuwenma): return instance of kpt-managed mode or skaffold-managed mode defer to the config.Path fields.
4951
// The alpha implementation only has skaffold-managed mode.
5052
// TODO(yuwenma): The current work directory may not be accurate if users use --filepath flag.
5153
hydrationDir := filepath.Join(workingDir, DefaultHydrationDir)
52-
generator := generate.NewGenerator(workingDir, *config.Generate)
53-
return &SkaffoldRenderer{Generator: *generator, workingDir: workingDir, hydrationDir: hydrationDir}
54+
55+
var generator *generate.Generator
56+
if config.Generate == nil {
57+
// If render.generate is not given, default to current working directory.
58+
defaultManifests := filepath.Join(workingDir, "*.yaml")
59+
generator = generate.NewGenerator(workingDir, latestV2.Generate{Manifests: []string{defaultManifests}})
60+
} else {
61+
generator = generate.NewGenerator(workingDir, *config.Generate)
62+
}
63+
64+
var validator *validate.Validator
65+
if config.Validate != nil {
66+
var err error
67+
validator, err = validate.NewValidator(*config.Validate)
68+
if err != nil {
69+
return nil, err
70+
}
71+
} else {
72+
validator, _ = validate.NewValidator([]latestV2.Validator{})
73+
}
74+
return &SkaffoldRenderer{Generator: *generator, Validator: *validator, workingDir: workingDir, hydrationDir: hydrationDir}, nil
5475
}
5576

5677
type SkaffoldRenderer struct {
5778
generate.Generator
79+
validate.Validator
5880
workingDir string
5981
hydrationDir string
6082
labels map[string]string
@@ -68,14 +90,16 @@ func (r *SkaffoldRenderer) prepareHydrationDir(ctx context.Context) error {
6890
if _, err := os.Stat(r.hydrationDir); os.IsNotExist(err) {
6991
logrus.Debugf("creating render directory: %v", r.hydrationDir)
7092
if err := os.MkdirAll(r.hydrationDir, os.ModePerm); err != nil {
71-
return fmt.Errorf("creating cache directory for hydration: %w", err)
93+
return fmt.Errorf("creating render directory for hydration: %w", err)
7294
}
7395
}
7496
kptFilePath := filepath.Join(r.hydrationDir, kptfile.KptFileName)
7597
if _, err := os.Stat(kptFilePath); os.IsNotExist(err) {
7698
cmd := exec.CommandContext(ctx, "kpt", "pkg", "init", r.hydrationDir)
7799
if _, err := util.RunCmdOut(cmd); err != nil {
78-
return err
100+
// TODO: user error. need manual init
101+
return fmt.Errorf("unable to initialize kpt directory in %v, please manually run `kpt pkg init %v`",
102+
kptFilePath, kptFilePath)
79103
}
80104
}
81105
return nil
@@ -111,10 +135,23 @@ func (r *SkaffoldRenderer) Render(ctx context.Context, out io.Writer, builds []g
111135
defer file.Close()
112136
kfConfig := &kptfile.KptFile{}
113137
if err := yaml.NewDecoder(file).Decode(&kfConfig); err != nil {
114-
return err
138+
// TODO: user error.
139+
return fmt.Errorf("unable to parse %v: %w, please check if the kptfile is updated to new apiVersion > v1alpha2",
140+
kptFilePath, err)
141+
}
142+
if kfConfig.Pipeline == nil {
143+
kfConfig.Pipeline = &kptfile.Pipeline{}
115144
}
116145

117-
// TODO: Update the Kptfile with the new validators.
146+
kfConfig.Pipeline.Validators = r.GetDeclarativeValidators()
118147
// TODO: Update the Kptfile with the new mutators.
148+
149+
configByte, err := yaml.Marshal(kfConfig)
150+
if err != nil {
151+
return fmt.Errorf("unable to marshal Kptfile config %v", kfConfig)
152+
}
153+
if err = ioutil.WriteFile(kptFilePath, configByte, 0644); err != nil {
154+
return fmt.Errorf("unable to update %v", kptFilePath)
155+
}
119156
return nil
120157
}

pkg/skaffold/render/renderer/renderer_test.go

+93-12
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ limitations under the License.
1616
package renderer
1717

1818
import (
19+
"bytes"
20+
"context"
1921
"fmt"
2022
"path/filepath"
2123
"testing"
2224

25+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
2326
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/kptfile"
2427
latestV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v2"
2528
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
@@ -54,17 +57,95 @@ metadata:
5457
`
5558
)
5659

57-
func TestRender_StoredInCache(t *testing.T) {
58-
testutil.Run(t, "", func(t *testutil.T) {
59-
r := NewSkaffoldRenderer(&latestV2.RenderConfig{Generate: &latestV2.Generate{
60-
Manifests: []string{"pod.yaml"}}}, "")
61-
fakeCmd := testutil.CmdRunOut(fmt.Sprintf("kpt pkg init %v", DefaultHydrationDir), "")
62-
t.Override(&util.DefaultExecCommand, fakeCmd)
63-
t.NewTempDir().
64-
Write("pod.yaml", podYaml).
65-
Write(filepath.Join(DefaultHydrationDir, kptfile.KptFileName), initKptfile).
66-
Touch("empty.ignored").
67-
Chdir()
60+
func TestRender(t *testing.T) {
61+
tests := []struct {
62+
description string
63+
renderConfig *latestV2.RenderConfig
64+
originalKptfile string
65+
updatedKptfile string
66+
}{
67+
{
68+
description: "single manifests, no hydration rule",
69+
renderConfig: &latestV2.RenderConfig{
70+
Generate: &latestV2.Generate{Manifests: []string{"pod.yaml"}},
71+
},
72+
originalKptfile: initKptfile,
73+
updatedKptfile: `apiVersion: kpt.dev/v1alpha2
74+
kind: Kptfile
75+
metadata:
76+
name: skaffold
77+
pipeline: {}
78+
`,
79+
},
80+
81+
{
82+
description: "manifests not given.",
83+
renderConfig: &latestV2.RenderConfig{},
84+
originalKptfile: initKptfile,
85+
updatedKptfile: `apiVersion: kpt.dev/v1alpha2
86+
kind: Kptfile
87+
metadata:
88+
name: skaffold
89+
pipeline: {}
90+
`,
91+
},
92+
{
93+
description: "single manifests with validation rule.",
94+
renderConfig: &latestV2.RenderConfig{
95+
Generate: &latestV2.Generate{Manifests: []string{"pod.yaml"}},
96+
Validate: &[]latestV2.Validator{{Name: "kubeval"}},
97+
},
98+
originalKptfile: initKptfile,
99+
updatedKptfile: `apiVersion: kpt.dev/v1alpha2
100+
kind: Kptfile
101+
metadata:
102+
name: skaffold
103+
pipeline:
104+
validators:
105+
- image: gcr.io/kpt-fn/kubeval:v0.1
106+
`,
107+
},
108+
{
109+
description: "Validation rule needs to be updated.",
110+
renderConfig: &latestV2.RenderConfig{
111+
Generate: &latestV2.Generate{Manifests: []string{"pod.yaml"}},
112+
Validate: &[]latestV2.Validator{{Name: "kubeval"}},
113+
},
114+
originalKptfile: `apiVersion: kpt.dev/v1alpha2
115+
kind: Kptfile
116+
metadata:
117+
name: skaffold
118+
pipeline:
119+
validators:
120+
- image: gcr.io/kpt-fn/SOME-OTHER-FUNC
121+
`,
122+
updatedKptfile: `apiVersion: kpt.dev/v1alpha2
123+
kind: Kptfile
124+
metadata:
125+
name: skaffold
126+
pipeline:
127+
validators:
128+
- image: gcr.io/kpt-fn/kubeval:v0.1
129+
`,
130+
},
68131
}
132+
for _, test := range tests {
133+
testutil.Run(t, test.description, func(t *testutil.T) {
134+
r, err := NewSkaffoldRenderer(test.renderConfig, "")
135+
t.CheckNoError(err)
136+
fakeCmd := testutil.CmdRunOut(fmt.Sprintf("kpt pkg init %v", DefaultHydrationDir), "")
137+
t.Override(&util.DefaultExecCommand, fakeCmd)
138+
t.NewTempDir().
139+
Write("pod.yaml", podYaml).
140+
Write(filepath.Join(DefaultHydrationDir, kptfile.KptFileName), test.originalKptfile).
141+
Touch("empty.ignored").
142+
Chdir()
69143

70-
}
144+
var b bytes.Buffer
145+
err = r.Render(context.Background(), &b, []graph.Artifact{{ImageName: "leeroy-web", Tag: "leeroy-web:v1"}})
146+
t.CheckNoError(err)
147+
t.CheckFileExistAndContent(filepath.Join(DefaultHydrationDir, dryFileName), []byte(labeledPodYaml))
148+
t.CheckFileExistAndContent(filepath.Join(DefaultHydrationDir, kptfile.KptFileName), []byte(test.updatedKptfile))
149+
})
150+
}
151+
}
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
Copyright 2021 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package validate
17+
18+
import (
19+
"fmt"
20+
21+
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/kptfile"
22+
latestV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v2"
23+
)
24+
25+
var validatorWhitelist = map[string]kptfile.Function{
26+
"kubeval": {Image: "gcr.io/kpt-fn/kubeval:v0.1"},
27+
// TODO: Add conftest validator in kpt catalog.
28+
}
29+
30+
// NewValidator instantiates a Validator object.
31+
func NewValidator(config []latestV2.Validator) (*Validator, error) {
32+
var fns []kptfile.Function
33+
for _, c := range config {
34+
fn, ok := validatorWhitelist[c.Name]
35+
if !ok {
36+
// TODO: kpt user error
37+
return nil, fmt.Errorf("unsupported validator %v", c.Name)
38+
}
39+
fns = append(fns, fn)
40+
}
41+
return &Validator{kptFn: fns}, nil
42+
}
43+
44+
type Validator struct {
45+
kptFn []kptfile.Function
46+
}
47+
48+
// GetDeclarativeValidators transforms and returns the skaffold validators defined in skaffold.yaml
49+
func (v *Validator) GetDeclarativeValidators() []kptfile.Function {
50+
// TODO: guarantee the v.kptFn is updated once users changed skaffold.yaml file.
51+
return v.kptFn
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
Copyright 2021 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package validate
17+
18+
import (
19+
"testing"
20+
21+
latestV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v2"
22+
"github.com/GoogleContainerTools/skaffold/testutil"
23+
)
24+
25+
func TestValidatorInit(t *testing.T) {
26+
tests := []struct {
27+
description string
28+
config []latestV2.Validator
29+
shouldErr bool
30+
}{
31+
{
32+
description: "no validation",
33+
config: []latestV2.Validator{},
34+
shouldErr: false,
35+
},
36+
{
37+
description: "kubeval validator",
38+
config: []latestV2.Validator{
39+
{Name: "kubeval"},
40+
},
41+
shouldErr: false,
42+
},
43+
{
44+
description: "invalid validator",
45+
config: []latestV2.Validator{
46+
{Name: "bad-validator"},
47+
},
48+
shouldErr: true,
49+
},
50+
}
51+
for _, test := range tests {
52+
testutil.Run(t, test.description, func(t *testutil.T) {
53+
_, err := NewValidator(test.config)
54+
t.CheckError(test.shouldErr, err)
55+
})
56+
}
57+
}

0 commit comments

Comments
 (0)