mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
Add imagetag filter based on kayml libraries
This commit is contained in:
12
api/filters/imagetag/doc.go
Normal file
12
api/filters/imagetag/doc.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Package imagetag contains two kio.Filter implementations to cover the
|
||||||
|
// functionality of the kustomize imagetag transformer.
|
||||||
|
//
|
||||||
|
// Filter updates fields based on a FieldSpec and an ImageTag.
|
||||||
|
//
|
||||||
|
// LegacyFilter doesn't use a FieldSpec, and instead only updates image
|
||||||
|
// references if the field is name image and it is underneath a field called
|
||||||
|
// either containers or initContainers.
|
||||||
|
package imagetag
|
||||||
126
api/filters/imagetag/example_test.go
Normal file
126
api/filters/imagetag/example_test.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package imagetag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleFilter() {
|
||||||
|
err := kio.Pipeline{
|
||||||
|
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: FooBar
|
||||||
|
image: nginx
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Bar
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: BarFoo
|
||||||
|
image: nginx:1.2.1
|
||||||
|
`)}},
|
||||||
|
Filters: []kio.Filter{Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "apache",
|
||||||
|
Digest: "12345",
|
||||||
|
},
|
||||||
|
FsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/containers[]/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||||
|
}.Execute()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// apiVersion: example.com/v1
|
||||||
|
// kind: Foo
|
||||||
|
// metadata:
|
||||||
|
// name: instance
|
||||||
|
// spec:
|
||||||
|
// containers:
|
||||||
|
// - name: FooBar
|
||||||
|
// image: apache@12345
|
||||||
|
// ---
|
||||||
|
// apiVersion: example.com/v1
|
||||||
|
// kind: Bar
|
||||||
|
// metadata:
|
||||||
|
// name: instance
|
||||||
|
// spec:
|
||||||
|
// containers:
|
||||||
|
// - name: BarFoo
|
||||||
|
// image: apache@12345
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleLegacyFilter() {
|
||||||
|
err := kio.Pipeline{
|
||||||
|
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: FooBar
|
||||||
|
image: nginx
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Bar
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: BarFoo
|
||||||
|
image: nginx:1.2.1
|
||||||
|
`)}},
|
||||||
|
Filters: []kio.Filter{LegacyFilter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "apache",
|
||||||
|
Digest: "12345",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||||
|
}.Execute()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// apiVersion: example.com/v1
|
||||||
|
// kind: Foo
|
||||||
|
// metadata:
|
||||||
|
// name: instance
|
||||||
|
// spec:
|
||||||
|
// containers:
|
||||||
|
// - name: FooBar
|
||||||
|
// image: apache@12345
|
||||||
|
// ---
|
||||||
|
// apiVersion: example.com/v1
|
||||||
|
// kind: Bar
|
||||||
|
// metadata:
|
||||||
|
// name: instance
|
||||||
|
// spec:
|
||||||
|
// containers:
|
||||||
|
// - name: BarFoo
|
||||||
|
// image: apache@12345
|
||||||
|
}
|
||||||
44
api/filters/imagetag/imagetag.go
Normal file
44
api/filters/imagetag/imagetag.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package imagetag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Filter struct {
|
||||||
|
// imageTag is the tag we want to apply to the inputs
|
||||||
|
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
||||||
|
|
||||||
|
// FsSlice contains the FieldSpecs to locate the namespace field
|
||||||
|
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ kio.Filter = Filter{}
|
||||||
|
|
||||||
|
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
|
_, err := kio.FilterAll(yaml.FilterFunc(f.filter)).Filter(nodes)
|
||||||
|
return nodes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Filter) filter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||||
|
if err := node.PipeE(fsslice.Filter{
|
||||||
|
FsSlice: f.FsSlice,
|
||||||
|
SetValue: updateImageTagFn(f.ImageTag),
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateImageTagFn(imageTag types.Image) fsslice.SetFn {
|
||||||
|
return func(node *yaml.RNode) error {
|
||||||
|
return node.PipeE(imageTagUpdater{
|
||||||
|
ImageTag: imageTag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
101
api/filters/imagetag/imagetag_test.go
Normal file
101
api/filters/imagetag/imagetag_test.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package imagetag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestImageTagUpdater_Filter(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
input string
|
||||||
|
expectedOutput string
|
||||||
|
filter Filter
|
||||||
|
fsSlice types.FsSlice
|
||||||
|
}{
|
||||||
|
"update with digest": {
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
image: nginx:1.2.1
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
image: apache@12345
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "apache",
|
||||||
|
Digest: "12345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multiple matches in sequence": {
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx:1.2.1
|
||||||
|
- image: not_nginx@54321
|
||||||
|
- image: nginx:1.2.1
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: apache:3.2.1
|
||||||
|
- image: not_nginx@54321
|
||||||
|
- image: apache:3.2.1
|
||||||
|
`,
|
||||||
|
filter: Filter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "apache",
|
||||||
|
NewTag: "3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fsSlice: []types.FieldSpec{
|
||||||
|
{
|
||||||
|
Path: "spec/containers/image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for tn, tc := range testCases {
|
||||||
|
t.Run(tn, func(t *testing.T) {
|
||||||
|
filter := tc.filter
|
||||||
|
filter.FsSlice = tc.fsSlice
|
||||||
|
if !assert.Equal(t,
|
||||||
|
strings.TrimSpace(tc.expectedOutput),
|
||||||
|
strings.TrimSpace(filtertest.RunFilter(t, tc.input, filter))) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
113
api/filters/imagetag/legacy.go
Normal file
113
api/filters/imagetag/legacy.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package imagetag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LegacyFilter is an implementation of the kio.Filter interface
|
||||||
|
// that scans through the provided kyaml data structure and updates
|
||||||
|
// any values of any image fields that is inside a sequence under
|
||||||
|
// a field called either containers or initContainers. The field is only
|
||||||
|
// update if it has a value that matches and image reference and the name
|
||||||
|
// of the image is a match with the provided ImageTag.
|
||||||
|
type LegacyFilter struct {
|
||||||
|
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ kio.Filter = LegacyFilter{}
|
||||||
|
|
||||||
|
func (lf LegacyFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
|
return kio.FilterAll(yaml.FilterFunc(lf.filter)).Filter(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lf LegacyFilter) filter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||||
|
meta, err := node.GetMeta()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not make any changes if the type of the resource
|
||||||
|
// is CustomResourceDefinition.
|
||||||
|
if meta.Kind == `CustomResourceDefinition` {
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fff := findFieldsFilter{
|
||||||
|
fields: []string{"containers", "initContainers"},
|
||||||
|
fieldCallback: checkImageTagsFn(lf.ImageTag),
|
||||||
|
}
|
||||||
|
if err := node.PipeE(fff); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type fieldCallback func(node *yaml.RNode) error
|
||||||
|
|
||||||
|
// findFieldsFilter is an implementation of the kio.Filter
|
||||||
|
// interface. It will walk the data structure and look for fields
|
||||||
|
// that matches the provided list of field names. For each match,
|
||||||
|
// the value of the field will be passed in as a parameter to the
|
||||||
|
// provided fieldCallback.
|
||||||
|
// TODO: move this to kyaml/filterutils
|
||||||
|
type findFieldsFilter struct {
|
||||||
|
fields []string
|
||||||
|
|
||||||
|
fieldCallback fieldCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f findFieldsFilter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
||||||
|
return obj, f.walk(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f findFieldsFilter) walk(node *yaml.RNode) error {
|
||||||
|
switch node.YNode().Kind {
|
||||||
|
case yaml.MappingNode:
|
||||||
|
return node.VisitFields(func(n *yaml.MapNode) error {
|
||||||
|
err := f.walk(n.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key := n.Key.YNode().Value
|
||||||
|
if contains(f.fields, key) {
|
||||||
|
return f.fieldCallback(n.Value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
case yaml.SequenceNode:
|
||||||
|
return node.VisitElements(func(n *yaml.RNode) error {
|
||||||
|
return f.walk(n)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(slice []string, str string) bool {
|
||||||
|
for _, s := range slice {
|
||||||
|
if s == str {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkImageTagsFn(imageTag types.Image) fieldCallback {
|
||||||
|
return func(node *yaml.RNode) error {
|
||||||
|
if node.YNode().Kind != yaml.SequenceNode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.VisitElements(func(n *yaml.RNode) error {
|
||||||
|
// Look up any fields on the provided node that is named
|
||||||
|
// image.
|
||||||
|
return n.PipeE(yaml.Get("image"), imageTagUpdater{
|
||||||
|
ImageTag: imageTag,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
136
api/filters/imagetag/legacy_test.go
Normal file
136
api/filters/imagetag/legacy_test.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package imagetag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLegacyImageTag_Filter(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
input string
|
||||||
|
expectedOutput string
|
||||||
|
filter LegacyFilter
|
||||||
|
}{
|
||||||
|
"updates multiple images inside containers": {
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx:1.2.1
|
||||||
|
- image: nginx:2.1.2
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: apache@12345
|
||||||
|
- image: apache@12345
|
||||||
|
`,
|
||||||
|
filter: LegacyFilter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "apache",
|
||||||
|
Digest: "12345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"updates inside both containers and initContainers": {
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx:1.2.1
|
||||||
|
- image: tomcat:1.2.3
|
||||||
|
initContainers:
|
||||||
|
- image: nginx:1.2.1
|
||||||
|
- image: apache:1.2.3
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: apache:3.2.1
|
||||||
|
- image: tomcat:1.2.3
|
||||||
|
initContainers:
|
||||||
|
- image: apache:3.2.1
|
||||||
|
- image: apache:1.2.3
|
||||||
|
`,
|
||||||
|
filter: LegacyFilter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "apache",
|
||||||
|
NewTag: "3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"updates on multiple depths": {
|
||||||
|
input: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx:1.2.1
|
||||||
|
- image: tomcat:1.2.3
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
initContainers:
|
||||||
|
- image: nginx:1.2.1
|
||||||
|
- image: apache:1.2.3
|
||||||
|
`,
|
||||||
|
expectedOutput: `
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: apache:3.2.1
|
||||||
|
- image: tomcat:1.2.3
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
initContainers:
|
||||||
|
- image: apache:3.2.1
|
||||||
|
- image: apache:1.2.3
|
||||||
|
`,
|
||||||
|
filter: LegacyFilter{
|
||||||
|
ImageTag: types.Image{
|
||||||
|
Name: "nginx",
|
||||||
|
NewName: "apache",
|
||||||
|
NewTag: "3.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for tn, tc := range testCases {
|
||||||
|
t.Run(tn, func(t *testing.T) {
|
||||||
|
filter := tc.filter
|
||||||
|
if !assert.Equal(t,
|
||||||
|
strings.TrimSpace(tc.expectedOutput),
|
||||||
|
strings.TrimSpace(filtertest.RunFilter(t, tc.input, filter))) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
43
api/filters/imagetag/updater.go
Normal file
43
api/filters/imagetag/updater.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package imagetag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/api/image"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// imageTagUpdater is an implementation of the kio.Filter interface
|
||||||
|
// that will update the value of the yaml node based on the provided
|
||||||
|
// ImageTag if the current value matches the format of an image reference.
|
||||||
|
type imageTagUpdater struct {
|
||||||
|
Kind string `yaml:"kind,omitempty"`
|
||||||
|
ImageTag types.Image `yaml:"imageTag,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u imageTagUpdater) Filter(rn *yaml.RNode) (*yaml.RNode, error) {
|
||||||
|
if err := yaml.ErrorIfInvalid(rn, yaml.ScalarNode); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := rn.YNode().Value
|
||||||
|
|
||||||
|
if !image.IsImageMatched(value, u.ImageTag.Name) {
|
||||||
|
return rn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
name, tag := image.Split(value)
|
||||||
|
if u.ImageTag.NewName != "" {
|
||||||
|
name = u.ImageTag.NewName
|
||||||
|
}
|
||||||
|
if u.ImageTag.NewTag != "" {
|
||||||
|
tag = ":" + u.ImageTag.NewTag
|
||||||
|
}
|
||||||
|
if u.ImageTag.Digest != "" {
|
||||||
|
tag = "@" + u.ImageTag.Digest
|
||||||
|
}
|
||||||
|
|
||||||
|
return rn.Pipe(yaml.FieldSetter{StringValue: name + tag})
|
||||||
|
}
|
||||||
50
api/image/image.go
Normal file
50
api/image/image.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsImageMatched returns true if the value of t is identical to the
|
||||||
|
// image name in the full image name and tag as given by s.
|
||||||
|
func IsImageMatched(s, t string) bool {
|
||||||
|
// Tag values are limited to [a-zA-Z0-9_.{}-].
|
||||||
|
// Some tools like Bazel rules_k8s allow tag patterns with {} characters.
|
||||||
|
// More info: https://github.com/bazelbuild/rules_k8s/pull/423
|
||||||
|
pattern, _ := regexp.Compile("^" + t + "(@sha256)?(:[a-zA-Z0-9_.{}-]*)?$")
|
||||||
|
return pattern.MatchString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split separates and returns the name and tag parts
|
||||||
|
// from the image string using either colon `:` or at `@` separators.
|
||||||
|
// Note that the returned tag keeps its separator.
|
||||||
|
func Split(imageName string) (name string, tag string) {
|
||||||
|
// check if image name contains a domain
|
||||||
|
// if domain is present, ignore domain and check for `:`
|
||||||
|
ic := -1
|
||||||
|
if slashIndex := strings.Index(imageName, "/"); slashIndex < 0 {
|
||||||
|
ic = strings.LastIndex(imageName, ":")
|
||||||
|
} else {
|
||||||
|
lastIc := strings.LastIndex(imageName[slashIndex:], ":")
|
||||||
|
// set ic only if `:` is present
|
||||||
|
if lastIc > 0 {
|
||||||
|
ic = slashIndex + lastIc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ia := strings.LastIndex(imageName, "@")
|
||||||
|
if ic < 0 && ia < 0 {
|
||||||
|
return imageName, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
i := ic
|
||||||
|
if ia > 0 {
|
||||||
|
i = ia
|
||||||
|
}
|
||||||
|
|
||||||
|
name = imageName[:i]
|
||||||
|
tag = imageName[i:]
|
||||||
|
return
|
||||||
|
}
|
||||||
80
api/image/image_test.go
Normal file
80
api/image/image_test.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2020 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsImageMatched(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
testName string
|
||||||
|
value string
|
||||||
|
name string
|
||||||
|
isMatched bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "identical",
|
||||||
|
value: "nginx",
|
||||||
|
name: "nginx",
|
||||||
|
isMatched: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "name is match",
|
||||||
|
value: "nginx:12345",
|
||||||
|
name: "nginx",
|
||||||
|
isMatched: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "name is not a match",
|
||||||
|
value: "apache:12345",
|
||||||
|
name: "nginx",
|
||||||
|
isMatched: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.isMatched, IsImageMatched(tc.value, tc.name))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplit(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
testName string
|
||||||
|
value string
|
||||||
|
name string
|
||||||
|
tag string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "no tag",
|
||||||
|
value: "nginx",
|
||||||
|
name: "nginx",
|
||||||
|
tag: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "with tag",
|
||||||
|
value: "nginx:1.2.3",
|
||||||
|
name: "nginx",
|
||||||
|
tag: ":1.2.3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "with digest",
|
||||||
|
value: "nginx@12345",
|
||||||
|
name: "nginx",
|
||||||
|
tag: "@12345",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
name, tag := Split(tc.value)
|
||||||
|
assert.Equal(t, tc.name, name)
|
||||||
|
assert.Equal(t, tc.tag, tag)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user