Merge pull request #2043 from pwittrock/master

Fix various cmd/config issues
This commit is contained in:
Kubernetes Prow Robot
2020-01-08 11:23:41 -08:00
committed by GitHub
35 changed files with 661 additions and 143 deletions

View File

@@ -80,6 +80,8 @@ func NewConfigCommand(name string) *cobra.Command {
root.AddCommand(commands.SetCommand(name))
root.AddCommand(commands.ListSettersCommand(name))
root.AddCommand(commands.CreateSetterCommand(name))
root.AddCommand(commands.SinkCommand(name))
root.AddCommand(commands.SourceCommand(name))
root.AddCommand(&cobra.Command{
Use: "docs-merge",

View File

@@ -22,7 +22,7 @@ order they appear in the file).
#### Config Functions:
Config functions are specified as Kubernetes types containing a metadata.annotations.[config.k8s.io/function]
Config functions are specified as Kubernetes types containing a metadata.annotations.[config.kubernetes.io/function]
field specifying an image for the container to run. This image tells run how to invoke the container.
Example config function:
@@ -32,7 +32,7 @@ order they appear in the file).
kind: ExampleFunctionKind
metadata:
annotations:
config.k8s.io/function: |
config.kubernetes.io/function: |
container:
# function is invoked as a container running this image
image: gcr.io/example/examplefunction:v1.0.1
@@ -41,7 +41,7 @@ order they appear in the file).
configField: configValue
In the preceding example, 'kustomize config run example/' would identify the function by
the metadata.annotations.[config.k8s.io/function] field. It would then write all Resources in the directory to
the metadata.annotations.[config.kubernetes.io/function] field. It would then write all Resources in the directory to
a container stdin (running the gcr.io/example/examplefunction:v1.0.1 image). It
would then write the container stdout back to example/, replacing the directory
file contents.

View File

@@ -0,0 +1,18 @@
## sink
[Alpha] Implement a Sink by writing input to a local directory.
### Synopsis
[Alpha] Implement a Sink by writing input to a local directory.
kustomize config sink DIR
DIR:
Path to local directory.
`sink` writes its input to a directory
### Examples
kustomize config source DIR/ | your-function | kustomize config sink DIR/

View File

@@ -0,0 +1,21 @@
## source
[Alpha] Implement a Source by reading a local directory.
### Synopsis
[Alpha] Implement a Source by reading a local directory.
kustomize config source DIR
DIR:
Path to local directory.
`source` emits configuration to act as input to a function
### Examples
# emity configuration directory as input source to a function
kustomize config source DIR/
kustomize config source DIR/ | your-function | kustomize config sink DIR/

View File

@@ -121,6 +121,13 @@ func (r *CatRunner) runE(c *cobra.Command, args []string) error {
out = o
}
// remove this annotation explicitly, the ByteWriter won't clear it by
// default because it doesn't set it
clear := []string{"config.kubernetes.io/path"}
if r.KeepAnnotations {
clear = nil
}
var outputs []kio.Writer
outputs = append(outputs, kio.ByteWriter{
Writer: out,
@@ -129,6 +136,7 @@ func (r *CatRunner) runE(c *cobra.Command, args []string) error {
WrappingAPIVersion: r.WrapApiVersion,
FunctionConfig: functionConfig,
Style: yaml.GetStyle(r.Styles...),
ClearAnnotations: clear,
})
return handleError(c, kio.Pipeline{Inputs: inputs, Filters: fltr, Outputs: outputs}.Execute())

View File

@@ -90,8 +90,6 @@ metadata:
name: foo
annotations:
app: nginx2
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
---
@@ -100,8 +98,6 @@ metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -114,8 +110,6 @@ metadata:
app: nginx
annotations:
app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
spec:
replicas: 3
`, b.String()) {
@@ -196,8 +190,6 @@ metadata:
name: foo
annotations:
app: nginx2
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
---
@@ -206,8 +198,6 @@ metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -218,8 +208,6 @@ metadata:
name: foo
annotations:
config.kubernetes.io/local-config: "true"
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
configFn:
container:
image: gcr.io/example/image:version
@@ -233,8 +221,6 @@ metadata:
name: bar
annotations:
app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
spec:
replicas: 3
`, b.String()) {
@@ -314,8 +300,6 @@ metadata:
name: foo
annotations:
config.kubernetes.io/local-config: "true"
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
configFn:
container:
image: gcr.io/example/reconciler:v1
@@ -414,8 +398,6 @@ metadata:
name: foo
annotations:
app: nginx2
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
---
@@ -424,8 +406,6 @@ metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -438,8 +418,6 @@ metadata:
app: nginx
annotations:
app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
spec:
replicas: 3
`, string(actual)) {
@@ -536,8 +514,6 @@ metadata:
name: foo
annotations:
app: nginx2
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
---
@@ -546,8 +522,6 @@ metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -560,8 +534,6 @@ metadata:
app: nginx
annotations:
app: nginx
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f2.yaml'
spec:
replicas: 3
`, string(actual)) {

View File

@@ -76,7 +76,6 @@ metadata:
annotations:
app: nginx2
config.kubernetes.io/index: '0'
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
@@ -87,7 +86,6 @@ metadata:
annotations:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'f1.yaml'
spec:
selector:

View File

@@ -0,0 +1,48 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
)
// GetSinkRunner returns a command for Sink.
func GetSinkRunner(name string) *SinkRunner {
r := &SinkRunner{}
c := &cobra.Command{
Use: "sink DIR",
Short: commands.SinkShort,
Long: commands.SinkLong,
Example: commands.SinkExamples,
RunE: r.runE,
Args: cobra.ExactArgs(1),
}
fixDocs(name, c)
r.Command = c
return r
}
func SinkCommand(name string) *cobra.Command {
return GetSinkRunner(name).Command
}
// SinkRunner contains the run function
type SinkRunner struct {
Command *cobra.Command
}
func (r *SinkRunner) runE(c *cobra.Command, args []string) error {
err := kio.Pipeline{
Inputs: []kio.Reader{&kio.ByteReader{Reader: c.InOrStdin()}},
Outputs: []kio.Writer{
&kio.LocalPackageWriter{
PackagePath: args[0],
ClearAnnotations: []string{"config.kubernetes.io/path"},
},
},
}.Execute()
return handleError(c, err)
}

View File

@@ -0,0 +1,140 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
)
func TestSinkCommand(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-source-test")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(d)
// fmt the files
b := &bytes.Buffer{}
r := commands.GetSinkRunner("")
r.Command.SetIn(bytes.NewBufferString(`apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
- kind: Service
metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
- apiVersion: v1
kind: Abstraction
metadata:
name: foo
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example/reconciler:v1
config.kubernetes.io/local-config: "true"
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
spec:
replicas: 3
- apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f2.yaml'
spec:
replicas: 3
`))
r.Command.SetArgs([]string{d})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
t.FailNow()
}
actual, err := ioutil.ReadFile(filepath.Join(d, "f1.yaml"))
if !assert.NoError(t, err) {
t.FailNow()
}
expected := `kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`
if !assert.Equal(t, expected, string(actual)) {
t.FailNow()
}
actual, err = ioutil.ReadFile(filepath.Join(d, "f2.yaml"))
if !assert.NoError(t, err) {
t.FailNow()
}
expected = `apiVersion: v1
kind: Abstraction
metadata:
name: foo
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example/reconciler:v1
config.kubernetes.io/local-config: "true"
spec:
replicas: 3
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`
if !assert.Equal(t, expected, string(actual)) {
t.FailNow()
}
}

View File

@@ -0,0 +1,77 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands
import (
"fmt"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// GetSourceRunner returns a command for Source.
func GetSourceRunner(name string) *SourceRunner {
r := &SourceRunner{}
c := &cobra.Command{
Use: "source DIR",
Short: commands.SourceShort,
Long: commands.SourceLong,
Example: commands.SourceExamples,
RunE: r.runE,
Args: cobra.ExactArgs(1),
}
fixDocs(name, c)
c.Flags().StringVar(&r.WrapKind, "wrap-kind", kio.ResourceListKind,
"output using this format.")
c.Flags().StringVar(&r.WrapApiVersion, "wrap-version", kio.ResourceListAPIVersion,
"output using this format.")
c.Flags().StringVar(&r.FunctionConfig, "function-config", "",
"path to function config.")
r.Command = c
_ = c.MarkFlagFilename("function-config", "yaml", "json", "yml")
return r
}
func SourceCommand(name string) *cobra.Command {
return GetSourceRunner(name).Command
}
// SourceRunner contains the run function
type SourceRunner struct {
WrapKind string
WrapApiVersion string
FunctionConfig string
Command *cobra.Command
}
func (r *SourceRunner) runE(c *cobra.Command, args []string) error {
// if there is a function-config specified, emit it
var functionConfig *yaml.RNode
if r.FunctionConfig != "" {
configs, err := kio.LocalPackageReader{PackagePath: r.FunctionConfig}.Read()
if err != nil {
return err
}
if len(configs) != 1 {
return fmt.Errorf("expected exactly 1 functionConfig, found %d", len(configs))
}
functionConfig = configs[0]
}
var outputs []kio.Writer
outputs = append(outputs, kio.ByteWriter{
Writer: c.OutOrStdout(),
KeepReaderAnnotations: true,
WrappingKind: r.WrapKind,
WrappingAPIVersion: r.WrapApiVersion,
FunctionConfig: functionConfig,
})
err := kio.Pipeline{
Inputs: []kio.Reader{kio.LocalPackageReader{PackagePath: args[0]}},
Outputs: outputs}.Execute()
return handleError(c, err)
}

View File

@@ -0,0 +1,136 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package commands_test
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/cmd/config/internal/commands"
)
func TestSourceCommand(t *testing.T) {
d, err := ioutil.TempDir("", "kustomize-source-test")
if !assert.NoError(t, err) {
return
}
defer os.RemoveAll(d)
err = ioutil.WriteFile(filepath.Join(d, "f1.yaml"), []byte(`
kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
spec:
replicas: 1
---
kind: Service
metadata:
name: foo
annotations:
app: nginx
spec:
selector:
app: nginx
`), 0600)
if !assert.NoError(t, err) {
return
}
err = ioutil.WriteFile(filepath.Join(d, "f2.yaml"), []byte(`
apiVersion: v1
kind: Abstraction
metadata:
name: foo
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example/reconciler:v1
config.kubernetes.io/local-config: "true"
spec:
replicas: 3
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
spec:
replicas: 3
`), 0600)
if !assert.NoError(t, err) {
return
}
// fmt the files
b := &bytes.Buffer{}
r := commands.GetSourceRunner("")
r.Command.SetArgs([]string{d})
r.Command.SetOut(b)
if !assert.NoError(t, r.Command.Execute()) {
return
}
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
metadata:
labels:
app: nginx2
name: foo
annotations:
app: nginx2
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
- kind: Service
metadata:
name: foo
annotations:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
- apiVersion: v1
kind: Abstraction
metadata:
name: foo
annotations:
config.kubernetes.io/function: |
container:
image: gcr.io/example/reconciler:v1
config.kubernetes.io/local-config: "true"
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
spec:
replicas: 3
- apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: bar
annotations:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f2.yaml'
spec:
replicas: 3
`, b.String()) {
return
}
}

View File

@@ -104,7 +104,6 @@ metadata:
namespace: default
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
@@ -118,7 +117,6 @@ metadata:
namespace: default
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
@@ -132,7 +130,6 @@ metadata:
namespace: default
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
@@ -146,7 +143,6 @@ metadata:
namespace: default2
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
@@ -160,7 +156,6 @@ metadata:
namespace: default
annotations:
app: nginx3
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
@@ -171,8 +166,7 @@ metadata:
app: nginx
annotations:
app: nginx
config.kubernetes.io/package: bar-package
config.kubernetes.io/path: f2.yaml
config.kubernetes.io/path: bar-package/f2.yaml
name: bar
spec:
replicas: 3
@@ -183,7 +177,6 @@ metadata:
namespace: default
annotations:
app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
selector:

View File

@@ -220,7 +220,7 @@ order they appear in the file).
#### Config Functions:
Config functions are specified as Kubernetes types containing a metadata.annotations.[config.k8s.io/function]
Config functions are specified as Kubernetes types containing a metadata.annotations.[config.kubernetes.io/function]
field specifying an image for the container to run. This image tells run how to invoke the container.
Example config function:
@@ -230,7 +230,7 @@ order they appear in the file).
kind: ExampleFunctionKind
metadata:
annotations:
config.k8s.io/function: |
config.kubernetes.io/function: |
container:
# function is invoked as a container running this image
image: gcr.io/example/examplefunction:v1.0.1
@@ -239,7 +239,7 @@ order they appear in the file).
configField: configValue
In the preceding example, 'kustomize config run example/' would identify the function by
the metadata.annotations.[config.k8s.io/function] field. It would then write all Resources in the directory to
the metadata.annotations.[config.kubernetes.io/function] field. It would then write all Resources in the directory to
a container stdin (running the gcr.io/example/examplefunction:v1.0.1 image). It
would then write the container stdout back to example/, replacing the directory
file contents.
@@ -332,6 +332,37 @@ var SetExamples = `
name: test-app2 # {"description":"test environment","type":"string","x-kustomize":{"setBy":"dev","partialFieldSetters":[{"name":"name-prefix","value":"test"}]}}
...`
var SinkShort = `[Alpha] Implement a Sink by writing input to a local directory.`
var SinkLong = `
[Alpha] Implement a Sink by writing input to a local directory.
kustomize config sink DIR
DIR:
Path to local directory.
` + "`" + `sink` + "`" + ` writes its input to a directory
`
var SinkExamples = `
kustomize config source DIR/ | your-function | kustomize config sink DIR/`
var SourceShort = `[Alpha] Implement a Source by reading a local directory.`
var SourceLong = `
[Alpha] Implement a Source by reading a local directory.
kustomize config source DIR
DIR:
Path to local directory.
` + "`" + `source` + "`" + ` emits configuration to act as input to a function
`
var SourceExamples = `
# emity configuration directory as input source to a function
kustomize config source DIR/
kustomize config source DIR/ | your-function | kustomize config sink DIR/`
var TreeShort = `[Alpha] Display Resource structure from a directory or stdin.`
var TreeLong = `
[Alpha] Display Resource structure from a directory or stdin.

View File

@@ -9,6 +9,7 @@ package main
import (
"os"
"sigs.k8s.io/kustomize/cmd/config/complete"
"sigs.k8s.io/kustomize/cmd/config/configcobra"
"sigs.k8s.io/kustomize/kyaml/commandutil"
)
@@ -16,7 +17,10 @@ import (
func main() {
// enable the config commands
os.Setenv(commandutil.EnableAlphaCommmandsEnvName, "true")
if err := configcobra.NewConfigCommand("").Execute(); err != nil {
cmd := configcobra.NewConfigCommand("")
complete.Complete(cmd).Complete("config")
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}

View File

@@ -15,7 +15,7 @@ Resource configuration, and looks for invalid configuration.
## Function invocation
The function is invoked by authoring a [local Resource](local-resource)
with `metadata.annotations.[config.k8s.io/function]` and running:
with `metadata.annotations.[config.kubernetes.io/function]` and running:
kustomize config run local-resource/

View File

@@ -5,7 +5,7 @@ apiVersion: examples.config.kubernetes.io/v1beta1
kind: Validator
metadata:
annotations:
config.k8s.io/function: |
config.kubernetes.io/function: |
container:
image: gcr.io/kustomize-functions/example-tshirt:v0.1.0
---

View File

@@ -25,7 +25,7 @@ function input, and writing the function output.
## Function invocation
The function is invoked by authoring a [local Resource](local-resource)
with `metadata.annotations.[config.k8s.io/function]` and running:
with `metadata.annotations.[config.kubernetes.io/function]` and running:
kustomize config run local-resource/

View File

@@ -6,7 +6,7 @@ kind: Nginx
metadata:
name: demo
annotations:
config.k8s.io/function: |
config.kubernetes.io/function: |
container:
image: gcr.io/kustomize-functions/example-nginx:v0.1.0
spec:

View File

@@ -20,7 +20,7 @@ heavy lifting of implementing the function interface.
## Function invocation
The function is invoked by authoring a [local Resource](local-resource)
with `metadata.annotations.[config.k8s.io/function]` and running:
with `metadata.annotations.[config.kubernetes.io/function]` and running:
kustomize config run local-resource/

View File

@@ -7,7 +7,7 @@ kind: CockroachDB
metadata:
name: demo
annotations:
config.k8s.io/function: |
config.kubernetes.io/function: |
container:
image: gcr.io/kustomize-functions/example-cockroachdb:v0.1.0
spec:

View File

@@ -18,7 +18,7 @@ the `API` struct definition in [main.go](image/main.go) for documentation.
## Function invocation
The function is invoked by authoring a [local Resource](local-resource)
with `metadata.annotations.[config.k8s.io/function]` and running:
with `metadata.annotations.[config.kubernetes.io/function]` and running:
kustomize config run local-resource/

View File

@@ -5,7 +5,7 @@ apiVersion: examples.config.kubernetes.io/v1beta1
kind: Kubeval
metadata:
annotations:
config.k8s.io/function: |
config.kubernetes.io/function: |
container:
image: gcr.io/kustomize-functions/example-validator-kubeval:v0.1.0
spec:

View File

@@ -15,7 +15,7 @@ Resource configuration, and looks for invalid configuration.
## Function invocation
The function is invoked by authoring a [local Resource](local-resource)
with `metadata.annotations.[config.k8s.io/function]` and running:
with `metadata.annotations.[config.kubernetes.io/function]` and running:
kustomize config run local-resource/

View File

@@ -5,7 +5,7 @@ apiVersion: examples.config.kubernetes.io/v1beta1
kind: Validator
metadata:
annotations:
config.k8s.io/function: |
config.kubernetes.io/function: |
container:
image: gcr.io/kustomize-functions/example-validator:v0.1.0
---

View File

@@ -6,35 +6,17 @@ package main
import (
"os"
"path/filepath"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/complete"
"sigs.k8s.io/kustomize/kustomize/v3/internal/commands"
)
func main() {
cmd := commands.NewDefaultCommand()
completion(cmd)
complete.Complete(cmd).Complete("kustomize")
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
os.Exit(0)
}
// completion performs shell completion if kustomize is being called to provide
// shell completion commands.
func completion(cmd *cobra.Command) {
// bash shell completion passes the command name as the first argument
// do this after configuring cmd so it has all the subcommands
if len(os.Args) > 1 {
// use the base name in case kustomize is called with an absolute path
name := filepath.Base(os.Args[1])
if name == "kustomize" {
// complete calls kustomize with itself as an argument
complete.Complete(cmd).Complete("kustomize")
os.Exit(0)
}
}
}

View File

@@ -194,6 +194,13 @@ func (c *IsReconcilerFilter) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error)
return out, nil
}
const (
FunctionAnnotationKey = "config.kubernetes.io/function"
oldFunctionAnnotationKey = "config.k8s.io/function"
)
var functionAnnotationKeys = []string{FunctionAnnotationKey, oldFunctionAnnotationKey}
// GetContainerName returns the container image for an API if one exists
func GetContainerName(n *yaml.RNode) (string, string) {
meta, _ := n.GetMeta()
@@ -201,11 +208,14 @@ func GetContainerName(n *yaml.RNode) (string, string) {
// path to the function, this will be mounted into the container
path := meta.Annotations[kioutil.PathAnnotation]
functionAnnotation := meta.Annotations["config.k8s.io/function"]
if functionAnnotation != "" {
annotationContent, _ := yaml.Parse(functionAnnotation)
image, _ := annotationContent.Pipe(yaml.Lookup("container", "image"))
return image.YNode().Value, path
// check previous keys for backwards compatibility
for _, s := range functionAnnotationKeys {
functionAnnotation := meta.Annotations[s]
if functionAnnotation != "" {
annotationContent, _ := yaml.Parse(functionAnnotation)
image, _ := annotationContent.Pipe(yaml.Lookup("container", "image"))
return image.YNode().Value, path
}
}
container := meta.Annotations["config.kubernetes.io/container"]

View File

@@ -333,12 +333,12 @@ metadata:
c, _ = GetContainerName(n)
assert.Equal(t, "gcr.io/foo/bar:something", c)
// container from config.k8s.io/function annotation
// container from config.kubernetes.io/function annotation
n, err = yaml.Parse(`apiVersion: v1
kind: MyThing
metadata:
annotations:
config.k8s.io/function: |
config.kubernetes.io/function: |
container:
image: gcr.io/foo/bar:something
`)

View File

@@ -20,9 +20,6 @@ const (
// PathAnnotation records the path to the file the Resource was read from
PathAnnotation AnnotationKey = "config.kubernetes.io/path"
// PackageAnnotation records the name of the package the Resource was read from
PackageAnnotation AnnotationKey = "config.kubernetes.io/package"
)
func GetFileAnnotations(rn *yaml.RNode) (string, string, error) {

View File

@@ -263,7 +263,6 @@ func (r *LocalPackageReader) initReaderAnnotations(path string, _ os.FileInfo) {
r.SetAnnotations = map[string]string{}
}
if !r.OmitReaderAnnotations {
r.SetAnnotations[kioutil.PackageAnnotation] = filepath.Dir(path)
r.SetAnnotations[kioutil.PathAnnotation] = path
}
}

View File

@@ -109,14 +109,12 @@ func TestLocalPackageReader_Read_pkg(t *testing.T) {
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'a_test.yaml'
`,
`# second thing
@@ -128,7 +126,6 @@ g:
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'b_test.yaml'
`,
}
@@ -171,14 +168,12 @@ func TestLocalPackageReader_Read_file(t *testing.T) {
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/package: '.'
config.kubernetes.io/path: 'a_test.yaml'
`,
}
@@ -270,14 +265,12 @@ func TestLocalPackageReader_Read_nestedDirs(t *testing.T) {
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/package: 'a${SEP}b'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/package: 'a${SEP}b'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`# second thing
@@ -289,7 +282,6 @@ g:
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/package: 'a${SEP}b'
config.kubernetes.io/path: 'a${SEP}b${SEP}b_test.yaml'
`,
}
@@ -328,14 +320,12 @@ func TestLocalPackageReader_Read_matchRegex(t *testing.T) {
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/package: 'a${SEP}b'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/package: 'a${SEP}b'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
}
@@ -371,14 +361,12 @@ func TestLocalPackageReader_Read_skipSubpackage(t *testing.T) {
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/package: 'a${SEP}b'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/package: 'a${SEP}b'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
}
@@ -414,14 +402,12 @@ func TestLocalPackageReader_Read_includeSubpackage(t *testing.T) {
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/package: 'a${SEP}b'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/package: 'a${SEP}b'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`# second thing
@@ -433,7 +419,6 @@ g:
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/package: 'a${SEP}c'
config.kubernetes.io/path: 'a${SEP}c${SEP}c_test.yaml'
`,
}

View File

@@ -58,7 +58,6 @@ func (r LocalPackageWriter) Write(nodes []*yaml.RNode) error {
}
if !r.KeepReaderAnnotations {
r.ClearAnnotations = append(r.ClearAnnotations, kioutil.PackageAnnotation)
r.ClearAnnotations = append(r.ClearAnnotations, kioutil.PathAnnotation)
}

View File

@@ -258,7 +258,7 @@ func (p TreeWriter) index(nodes []*yaml.RNode) map[string][]*yaml.RNode {
// not a resource
continue
}
pkg := meta.Annotations[kioutil.PackageAnnotation]
pkg := filepath.Dir(meta.Annotations[kioutil.PathAnnotation])
indexByPackage[pkg] = append(indexByPackage[pkg], nodes[i])
}
return indexByPackage

View File

@@ -21,8 +21,7 @@ metadata:
namespace: default
annotations:
app: nginx3
config.kubernetes.io/package: foo-package/3
config.kubernetes.io/path: f3.yaml
config.kubernetes.io/path: foo-package/3/f3.yaml
spec:
replicas: 1
---
@@ -34,8 +33,7 @@ metadata:
namespace: default
annotations:
app: nginx2
config.kubernetes.io/package: foo-package
config.kubernetes.io/path: f1.yaml
config.kubernetes.io/path: foo-package/f1.yaml
spec:
replicas: 1
---
@@ -45,8 +43,7 @@ metadata:
app: nginx
annotations:
app: nginx
config.kubernetes.io/package: bar-package
config.kubernetes.io/path: f2.yaml
config.kubernetes.io/path: bar-package/f2.yaml
name: bar
spec:
replicas: 3
@@ -57,8 +54,7 @@ metadata:
namespace: default
annotations:
app: nginx
config.kubernetes.io/package: foo-package
config.kubernetes.io/path: f1.yaml
config.kubernetes.io/path: foo-package/f1.yaml
spec:
selector:
app: nginx
@@ -94,7 +90,6 @@ metadata:
namespace: default
annotations:
app: nginx3
config.kubernetes.io/package: .
config.kubernetes.io/path: f3.yaml
spec:
replicas: 1
@@ -107,8 +102,7 @@ metadata:
namespace: default
annotations:
app: nginx2
config.kubernetes.io/package: foo-package
config.kubernetes.io/path: f1.yaml
config.kubernetes.io/path: foo-package/f1.yaml
spec:
replicas: 1
---
@@ -118,8 +112,8 @@ metadata:
app: nginx
annotations:
app: nginx
config.kubernetes.io/package: bar-package
config.kubernetes.io/path: f2.yaml
config.kubernetes.io/package:
config.kubernetes.io/path: bar-package/f2.yaml
name: bar
spec:
replicas: 3
@@ -167,7 +161,6 @@ metadata:
namespace: default
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
@@ -181,7 +174,6 @@ metadata:
namespace: default
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
@@ -195,7 +187,6 @@ metadata:
namespace: default
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
@@ -209,7 +200,6 @@ metadata:
namespace: default2
annotations:
app: nginx2
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
@@ -223,7 +213,6 @@ metadata:
namespace: default
annotations:
app: nginx3
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
replicas: 1
@@ -234,8 +223,7 @@ metadata:
app: nginx
annotations:
app: nginx
config.kubernetes.io/package: bar-package
config.kubernetes.io/path: f2.yaml
config.kubernetes.io/path: bar-package/f2.yaml
name: bar
spec:
replicas: 3
@@ -246,7 +234,6 @@ metadata:
namespace: default
annotations:
app: nginx
config.kubernetes.io/package: .
config.kubernetes.io/path: f1.yaml
spec:
selector:
@@ -550,13 +537,12 @@ metadata:
}
if !assert.Equal(t, `
──
├── [.] Service myapp-staging/cockroachdb
├── [.] StatefulSet myapp-staging/cockroachdb
├── [.] Pod myapp-staging/cockroachdb-0
├── [.] Pod myapp-staging/cockroachdb-1
── [.] Pod myapp-staging/cockroachdb-2
└── [.] Application myapp-staging/myapp
── [.] Service myapp-staging/cockroachdb
├── [.] StatefulSet myapp-staging/cockroachdb
├── [.] Pod myapp-staging/cockroachdb-0
├── [.] Pod myapp-staging/cockroachdb-1
├── [.] Pod myapp-staging/cockroachdb-2
── [.] Application myapp-staging/myapp
`, out.String()) {
t.FailNow()
}

View File

@@ -6,7 +6,6 @@ package yaml
import (
"bytes"
"fmt"
"reflect"
"strings"
"gopkg.in/yaml.v3"
@@ -70,6 +69,14 @@ func IsFieldEmpty(node *MapNode) bool {
return false
}
// GetValue returns underlying yaml.Node Value field
func GetValue(node *RNode) string {
if IsEmpty(node) {
return ""
}
return node.YNode().Value
}
func IsFieldNull(node *MapNode) bool {
return node != nil && node.Value != nil && node.Value.YNode() != nil &&
node.Value.YNode().Tag == NullNodeTag
@@ -280,23 +287,79 @@ func (r *ResourceIdentifier) GetKind() string {
var ErrMissingMetadata = fmt.Errorf("missing Resource metadata")
// GetMeta returns the ResourceMeta for a RNode
// Field names
const (
AnnotationsField = "annotations"
APIVersionField = "apiVersion"
KindField = "kind"
MetadataField = "metadata"
NameField = "name"
NamespaceField = "namespace"
LabelsField = "labels"
)
// GetMeta returns the ResourceMeta for an RNode
func (rn *RNode) GetMeta() (ResourceMeta, error) {
if IsEmpty(rn) {
return ResourceMeta{}, nil
}
missingMeta := true
n := rn
if n.YNode().Kind == DocumentNode {
// get the content is this is the document node
n = NewRNode(n.Content()[0])
}
// don't decode into the struct directly or it will fail on UTF-8 issues
// which appear in comments
m := ResourceMeta{}
b := &bytes.Buffer{}
e := NewEncoder(b)
if err := e.Encode(rn.YNode()); err != nil {
return m, errors.Wrap(err)
// TODO: consider optimizing this parsing
if f := n.Field(APIVersionField); !IsFieldEmpty(f) {
m.APIVersion = GetValue(f.Value)
missingMeta = false
}
if err := e.Close(); err != nil {
return m, errors.Wrap(err)
if f := n.Field(KindField); !IsFieldEmpty(f) {
m.Kind = GetValue(f.Value)
missingMeta = false
}
d := yaml.NewDecoder(b)
d.KnownFields(false) // only want to parse the metadata
if err := d.Decode(&m); err != nil {
return m, errors.Wrap(err)
mf := n.Field(MetadataField)
if IsFieldEmpty(mf) {
if missingMeta {
return m, ErrMissingMetadata
}
return m, nil
}
if reflect.DeepEqual(m, ResourceMeta{}) {
meta := mf.Value
if f := meta.Field(NameField); !IsFieldEmpty(f) {
m.Name = f.Value.YNode().Value
missingMeta = false
}
if f := meta.Field(NamespaceField); !IsFieldEmpty(f) {
m.Namespace = GetValue(f.Value)
missingMeta = false
}
if f := meta.Field(LabelsField); !IsFieldEmpty(f) {
m.Labels = map[string]string{}
_ = f.Value.VisitFields(func(node *MapNode) error {
m.Labels[GetValue(node.Key)] = GetValue(node.Value)
return nil
})
missingMeta = false
}
if f := meta.Field(AnnotationsField); !IsFieldEmpty(f) {
m.Annotations = map[string]string{}
_ = f.Value.VisitFields(func(node *MapNode) error {
m.Annotations[GetValue(node.Key)] = GetValue(node.Value)
return nil
})
missingMeta = false
}
if missingMeta {
return m, ErrMissingMetadata
}
return m, nil

49
kyaml/yaml/types_test.go Normal file
View File

@@ -0,0 +1,49 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"testing"
"github.com/stretchr/testify/assert"
)
// Test that non-UTF8 characters in comments don't cause failures
func TestRNode_GetMeta_UTF16(t *testing.T) {
sr, err := Parse(`apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRole
metadata:
name: wildcard
namespace: default
# If set to [“*”], it refers to all services in the namespace
annotations:
foo: bar
spec:
rules:
# There is one service in default namespace, should not result in a validation error
# If set to [“*”], it refers to all services in the namespace
- services: ["*"]
methods: ["GET", "HEAD"]
`)
if !assert.NoError(t, err) {
t.FailNow()
}
actual, err := sr.GetMeta()
if !assert.NoError(t, err) {
t.FailNow()
}
expected := ResourceMeta{
APIVersion: "rbac.istio.io/v1alpha1",
Kind: "ServiceRole",
ObjectMeta: ObjectMeta{
Name: "wildcard",
Namespace: "default",
Annotations: map[string]string{"foo": "bar"},
},
}
if !assert.Equal(t, expected, actual) {
t.FailNow()
}
}