mirror of https://github.com/k3s-io/k3s
Merge pull request #73033 from Liujingfang1/kustomizeSubcommand
add kustomize as a subcommand in kubectlpull/564/head
commit
66149bd61d
|
@ -4209,6 +4209,116 @@
|
||||||
"ImportPath": "k8s.io/utils/trace",
|
"ImportPath": "k8s.io/utils/trace",
|
||||||
"Rev": "ed37f7428a91fc2a81070808937195dcd46d320e"
|
"Rev": "ed37f7428a91fc2a81070808937195dcd46d320e"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/commands/build",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/constants",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/expansion",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/factory",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/fs",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/git",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/gvk",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/ifc",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/ifc/transformer",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/image",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/internal/error",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/loader",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/patch",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/patch/transformer",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/resid",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/resmap",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/resource",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/target",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/transformers",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/transformers/config",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/transformers/config/defaultconfig",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/types",
|
||||||
|
"Comment": "v2.0.1",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
|
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
|
||||||
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
|
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -193,6 +193,7 @@ package_group(
|
||||||
"//pkg/kubectl/cmd/expose",
|
"//pkg/kubectl/cmd/expose",
|
||||||
"//pkg/kubectl/cmd/get",
|
"//pkg/kubectl/cmd/get",
|
||||||
"//pkg/kubectl/cmd/help",
|
"//pkg/kubectl/cmd/help",
|
||||||
|
"//pkg/kubectl/cmd/kustomize",
|
||||||
"//pkg/kubectl/cmd/label",
|
"//pkg/kubectl/cmd/label",
|
||||||
"//pkg/kubectl/cmd/logs",
|
"//pkg/kubectl/cmd/logs",
|
||||||
"//pkg/kubectl/cmd/options",
|
"//pkg/kubectl/cmd/options",
|
||||||
|
|
|
@ -38,6 +38,7 @@ go_library(
|
||||||
"//pkg/kubectl/cmd/explain:go_default_library",
|
"//pkg/kubectl/cmd/explain:go_default_library",
|
||||||
"//pkg/kubectl/cmd/expose:go_default_library",
|
"//pkg/kubectl/cmd/expose:go_default_library",
|
||||||
"//pkg/kubectl/cmd/get:go_default_library",
|
"//pkg/kubectl/cmd/get:go_default_library",
|
||||||
|
"//pkg/kubectl/cmd/kustomize:go_default_library",
|
||||||
"//pkg/kubectl/cmd/label:go_default_library",
|
"//pkg/kubectl/cmd/label:go_default_library",
|
||||||
"//pkg/kubectl/cmd/logs:go_default_library",
|
"//pkg/kubectl/cmd/logs:go_default_library",
|
||||||
"//pkg/kubectl/cmd/options:go_default_library",
|
"//pkg/kubectl/cmd/options:go_default_library",
|
||||||
|
@ -116,6 +117,7 @@ filegroup(
|
||||||
"//pkg/kubectl/cmd/expose:all-srcs",
|
"//pkg/kubectl/cmd/expose:all-srcs",
|
||||||
"//pkg/kubectl/cmd/get:all-srcs",
|
"//pkg/kubectl/cmd/get:all-srcs",
|
||||||
"//pkg/kubectl/cmd/help:all-srcs",
|
"//pkg/kubectl/cmd/help:all-srcs",
|
||||||
|
"//pkg/kubectl/cmd/kustomize:all-srcs",
|
||||||
"//pkg/kubectl/cmd/label:all-srcs",
|
"//pkg/kubectl/cmd/label:all-srcs",
|
||||||
"//pkg/kubectl/cmd/logs:all-srcs",
|
"//pkg/kubectl/cmd/logs:all-srcs",
|
||||||
"//pkg/kubectl/cmd/options:all-srcs",
|
"//pkg/kubectl/cmd/options:all-srcs",
|
||||||
|
|
|
@ -73,6 +73,7 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/kubectl/util/templates"
|
"k8s.io/kubernetes/pkg/kubectl/util/templates"
|
||||||
|
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/cmd/kustomize"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -521,6 +522,7 @@ func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
|
||||||
replace.NewCmdReplace(f, ioStreams),
|
replace.NewCmdReplace(f, ioStreams),
|
||||||
wait.NewCmdWait(f, ioStreams),
|
wait.NewCmdWait(f, ioStreams),
|
||||||
convert.NewCmdConvert(f, ioStreams),
|
convert.NewCmdConvert(f, ioStreams),
|
||||||
|
kustomize.NewCmdKustomize(ioStreams),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["kustomize.go"],
|
||||||
|
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/kustomize",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/kubectl/util/i18n:go_default_library",
|
||||||
|
"//pkg/kubectl/util/templates:go_default_library",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize:go_default_library",
|
||||||
|
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["kustomize_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
)
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kustomize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
|
||||||
|
"k8s.io/kubernetes/pkg/kubectl/util/templates"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type kustomizeOptions struct {
|
||||||
|
kustomizationDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
kustomizeLong = templates.LongDesc(i18n.T(`
|
||||||
|
Print a set of API resources generated from instructions in a kustomization.yaml file.
|
||||||
|
|
||||||
|
The argument must be the path to the directory containing
|
||||||
|
the file, or a git repository
|
||||||
|
URL with a path suffix specifying same with respect to the
|
||||||
|
repository root.
|
||||||
|
|
||||||
|
kubectl kustomize somedir
|
||||||
|
`))
|
||||||
|
|
||||||
|
kustomizeExample = templates.Examples(i18n.T(`
|
||||||
|
# Use the current working directory
|
||||||
|
kubectl kustomize .
|
||||||
|
|
||||||
|
# Use some shared configuration directory
|
||||||
|
kubectl kustomize /home/configuration/production
|
||||||
|
|
||||||
|
# Use a URL
|
||||||
|
kubectl kustomize github.com/kubernetes-sigs/kustomize.git/examples/helloWorld?ref=v1.0.6
|
||||||
|
`))
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCmdKustomize returns a kustomize command
|
||||||
|
func NewCmdKustomize(streams genericclioptions.IOStreams) *cobra.Command {
|
||||||
|
var o kustomizeOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "kustomize <dir>",
|
||||||
|
Short: i18n.T("Build a kustomization target from a directory or a remote url."),
|
||||||
|
Long: kustomizeLong,
|
||||||
|
Example: kustomizeExample,
|
||||||
|
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := o.Validate(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return kustomize.RunKustomizeBuild(streams.Out, fs.MakeRealFS(), o.kustomizationDir)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates build command.
|
||||||
|
func (o *kustomizeOptions) Validate(args []string) error {
|
||||||
|
if len(args) > 1 {
|
||||||
|
return errors.New("specify one path to a kustomization directory")
|
||||||
|
}
|
||||||
|
if len(args) == 0 {
|
||||||
|
o.kustomizationDir = "./"
|
||||||
|
} else {
|
||||||
|
o.kustomizationDir = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kustomize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidate(t *testing.T) {
|
||||||
|
var cases = []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
path string
|
||||||
|
erMsg string
|
||||||
|
}{
|
||||||
|
{"noargs", []string{}, "./", ""},
|
||||||
|
{"file", []string{"beans"}, "beans", ""},
|
||||||
|
{"path", []string{"a/b/c"}, "a/b/c", ""},
|
||||||
|
{"path", []string{"too", "many"},
|
||||||
|
"", "specify one path to a kustomization directory"},
|
||||||
|
}
|
||||||
|
for _, mycase := range cases {
|
||||||
|
opts := kustomizeOptions{}
|
||||||
|
e := opts.Validate(mycase.args)
|
||||||
|
if len(mycase.erMsg) > 0 {
|
||||||
|
if e == nil {
|
||||||
|
t.Errorf("%s: Expected an error %v", mycase.name, mycase.erMsg)
|
||||||
|
}
|
||||||
|
if e.Error() != mycase.erMsg {
|
||||||
|
t.Errorf("%s: Expected error %s, but got %v", mycase.name, mycase.erMsg, e)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if e != nil {
|
||||||
|
t.Errorf("%s: unknown error %v", mycase.name, e)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if opts.kustomizationDir != mycase.path {
|
||||||
|
t.Errorf("%s: expected path '%s', got '%s'", mycase.name, mycase.path, opts.kustomizationDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,6 +110,7 @@ filegroup(
|
||||||
"//staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook:all-srcs",
|
"//staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook:all-srcs",
|
||||||
"//staging/src/k8s.io/cli-runtime/artifacts:all-srcs",
|
"//staging/src/k8s.io/cli-runtime/artifacts:all-srcs",
|
||||||
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:all-srcs",
|
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:all-srcs",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/deprecated-dynamic:all-srcs",
|
"//staging/src/k8s.io/client-go/deprecated-dynamic:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/discovery:all-srcs",
|
"//staging/src/k8s.io/client-go/discovery:all-srcs",
|
||||||
"//staging/src/k8s.io/client-go/dynamic:all-srcs",
|
"//staging/src/k8s.io/client-go/dynamic:all-srcs",
|
||||||
|
|
|
@ -6,14 +6,50 @@
|
||||||
"./..."
|
"./..."
|
||||||
],
|
],
|
||||||
"Deps": [
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/PuerkitoBio/purell",
|
||||||
|
"Rev": "8a290539e2e8629dbc4e6bad948158f790ec31f4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/PuerkitoBio/urlesc",
|
||||||
|
"Rev": "5bd2802263f21d8788851d5305584c82a5c75d7e"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||||
"Rev": "782f4967f2dc4564575ca782fe2d04090b5faca8"
|
"Rev": "782f4967f2dc4564575ca782fe2d04090b5faca8"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/emicklei/go-restful",
|
||||||
|
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/emicklei/go-restful/log",
|
||||||
|
"Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/evanphx/json-patch",
|
"ImportPath": "github.com/evanphx/json-patch",
|
||||||
"Rev": "5858425f75500d40c52783dce87d085a483ce135"
|
"Rev": "5858425f75500d40c52783dce87d085a483ce135"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/ghodss/yaml",
|
||||||
|
"Rev": "c7ce16629ff4cd059ed96ed06419dd3856fd3577"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/go-openapi/jsonpointer",
|
||||||
|
"Rev": "ef5f0afec364d3b9396b7b77b43dbe26bf1f8004"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/go-openapi/jsonreference",
|
||||||
|
"Rev": "8483a886a90412cd6858df4ea3483dce9c8e35a3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/go-openapi/spec",
|
||||||
|
"Rev": "5bae59e25b21498baea7f9d46e9c147ec106a42e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/go-openapi/swag",
|
||||||
|
"Rev": "5899d5c5e619fda5fa86e14795a835f473ca284c"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/gogo/protobuf/proto",
|
"ImportPath": "github.com/gogo/protobuf/proto",
|
||||||
"Rev": "342cbe0a04158f6dcb03ca0079991a51a4248c02"
|
"Rev": "342cbe0a04158f6dcb03ca0079991a51a4248c02"
|
||||||
|
@ -82,6 +118,18 @@
|
||||||
"ImportPath": "github.com/json-iterator/go",
|
"ImportPath": "github.com/json-iterator/go",
|
||||||
"Rev": "ab8a2e0c74be9d3be70b3184d9acc634935ded82"
|
"Rev": "ab8a2e0c74be9d3be70b3184d9acc634935ded82"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/mailru/easyjson/buffer",
|
||||||
|
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/mailru/easyjson/jlexer",
|
||||||
|
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/mailru/easyjson/jwriter",
|
||||||
|
"Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/modern-go/concurrent",
|
"ImportPath": "github.com/modern-go/concurrent",
|
||||||
"Rev": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
"Rev": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
|
||||||
|
@ -94,6 +142,10 @@
|
||||||
"ImportPath": "github.com/peterbourgon/diskv",
|
"ImportPath": "github.com/peterbourgon/diskv",
|
||||||
"Rev": "5f041e8faa004a95c88a202771f4cc3e991971e6"
|
"Rev": "5f041e8faa004a95c88a202771f4cc3e991971e6"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/pkg/errors",
|
||||||
|
"Rev": "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
||||||
"Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
|
"Rev": "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
|
||||||
|
@ -190,6 +242,10 @@
|
||||||
"ImportPath": "golang.org/x/text/unicode/norm",
|
"ImportPath": "golang.org/x/text/unicode/norm",
|
||||||
"Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01"
|
"Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/text/width",
|
||||||
|
"Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/time/rate",
|
"ImportPath": "golang.org/x/time/rate",
|
||||||
"Rev": "f51c12702a4d776e4c1fa9b0fabab841babae631"
|
"Rev": "f51c12702a4d776e4c1fa9b0fabab841babae631"
|
||||||
|
@ -350,6 +406,10 @@
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
|
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "k8s.io/apimachinery/pkg/api/validation",
|
||||||
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
|
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
@ -362,6 +422,10 @@
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme",
|
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/validation",
|
||||||
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1",
|
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
@ -442,6 +506,10 @@
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
|
||||||
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/naming",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/naming",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
@ -458,6 +526,10 @@
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||||
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
@ -478,6 +550,10 @@
|
||||||
"ImportPath": "k8s.io/apimachinery/pkg/watch",
|
"ImportPath": "k8s.io/apimachinery/pkg/watch",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
|
||||||
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
|
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
@ -566,6 +642,14 @@
|
||||||
"ImportPath": "k8s.io/klog",
|
"ImportPath": "k8s.io/klog",
|
||||||
"Rev": "8139d8cb77af419532b33dfa7dd09fbc5f1d344f"
|
"Rev": "8139d8cb77af419532b33dfa7dd09fbc5f1d344f"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "k8s.io/kube-openapi/pkg/common",
|
||||||
|
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto",
|
||||||
|
"Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "k8s.io/client-go/discovery",
|
"ImportPath": "k8s.io/client-go/discovery",
|
||||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
@ -614,6 +698,94 @@
|
||||||
"ImportPath": "k8s.io/utils/integer",
|
"ImportPath": "k8s.io/utils/integer",
|
||||||
"Rev": "ed37f7428a91fc2a81070808937195dcd46d320e"
|
"Rev": "ed37f7428a91fc2a81070808937195dcd46d320e"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/commands/build",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/constants",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/expansion",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/factory",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/fs",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/git",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/gvk",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/ifc",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/ifc/transformer",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/image",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/internal/error",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/loader",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/patch",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/patch/transformer",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/resid",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/resmap",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/resource",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/target",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/transformers",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/transformers/config",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/transformers/config/defaultconfig",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "sigs.k8s.io/kustomize/pkg/types",
|
||||||
|
"Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
|
"ImportPath": "sigs.k8s.io/structured-merge-diff/fieldpath",
|
||||||
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
|
"Rev": "e5e029740eb81ee0217ecf9d950c25a0eeb9688a"
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["builder.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize",
|
||||||
|
importpath = "k8s.io/cli-runtime/pkg/kustomize",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/commands/build:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps:all-srcs",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["builder_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = ["//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library"],
|
||||||
|
)
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kustomize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/commands/build"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RunKustomizeBuild runs kustomize build given a filesystem and a path
|
||||||
|
func RunKustomizeBuild(out io.Writer, fSys fs.FileSystem, path string) error {
|
||||||
|
f := k8sdeps.NewFactory()
|
||||||
|
o := build.NewOptions(path, "")
|
||||||
|
return o.RunBuild(out, fSys, f.ResmapF, f.TransformerF)
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kustomize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/pkg/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
kustomizationContent1 = `
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namePrefix: foo-
|
||||||
|
nameSuffix: -bar
|
||||||
|
namespace: ns1
|
||||||
|
commonLabels:
|
||||||
|
app: nginx
|
||||||
|
commonAnnotations:
|
||||||
|
note: This is a test annotation
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
- namespace.yaml
|
||||||
|
configMapGenerator:
|
||||||
|
- name: literalConfigMap
|
||||||
|
literals:
|
||||||
|
- DB_USERNAME=admin
|
||||||
|
- DB_PASSWORD=somepw
|
||||||
|
secretGenerator:
|
||||||
|
- name: secret
|
||||||
|
literals:
|
||||||
|
- DB_USERNAME=admin
|
||||||
|
- DB_PASSWORD=somepw
|
||||||
|
type: Opaque
|
||||||
|
patchesJson6902:
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: Deployment
|
||||||
|
name: dply1
|
||||||
|
path: jsonpatch.json
|
||||||
|
`
|
||||||
|
deploymentContent = `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
metadata:
|
||||||
|
name: dply1
|
||||||
|
kind: Deployment
|
||||||
|
`
|
||||||
|
namespaceContent = `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: ns1
|
||||||
|
`
|
||||||
|
jsonpatchContent = `[
|
||||||
|
{"op": "add", "path": "/spec/replica", "value": "3"}
|
||||||
|
]`
|
||||||
|
|
||||||
|
expectedContent = `apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
note: This is a test annotation
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
name: foo-ns1-bar
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
DB_PASSWORD: somepw
|
||||||
|
DB_USERNAME: admin
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
note: This is a test annotation
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
name: foo-literalConfigMap-bar-8d2dkb8k24
|
||||||
|
namespace: ns1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
DB_PASSWORD: c29tZXB3
|
||||||
|
DB_USERNAME: YWRtaW4=
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
note: This is a test annotation
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
name: foo-secret-bar-9btc7bt4kb
|
||||||
|
namespace: ns1
|
||||||
|
type: Opaque
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
note: This is a test annotation
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
name: foo-dply1-bar
|
||||||
|
namespace: ns1
|
||||||
|
spec:
|
||||||
|
replica: "3"
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: nginx
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
note: This is a test annotation
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResources1(t *testing.T) {
|
||||||
|
fSys := fs.MakeFakeFS()
|
||||||
|
fSys.WriteFile("/kustomization.yaml", []byte(kustomizationContent1))
|
||||||
|
fSys.WriteFile("/deployment.yaml", []byte(deploymentContent))
|
||||||
|
fSys.WriteFile("/namespace.yaml", []byte(namespaceContent))
|
||||||
|
fSys.WriteFile("/jsonpatch.json", []byte(jsonpatchContent))
|
||||||
|
|
||||||
|
var out bytes.Buffer
|
||||||
|
err := RunKustomizeBuild(&out, fSys, "/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected Resources error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.String() != expectedContent {
|
||||||
|
t.Fatalf("expected:\n%s\nbut got:\n%s", expectedContent, out.String())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"doc.go",
|
||||||
|
"factory.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps",
|
||||||
|
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct:go_default_library",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer:go_default_library",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/factory:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret:all-srcs",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct:all-srcs",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv:all-srcs",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer:all-srcs",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator:all-srcs",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,53 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"configmapfactory.go",
|
||||||
|
"kv.go",
|
||||||
|
"secretfactory.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret",
|
||||||
|
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv:go_default_library",
|
||||||
|
"//vendor/github.com/pkg/errors:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = [
|
||||||
|
"configmapfactory_test.go",
|
||||||
|
"kv_test.go",
|
||||||
|
"secretfactory_test.go",
|
||||||
|
],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/loader:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package configmapandsecret generates configmaps and secrets per generator rules.
|
||||||
|
package configmapandsecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigMapFactory makes ConfigMaps.
|
||||||
|
type ConfigMapFactory struct {
|
||||||
|
ldr ifc.Loader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigMapFactory returns a new ConfigMapFactory.
|
||||||
|
func NewConfigMapFactory(l ifc.Loader) *ConfigMapFactory {
|
||||||
|
return &ConfigMapFactory{ldr: l}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ConfigMapFactory) makeFreshConfigMap(
|
||||||
|
args *types.ConfigMapArgs) *corev1.ConfigMap {
|
||||||
|
cm := &corev1.ConfigMap{}
|
||||||
|
cm.APIVersion = "v1"
|
||||||
|
cm.Kind = "ConfigMap"
|
||||||
|
cm.Name = args.Name
|
||||||
|
cm.Namespace = args.Namespace
|
||||||
|
cm.Data = map[string]string{}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeConfigMap returns a new ConfigMap, or nil and an error.
|
||||||
|
func (f *ConfigMapFactory) MakeConfigMap(
|
||||||
|
args *types.ConfigMapArgs, options *types.GeneratorOptions) (*corev1.ConfigMap, error) {
|
||||||
|
var all []kv.Pair
|
||||||
|
var err error
|
||||||
|
cm := f.makeFreshConfigMap(args)
|
||||||
|
|
||||||
|
pairs, err := keyValuesFromEnvFile(f.ldr, args.EnvSource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||||
|
"env source file: %s",
|
||||||
|
args.EnvSource))
|
||||||
|
}
|
||||||
|
all = append(all, pairs...)
|
||||||
|
|
||||||
|
pairs, err = keyValuesFromLiteralSources(args.LiteralSources)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||||
|
"literal sources %v", args.LiteralSources))
|
||||||
|
}
|
||||||
|
all = append(all, pairs...)
|
||||||
|
|
||||||
|
pairs, err = keyValuesFromFileSources(f.ldr, args.FileSources)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||||
|
"file sources: %v", args.FileSources))
|
||||||
|
}
|
||||||
|
all = append(all, pairs...)
|
||||||
|
|
||||||
|
for _, kv := range all {
|
||||||
|
err = addKvToConfigMap(cm, kv.Key, kv.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options != nil {
|
||||||
|
cm.SetLabels(options.Labels)
|
||||||
|
cm.SetAnnotations(options.Annotations)
|
||||||
|
}
|
||||||
|
return cm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addKvToConfigMap adds the given key and data to the given config map.
|
||||||
|
// Error if key invalid, or already exists.
|
||||||
|
func addKvToConfigMap(configMap *v1.ConfigMap, keyName, data string) error {
|
||||||
|
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
|
||||||
|
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
|
||||||
|
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";"))
|
||||||
|
}
|
||||||
|
|
||||||
|
keyExistsErrorMsg := "cannot add key %s, another key by that name already exists: %v"
|
||||||
|
|
||||||
|
// If the configmap data contains byte sequences that are all in the UTF-8
|
||||||
|
// range, we will write it to .Data
|
||||||
|
if utf8.Valid([]byte(data)) {
|
||||||
|
if _, entryExists := configMap.Data[keyName]; entryExists {
|
||||||
|
return fmt.Errorf(keyExistsErrorMsg, keyName, configMap.Data)
|
||||||
|
}
|
||||||
|
configMap.Data[keyName] = data
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, it's BinaryData
|
||||||
|
if configMap.BinaryData == nil {
|
||||||
|
configMap.BinaryData = map[string][]byte{}
|
||||||
|
}
|
||||||
|
if _, entryExists := configMap.BinaryData[keyName]; entryExists {
|
||||||
|
return fmt.Errorf(keyExistsErrorMsg, keyName, configMap.BinaryData)
|
||||||
|
}
|
||||||
|
configMap.BinaryData[keyName] = []byte(data)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package configmapandsecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/fs"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/loader"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeEnvConfigMap(name string) *corev1.ConfigMap {
|
||||||
|
return &corev1.ConfigMap{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"DB_USERNAME": "admin",
|
||||||
|
"DB_PASSWORD": "somepw",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFileConfigMap(name string) *corev1.ConfigMap {
|
||||||
|
return &corev1.ConfigMap{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"app-init.ini": `FOO=bar
|
||||||
|
BAR=baz
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
BinaryData: map[string][]byte{
|
||||||
|
"app.bin": {0xff, 0xfd},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeLiteralConfigMap(name string) *corev1.ConfigMap {
|
||||||
|
cm := &corev1.ConfigMap{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"a": "x",
|
||||||
|
"b": "y",
|
||||||
|
"c": "Hello World",
|
||||||
|
"d": "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cm.SetLabels(map[string]string{"foo": "bar"})
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConstructConfigMap(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
description string
|
||||||
|
input types.ConfigMapArgs
|
||||||
|
options *types.GeneratorOptions
|
||||||
|
expected *corev1.ConfigMap
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []testCase{
|
||||||
|
{
|
||||||
|
description: "construct config map from env",
|
||||||
|
input: types.ConfigMapArgs{
|
||||||
|
GeneratorArgs: types.GeneratorArgs{
|
||||||
|
Name: "envConfigMap",
|
||||||
|
DataSources: types.DataSources{
|
||||||
|
EnvSource: "configmap/app.env",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: nil,
|
||||||
|
expected: makeEnvConfigMap("envConfigMap"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "construct config map from file",
|
||||||
|
input: types.ConfigMapArgs{
|
||||||
|
GeneratorArgs: types.GeneratorArgs{
|
||||||
|
Name: "fileConfigMap",
|
||||||
|
DataSources: types.DataSources{
|
||||||
|
FileSources: []string{"configmap/app-init.ini", "configmap/app.bin"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: nil,
|
||||||
|
expected: makeFileConfigMap("fileConfigMap"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "construct config map from literal",
|
||||||
|
input: types.ConfigMapArgs{
|
||||||
|
GeneratorArgs: types.GeneratorArgs{
|
||||||
|
Name: "literalConfigMap",
|
||||||
|
DataSources: types.DataSources{
|
||||||
|
LiteralSources: []string{"a=x", "b=y", "c=\"Hello World\"", "d='true'"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: &types.GeneratorOptions{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: makeLiteralConfigMap("literalConfigMap"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fSys := fs.MakeFakeFS()
|
||||||
|
fSys.WriteFile("/configmap/app.env", []byte("DB_USERNAME=admin\nDB_PASSWORD=somepw\n"))
|
||||||
|
fSys.WriteFile("/configmap/app-init.ini", []byte("FOO=bar\nBAR=baz\n"))
|
||||||
|
fSys.WriteFile("/configmap/app.bin", []byte{0xff, 0xfd})
|
||||||
|
f := NewConfigMapFactory(loader.NewFileLoaderAtRoot(fSys))
|
||||||
|
for _, tc := range testCases {
|
||||||
|
cm, err := f.MakeConfigMap(&tc.input, tc.options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*cm, *tc.expected) {
|
||||||
|
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, *cm, tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package configmapandsecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func keyValuesFromLiteralSources(sources []string) ([]kv.Pair, error) {
|
||||||
|
var kvs []kv.Pair
|
||||||
|
for _, s := range sources {
|
||||||
|
k, v, err := parseLiteralSource(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kvs = append(kvs, kv.Pair{Key: k, Value: v})
|
||||||
|
}
|
||||||
|
return kvs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyValuesFromFileSources(ldr ifc.Loader, sources []string) ([]kv.Pair, error) {
|
||||||
|
var kvs []kv.Pair
|
||||||
|
for _, s := range sources {
|
||||||
|
k, fPath, err := parseFileSource(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
content, err := ldr.Load(fPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kvs = append(kvs, kv.Pair{Key: k, Value: string(content)})
|
||||||
|
}
|
||||||
|
return kvs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyValuesFromEnvFile(l ifc.Loader, path string) ([]kv.Pair, error) {
|
||||||
|
if path == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
content, err := l.Load(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return kv.KeyValuesFromLines(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFileSource parses the source given.
|
||||||
|
//
|
||||||
|
// Acceptable formats include:
|
||||||
|
// 1. source-path: the basename will become the key name
|
||||||
|
// 2. source-name=source-path: the source-name will become the key name and
|
||||||
|
// source-path is the path to the key file.
|
||||||
|
//
|
||||||
|
// Key names cannot include '='.
|
||||||
|
func parseFileSource(source string) (keyName, filePath string, err error) {
|
||||||
|
numSeparators := strings.Count(source, "=")
|
||||||
|
switch {
|
||||||
|
case numSeparators == 0:
|
||||||
|
return path.Base(source), source, nil
|
||||||
|
case numSeparators == 1 && strings.HasPrefix(source, "="):
|
||||||
|
return "", "", fmt.Errorf("key name for file path %v missing", strings.TrimPrefix(source, "="))
|
||||||
|
case numSeparators == 1 && strings.HasSuffix(source, "="):
|
||||||
|
return "", "", fmt.Errorf("file path for key name %v missing", strings.TrimSuffix(source, "="))
|
||||||
|
case numSeparators > 1:
|
||||||
|
return "", "", errors.New("key names or file paths cannot contain '='")
|
||||||
|
default:
|
||||||
|
components := strings.Split(source, "=")
|
||||||
|
return components[0], components[1], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLiteralSource parses the source key=val pair into its component pieces.
|
||||||
|
// This functionality is distinguished from strings.SplitN(source, "=", 2) since
|
||||||
|
// it returns an error in the case of empty keys, values, or a missing equals sign.
|
||||||
|
func parseLiteralSource(source string) (keyName, value string, err error) {
|
||||||
|
// leading equal is invalid
|
||||||
|
if strings.Index(source, "=") == 0 {
|
||||||
|
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
|
||||||
|
}
|
||||||
|
// split after the first equal (so values can have the = character)
|
||||||
|
items := strings.SplitN(source, "=", 2)
|
||||||
|
if len(items) != 2 {
|
||||||
|
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
|
||||||
|
}
|
||||||
|
return items[0], strings.Trim(items[1], "\"'"), nil
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package configmapandsecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/fs"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKeyValuesFromFileSources(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
description string
|
||||||
|
sources []string
|
||||||
|
expected []kv.Pair
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "create kvs from file sources",
|
||||||
|
sources: []string{"files/app-init.ini"},
|
||||||
|
expected: []kv.Pair{
|
||||||
|
{
|
||||||
|
Key: "app-init.ini",
|
||||||
|
Value: "FOO=bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fSys := fs.MakeFakeFS()
|
||||||
|
fSys.WriteFile("/files/app-init.ini", []byte("FOO=bar"))
|
||||||
|
for _, tc := range tests {
|
||||||
|
kvs, err := keyValuesFromFileSources(loader.NewFileLoaderAtRoot(fSys), tc.sources)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(kvs, tc.expected) {
|
||||||
|
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, kvs, tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package configmapandsecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SecretFactory makes Secrets.
|
||||||
|
type SecretFactory struct {
|
||||||
|
ldr ifc.Loader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSecretFactory returns a new SecretFactory.
|
||||||
|
func NewSecretFactory(ldr ifc.Loader) *SecretFactory {
|
||||||
|
return &SecretFactory{ldr: ldr}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *SecretFactory) makeFreshSecret(args *types.SecretArgs) *corev1.Secret {
|
||||||
|
s := &corev1.Secret{}
|
||||||
|
s.APIVersion = "v1"
|
||||||
|
s.Kind = "Secret"
|
||||||
|
s.Name = args.Name
|
||||||
|
s.Namespace = args.Namespace
|
||||||
|
s.Type = corev1.SecretType(args.Type)
|
||||||
|
if s.Type == "" {
|
||||||
|
s.Type = corev1.SecretTypeOpaque
|
||||||
|
}
|
||||||
|
s.Data = map[string][]byte{}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeSecret returns a new secret.
|
||||||
|
func (f *SecretFactory) MakeSecret(args *types.SecretArgs, options *types.GeneratorOptions) (*corev1.Secret, error) {
|
||||||
|
var all []kv.Pair
|
||||||
|
var err error
|
||||||
|
s := f.makeFreshSecret(args)
|
||||||
|
|
||||||
|
pairs, err := keyValuesFromEnvFile(f.ldr, args.EnvSource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||||
|
"env source file: %s",
|
||||||
|
args.EnvSource))
|
||||||
|
}
|
||||||
|
all = append(all, pairs...)
|
||||||
|
|
||||||
|
pairs, err = keyValuesFromLiteralSources(args.LiteralSources)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||||
|
"literal sources %v", args.LiteralSources))
|
||||||
|
}
|
||||||
|
all = append(all, pairs...)
|
||||||
|
|
||||||
|
pairs, err = keyValuesFromFileSources(f.ldr, args.FileSources)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf(
|
||||||
|
"file sources: %v", args.FileSources))
|
||||||
|
}
|
||||||
|
all = append(all, pairs...)
|
||||||
|
|
||||||
|
for _, kv := range all {
|
||||||
|
err = addKvToSecret(s, kv.Key, kv.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options != nil {
|
||||||
|
s.SetLabels(options.Labels)
|
||||||
|
s.SetAnnotations(options.Annotations)
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addKvToSecret(secret *corev1.Secret, keyName, data string) error {
|
||||||
|
// Note, the rules for SecretKeys keys are the exact same as the ones for ConfigMap.
|
||||||
|
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
|
||||||
|
return fmt.Errorf("%q is not a valid key name for a Secret: %s", keyName, strings.Join(errs, ";"))
|
||||||
|
}
|
||||||
|
if _, entryExists := secret.Data[keyName]; entryExists {
|
||||||
|
return fmt.Errorf("cannot add key %s, another key by that name already exists", keyName)
|
||||||
|
}
|
||||||
|
secret.Data[keyName] = []byte(data)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package configmapandsecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/fs"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/loader"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeEnvSecret(name string) *corev1.Secret {
|
||||||
|
return &corev1.Secret{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"DB_PASSWORD": []byte("somepw"),
|
||||||
|
"DB_USERNAME": []byte("admin"),
|
||||||
|
},
|
||||||
|
Type: "Opaque",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFileSecret(name string) *corev1.Secret {
|
||||||
|
return &corev1.Secret{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"app-init.ini": []byte(`FOO=bar
|
||||||
|
BAR=baz
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
Type: "Opaque",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeLiteralSecret(name string) *corev1.Secret {
|
||||||
|
s := &corev1.Secret{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"a": []byte("x"),
|
||||||
|
"b": []byte("y"),
|
||||||
|
},
|
||||||
|
Type: "Opaque",
|
||||||
|
}
|
||||||
|
s.SetLabels(map[string]string{"foo": "bar"})
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConstructSecret(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
description string
|
||||||
|
input types.SecretArgs
|
||||||
|
options *types.GeneratorOptions
|
||||||
|
expected *corev1.Secret
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []testCase{
|
||||||
|
{
|
||||||
|
description: "construct secret from env",
|
||||||
|
input: types.SecretArgs{
|
||||||
|
GeneratorArgs: types.GeneratorArgs{
|
||||||
|
Name: "envSecret",
|
||||||
|
DataSources: types.DataSources{
|
||||||
|
EnvSource: "secret/app.env",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: nil,
|
||||||
|
expected: makeEnvSecret("envSecret"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "construct secret from file",
|
||||||
|
input: types.SecretArgs{
|
||||||
|
GeneratorArgs: types.GeneratorArgs{
|
||||||
|
Name: "fileSecret",
|
||||||
|
DataSources: types.DataSources{
|
||||||
|
FileSources: []string{"secret/app-init.ini"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: nil,
|
||||||
|
expected: makeFileSecret("fileSecret"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "construct secret from literal",
|
||||||
|
input: types.SecretArgs{
|
||||||
|
GeneratorArgs: types.GeneratorArgs{
|
||||||
|
Name: "literalSecret",
|
||||||
|
DataSources: types.DataSources{
|
||||||
|
LiteralSources: []string{"a=x", "b=y"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: &types.GeneratorOptions{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: makeLiteralSecret("literalSecret"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fSys := fs.MakeFakeFS()
|
||||||
|
fSys.WriteFile("/secret/app.env", []byte("DB_USERNAME=admin\nDB_PASSWORD=somepw\n"))
|
||||||
|
fSys.WriteFile("/secret/app-init.ini", []byte("FOO=bar\nBAR=baz\n"))
|
||||||
|
f := NewSecretFactory(loader.NewFileLoaderAtRoot(fSys))
|
||||||
|
for _, tc := range testCases {
|
||||||
|
cm, err := f.MakeSecret(&tc.input, tc.options)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*cm, *tc.expected) {
|
||||||
|
t.Fatalf("in testcase: %q updated:\n%#v\ndoesn't match expected:\n%#v\n", tc.description, *cm, tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// It's possible that kustomize's features will be vendored into
|
||||||
|
// the kubernetes/kubernetes repo and made available to kubectl
|
||||||
|
// commands, while at the same time the kustomize program will
|
||||||
|
// continue to exist as an independent CLI. Vendoring snapshots
|
||||||
|
// would be taken just before a kubectl release.
|
||||||
|
//
|
||||||
|
// This creates a problem in that freestanding-kustomize depends on
|
||||||
|
// (for example):
|
||||||
|
//
|
||||||
|
// https://github.com/kubernetes/apimachinery/
|
||||||
|
// tree/master/pkg/util/yaml
|
||||||
|
//
|
||||||
|
// It vendors that package into
|
||||||
|
// sigs.k8s.io/kustomize/vendor/k8s.io/apimachinery/
|
||||||
|
//
|
||||||
|
// Whereas kubectl-kustomize would have to depend on the "staging"
|
||||||
|
// version of this code, located at
|
||||||
|
//
|
||||||
|
// https://github.com/kubernetes/kubernetes/
|
||||||
|
// blob/master/staging/src/k8s.io/apimachinery/pkg/util/yaml
|
||||||
|
//
|
||||||
|
// which is "vendored" via symlinks:
|
||||||
|
// k8s.io/kubernetes/vendor/k8s.io/apimachinery
|
||||||
|
// is a symlink to
|
||||||
|
// ../../staging/src/k8s.io/apimachinery
|
||||||
|
//
|
||||||
|
// The staging version is the canonical, under-development
|
||||||
|
// version of the code that kubectl depends on, whereas the packages
|
||||||
|
// at kubernetes/apimachinery are periodic snapshots of staging made
|
||||||
|
// for outside tools to depend on.
|
||||||
|
//
|
||||||
|
// apimachinery isn't the only package that poses this problem, just
|
||||||
|
// using it as a specific example.
|
||||||
|
//
|
||||||
|
// The kubectl binary cannot vendor in kustomize code that in
|
||||||
|
// turn vendors in the non-staging packages.
|
||||||
|
//
|
||||||
|
// One way to fix some of this would be to copy code - a hard fork.
|
||||||
|
// This has all the problems associated with a hard forking.
|
||||||
|
//
|
||||||
|
// Another way would be to break the kustomize repo into three:
|
||||||
|
//
|
||||||
|
// (1) kustomize - repo with the main() function,
|
||||||
|
// vendoring (2) and (3).
|
||||||
|
//
|
||||||
|
// (2) kustomize-libs - packages used by (1) with no
|
||||||
|
// apimachinery dependence.
|
||||||
|
//
|
||||||
|
// (3) kustomize-k8sdeps - A thin code layer that depends
|
||||||
|
// on (vendors) apimachinery to provide thin implementations
|
||||||
|
// to interfaces used in (2).
|
||||||
|
//
|
||||||
|
// The kubectl repo would then vendor from (2) only, and have
|
||||||
|
// a local implementation of (3). With that in mind, it's clear
|
||||||
|
// that (3) doesn't have to be a repo; the kustomize version of
|
||||||
|
// the thin layer can live directly in (1).
|
||||||
|
//
|
||||||
|
// This package is the code in (3), meant for kustomize.
|
||||||
|
|
||||||
|
package k8sdeps
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package k8sdeps provides kustomize factory with k8s dependencies
|
||||||
|
package k8sdeps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct"
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer"
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/factory"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewFactory creates an instance of KustFactory using k8sdeps factories
|
||||||
|
func NewFactory() *factory.KustFactory {
|
||||||
|
return factory.NewKustFactory(
|
||||||
|
kunstruct.NewKunstructuredFactoryImpl(),
|
||||||
|
validator.NewKustValidator(),
|
||||||
|
transformer.NewFactoryImpl(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"factory.go",
|
||||||
|
"helper.go",
|
||||||
|
"kunstruct.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct",
|
||||||
|
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = [
|
||||||
|
"factory_test.go",
|
||||||
|
"kunstruct_test.go",
|
||||||
|
],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = ["//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kunstruct
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KunstructuredFactoryImpl hides construction using apimachinery types.
|
||||||
|
type KunstructuredFactoryImpl struct {
|
||||||
|
cmFactory *configmapandsecret.ConfigMapFactory
|
||||||
|
secretFactory *configmapandsecret.SecretFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ifc.KunstructuredFactory = &KunstructuredFactoryImpl{}
|
||||||
|
|
||||||
|
// NewKunstructuredFactoryImpl returns a factory.
|
||||||
|
func NewKunstructuredFactoryImpl() ifc.KunstructuredFactory {
|
||||||
|
return &KunstructuredFactoryImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceFromBytes returns a slice of Kunstructured.
|
||||||
|
func (kf *KunstructuredFactoryImpl) SliceFromBytes(
|
||||||
|
in []byte) ([]ifc.Kunstructured, error) {
|
||||||
|
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
|
||||||
|
var result []ifc.Kunstructured
|
||||||
|
var err error
|
||||||
|
for err == nil || isEmptyYamlError(err) {
|
||||||
|
var out unstructured.Unstructured
|
||||||
|
err = decoder.Decode(&out)
|
||||||
|
if err == nil {
|
||||||
|
if len(out.Object) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = kf.validate(out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = append(result, &UnstructAdapter{Unstructured: out})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmptyYamlError(err error) bool {
|
||||||
|
return strings.Contains(err.Error(), "is missing in 'null'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromMap returns an instance of Kunstructured.
|
||||||
|
func (kf *KunstructuredFactoryImpl) FromMap(
|
||||||
|
m map[string]interface{}) ifc.Kunstructured {
|
||||||
|
return &UnstructAdapter{Unstructured: unstructured.Unstructured{Object: m}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeConfigMap returns an instance of Kunstructured for ConfigMap
|
||||||
|
func (kf *KunstructuredFactoryImpl) MakeConfigMap(args *types.ConfigMapArgs, options *types.GeneratorOptions) (ifc.Kunstructured, error) {
|
||||||
|
cm, err := kf.cmFactory.MakeConfigMap(args, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewKunstructuredFromObject(cm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeSecret returns an instance of Kunstructured for Secret
|
||||||
|
func (kf *KunstructuredFactoryImpl) MakeSecret(args *types.SecretArgs, options *types.GeneratorOptions) (ifc.Kunstructured, error) {
|
||||||
|
sec, err := kf.secretFactory.MakeSecret(args, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewKunstructuredFromObject(sec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets loader
|
||||||
|
func (kf *KunstructuredFactoryImpl) Set(ldr ifc.Loader) {
|
||||||
|
kf.cmFactory = configmapandsecret.NewConfigMapFactory(ldr)
|
||||||
|
kf.secretFactory = configmapandsecret.NewSecretFactory(ldr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate validates that u has kind and name
|
||||||
|
// except for kind `List`, which doesn't require a name
|
||||||
|
func (kf *KunstructuredFactoryImpl) validate(u unstructured.Unstructured) error {
|
||||||
|
kind := u.GetKind()
|
||||||
|
if kind == "" {
|
||||||
|
return fmt.Errorf("missing kind in object %v", u)
|
||||||
|
} else if kind == "List" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if u.GetName() == "" {
|
||||||
|
return fmt.Errorf("missing metadata.name in object %v", u)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kunstruct
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSliceFromBytes(t *testing.T) {
|
||||||
|
factory := NewKunstructuredFactoryImpl()
|
||||||
|
testConfigMap := factory.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "winnie",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testList := factory.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "List",
|
||||||
|
"items": []interface{}{
|
||||||
|
testConfigMap.Map(),
|
||||||
|
testConfigMap.Map(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expectedOut []ifc.Kunstructured
|
||||||
|
expectedErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "garbage",
|
||||||
|
input: []byte("garbageIn: garbageOut"),
|
||||||
|
expectedOut: []ifc.Kunstructured{},
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noBytes",
|
||||||
|
input: []byte{},
|
||||||
|
expectedOut: []ifc.Kunstructured{},
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "goodJson",
|
||||||
|
input: []byte(`
|
||||||
|
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
|
||||||
|
`),
|
||||||
|
expectedOut: []ifc.Kunstructured{testConfigMap},
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "goodYaml1",
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
expectedOut: []ifc.Kunstructured{testConfigMap},
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "goodYaml2",
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
expectedOut: []ifc.Kunstructured{testConfigMap, testConfigMap},
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "garbageInOneOfTwoObjects",
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
---
|
||||||
|
WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
|
||||||
|
`),
|
||||||
|
expectedOut: []ifc.Kunstructured{},
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emptyObjects",
|
||||||
|
input: []byte(`
|
||||||
|
---
|
||||||
|
#a comment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`),
|
||||||
|
expectedOut: []ifc.Kunstructured{},
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing .metadata.name in object",
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
`),
|
||||||
|
expectedOut: nil,
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "List",
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: List
|
||||||
|
items:
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
expectedOut: []ifc.Kunstructured{testList},
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
rs, err := factory.SliceFromBytes(test.input)
|
||||||
|
if test.expectedErr && err == nil {
|
||||||
|
t.Fatalf("%v: should return error", test.name)
|
||||||
|
}
|
||||||
|
if !test.expectedErr && err != nil {
|
||||||
|
t.Fatalf("%v: unexpected error: %s", test.name, err)
|
||||||
|
}
|
||||||
|
if len(rs) != len(test.expectedOut) {
|
||||||
|
t.Fatalf("%s: length mismatch %d != %d",
|
||||||
|
test.name, len(rs), len(test.expectedOut))
|
||||||
|
}
|
||||||
|
for i := range rs {
|
||||||
|
if !reflect.DeepEqual(test.expectedOut[i], rs[i]) {
|
||||||
|
t.Fatalf("%s: Got: %v\nexpected:%v",
|
||||||
|
test.name, test.expectedOut[i], rs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package kunstruct provides unstructured from api machinery and factory for creating unstructured
|
||||||
|
package kunstruct
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseFields(path string) ([]string, error) {
|
||||||
|
if !strings.Contains(path, "[") {
|
||||||
|
return strings.Split(path, "."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields []string
|
||||||
|
start := 0
|
||||||
|
insideParentheses := false
|
||||||
|
for i := range path {
|
||||||
|
switch path[i] {
|
||||||
|
case '.':
|
||||||
|
if !insideParentheses {
|
||||||
|
fields = append(fields, path[start:i])
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
case '[':
|
||||||
|
if !insideParentheses {
|
||||||
|
if i == start {
|
||||||
|
start = i + 1
|
||||||
|
} else {
|
||||||
|
fields = append(fields, path[start:i])
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
insideParentheses = true
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("nested parentheses are not allowed: %s", path)
|
||||||
|
}
|
||||||
|
case ']':
|
||||||
|
if insideParentheses {
|
||||||
|
fields = append(fields, path[start:i])
|
||||||
|
start = i + 1
|
||||||
|
insideParentheses = false
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("invalid field path %s", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if start < len(path)-1 {
|
||||||
|
fields = append(fields, path[start:])
|
||||||
|
}
|
||||||
|
for i, f := range fields {
|
||||||
|
if strings.HasPrefix(f, "\"") || strings.HasPrefix(f, "'") {
|
||||||
|
fields[i] = strings.Trim(f, "\"'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields, nil
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package kunstruct provides unstructured from api machinery and factory for creating unstructured
|
||||||
|
package kunstruct
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ ifc.Kunstructured = &UnstructAdapter{}
|
||||||
|
|
||||||
|
// UnstructAdapter wraps unstructured.Unstructured from
|
||||||
|
// https://github.com/kubernetes/apimachinery/blob/master/
|
||||||
|
// pkg/apis/meta/v1/unstructured/unstructured.go
|
||||||
|
// to isolate dependence on apimachinery.
|
||||||
|
type UnstructAdapter struct {
|
||||||
|
unstructured.Unstructured
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKunstructuredFromObject returns a new instance of Kunstructured.
|
||||||
|
func NewKunstructuredFromObject(obj runtime.Object) (ifc.Kunstructured, error) {
|
||||||
|
// Convert obj to a byte stream, then convert that to JSON (Unstructured).
|
||||||
|
marshaled, err := json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return &UnstructAdapter{}, err
|
||||||
|
}
|
||||||
|
var u unstructured.Unstructured
|
||||||
|
err = u.UnmarshalJSON(marshaled)
|
||||||
|
// creationTimestamp always 'null', remove it
|
||||||
|
u.SetCreationTimestamp(metav1.Time{})
|
||||||
|
return &UnstructAdapter{Unstructured: u}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGvk returns the Gvk name of the object.
|
||||||
|
func (fs *UnstructAdapter) GetGvk() gvk.Gvk {
|
||||||
|
x := fs.GroupVersionKind()
|
||||||
|
return gvk.Gvk{
|
||||||
|
Group: x.Group,
|
||||||
|
Version: x.Version,
|
||||||
|
Kind: x.Kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy provides a copy behind an interface.
|
||||||
|
func (fs *UnstructAdapter) Copy() ifc.Kunstructured {
|
||||||
|
return &UnstructAdapter{*fs.DeepCopy()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map returns the unstructured content map.
|
||||||
|
func (fs *UnstructAdapter) Map() map[string]interface{} {
|
||||||
|
return fs.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMap overrides the unstructured content map.
|
||||||
|
func (fs *UnstructAdapter) SetMap(m map[string]interface{}) {
|
||||||
|
fs.Object = m
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFieldValue returns value at the given fieldpath.
|
||||||
|
func (fs *UnstructAdapter) GetFieldValue(path string) (string, error) {
|
||||||
|
fields, err := parseFields(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
s, found, err := unstructured.NestedString(
|
||||||
|
fs.UnstructuredContent(), fields...)
|
||||||
|
if found || err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no field named '%s'", path)
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kunstruct
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetFieldValue(t *testing.T) {
|
||||||
|
factory := NewKunstructuredFactoryImpl()
|
||||||
|
kunstructured := factory.FromMap(map[string]interface{}{
|
||||||
|
"Kind": "Service",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"labels": map[string]string{
|
||||||
|
"app": "application-name",
|
||||||
|
},
|
||||||
|
"name": "service-name",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"ports": map[string]interface{}{
|
||||||
|
"port": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"this": map[string]interface{}{
|
||||||
|
"is": map[string]interface{}{
|
||||||
|
"aNumber": 1000,
|
||||||
|
"aNilValue": nil,
|
||||||
|
"anEmptyMap": map[string]interface{}{},
|
||||||
|
"unrecognizable": testing.InternalExample{
|
||||||
|
Name: "fooBar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pathToField string
|
||||||
|
expectedValue string
|
||||||
|
errorExpected bool
|
||||||
|
errorMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "oneField",
|
||||||
|
pathToField: "Kind",
|
||||||
|
expectedValue: "Service",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twoFields",
|
||||||
|
pathToField: "metadata.name",
|
||||||
|
expectedValue: "service-name",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "threeFields",
|
||||||
|
pathToField: "spec.ports.port",
|
||||||
|
expectedValue: "80",
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
pathToField: "",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named ''",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emptyDotEmpty",
|
||||||
|
pathToField: ".",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named '.'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "twoFieldsOneMissing",
|
||||||
|
pathToField: "metadata.banana",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'metadata.banana'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deeperMissingField",
|
||||||
|
pathToField: "this.is.aDeep.field.that.does.not.exist",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: "no field named 'this.is.aDeep.field.that.does.not.exist'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "emptyMap",
|
||||||
|
pathToField: "this.is.anEmptyMap",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: ".this.is.anEmptyMap accessor error: map[] is of the type map[string]interface {}, expected string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "numberAsValue",
|
||||||
|
pathToField: "this.is.aNumber",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: ".this.is.aNumber accessor error: 1000 is of the type int, expected string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nilAsValue",
|
||||||
|
pathToField: "this.is.aNilValue",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: ".this.is.aNilValue accessor error: <nil> is of the type <nil>, expected string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unrecognizable",
|
||||||
|
pathToField: "this.is.unrecognizable.Name",
|
||||||
|
errorExpected: true,
|
||||||
|
errorMsg: ".this.is.unrecognizable.Name accessor error: {fooBar <nil> false} is of the type testing.InternalExample, expected map[string]interface{}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
s, err := kunstructured.GetFieldValue(test.pathToField)
|
||||||
|
if test.errorExpected {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("%q; path %q - should return error, but no error returned",
|
||||||
|
test.name, test.pathToField)
|
||||||
|
}
|
||||||
|
if test.errorMsg != err.Error() {
|
||||||
|
t.Fatalf("%q; path %q - expected error: \"%s\", got error: \"%v\"",
|
||||||
|
test.name, test.pathToField, test.errorMsg, err.Error())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%q; path %q - unexpected error %v",
|
||||||
|
test.name, test.pathToField, err)
|
||||||
|
}
|
||||||
|
if test.expectedValue != s {
|
||||||
|
t.Fatalf("%q; Got: %s expected: %s",
|
||||||
|
test.name, s, test.expectedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["kv.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv",
|
||||||
|
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["kv_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pair represents a <key, value> pair.
|
||||||
|
type Pair struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
|
||||||
|
|
||||||
|
// KeyValuesFromLines parses given content in to a list of key-value pairs.
|
||||||
|
func KeyValuesFromLines(content []byte) ([]Pair, error) {
|
||||||
|
var kvs []Pair
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(content))
|
||||||
|
currentLine := 0
|
||||||
|
for scanner.Scan() {
|
||||||
|
// Process the current line, retrieving a key/value pair if
|
||||||
|
// possible.
|
||||||
|
scannedBytes := scanner.Bytes()
|
||||||
|
kv, err := KeyValuesFromLine(scannedBytes, currentLine)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
currentLine++
|
||||||
|
|
||||||
|
if len(kv.Key) == 0 {
|
||||||
|
// no key means line was empty or a comment
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
kvs = append(kvs, kv)
|
||||||
|
}
|
||||||
|
return kvs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyValuesFromLine returns a kv with blank key if the line is empty or a comment.
|
||||||
|
// The value will be retrieved from the environment if necessary.
|
||||||
|
func KeyValuesFromLine(line []byte, currentLine int) (Pair, error) {
|
||||||
|
kv := Pair{}
|
||||||
|
|
||||||
|
if !utf8.Valid(line) {
|
||||||
|
return kv, fmt.Errorf("line %d has invalid utf8 bytes : %v", line, string(line))
|
||||||
|
}
|
||||||
|
|
||||||
|
// We trim UTF8 BOM from the first line of the file but no others
|
||||||
|
if currentLine == 0 {
|
||||||
|
line = bytes.TrimPrefix(line, utf8bom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim the line from all leading whitespace first
|
||||||
|
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
||||||
|
|
||||||
|
// If the line is empty or a comment, we return a blank key/value pair.
|
||||||
|
if len(line) == 0 || line[0] == '#' {
|
||||||
|
return kv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data := strings.SplitN(string(line), "=", 2)
|
||||||
|
key := data[0]
|
||||||
|
if errs := validation.IsEnvVarName(key); len(errs) != 0 {
|
||||||
|
return kv, fmt.Errorf("%q is not a valid key name: %s", key, strings.Join(errs, ";"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 2 {
|
||||||
|
kv.Value = data[1]
|
||||||
|
} else {
|
||||||
|
// No value (no `=` in the line) is a signal to obtain the value
|
||||||
|
// from the environment.
|
||||||
|
kv.Value = os.Getenv(key)
|
||||||
|
}
|
||||||
|
kv.Key = key
|
||||||
|
return kv, nil
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKeyValuesFromLines(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
content string
|
||||||
|
expectedPairs []Pair
|
||||||
|
expectedErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid kv content parse",
|
||||||
|
content: `
|
||||||
|
k1=v1
|
||||||
|
k2=v2
|
||||||
|
`,
|
||||||
|
expectedPairs: []Pair{
|
||||||
|
{Key: "k1", Value: "v1"},
|
||||||
|
{Key: "k2", Value: "v2"},
|
||||||
|
},
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "content with comments",
|
||||||
|
content: `
|
||||||
|
k1=v1
|
||||||
|
#k2=v2
|
||||||
|
`,
|
||||||
|
expectedPairs: []Pair{
|
||||||
|
{Key: "k1", Value: "v1"},
|
||||||
|
},
|
||||||
|
expectedErr: false,
|
||||||
|
},
|
||||||
|
// TODO: add negative testcases
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
pairs, err := KeyValuesFromLines([]byte(test.content))
|
||||||
|
if test.expectedErr && err == nil {
|
||||||
|
t.Fatalf("%s should not return error", test.desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(pairs, test.expectedPairs) {
|
||||||
|
t.Errorf("%s should succeed, got:%v exptected:%v", test.desc, pairs, test.expectedPairs)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["factory.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer",
|
||||||
|
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash:go_default_library",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/transformers:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash:all-srcs",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch:all-srcs",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package transformer provides transformer factory
|
||||||
|
package transformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash"
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resource"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FactoryImpl makes patch transformer and name hash transformer
|
||||||
|
type FactoryImpl struct{}
|
||||||
|
|
||||||
|
// NewFactoryImpl makes a new factoryImpl instance
|
||||||
|
func NewFactoryImpl() *FactoryImpl {
|
||||||
|
return &FactoryImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakePatchTransformer makes a new patch transformer
|
||||||
|
func (p *FactoryImpl) MakePatchTransformer(slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error) {
|
||||||
|
return patch.NewPatchTransformer(slice, rf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeHashTransformer makes a new name hash transformer
|
||||||
|
func (p *FactoryImpl) MakeHashTransformer() transformers.Transformer {
|
||||||
|
return hash.NewNameHashTransformer()
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"hash.go",
|
||||||
|
"namehash.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash",
|
||||||
|
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/transformers:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = [
|
||||||
|
"hash_test.go",
|
||||||
|
"namehash_test.go",
|
||||||
|
],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resid:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package hash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KustHash compute hash for unstructured objects
|
||||||
|
type KustHash struct{}
|
||||||
|
|
||||||
|
// NewKustHash returns a KustHash object
|
||||||
|
func NewKustHash() *KustHash {
|
||||||
|
return &KustHash{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns a hash of either a ConfigMap or a Secret
|
||||||
|
func (h *KustHash) Hash(m map[string]interface{}) (string, error) {
|
||||||
|
u := unstructured.Unstructured{
|
||||||
|
Object: m,
|
||||||
|
}
|
||||||
|
kind := u.GetKind()
|
||||||
|
switch kind {
|
||||||
|
case "ConfigMap":
|
||||||
|
cm, err := unstructuredToConfigmap(u)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return ConfigMapHash(cm)
|
||||||
|
case "Secret":
|
||||||
|
sec, err := unstructuredToSecret(u)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return SecretHash(sec)
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("type %s is supported for hashing in %v", kind, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigMapHash returns a hash of the ConfigMap.
|
||||||
|
// The Data, Kind, and Name are taken into account.
|
||||||
|
func ConfigMapHash(cm *v1.ConfigMap) (string, error) {
|
||||||
|
encoded, err := encodeConfigMap(cm)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
h, err := encodeHash(hash(encoded))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecretHash returns a hash of the Secret.
|
||||||
|
// The Data, Kind, Name, and Type are taken into account.
|
||||||
|
func SecretHash(sec *v1.Secret) (string, error) {
|
||||||
|
encoded, err := encodeSecret(sec)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
h, err := encodeHash(hash(encoded))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeConfigMap encodes a ConfigMap.
|
||||||
|
// Data, Kind, and Name are taken into account.
|
||||||
|
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
||||||
|
// json.Marshal sorts the keys in a stable order in the encoding
|
||||||
|
m := map[string]interface{}{"kind": "ConfigMap", "name": cm.Name, "data": cm.Data}
|
||||||
|
if len(cm.BinaryData) > 0 {
|
||||||
|
m["binaryData"] = cm.BinaryData
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSecret encodes a Secret.
|
||||||
|
// Data, Kind, Name, and Type are taken into account.
|
||||||
|
func encodeSecret(sec *v1.Secret) (string, error) {
|
||||||
|
// json.Marshal sorts the keys in a stable order in the encoding
|
||||||
|
data, err := json.Marshal(map[string]interface{}{"kind": "Secret", "type": sec.Type, "name": sec.Name, "data": sec.Data})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeHash extracts the first 40 bits of the hash from the hex string
|
||||||
|
// (1 hex char represents 4 bits), and then maps vowels and vowel-like hex
|
||||||
|
// characters to consonants to prevent bad words from being formed (the theory
|
||||||
|
// is that no vowels makes it really hard to make bad words). Since the string
|
||||||
|
// is hex, the only vowels it can contain are 'a' and 'e'.
|
||||||
|
// We picked some arbitrary consonants to map to from the same character set as GenerateName.
|
||||||
|
// See: https://github.com/kubernetes/apimachinery/blob/dc1f89aff9a7509782bde3b68824c8043a3e58cc/pkg/util/rand/rand.go#L75
|
||||||
|
// If the hex string contains fewer than ten characters, returns an error.
|
||||||
|
func encodeHash(hex string) (string, error) {
|
||||||
|
if len(hex) < 10 {
|
||||||
|
return "", fmt.Errorf("the hex string must contain at least 10 characters")
|
||||||
|
}
|
||||||
|
enc := []rune(hex[:10])
|
||||||
|
for i := range enc {
|
||||||
|
switch enc[i] {
|
||||||
|
case '0':
|
||||||
|
enc[i] = 'g'
|
||||||
|
case '1':
|
||||||
|
enc[i] = 'h'
|
||||||
|
case '3':
|
||||||
|
enc[i] = 'k'
|
||||||
|
case 'a':
|
||||||
|
enc[i] = 'm'
|
||||||
|
case 'e':
|
||||||
|
enc[i] = 't'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(enc), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hash hashes `data` with sha256 and returns the hex string
|
||||||
|
func hash(data string) string {
|
||||||
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func unstructuredToConfigmap(u unstructured.Unstructured) (*v1.ConfigMap, error) {
|
||||||
|
marshaled, err := json.Marshal(u.Object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var out v1.ConfigMap
|
||||||
|
err = json.Unmarshal(marshaled, &out)
|
||||||
|
return &out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func unstructuredToSecret(u unstructured.Unstructured) (*v1.Secret, error) {
|
||||||
|
marshaled, err := json.Marshal(u.Object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var out v1.Secret
|
||||||
|
err = json.Unmarshal(marshaled, &out)
|
||||||
|
return &out, err
|
||||||
|
}
|
|
@ -0,0 +1,208 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package hash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||||
|
)
|
||||||
|
|
||||||
|
var service = gvk.Gvk{Version: "v1", Kind: "Service"}
|
||||||
|
var secret = gvk.Gvk{Version: "v1", Kind: "Secret"}
|
||||||
|
var cmap = gvk.Gvk{Version: "v1", Kind: "ConfigMap"}
|
||||||
|
var deploy = gvk.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"}
|
||||||
|
|
||||||
|
func TestConfigMapHash(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
cm *v1.ConfigMap
|
||||||
|
hash string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
// empty map
|
||||||
|
{"empty data", &v1.ConfigMap{Data: map[string]string{}, BinaryData: map[string][]byte{}}, "42745tchd9", ""},
|
||||||
|
// one key
|
||||||
|
{"one key", &v1.ConfigMap{Data: map[string]string{"one": ""}}, "9g67k2htb6", ""},
|
||||||
|
// three keys (tests sorting order)
|
||||||
|
{"three keys", &v1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, "f5h7t85m9b", ""},
|
||||||
|
// empty binary data map
|
||||||
|
{"empty binary data", &v1.ConfigMap{BinaryData: map[string][]byte{}}, "dk855m5d49", ""},
|
||||||
|
// one key with binary data
|
||||||
|
{"one key with binary data", &v1.ConfigMap{BinaryData: map[string][]byte{"one": []byte("")}}, "mk79584b8c", ""},
|
||||||
|
// three keys with binary data (tests sorting order)
|
||||||
|
{"three keys with binary data", &v1.ConfigMap{BinaryData: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "t458mc6db2", ""},
|
||||||
|
// two keys, one with string and another with binary data
|
||||||
|
{"two keys with one each", &v1.ConfigMap{Data: map[string]string{"one": ""}, BinaryData: map[string][]byte{"two": []byte("")}}, "698h7c7t9m", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
h, err := ConfigMapHash(c.cm)
|
||||||
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c.hash != h {
|
||||||
|
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecretHash(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
secret *v1.Secret
|
||||||
|
hash string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
// empty map
|
||||||
|
{"empty data", &v1.Secret{Type: "my-type", Data: map[string][]byte{}}, "t75bgf6ctb", ""},
|
||||||
|
// one key
|
||||||
|
{"one key", &v1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, "74bd68bm66", ""},
|
||||||
|
// three keys (tests sorting order)
|
||||||
|
{"three keys", &v1.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "dgcb6h9tmk", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
h, err := SecretHash(c.secret)
|
||||||
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c.hash != h {
|
||||||
|
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeConfigMap(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
cm *v1.ConfigMap
|
||||||
|
expect string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
// empty map
|
||||||
|
{"empty data", &v1.ConfigMap{Data: map[string]string{}}, `{"data":{},"kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// one key
|
||||||
|
{"one key", &v1.ConfigMap{Data: map[string]string{"one": ""}}, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// three keys (tests sorting order)
|
||||||
|
{"three keys", &v1.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}},
|
||||||
|
`{"data":{"one":"","three":"3","two":"2"},"kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// empty binary map
|
||||||
|
{"empty data", &v1.ConfigMap{BinaryData: map[string][]byte{}}, `{"data":null,"kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// one key with binary data
|
||||||
|
{"one key", &v1.ConfigMap{BinaryData: map[string][]byte{"one": []byte("")}},
|
||||||
|
`{"binaryData":{"one":""},"data":null,"kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// three keys with binary data (tests sorting order)
|
||||||
|
{"three keys", &v1.ConfigMap{BinaryData: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}},
|
||||||
|
`{"binaryData":{"one":"","three":"Mw==","two":"Mg=="},"data":null,"kind":"ConfigMap","name":""}`, ""},
|
||||||
|
// two keys, one string and one binary values
|
||||||
|
{"two keys with one each", &v1.ConfigMap{Data: map[string]string{"one": ""}, BinaryData: map[string][]byte{"two": []byte("")}},
|
||||||
|
`{"binaryData":{"two":""},"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
s, err := encodeConfigMap(c.cm)
|
||||||
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s != c.expect {
|
||||||
|
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.cm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeSecret(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
desc string
|
||||||
|
secret *v1.Secret
|
||||||
|
expect string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
// empty map
|
||||||
|
{"empty data", &v1.Secret{Type: "my-type", Data: map[string][]byte{}}, `{"data":{},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||||
|
// one key
|
||||||
|
{"one key", &v1.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||||
|
// three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte
|
||||||
|
{"three keys", &v1.Secret{
|
||||||
|
Type: "my-type",
|
||||||
|
Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")},
|
||||||
|
},
|
||||||
|
`{"data":{"one":"","three":"Mw==","two":"Mg=="},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
s, err := encodeSecret(c.secret)
|
||||||
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s != c.expect {
|
||||||
|
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.secret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHash(t *testing.T) {
|
||||||
|
// hash the empty string to be sure that sha256 is being used
|
||||||
|
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
sum := hash("")
|
||||||
|
if expect != sum {
|
||||||
|
t.Errorf("expected hash %q but got %q", expect, sum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// warn devs who change types that they might have to update a hash function
|
||||||
|
// not perfect, as it only checks the number of top-level fields
|
||||||
|
func TestTypeStability(t *testing.T) {
|
||||||
|
errfmt := `case %q, expected %d fields but got %d
|
||||||
|
Depending on the field(s) you added, you may need to modify the hash function for this type.
|
||||||
|
To guide you: the hash function targets fields that comprise the contents of objects,
|
||||||
|
not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta).
|
||||||
|
`
|
||||||
|
cases := []struct {
|
||||||
|
typeName string
|
||||||
|
obj interface{}
|
||||||
|
expect int
|
||||||
|
}{
|
||||||
|
{"ConfigMap", v1.ConfigMap{}, 4},
|
||||||
|
{"Secret", v1.Secret{}, 5},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
val := reflect.ValueOf(c.obj)
|
||||||
|
if num := val.NumField(); c.expect != num {
|
||||||
|
t.Errorf(errfmt, c.typeName, c.expect, num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipRest returns true if there was a non-nil error or if we expected an error that didn't happen,
|
||||||
|
// and logs the appropriate error on the test object.
|
||||||
|
// The return value indicates whether we should skip the rest of the test case due to the error result.
|
||||||
|
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
|
||||||
|
if err != nil {
|
||||||
|
if len(contains) == 0 {
|
||||||
|
t.Errorf("case %q, expect nil error but got %q", desc, err.Error())
|
||||||
|
} else if !strings.Contains(err.Error(), contains) {
|
||||||
|
t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else if len(contains) > 0 {
|
||||||
|
t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package hash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type nameHashTransformer struct{}
|
||||||
|
|
||||||
|
var _ transformers.Transformer = &nameHashTransformer{}
|
||||||
|
|
||||||
|
// NewNameHashTransformer construct a nameHashTransformer.
|
||||||
|
func NewNameHashTransformer() transformers.Transformer {
|
||||||
|
return &nameHashTransformer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform appends hash to generated resources.
|
||||||
|
func (o *nameHashTransformer) Transform(m resmap.ResMap) error {
|
||||||
|
for _, res := range m {
|
||||||
|
if res.NeedHashSuffix() {
|
||||||
|
h, err := NewKustHash().Hash(res.Map())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package hash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resid"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resource"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNameHashTransformer(t *testing.T) {
|
||||||
|
rf := resource.NewFactory(
|
||||||
|
kunstruct.NewKunstructuredFactoryImpl())
|
||||||
|
objs := resmap.ResMap{
|
||||||
|
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm1",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "apps",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"labels": map[string]interface{}{
|
||||||
|
"old-label": "old-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:1.7.9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
resid.NewResId(service, "svc1"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Service",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "svc1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"ports": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "port1",
|
||||||
|
"port": "12345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
resid.NewResId(secret, "secret1"): rf.FromMapAndOption(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Secret",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "secret1",
|
||||||
|
},
|
||||||
|
}, &types.GeneratorArgs{Behavior: "create"}, &types.GeneratorOptions{DisableNameSuffixHash: false}),
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := resmap.ResMap{
|
||||||
|
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm1",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "apps",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"labels": map[string]interface{}{
|
||||||
|
"old-label": "old-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:1.7.9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
resid.NewResId(service, "svc1"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Service",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "svc1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"ports": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "port1",
|
||||||
|
"port": "12345",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
resid.NewResId(secret, "secret1"): rf.FromMapAndOption(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Secret",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "secret1-7kc45hd5f7",
|
||||||
|
},
|
||||||
|
}, &types.GeneratorArgs{Behavior: "create"}, &types.GeneratorOptions{DisableNameSuffixHash: false}),
|
||||||
|
}
|
||||||
|
|
||||||
|
tran := NewNameHashTransformer()
|
||||||
|
tran.Transform(objs)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(objs, expected) {
|
||||||
|
err := expected.ErrorIfNotEqual(objs)
|
||||||
|
t.Fatalf("actual doesn't match expected: %v", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"patch.go",
|
||||||
|
"patchconflictdetector.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch",
|
||||||
|
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
|
||||||
|
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||||
|
"//vendor/github.com/evanphx/json-patch:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/transformers:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["patch_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resid:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package patch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/evanphx/json-patch"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resource"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// patchTransformer applies patches.
|
||||||
|
type patchTransformer struct {
|
||||||
|
patches []*resource.Resource
|
||||||
|
rf *resource.Factory
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ transformers.Transformer = &patchTransformer{}
|
||||||
|
|
||||||
|
// NewPatchTransformer constructs a patchTransformer.
|
||||||
|
func NewPatchTransformer(
|
||||||
|
slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error) {
|
||||||
|
if len(slice) == 0 {
|
||||||
|
return transformers.NewNoOpTransformer(), nil
|
||||||
|
}
|
||||||
|
return &patchTransformer{patches: slice, rf: rf}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform apply the patches on top of the base resources.
|
||||||
|
func (pt *patchTransformer) Transform(baseResourceMap resmap.ResMap) error {
|
||||||
|
// Merge and then index the patches by Id.
|
||||||
|
patches, err := pt.mergePatches()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategic merge the resources exist in both base and patches.
|
||||||
|
for _, patch := range patches {
|
||||||
|
// Merge patches with base resource.
|
||||||
|
id := patch.Id()
|
||||||
|
matchedIds := baseResourceMap.FindByGVKN(id)
|
||||||
|
if len(matchedIds) == 0 {
|
||||||
|
return fmt.Errorf("failed to find an object with %s to apply the patch", id.GvknString())
|
||||||
|
}
|
||||||
|
if len(matchedIds) > 1 {
|
||||||
|
return fmt.Errorf("found multiple objects %#v targeted by patch %#v (ambiguous)", matchedIds, id)
|
||||||
|
}
|
||||||
|
id = matchedIds[0]
|
||||||
|
base := baseResourceMap[id]
|
||||||
|
merged := map[string]interface{}{}
|
||||||
|
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk()))
|
||||||
|
baseName := base.GetName()
|
||||||
|
switch {
|
||||||
|
case runtime.IsNotRegisteredError(err):
|
||||||
|
// Use JSON merge patch to handle types w/o schema
|
||||||
|
baseBytes, err := json.Marshal(base.Map())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
patchBytes, err := json.Marshal(patch.Map())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mergedBytes, err := jsonpatch.MergePatch(baseBytes, patchBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(mergedBytes, &merged)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
// Use Strategic-Merge-Patch to handle types w/ schema
|
||||||
|
// TODO: Change this to use the new Merge package.
|
||||||
|
// Store the name of the base object, because this name may have been munged.
|
||||||
|
// Apply this name to the patched object.
|
||||||
|
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
merged, err = strategicpatch.StrategicMergeMapPatchUsingLookupPatchMeta(
|
||||||
|
base.Map(),
|
||||||
|
patch.Map(),
|
||||||
|
lookupPatchMeta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
base.SetName(baseName)
|
||||||
|
baseResourceMap[id].SetMap(merged)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergePatches merge and index patches by Id.
|
||||||
|
// It errors out if there is conflict between patches.
|
||||||
|
func (pt *patchTransformer) mergePatches() (resmap.ResMap, error) {
|
||||||
|
rc := resmap.ResMap{}
|
||||||
|
for ix, patch := range pt.patches {
|
||||||
|
id := patch.Id()
|
||||||
|
existing, found := rc[id]
|
||||||
|
if !found {
|
||||||
|
rc[id] = patch
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
versionedObj, err := scheme.Scheme.New(toSchemaGvk(id.Gvk()))
|
||||||
|
if err != nil && !runtime.IsNotRegisteredError(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var cd conflictDetector
|
||||||
|
if err != nil {
|
||||||
|
cd = newJMPConflictDetector(pt.rf)
|
||||||
|
} else {
|
||||||
|
cd, err = newSMPConflictDetector(versionedObj, pt.rf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conflict, err := cd.hasConflict(existing, patch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if conflict {
|
||||||
|
conflictingPatch, err := cd.findConflict(ix, pt.patches)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"conflict between %#v and %#v",
|
||||||
|
conflictingPatch.Map(), patch.Map())
|
||||||
|
}
|
||||||
|
merged, err := cd.mergePatches(existing, patch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rc[id] = merged
|
||||||
|
}
|
||||||
|
return rc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toSchemaGvk converts to a schema.GroupVersionKind.
|
||||||
|
func toSchemaGvk(x gvk.Gvk) schema.GroupVersionKind {
|
||||||
|
return schema.GroupVersionKind{
|
||||||
|
Group: x.Group,
|
||||||
|
Version: x.Version,
|
||||||
|
Kind: x.Kind,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,563 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package patch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resid"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rf = resource.NewFactory(
|
||||||
|
kunstruct.NewKunstructuredFactoryImpl())
|
||||||
|
var deploy = gvk.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"}
|
||||||
|
var foo = gvk.Gvk{Group: "example.com", Version: "v1", Kind: "Foo"}
|
||||||
|
|
||||||
|
func TestOverlayRun(t *testing.T) {
|
||||||
|
base := resmap.ResMap{
|
||||||
|
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"labels": map[string]interface{}{
|
||||||
|
"old-label": "old-value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
patch := []*resource.Resource{
|
||||||
|
rf.FromMap(map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"labels": map[string]interface{}{
|
||||||
|
"another-label": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:latest",
|
||||||
|
"env": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "SOMEENV",
|
||||||
|
"value": "BAR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
expected := resmap.ResMap{
|
||||||
|
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"labels": map[string]interface{}{
|
||||||
|
"old-label": "old-value",
|
||||||
|
"another-label": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:latest",
|
||||||
|
"env": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "SOMEENV",
|
||||||
|
"value": "BAR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
lt, err := NewPatchTransformer(patch, rf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
err = lt.Transform(base)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(base, expected) {
|
||||||
|
err = expected.ErrorIfNotEqual(base)
|
||||||
|
t.Fatalf("actual doesn't match expected: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiplePatches(t *testing.T) {
|
||||||
|
base := resmap.ResMap{
|
||||||
|
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
patch := []*resource.Resource{
|
||||||
|
rf.FromMap(map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:latest",
|
||||||
|
"env": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "SOMEENV",
|
||||||
|
"value": "BAR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
rf.FromMap(map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"env": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "ANOTHERENV",
|
||||||
|
"value": "HELLO",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "busybox",
|
||||||
|
"image": "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
expected := resmap.ResMap{
|
||||||
|
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:latest",
|
||||||
|
"env": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "ANOTHERENV",
|
||||||
|
"value": "HELLO",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "SOMEENV",
|
||||||
|
"value": "BAR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "busybox",
|
||||||
|
"image": "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
lt, err := NewPatchTransformer(patch, rf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
err = lt.Transform(base)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(base, expected) {
|
||||||
|
err = expected.ErrorIfNotEqual(base)
|
||||||
|
t.Fatalf("actual doesn't match expected: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiplePatchesWithConflict(t *testing.T) {
|
||||||
|
base := resmap.ResMap{
|
||||||
|
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
patch := []*resource.Resource{
|
||||||
|
rf.FromMap(map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:latest",
|
||||||
|
"env": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "SOMEENV",
|
||||||
|
"value": "BAR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
rf.FromMap(map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deploy1",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"containers": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "nginx",
|
||||||
|
"image": "nginx:1.7.9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
lt, err := NewPatchTransformer(patch, rf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
err = lt.Transform(base)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("did not get expected error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "conflict") {
|
||||||
|
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoSchemaOverlayRun(t *testing.T) {
|
||||||
|
base := resmap.ResMap{
|
||||||
|
resid.NewResId(foo, "my-foo"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "example.com/v1",
|
||||||
|
"kind": "Foo",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-foo",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"A": "X",
|
||||||
|
"B": "Y",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
patch := []*resource.Resource{
|
||||||
|
rf.FromMap(map[string]interface{}{
|
||||||
|
"apiVersion": "example.com/v1",
|
||||||
|
"kind": "Foo",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-foo",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"B": nil,
|
||||||
|
"C": "Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
expected := resmap.ResMap{
|
||||||
|
resid.NewResId(foo, "my-foo"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "example.com/v1",
|
||||||
|
"kind": "Foo",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-foo",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"A": "X",
|
||||||
|
"C": "Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
lt, err := NewPatchTransformer(patch, rf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
err = lt.Transform(base)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if err = expected.ErrorIfNotEqual(base); err != nil {
|
||||||
|
t.Fatalf("actual doesn't match expected: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoSchemaMultiplePatches(t *testing.T) {
|
||||||
|
base := resmap.ResMap{
|
||||||
|
resid.NewResId(foo, "my-foo"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "example.com/v1",
|
||||||
|
"kind": "Foo",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-foo",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"A": "X",
|
||||||
|
"B": "Y",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
patch := []*resource.Resource{
|
||||||
|
rf.FromMap(map[string]interface{}{
|
||||||
|
"apiVersion": "example.com/v1",
|
||||||
|
"kind": "Foo",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-foo",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"B": nil,
|
||||||
|
"C": "Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
rf.FromMap(map[string]interface{}{
|
||||||
|
"apiVersion": "example.com/v1",
|
||||||
|
"kind": "Foo",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-foo",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"C": "Z",
|
||||||
|
"D": "W",
|
||||||
|
},
|
||||||
|
"baz": map[string]interface{}{
|
||||||
|
"hello": "world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
expected := resmap.ResMap{
|
||||||
|
resid.NewResId(foo, "my-foo"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "example.com/v1",
|
||||||
|
"kind": "Foo",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-foo",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"A": "X",
|
||||||
|
"C": "Z",
|
||||||
|
"D": "W",
|
||||||
|
},
|
||||||
|
"baz": map[string]interface{}{
|
||||||
|
"hello": "world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
lt, err := NewPatchTransformer(patch, rf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
err = lt.Transform(base)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if err = expected.ErrorIfNotEqual(base); err != nil {
|
||||||
|
t.Fatalf("actual doesn't match expected: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoSchemaMultiplePatchesWithConflict(t *testing.T) {
|
||||||
|
base := resmap.ResMap{
|
||||||
|
resid.NewResId(foo, "my-foo"): rf.FromMap(
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "example.com/v1",
|
||||||
|
"kind": "Foo",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-foo",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"A": "X",
|
||||||
|
"B": "Y",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
patch := []*resource.Resource{
|
||||||
|
rf.FromMap(map[string]interface{}{
|
||||||
|
"apiVersion": "example.com/v1",
|
||||||
|
"kind": "Foo",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-foo",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"B": nil,
|
||||||
|
"C": "Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
rf.FromMap(map[string]interface{}{
|
||||||
|
"apiVersion": "example.com/v1",
|
||||||
|
"kind": "Foo",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-foo",
|
||||||
|
},
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"bar": map[string]interface{}{
|
||||||
|
"C": "NOT_Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
lt, err := NewPatchTransformer(patch, rf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
err = lt.Transform(base)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("did not get expected error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "conflict") {
|
||||||
|
t.Fatalf("expected error to contain %q but get %v", "conflict", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package patch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/evanphx/json-patch"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/mergepatch"
|
||||||
|
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
type conflictDetector interface {
|
||||||
|
hasConflict(patch1, patch2 *resource.Resource) (bool, error)
|
||||||
|
findConflict(conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error)
|
||||||
|
mergePatches(patch1, patch2 *resource.Resource) (*resource.Resource, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonMergePatch struct {
|
||||||
|
rf *resource.Factory
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ conflictDetector = &jsonMergePatch{}
|
||||||
|
|
||||||
|
func newJMPConflictDetector(rf *resource.Factory) conflictDetector {
|
||||||
|
return &jsonMergePatch{rf: rf}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jmp *jsonMergePatch) hasConflict(
|
||||||
|
patch1, patch2 *resource.Resource) (bool, error) {
|
||||||
|
return mergepatch.HasConflicts(patch1.Map(), patch2.Map())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jmp *jsonMergePatch) findConflict(
|
||||||
|
conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error) {
|
||||||
|
for i, patch := range patches {
|
||||||
|
if i == conflictingPatchIdx {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !patches[conflictingPatchIdx].Id().GvknEquals(patch.Id()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conflict, err := mergepatch.HasConflicts(
|
||||||
|
patch.Map(),
|
||||||
|
patches[conflictingPatchIdx].Map())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if conflict {
|
||||||
|
return patch, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jmp *jsonMergePatch) mergePatches(
|
||||||
|
patch1, patch2 *resource.Resource) (*resource.Resource, error) {
|
||||||
|
baseBytes, err := json.Marshal(patch1.Map())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
patchBytes, err := json.Marshal(patch2.Map())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mergedBytes, err := jsonpatch.MergeMergePatches(baseBytes, patchBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mergedMap := make(map[string]interface{})
|
||||||
|
err = json.Unmarshal(mergedBytes, &mergedMap)
|
||||||
|
return jmp.rf.FromMap(mergedMap), err
|
||||||
|
}
|
||||||
|
|
||||||
|
type strategicMergePatch struct {
|
||||||
|
lookupPatchMeta strategicpatch.LookupPatchMeta
|
||||||
|
rf *resource.Factory
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ conflictDetector = &strategicMergePatch{}
|
||||||
|
|
||||||
|
func newSMPConflictDetector(
|
||||||
|
versionedObj runtime.Object,
|
||||||
|
rf *resource.Factory) (conflictDetector, error) {
|
||||||
|
lookupPatchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObj)
|
||||||
|
return &strategicMergePatch{lookupPatchMeta: lookupPatchMeta, rf: rf}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smp *strategicMergePatch) hasConflict(p1, p2 *resource.Resource) (bool, error) {
|
||||||
|
return strategicpatch.MergingMapsHaveConflicts(
|
||||||
|
p1.Map(), p2.Map(), smp.lookupPatchMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smp *strategicMergePatch) findConflict(
|
||||||
|
conflictingPatchIdx int, patches []*resource.Resource) (*resource.Resource, error) {
|
||||||
|
for i, patch := range patches {
|
||||||
|
if i == conflictingPatchIdx {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !patches[conflictingPatchIdx].Id().GvknEquals(patch.Id()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conflict, err := strategicpatch.MergingMapsHaveConflicts(
|
||||||
|
patch.Map(),
|
||||||
|
patches[conflictingPatchIdx].Map(),
|
||||||
|
smp.lookupPatchMeta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if conflict {
|
||||||
|
return patch, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (smp *strategicMergePatch) mergePatches(patch1, patch2 *resource.Resource) (*resource.Resource, error) {
|
||||||
|
mergeJSONMap, err := strategicpatch.MergeStrategicMergeMapPatchUsingLookupPatchMeta(
|
||||||
|
smp.lookupPatchMeta, patch1.Map(), patch2.Map())
|
||||||
|
return smp.rf.FromMap(mergeJSONMap), err
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["validators.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator",
|
||||||
|
importpath = "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/api/validation:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package validator provides functions to validate labels, annotations, namespace using apimachinery
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
apivalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||||
|
v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KustValidator validates Labels and annotations by apimachinery
|
||||||
|
type KustValidator struct{}
|
||||||
|
|
||||||
|
// NewKustValidator returns a KustValidator object
|
||||||
|
func NewKustValidator() *KustValidator {
|
||||||
|
return &KustValidator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeAnnotationValidator returns a MapValidatorFunc using apimachinery.
|
||||||
|
func (v *KustValidator) MakeAnnotationValidator() func(map[string]string) error {
|
||||||
|
return func(x map[string]string) error {
|
||||||
|
errs := apivalidation.ValidateAnnotations(x, field.NewPath("field"))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.New(errs.ToAggregate().Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeLabelValidator returns a MapValidatorFunc using apimachinery.
|
||||||
|
func (v *KustValidator) MakeLabelValidator() func(map[string]string) error {
|
||||||
|
return func(x map[string]string) error {
|
||||||
|
errs := v1validation.ValidateLabels(x, field.NewPath("field"))
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.New(errs.ToAggregate().Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateNamespace validates a string is a valid namespace using apimachinery.
|
||||||
|
func (v *KustValidator) ValidateNamespace(s string) []string {
|
||||||
|
return validation.IsDNS1123Label(s)
|
||||||
|
}
|
|
@ -491,6 +491,24 @@ filegroup(
|
||||||
"//vendor/k8s.io/utils/pointer:all-srcs",
|
"//vendor/k8s.io/utils/pointer:all-srcs",
|
||||||
"//vendor/k8s.io/utils/strings:all-srcs",
|
"//vendor/k8s.io/utils/strings:all-srcs",
|
||||||
"//vendor/k8s.io/utils/trace:all-srcs",
|
"//vendor/k8s.io/utils/trace:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/commands/build:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/constants:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/expansion:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/factory:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/fs:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/git:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/gvk:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/ifc:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/image:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/internal/error:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/loader:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/patch:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resid:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resource:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/target:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/transformers:all-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/types:all-srcs",
|
||||||
"//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:all-srcs",
|
"//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:all-srcs",
|
||||||
"//vendor/sigs.k8s.io/structured-merge-diff/merge:all-srcs",
|
"//vendor/sigs.k8s.io/structured-merge-diff/merge:all-srcs",
|
||||||
"//vendor/sigs.k8s.io/structured-merge-diff/schema:all-srcs",
|
"//vendor/sigs.k8s.io/structured-merge-diff/schema:all-srcs",
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,33 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["build.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/commands/build",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/commands/build",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/pkg/errors:go_default_library",
|
||||||
|
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/constants:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/ifc/transformer:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/loader:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/target:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/constants"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/fs"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc/transformer"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/loader"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/target"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options contain the options for running a build
|
||||||
|
type Options struct {
|
||||||
|
kustomizationPath string
|
||||||
|
outputPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOptions creates a Options object
|
||||||
|
func NewOptions(p, o string) *Options {
|
||||||
|
return &Options{
|
||||||
|
kustomizationPath: p,
|
||||||
|
outputPath: o,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var examples = `
|
||||||
|
Use the file somedir/kustomization.yaml to generate a set of api resources:
|
||||||
|
build somedir
|
||||||
|
|
||||||
|
Use a url pointing to a remote directory/kustomization.yaml to generate a set of api resources:
|
||||||
|
build url
|
||||||
|
The url should follow hashicorp/go-getter URL format described in
|
||||||
|
https://github.com/hashicorp/go-getter#url-format
|
||||||
|
|
||||||
|
url examples:
|
||||||
|
sigs.k8s.io/kustomize//examples/multibases?ref=v1.0.6
|
||||||
|
github.com/Liujingfang1/mysql
|
||||||
|
github.com/Liujingfang1/kustomize//examples/helloWorld?ref=repoUrl2
|
||||||
|
`
|
||||||
|
|
||||||
|
// NewCmdBuild creates a new build command.
|
||||||
|
func NewCmdBuild(
|
||||||
|
out io.Writer, fs fs.FileSystem,
|
||||||
|
rf *resmap.Factory,
|
||||||
|
ptf transformer.Factory) *cobra.Command {
|
||||||
|
var o Options
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "build [path]",
|
||||||
|
Short: "Print current configuration per contents of " + constants.KustomizationFileNames[0],
|
||||||
|
Example: examples,
|
||||||
|
SilenceUsage: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
err := o.Validate(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return o.RunBuild(out, fs, rf, ptf)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVarP(
|
||||||
|
&o.outputPath,
|
||||||
|
"output", "o", "",
|
||||||
|
"If specified, write the build output to this path.")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates build command.
|
||||||
|
func (o *Options) Validate(args []string) error {
|
||||||
|
if len(args) > 1 {
|
||||||
|
return errors.New("specify one path to " + constants.KustomizationFileNames[0])
|
||||||
|
}
|
||||||
|
if len(args) == 0 {
|
||||||
|
o.kustomizationPath = "./"
|
||||||
|
} else {
|
||||||
|
o.kustomizationPath = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunBuild runs build command.
|
||||||
|
func (o *Options) RunBuild(
|
||||||
|
out io.Writer, fSys fs.FileSystem,
|
||||||
|
rf *resmap.Factory, ptf transformer.Factory) error {
|
||||||
|
ldr, err := loader.NewLoader(o.kustomizationPath, fSys)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ldr.Cleanup()
|
||||||
|
kt, err := target.NewKustTarget(ldr, fSys, rf, ptf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
allResources, err := kt.MakeCustomizedResMap()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Output the objects.
|
||||||
|
res, err := allResources.EncodeAsYaml()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if o.outputPath != "" {
|
||||||
|
return fSys.WriteFile(o.outputPath, res)
|
||||||
|
}
|
||||||
|
_, err = out.Write(res)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["constants.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/constants",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/constants",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package constants holds global constants for the kustomize tool.
|
||||||
|
package constants
|
||||||
|
|
||||||
|
// KustomizationFileNames is a list of filenames that can be recognized and consumbed
|
||||||
|
// by Kustomize.
|
||||||
|
// In each directory, Kustomize searches for file with the name in this list.
|
||||||
|
// Only one match is allowed.
|
||||||
|
var KustomizationFileNames = []string{
|
||||||
|
"kustomization.yaml",
|
||||||
|
"kustomization.yml",
|
||||||
|
"Kustomization",
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["expand.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/expansion",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/expansion",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package expansion provides functions find and replace $(FOO) style variables in strings.
|
||||||
|
package expansion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
operator = '$'
|
||||||
|
referenceOpener = '('
|
||||||
|
referenceCloser = ')'
|
||||||
|
)
|
||||||
|
|
||||||
|
// syntaxWrap returns the input string wrapped by the expansion syntax.
|
||||||
|
func syntaxWrap(input string) string {
|
||||||
|
return string(operator) + string(referenceOpener) + input + string(referenceCloser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MappingFuncFor returns a mapping function for use with Expand that
|
||||||
|
// implements the expansion semantics defined in the expansion spec; it
|
||||||
|
// returns the input string wrapped in the expansion syntax if no mapping
|
||||||
|
// for the input is found.
|
||||||
|
func MappingFuncFor(context ...map[string]string) func(string) string {
|
||||||
|
return func(input string) string {
|
||||||
|
for _, vars := range context {
|
||||||
|
val, ok := vars[input]
|
||||||
|
if ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return syntaxWrap(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand replaces variable references in the input string according to
|
||||||
|
// the expansion spec using the given mapping function to resolve the
|
||||||
|
// values of variables.
|
||||||
|
func Expand(input string, mapping func(string) string) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
checkpoint := 0
|
||||||
|
for cursor := 0; cursor < len(input); cursor++ {
|
||||||
|
if input[cursor] == operator && cursor+1 < len(input) {
|
||||||
|
// Copy the portion of the input string since the last
|
||||||
|
// checkpoint into the buffer
|
||||||
|
buf.WriteString(input[checkpoint:cursor])
|
||||||
|
|
||||||
|
// Attempt to read the variable name as defined by the
|
||||||
|
// syntax from the input string
|
||||||
|
read, isVar, advance := tryReadVariableName(input[cursor+1:])
|
||||||
|
|
||||||
|
if isVar {
|
||||||
|
// We were able to read a variable name correctly;
|
||||||
|
// apply the mapping to the variable name and copy the
|
||||||
|
// bytes into the buffer
|
||||||
|
buf.WriteString(mapping(read))
|
||||||
|
} else {
|
||||||
|
// Not a variable name; copy the read bytes into the buffer
|
||||||
|
buf.WriteString(read)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance the cursor in the input string to account for
|
||||||
|
// bytes consumed to read the variable name expression
|
||||||
|
cursor += advance
|
||||||
|
|
||||||
|
// Advance the checkpoint in the input string
|
||||||
|
checkpoint = cursor + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the buffer and any remaining unwritten bytes in the
|
||||||
|
// input string.
|
||||||
|
return buf.String() + input[checkpoint:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryReadVariableName attempts to read a variable name from the input
|
||||||
|
// string and returns the content read from the input, whether that content
|
||||||
|
// represents a variable name to perform mapping on, and the number of bytes
|
||||||
|
// consumed in the input string.
|
||||||
|
//
|
||||||
|
// The input string is assumed not to contain the initial operator.
|
||||||
|
func tryReadVariableName(input string) (string, bool, int) {
|
||||||
|
switch input[0] {
|
||||||
|
case operator:
|
||||||
|
// Escaped operator; return it.
|
||||||
|
return input[0:1], false, 1
|
||||||
|
case referenceOpener:
|
||||||
|
// Scan to expression closer
|
||||||
|
for i := 1; i < len(input); i++ {
|
||||||
|
if input[i] == referenceCloser {
|
||||||
|
return input[1:i], true, i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incomplete reference; return it.
|
||||||
|
return string(operator) + string(referenceOpener), false, 1
|
||||||
|
default:
|
||||||
|
// Not the beginning of an expression, ie, an operator
|
||||||
|
// that doesn't begin an expression. Return the operator
|
||||||
|
// and the first rune in the string.
|
||||||
|
return string(operator) + string(input[0]), false, 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["factory.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/factory",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/factory",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/ifc/transformer:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
// Package factory provides factories for kustomize.
|
||||||
|
package factory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc/transformer"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KustFactory provides different factories for kustomize
|
||||||
|
type KustFactory struct {
|
||||||
|
ResmapF *resmap.Factory
|
||||||
|
TransformerF transformer.Factory
|
||||||
|
ValidatorF ifc.Validator
|
||||||
|
UnstructF ifc.KunstructuredFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKustFactory creats a KustFactory instance
|
||||||
|
func NewKustFactory(u ifc.KunstructuredFactory, v ifc.Validator, t transformer.Factory) *KustFactory {
|
||||||
|
return &KustFactory{
|
||||||
|
ResmapF: resmap.NewFactory(resource.NewFactory(u)),
|
||||||
|
TransformerF: t,
|
||||||
|
ValidatorF: v,
|
||||||
|
UnstructF: u,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"confirmeddir.go",
|
||||||
|
"fakefile.go",
|
||||||
|
"fakefileinfo.go",
|
||||||
|
"fakefs.go",
|
||||||
|
"fs.go",
|
||||||
|
"realfile.go",
|
||||||
|
"realfs.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/fs",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/fs",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//vendor/sigs.k8s.io/kustomize/pkg/constants:go_default_library"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfirmedDir is a clean, absolute, delinkified path
|
||||||
|
// that was confirmed to point to an existing directory.
|
||||||
|
type ConfirmedDir string
|
||||||
|
|
||||||
|
// Return a temporary dir, else error.
|
||||||
|
// The directory is cleaned, no symlinks, etc. so its
|
||||||
|
// returned as a ConfirmedDir.
|
||||||
|
func NewTmpConfirmedDir() (ConfirmedDir, error) {
|
||||||
|
n, err := ioutil.TempDir("", "kustomize-")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return ConfirmedDir(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPrefix returns true if the directory argument
|
||||||
|
// is a prefix of self (d) from the point of view of
|
||||||
|
// a file system.
|
||||||
|
//
|
||||||
|
// I.e., it's true if the argument equals or contains
|
||||||
|
// self (d) in a file path sense.
|
||||||
|
//
|
||||||
|
// HasPrefix emulates the semantics of strings.HasPrefix
|
||||||
|
// such that the following are true:
|
||||||
|
//
|
||||||
|
// strings.HasPrefix("foobar", "foobar")
|
||||||
|
// strings.HasPrefix("foobar", "foo")
|
||||||
|
// strings.HasPrefix("foobar", "")
|
||||||
|
//
|
||||||
|
// d := fSys.ConfirmDir("/foo/bar")
|
||||||
|
// d.HasPrefix("/foo/bar")
|
||||||
|
// d.HasPrefix("/foo")
|
||||||
|
// d.HasPrefix("/")
|
||||||
|
//
|
||||||
|
// Not contacting a file system here to check for
|
||||||
|
// actual path existence.
|
||||||
|
//
|
||||||
|
// This is tested on linux, but will have trouble
|
||||||
|
// on other operating systems.
|
||||||
|
// TODO(monopole) Refactor when #golang/go/18358 closes.
|
||||||
|
// See also:
|
||||||
|
// https://github.com/golang/go/issues/18358
|
||||||
|
// https://github.com/golang/dep/issues/296
|
||||||
|
// https://github.com/golang/dep/blob/master/internal/fs/fs.go#L33
|
||||||
|
// https://codereview.appspot.com/5712045
|
||||||
|
func (d ConfirmedDir) HasPrefix(path ConfirmedDir) bool {
|
||||||
|
if path.String() == string(filepath.Separator) || path == d {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return strings.HasPrefix(
|
||||||
|
string(d),
|
||||||
|
string(path)+string(filepath.Separator))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d ConfirmedDir) Join(path string) string {
|
||||||
|
return filepath.Join(string(d), path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d ConfirmedDir) String() string {
|
||||||
|
return string(d)
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ File = &FakeFile{}
|
||||||
|
|
||||||
|
// FakeFile implements File in-memory for tests.
|
||||||
|
type FakeFile struct {
|
||||||
|
name string
|
||||||
|
content []byte
|
||||||
|
dir bool
|
||||||
|
open bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeDir makes a fake directory.
|
||||||
|
func makeDir(name string) *FakeFile {
|
||||||
|
return &FakeFile{name: name, dir: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close marks the fake file closed.
|
||||||
|
func (f *FakeFile) Close() error {
|
||||||
|
f.open = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read never fails, and doesn't mutate p.
|
||||||
|
func (f *FakeFile) Read(p []byte) (n int, err error) {
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write saves the contents of the argument to memory.
|
||||||
|
func (f *FakeFile) Write(p []byte) (n int, err error) {
|
||||||
|
f.content = p
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentMatches returns true if v matches fake file's content.
|
||||||
|
func (f *FakeFile) ContentMatches(v []byte) bool {
|
||||||
|
return bytes.Equal(v, f.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContent the content of a fake file.
|
||||||
|
func (f *FakeFile) GetContent() []byte {
|
||||||
|
return f.content
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns nil.
|
||||||
|
func (f *FakeFile) Stat() (os.FileInfo, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ os.FileInfo = &Fakefileinfo{}
|
||||||
|
|
||||||
|
// Fakefileinfo implements Fakefileinfo using a fake in-memory filesystem.
|
||||||
|
type Fakefileinfo struct {
|
||||||
|
*FakeFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the file
|
||||||
|
func (fi *Fakefileinfo) Name() string { return fi.name }
|
||||||
|
|
||||||
|
// Size returns the size of the file
|
||||||
|
func (fi *Fakefileinfo) Size() int64 { return int64(len(fi.content)) }
|
||||||
|
|
||||||
|
// Mode returns the file mode
|
||||||
|
func (fi *Fakefileinfo) Mode() os.FileMode { return 0777 }
|
||||||
|
|
||||||
|
// ModTime returns the modification time
|
||||||
|
func (fi *Fakefileinfo) ModTime() time.Time { return time.Time{} }
|
||||||
|
|
||||||
|
// IsDir returns if it is a directory
|
||||||
|
func (fi *Fakefileinfo) IsDir() bool { return fi.dir }
|
||||||
|
|
||||||
|
// Sys should return underlying data source, but it now returns nil
|
||||||
|
func (fi *Fakefileinfo) Sys() interface{} { return nil }
|
|
@ -0,0 +1,185 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/pkg/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ FileSystem = &fakeFs{}
|
||||||
|
|
||||||
|
// fakeFs implements FileSystem using a fake in-memory filesystem.
|
||||||
|
type fakeFs struct {
|
||||||
|
m map[string]*FakeFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeFakeFS returns an instance of fakeFs with no files in it.
|
||||||
|
func MakeFakeFS() *fakeFs {
|
||||||
|
result := &fakeFs{m: map[string]*FakeFile{}}
|
||||||
|
result.Mkdir("/")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// kustomizationContent is used in tests.
|
||||||
|
const kustomizationContent = `apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namePrefix: some-prefix
|
||||||
|
nameSuffix: some-suffix
|
||||||
|
# Labels to add to all objects and selectors.
|
||||||
|
# These labels would also be used to form the selector for apply --prune
|
||||||
|
# Named differently than “labels” to avoid confusion with metadata for this object
|
||||||
|
commonLabels:
|
||||||
|
app: helloworld
|
||||||
|
commonAnnotations:
|
||||||
|
note: This is an example annotation
|
||||||
|
resources: []
|
||||||
|
#- service.yaml
|
||||||
|
#- ../some-dir/
|
||||||
|
# There could also be configmaps in Base, which would make these overlays
|
||||||
|
configMapGenerator: []
|
||||||
|
# There could be secrets in Base, if just using a fork/rebase workflow
|
||||||
|
secretGenerator: []
|
||||||
|
`
|
||||||
|
|
||||||
|
// Create assures a fake file appears in the in-memory file system.
|
||||||
|
func (fs *fakeFs) Create(name string) (File, error) {
|
||||||
|
f := &FakeFile{}
|
||||||
|
f.open = true
|
||||||
|
fs.m[name] = f
|
||||||
|
return fs.m[name], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mkdir assures a fake directory appears in the in-memory file system.
|
||||||
|
func (fs *fakeFs) Mkdir(name string) error {
|
||||||
|
fs.m[name] = makeDir(name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAll delegates to Mkdir
|
||||||
|
func (fs *fakeFs) MkdirAll(name string) error {
|
||||||
|
return fs.Mkdir(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll presumably does rm -r on a path.
|
||||||
|
// There's no error.
|
||||||
|
func (fs *fakeFs) RemoveAll(name string) error {
|
||||||
|
var toRemove []string
|
||||||
|
for k := range fs.m {
|
||||||
|
if strings.HasPrefix(k, name) {
|
||||||
|
toRemove = append(toRemove, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, k := range toRemove {
|
||||||
|
delete(fs.m, k)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns a fake file in the open state.
|
||||||
|
func (fs *fakeFs) Open(name string) (File, error) {
|
||||||
|
if _, found := fs.m[name]; !found {
|
||||||
|
return nil, fmt.Errorf("file %q cannot be opened", name)
|
||||||
|
}
|
||||||
|
return fs.m[name], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanedAbs cannot fail.
|
||||||
|
func (fs *fakeFs) CleanedAbs(path string) (ConfirmedDir, string, error) {
|
||||||
|
if fs.IsDir(path) {
|
||||||
|
return ConfirmedDir(path), "", nil
|
||||||
|
}
|
||||||
|
d := filepath.Dir(path)
|
||||||
|
if d == path {
|
||||||
|
return ConfirmedDir(d), "", nil
|
||||||
|
}
|
||||||
|
return ConfirmedDir(d), filepath.Base(path), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists returns true if file is known.
|
||||||
|
func (fs *fakeFs) Exists(name string) bool {
|
||||||
|
_, found := fs.m[name]
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glob returns the list of matching files
|
||||||
|
func (fs *fakeFs) Glob(pattern string) ([]string, error) {
|
||||||
|
var result []string
|
||||||
|
for p := range fs.m {
|
||||||
|
if fs.pathMatch(p, pattern) {
|
||||||
|
result = append(result, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(result)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir returns true if the file exists and is a directory.
|
||||||
|
func (fs *fakeFs) IsDir(name string) bool {
|
||||||
|
f, found := fs.m[name]
|
||||||
|
if found && f.dir {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(name, "/") {
|
||||||
|
name = name + "/"
|
||||||
|
}
|
||||||
|
for k := range fs.m {
|
||||||
|
if strings.HasPrefix(k, name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile always returns an empty bytes and error depending on content of m.
|
||||||
|
func (fs *fakeFs) ReadFile(name string) ([]byte, error) {
|
||||||
|
if ff, found := fs.m[name]; found {
|
||||||
|
return ff.content, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("cannot read file %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *fakeFs) ReadTestKustomization() ([]byte, error) {
|
||||||
|
return fs.ReadFile(constants.KustomizationFileNames[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFile always succeeds and does nothing.
|
||||||
|
func (fs *fakeFs) WriteFile(name string, c []byte) error {
|
||||||
|
ff := &FakeFile{}
|
||||||
|
ff.Write(c)
|
||||||
|
fs.m[name] = ff
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTestKustomization writes a standard test file.
|
||||||
|
func (fs *fakeFs) WriteTestKustomization() {
|
||||||
|
fs.WriteTestKustomizationWith([]byte(kustomizationContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTestKustomizationWith writes a standard test file.
|
||||||
|
func (fs *fakeFs) WriteTestKustomizationWith(bytes []byte) {
|
||||||
|
fs.WriteFile(constants.KustomizationFileNames[0], bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *fakeFs) pathMatch(path, pattern string) bool {
|
||||||
|
match, _ := filepath.Match(pattern, path)
|
||||||
|
return match
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package fs provides a file system abstraction layer.
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileSystem groups basic os filesystem methods.
|
||||||
|
type FileSystem interface {
|
||||||
|
Create(name string) (File, error)
|
||||||
|
Mkdir(name string) error
|
||||||
|
MkdirAll(name string) error
|
||||||
|
RemoveAll(name string) error
|
||||||
|
Open(name string) (File, error)
|
||||||
|
IsDir(name string) bool
|
||||||
|
CleanedAbs(path string) (ConfirmedDir, string, error)
|
||||||
|
Exists(name string) bool
|
||||||
|
Glob(pattern string) ([]string, error)
|
||||||
|
ReadFile(name string) ([]byte, error)
|
||||||
|
WriteFile(name string, data []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// File groups the basic os.File methods.
|
||||||
|
type File interface {
|
||||||
|
io.ReadWriteCloser
|
||||||
|
Stat() (os.FileInfo, error)
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ File = &realFile{}
|
||||||
|
|
||||||
|
// realFile implements File using the local filesystem.
|
||||||
|
type realFile struct {
|
||||||
|
file *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes a file.
|
||||||
|
func (f *realFile) Close() error { return f.file.Close() }
|
||||||
|
|
||||||
|
// Read reads a file's content.
|
||||||
|
func (f *realFile) Read(p []byte) (n int, err error) { return f.file.Read(p) }
|
||||||
|
|
||||||
|
// Write writes bytes to a file
|
||||||
|
func (f *realFile) Write(p []byte) (n int, err error) { return f.file.Write(p) }
|
||||||
|
|
||||||
|
// Stat returns an interface which has all the information regarding the file.
|
||||||
|
func (f *realFile) Stat() (os.FileInfo, error) { return f.file.Stat() }
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ FileSystem = realFS{}
|
||||||
|
|
||||||
|
// realFS implements FileSystem using the local filesystem.
|
||||||
|
type realFS struct{}
|
||||||
|
|
||||||
|
// MakeRealFS makes an instance of realFS.
|
||||||
|
func MakeRealFS() FileSystem {
|
||||||
|
return realFS{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create delegates to os.Create.
|
||||||
|
func (realFS) Create(name string) (File, error) { return os.Create(name) }
|
||||||
|
|
||||||
|
// Mkdir delegates to os.Mkdir.
|
||||||
|
func (realFS) Mkdir(name string) error {
|
||||||
|
return os.Mkdir(name, 0777|os.ModeDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAll delegates to os.MkdirAll.
|
||||||
|
func (realFS) MkdirAll(name string) error {
|
||||||
|
return os.MkdirAll(name, 0777|os.ModeDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll delegates to os.RemoveAll.
|
||||||
|
func (realFS) RemoveAll(name string) error {
|
||||||
|
return os.RemoveAll(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open delegates to os.Open.
|
||||||
|
func (realFS) Open(name string) (File, error) { return os.Open(name) }
|
||||||
|
|
||||||
|
// CleanedAbs returns a cleaned, absolute path
|
||||||
|
// with no symbolic links split into directory
|
||||||
|
// and file components. If the entire path is
|
||||||
|
// a directory, the file component is an empty
|
||||||
|
// string.
|
||||||
|
func (x realFS) CleanedAbs(
|
||||||
|
path string) (ConfirmedDir, string, error) {
|
||||||
|
absRoot, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf(
|
||||||
|
"abs path error on '%s' : %v", path, err)
|
||||||
|
}
|
||||||
|
deLinked, err := filepath.EvalSymlinks(absRoot)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf(
|
||||||
|
"evalsymlink failure on '%s' : %v", path, err)
|
||||||
|
}
|
||||||
|
if x.IsDir(deLinked) {
|
||||||
|
return ConfirmedDir(deLinked), "", nil
|
||||||
|
}
|
||||||
|
d := filepath.Dir(deLinked)
|
||||||
|
if !x.IsDir(d) {
|
||||||
|
// Programmer/assumption error.
|
||||||
|
log.Fatalf("first part of '%s' not a directory", deLinked)
|
||||||
|
}
|
||||||
|
if d == deLinked {
|
||||||
|
// Programmer/assumption error.
|
||||||
|
log.Fatalf("d '%s' should be a subset of deLinked", d)
|
||||||
|
}
|
||||||
|
f := filepath.Base(deLinked)
|
||||||
|
if filepath.Join(d, f) != deLinked {
|
||||||
|
// Programmer/assumption error.
|
||||||
|
log.Fatalf("these should be equal: '%s', '%s'",
|
||||||
|
filepath.Join(d, f), deLinked)
|
||||||
|
}
|
||||||
|
return ConfirmedDir(d), f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists returns true if os.Stat succeeds.
|
||||||
|
func (realFS) Exists(name string) bool {
|
||||||
|
_, err := os.Stat(name)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glob returns the list of matching files
|
||||||
|
func (realFS) Glob(pattern string) ([]string, error) {
|
||||||
|
return filepath.Glob(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir delegates to os.Stat and FileInfo.IsDir
|
||||||
|
func (realFS) IsDir(name string) bool {
|
||||||
|
info, err := os.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return info.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile delegates to ioutil.ReadFile.
|
||||||
|
func (realFS) ReadFile(name string) ([]byte, error) { return ioutil.ReadFile(name) }
|
||||||
|
|
||||||
|
// WriteFile delegates to ioutil.WriteFile with read/write permissions.
|
||||||
|
func (realFS) WriteFile(name string, c []byte) error {
|
||||||
|
return ioutil.WriteFile(name, c, 0666)
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"cloner.go",
|
||||||
|
"repospec.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/git",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/git",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/pkg/errors:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cloner is a function that can clone a git repo.
|
||||||
|
type Cloner func(repoSpec *RepoSpec) error
|
||||||
|
|
||||||
|
// ClonerUsingGitExec uses a local git install, as opposed
|
||||||
|
// to say, some remote API, to obtain a local clone of
|
||||||
|
// a remote repo.
|
||||||
|
func ClonerUsingGitExec(repoSpec *RepoSpec) error {
|
||||||
|
gitProgram, err := exec.LookPath("git")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "no 'git' program on path")
|
||||||
|
}
|
||||||
|
repoSpec.cloneDir, err = fs.NewTmpConfirmedDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd := exec.Command(
|
||||||
|
gitProgram,
|
||||||
|
"clone",
|
||||||
|
repoSpec.CloneSpec(),
|
||||||
|
repoSpec.cloneDir.String())
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "trouble cloning %s", repoSpec.raw)
|
||||||
|
}
|
||||||
|
if repoSpec.ref == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cmd = exec.Command(gitProgram, "checkout", repoSpec.ref)
|
||||||
|
cmd.Dir = repoSpec.cloneDir.String()
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(
|
||||||
|
err, "trouble checking out href %s", repoSpec.ref)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoNothingCloner returns a cloner that only sets
|
||||||
|
// cloneDir field in the repoSpec. It's assumed that
|
||||||
|
// the cloneDir is associated with some fake filesystem
|
||||||
|
// used in a test.
|
||||||
|
func DoNothingCloner(dir fs.ConfirmedDir) Cloner {
|
||||||
|
return func(rs *RepoSpec) error {
|
||||||
|
rs.cloneDir = dir
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,207 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/pkg/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RepoSpec specifies a git repository and a branch and path therein.
|
||||||
|
type RepoSpec struct {
|
||||||
|
// Raw, original spec, used to look for cycles.
|
||||||
|
// TODO(monopole): Drop raw, use processed fields instead.
|
||||||
|
raw string
|
||||||
|
|
||||||
|
// Host, e.g. github.com
|
||||||
|
host string
|
||||||
|
|
||||||
|
// orgRepo name (organization/repoName),
|
||||||
|
// e.g. kubernetes-sigs/kustomize
|
||||||
|
orgRepo string
|
||||||
|
|
||||||
|
// ConfirmedDir where the orgRepo is cloned to.
|
||||||
|
cloneDir fs.ConfirmedDir
|
||||||
|
|
||||||
|
// Relative path in the repository, and in the cloneDir,
|
||||||
|
// to a Kustomization.
|
||||||
|
path string
|
||||||
|
|
||||||
|
// Branch or tag reference.
|
||||||
|
ref string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneSpec returns a string suitable for "git clone {spec}".
|
||||||
|
func (x *RepoSpec) CloneSpec() string {
|
||||||
|
if isAzureHost(x.host) || isAWSHost(x.host) {
|
||||||
|
return x.host + x.orgRepo
|
||||||
|
}
|
||||||
|
return x.host + x.orgRepo + gitSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RepoSpec) CloneDir() fs.ConfirmedDir {
|
||||||
|
return x.cloneDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RepoSpec) Raw() string {
|
||||||
|
return x.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RepoSpec) AbsPath() string {
|
||||||
|
return x.cloneDir.Join(x.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RepoSpec) Cleaner(fSys fs.FileSystem) func() error {
|
||||||
|
return func() error { return fSys.RemoveAll(x.cloneDir.String()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// From strings like git@github.com:someOrg/someRepo.git or
|
||||||
|
// https://github.com/someOrg/someRepo?ref=someHash, extract
|
||||||
|
// the parts.
|
||||||
|
func NewRepoSpecFromUrl(n string) (*RepoSpec, error) {
|
||||||
|
if filepath.IsAbs(n) {
|
||||||
|
return nil, fmt.Errorf("uri looks like abs path: %s", n)
|
||||||
|
}
|
||||||
|
host, orgRepo, path, gitRef := parseGithubUrl(n)
|
||||||
|
if orgRepo == "" {
|
||||||
|
return nil, fmt.Errorf("url lacks orgRepo: %s", n)
|
||||||
|
}
|
||||||
|
if host == "" {
|
||||||
|
return nil, fmt.Errorf("url lacks host: %s", n)
|
||||||
|
}
|
||||||
|
return &RepoSpec{
|
||||||
|
raw: n, host: host, orgRepo: orgRepo,
|
||||||
|
path: path, ref: gitRef}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
refQuery = "?ref="
|
||||||
|
gitSuffix = ".git"
|
||||||
|
)
|
||||||
|
|
||||||
|
// From strings like git@github.com:someOrg/someRepo.git or
|
||||||
|
// https://github.com/someOrg/someRepo?ref=someHash, extract
|
||||||
|
// the parts.
|
||||||
|
func parseGithubUrl(n string) (
|
||||||
|
host string, orgRepo string, path string, gitRef string) {
|
||||||
|
host, n = parseHostSpec(n)
|
||||||
|
|
||||||
|
if strings.Contains(n, gitSuffix) {
|
||||||
|
index := strings.Index(n, gitSuffix)
|
||||||
|
orgRepo = n[0:index]
|
||||||
|
n = n[index+len(gitSuffix):]
|
||||||
|
path, gitRef = peelQuery(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i := strings.Index(n, "/")
|
||||||
|
if i < 1 {
|
||||||
|
return "", "", "", ""
|
||||||
|
}
|
||||||
|
j := strings.Index(n[i+1:], "/")
|
||||||
|
if j >= 0 {
|
||||||
|
j += i + 1
|
||||||
|
orgRepo = n[:j]
|
||||||
|
path, gitRef = peelQuery(n[j+1:])
|
||||||
|
} else {
|
||||||
|
path = ""
|
||||||
|
orgRepo, gitRef = peelQuery(n)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func peelQuery(arg string) (string, string) {
|
||||||
|
j := strings.Index(arg, refQuery)
|
||||||
|
if j >= 0 {
|
||||||
|
return arg[:j], arg[j+len(refQuery):]
|
||||||
|
}
|
||||||
|
return arg, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseHostSpec(n string) (string, string) {
|
||||||
|
var host string
|
||||||
|
// Start accumulating the host part.
|
||||||
|
for _, p := range []string{
|
||||||
|
// Order matters here.
|
||||||
|
"git::", "gh:", "ssh://", "https://", "http://",
|
||||||
|
"git@", "github.com:", "github.com/"} {
|
||||||
|
if len(p) < len(n) && strings.ToLower(n[:len(p)]) == p {
|
||||||
|
n = n[len(p):]
|
||||||
|
host += p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if host == "git@" {
|
||||||
|
i := strings.Index(n, "/")
|
||||||
|
if i > -1 {
|
||||||
|
host += n[:i+1]
|
||||||
|
n = n[i+1:]
|
||||||
|
} else {
|
||||||
|
i = strings.Index(n, ":")
|
||||||
|
if i > -1 {
|
||||||
|
host += n[:i+1]
|
||||||
|
n = n[i+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return host, n
|
||||||
|
}
|
||||||
|
|
||||||
|
// If host is a http(s) or ssh URL, grab the domain part.
|
||||||
|
for _, p := range []string{
|
||||||
|
"ssh://", "https://", "http://"} {
|
||||||
|
if strings.HasSuffix(host, p) {
|
||||||
|
i := strings.Index(n, "/")
|
||||||
|
if i > -1 {
|
||||||
|
host = host + n[0:i+1]
|
||||||
|
n = n[i+1:]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeGitHostSpec(host), n
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeGitHostSpec(host string) string {
|
||||||
|
s := strings.ToLower(host)
|
||||||
|
if strings.Contains(s, "github.com") {
|
||||||
|
if strings.Contains(s, "git@") || strings.Contains(s, "ssh:") {
|
||||||
|
host = "git@github.com:"
|
||||||
|
} else {
|
||||||
|
host = "https://github.com/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(s, "git::") {
|
||||||
|
host = strings.TrimLeft(s, "git::")
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
|
||||||
|
// The format of Azure repo URL is documented
|
||||||
|
// https://docs.microsoft.com/en-us/azure/devops/repos/git/clone?view=vsts&tabs=visual-studio#clone_url
|
||||||
|
func isAzureHost(host string) bool {
|
||||||
|
return strings.Contains(host, "dev.azure.com") ||
|
||||||
|
strings.Contains(host, "visualstudio.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The format of AWS repo URL is documented
|
||||||
|
// https://docs.aws.amazon.com/codecommit/latest/userguide/regions.html
|
||||||
|
func isAWSHost(host string) bool {
|
||||||
|
return strings.Contains(host, "amazonaws.com")
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["gvk.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/gvk",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/gvk",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package gvk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gvk identifies a Kubernetes API type.
|
||||||
|
// https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md
|
||||||
|
type Gvk struct {
|
||||||
|
Group string `json:"group,omitempty" yaml:"group,omitempty"`
|
||||||
|
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||||
|
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromKind makes a Gvk with only the kind specified.
|
||||||
|
func FromKind(k string) Gvk {
|
||||||
|
return Gvk{
|
||||||
|
Kind: k,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values that are brief but meaningful in logs.
|
||||||
|
const (
|
||||||
|
noGroup = "~G"
|
||||||
|
noVersion = "~V"
|
||||||
|
noKind = "~K"
|
||||||
|
separator = "_"
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns a string representation of the GVK.
|
||||||
|
func (x Gvk) String() string {
|
||||||
|
g := x.Group
|
||||||
|
if g == "" {
|
||||||
|
g = noGroup
|
||||||
|
}
|
||||||
|
v := x.Version
|
||||||
|
if v == "" {
|
||||||
|
v = noVersion
|
||||||
|
}
|
||||||
|
k := x.Kind
|
||||||
|
if k == "" {
|
||||||
|
k = noKind
|
||||||
|
}
|
||||||
|
return strings.Join([]string{g, v, k}, separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns true if the Gvk's have equal fields.
|
||||||
|
func (x Gvk) Equals(o Gvk) bool {
|
||||||
|
return x.Group == o.Group && x.Version == o.Version && x.Kind == o.Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
// An attempt to order things to help k8s, e.g.
|
||||||
|
// a Service should come before things that refer to it.
|
||||||
|
// Namespace should be first.
|
||||||
|
// In some cases order just specified to provide determinism.
|
||||||
|
var order = []string{
|
||||||
|
"Namespace",
|
||||||
|
"StorageClass",
|
||||||
|
"CustomResourceDefinition",
|
||||||
|
"ServiceAccount",
|
||||||
|
"Role",
|
||||||
|
"ClusterRole",
|
||||||
|
"RoleBinding",
|
||||||
|
"ClusterRoleBinding",
|
||||||
|
"ConfigMap",
|
||||||
|
"Secret",
|
||||||
|
"Service",
|
||||||
|
"Deployment",
|
||||||
|
"StatefulSet",
|
||||||
|
"CronJob",
|
||||||
|
"PodDisruptionBudget",
|
||||||
|
}
|
||||||
|
var typeOrders = func() map[string]int {
|
||||||
|
m := map[string]int{}
|
||||||
|
for i, n := range order {
|
||||||
|
m[n] = i
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}()
|
||||||
|
|
||||||
|
// IsLessThan returns true if self is less than the argument.
|
||||||
|
func (x Gvk) IsLessThan(o Gvk) bool {
|
||||||
|
indexI, foundI := typeOrders[x.Kind]
|
||||||
|
indexJ, foundJ := typeOrders[o.Kind]
|
||||||
|
if foundI && foundJ {
|
||||||
|
if indexI != indexJ {
|
||||||
|
return indexI < indexJ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundI && !foundJ {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !foundI && foundJ {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return x.String() < o.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSelected returns true if `selector` selects `x`; otherwise, false.
|
||||||
|
// If `selector` and `x` are the same, return true.
|
||||||
|
// If `selector` is nil, it is considered a wildcard match, returning true.
|
||||||
|
// If selector fields are empty, they are considered wildcards matching
|
||||||
|
// anything in the corresponding fields, e.g.
|
||||||
|
//
|
||||||
|
// this item:
|
||||||
|
// <Group: "extensions", Version: "v1beta1", Kind: "Deployment">
|
||||||
|
//
|
||||||
|
// is selected by
|
||||||
|
// <Group: "", Version: "", Kind: "Deployment">
|
||||||
|
//
|
||||||
|
// but rejected by
|
||||||
|
// <Group: "apps", Version: "", Kind: "Deployment">
|
||||||
|
//
|
||||||
|
func (x Gvk) IsSelected(selector *Gvk) bool {
|
||||||
|
if selector == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(selector.Group) > 0 {
|
||||||
|
if x.Group != selector.Group {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(selector.Version) > 0 {
|
||||||
|
if x.Version != selector.Version {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(selector.Kind) > 0 {
|
||||||
|
if x.Kind != selector.Kind {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var clusterLevelKinds = []string{
|
||||||
|
"APIService",
|
||||||
|
"ClusterRoleBinding",
|
||||||
|
"ClusterRole",
|
||||||
|
"CustomResourceDefinition",
|
||||||
|
"Namespace",
|
||||||
|
"PersistentVolume",
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClusterKind returns true if x is a cluster-level Gvk
|
||||||
|
func (x Gvk) IsClusterKind() bool {
|
||||||
|
for _, k := range clusterLevelKinds {
|
||||||
|
if k == x.Kind {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClusterLevelGvks returns a slice of cluster-level Gvks
|
||||||
|
func ClusterLevelGvks() []Gvk {
|
||||||
|
var result []Gvk
|
||||||
|
for _, k := range clusterLevelKinds {
|
||||||
|
result = append(result, Gvk{Kind: k})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["ifc.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/ifc",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/ifc",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/ifc/transformer:all-srcs",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package ifc holds miscellaneous interfaces used by kustomize.
|
||||||
|
package ifc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validator provides functions to validate annotations and labels
|
||||||
|
type Validator interface {
|
||||||
|
MakeAnnotationValidator() func(map[string]string) error
|
||||||
|
MakeLabelValidator() func(map[string]string) error
|
||||||
|
ValidateNamespace(string) []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loader interface exposes methods to read bytes.
|
||||||
|
type Loader interface {
|
||||||
|
// Root returns the root location for this Loader.
|
||||||
|
Root() string
|
||||||
|
// New returns Loader located at newRoot.
|
||||||
|
New(newRoot string) (Loader, error)
|
||||||
|
// Load returns the bytes read from the location or an error.
|
||||||
|
Load(location string) ([]byte, error)
|
||||||
|
// Cleanup cleans the loader
|
||||||
|
Cleanup() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kunstructured allows manipulation of k8s objects
|
||||||
|
// that do not have Golang structs.
|
||||||
|
type Kunstructured interface {
|
||||||
|
Map() map[string]interface{}
|
||||||
|
SetMap(map[string]interface{})
|
||||||
|
Copy() Kunstructured
|
||||||
|
GetFieldValue(string) (string, error)
|
||||||
|
MarshalJSON() ([]byte, error)
|
||||||
|
UnmarshalJSON([]byte) error
|
||||||
|
GetGvk() gvk.Gvk
|
||||||
|
GetKind() string
|
||||||
|
GetName() string
|
||||||
|
SetName(string)
|
||||||
|
GetLabels() map[string]string
|
||||||
|
SetLabels(map[string]string)
|
||||||
|
GetAnnotations() map[string]string
|
||||||
|
SetAnnotations(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KunstructuredFactory makes instances of Kunstructured.
|
||||||
|
type KunstructuredFactory interface {
|
||||||
|
SliceFromBytes([]byte) ([]Kunstructured, error)
|
||||||
|
FromMap(m map[string]interface{}) Kunstructured
|
||||||
|
MakeConfigMap(args *types.ConfigMapArgs, options *types.GeneratorOptions) (Kunstructured, error)
|
||||||
|
MakeSecret(args *types.SecretArgs, options *types.GeneratorOptions) (Kunstructured, error)
|
||||||
|
Set(ldr Loader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See core.v1.SecretTypeOpaque
|
||||||
|
const SecretTypeOpaque = "Opaque"
|
|
@ -0,0 +1,27 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["factory.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/ifc/transformer",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/ifc/transformer",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/transformers:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package patch holds miscellaneous interfaces used by kustomize.
|
||||||
|
package transformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resource"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Factory makes transformers
|
||||||
|
type Factory interface {
|
||||||
|
MakePatchTransformer(slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error)
|
||||||
|
MakeHashTransformer() transformers.Transformer
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"deprecatedimage.go",
|
||||||
|
"image.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/image",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/image",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package image
|
||||||
|
|
||||||
|
// DeprecatedImage contains an image and a new tag,
|
||||||
|
// which will replace the original tag.
|
||||||
|
// Deprecated, instead use Image.
|
||||||
|
type DeprecatedImage struct {
|
||||||
|
// Name is a tag-less image name.
|
||||||
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
|
|
||||||
|
// NewTag is the value to use in replacing the original tag.
|
||||||
|
NewTag string `json:"newTag,omitempty" yaml:"newTag,omitempty"`
|
||||||
|
|
||||||
|
// Digest is the value used to replace the original image tag.
|
||||||
|
// If digest is present NewTag value is ignored.
|
||||||
|
Digest string `json:"digest,omitempty" yaml:"digest,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package image provides struct definitions and libraries
|
||||||
|
// for image overwriting of names, tags and digest.
|
||||||
|
package image
|
||||||
|
|
||||||
|
// Image contains an image name, a new name, a new tag or digest,
|
||||||
|
// which will replace the original name and tag.
|
||||||
|
type Image struct {
|
||||||
|
// Name is a tag-less image name.
|
||||||
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
|
|
||||||
|
// NewName is the value used to replace the original name.
|
||||||
|
NewName string `json:"newName,omitempty" yaml:"newName,omitempty"`
|
||||||
|
|
||||||
|
// NewTag is the value used to replace the original tag.
|
||||||
|
NewTag string `json:"newTag,omitempty" yaml:"newTag,omitempty"`
|
||||||
|
|
||||||
|
// Digest is the value used to replace the original image tag.
|
||||||
|
// If digest is present NewTag value is ignored.
|
||||||
|
Digest string `json:"digest,omitempty" yaml:"digest,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"configmaperror.go",
|
||||||
|
"kustomizationerror.go",
|
||||||
|
"patcherror.go",
|
||||||
|
"resourceerror.go",
|
||||||
|
"secreterror.go",
|
||||||
|
"yamlformaterror.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/internal/error",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/internal/error",
|
||||||
|
visibility = ["//vendor/sigs.k8s.io/kustomize/pkg:__subpackages__"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package error has contextual error types.
|
||||||
|
package error
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ConfigmapError represents error with a configmap.
|
||||||
|
type ConfigmapError struct {
|
||||||
|
Path string
|
||||||
|
ErrorMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ConfigmapError) Error() string {
|
||||||
|
return fmt.Sprintf("Kustomization file [%s] encounters a configmap error: %s\n", e.Path, e.ErrorMsg)
|
||||||
|
}
|
61
vendor/sigs.k8s.io/kustomize/pkg/internal/error/kustomizationerror.go
generated
vendored
Normal file
61
vendor/sigs.k8s.io/kustomize/pkg/internal/error/kustomizationerror.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package error
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KustomizationError represents an error with a kustomization.
|
||||||
|
type KustomizationError struct {
|
||||||
|
KustomizationPath string
|
||||||
|
ErrorMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ke KustomizationError) Error() string {
|
||||||
|
return fmt.Sprintf("Kustomization File [%s]: %s\n", ke.KustomizationPath, ke.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KustomizationErrors collects all errors.
|
||||||
|
type KustomizationErrors struct {
|
||||||
|
kErrors []error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ke *KustomizationErrors) Error() string {
|
||||||
|
errormsg := ""
|
||||||
|
for _, e := range ke.kErrors {
|
||||||
|
errormsg += e.Error() + "\n"
|
||||||
|
}
|
||||||
|
return errormsg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append adds error to a collection of errors.
|
||||||
|
func (ke *KustomizationErrors) Append(e error) {
|
||||||
|
ke.kErrors = append(ke.kErrors, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns all collected errors.
|
||||||
|
func (ke *KustomizationErrors) Get() []error {
|
||||||
|
return ke.kErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchAppend adds all errors from another KustomizationErrors
|
||||||
|
func (ke *KustomizationErrors) BatchAppend(e KustomizationErrors) {
|
||||||
|
for _, err := range e.Get() {
|
||||||
|
ke.kErrors = append(ke.kErrors, err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package error
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PatchError represents error during Patch.
|
||||||
|
type PatchError struct {
|
||||||
|
KustomizationPath string
|
||||||
|
PatchFilepath string
|
||||||
|
ErrorMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e PatchError) Error() string {
|
||||||
|
return fmt.Sprintf("Kustomization file [%s] encounters a patch error for [%s]: %s\n", e.KustomizationPath, e.PatchFilepath, e.ErrorMsg)
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package error
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ResourceError represents error in a resource.
|
||||||
|
type ResourceError struct {
|
||||||
|
KustomizationPath string
|
||||||
|
ResourceFilepath string
|
||||||
|
ErrorMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ResourceError) Error() string {
|
||||||
|
return fmt.Sprintf("Kustomization file [%s] encounters a resource error for [%s]: %s\n", e.KustomizationPath, e.ResourceFilepath, e.ErrorMsg)
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package error
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// SecretError represents error with a secret.
|
||||||
|
type SecretError struct {
|
||||||
|
KustomizationPath string
|
||||||
|
// ErrorMsg is an error message
|
||||||
|
ErrorMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e SecretError) Error() string {
|
||||||
|
return fmt.Sprintf("Kustomization file [%s] encounters a secret error: %s\n", e.KustomizationPath, e.ErrorMsg)
|
||||||
|
}
|
48
vendor/sigs.k8s.io/kustomize/pkg/internal/error/yamlformaterror.go
generated
vendored
Normal file
48
vendor/sigs.k8s.io/kustomize/pkg/internal/error/yamlformaterror.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package error has contextual error types.
|
||||||
|
package error
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// YamlFormatError represents error with yaml file name where json/yaml format error happens.
|
||||||
|
type YamlFormatError struct {
|
||||||
|
Path string
|
||||||
|
ErrorMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e YamlFormatError) Error() string {
|
||||||
|
return fmt.Sprintf("YAML file [%s] encounters a format error.\n%s\n", e.Path, e.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler handles YamlFormatError
|
||||||
|
func Handler(e error, path string) error {
|
||||||
|
if isYAMLSyntaxError(e) {
|
||||||
|
return YamlFormatError{
|
||||||
|
Path: path,
|
||||||
|
ErrorMsg: e.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func isYAMLSyntaxError(e error) bool {
|
||||||
|
return strings.Contains(e.Error(), "error converting YAML to JSON") || strings.Contains(e.Error(), "error unmarshaling JSON")
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"fileloader.go",
|
||||||
|
"loader.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/loader",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/loader",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/git:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,312 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package loader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/pkg/fs"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/git"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileLoader is a kustomization's interface to files.
|
||||||
|
//
|
||||||
|
// The directory in which a kustomization file sits
|
||||||
|
// is referred to below as the kustomization's root.
|
||||||
|
//
|
||||||
|
// An instance of fileLoader has an immutable root,
|
||||||
|
// and offers a `New` method returning a new loader
|
||||||
|
// with a new root.
|
||||||
|
//
|
||||||
|
// A kustomization file refers to two kinds of files:
|
||||||
|
//
|
||||||
|
// * supplemental data paths
|
||||||
|
//
|
||||||
|
// `Load` is used to visit these paths.
|
||||||
|
//
|
||||||
|
// They must terminate in or below the root.
|
||||||
|
//
|
||||||
|
// They hold things like resources, patches,
|
||||||
|
// data for ConfigMaps, etc.
|
||||||
|
//
|
||||||
|
// * bases; other kustomizations
|
||||||
|
//
|
||||||
|
// `New` is used to load bases.
|
||||||
|
//
|
||||||
|
// A base can be either a remote git repo URL, or
|
||||||
|
// a directory specified relative to the current
|
||||||
|
// root. In the former case, the repo is locally
|
||||||
|
// cloned, and the new loader is rooted on a path
|
||||||
|
// in that clone.
|
||||||
|
//
|
||||||
|
// As loaders create new loaders, a root history
|
||||||
|
// is established, and used to disallow:
|
||||||
|
//
|
||||||
|
// - A base that is a repository that, in turn,
|
||||||
|
// specifies a base repository seen previously
|
||||||
|
// in the loading stack (a cycle).
|
||||||
|
//
|
||||||
|
// - An overlay depending on a base positioned at
|
||||||
|
// or above it. I.e. '../foo' is OK, but '.',
|
||||||
|
// '..', '../..', etc. are disallowed. Allowing
|
||||||
|
// such a base has no advantages and encourages
|
||||||
|
// cycles, particularly if some future change
|
||||||
|
// were to introduce globbing to file
|
||||||
|
// specifications in the kustomization file.
|
||||||
|
//
|
||||||
|
// These restrictions assure that kustomizations
|
||||||
|
// are self-contained and relocatable, and impose
|
||||||
|
// some safety when relying on remote kustomizations,
|
||||||
|
// e.g. a ConfigMap generator specified to read
|
||||||
|
// from /etc/passwd will fail.
|
||||||
|
//
|
||||||
|
type fileLoader struct {
|
||||||
|
// Loader that spawned this loader.
|
||||||
|
// Used to avoid cycles.
|
||||||
|
referrer *fileLoader
|
||||||
|
// An absolute, cleaned path to a directory.
|
||||||
|
// The Load function reads from this directory,
|
||||||
|
// or directories below it.
|
||||||
|
root fs.ConfirmedDir
|
||||||
|
// If this is non-nil, the files were
|
||||||
|
// obtained from the given repository.
|
||||||
|
repoSpec *git.RepoSpec
|
||||||
|
// File system utilities.
|
||||||
|
fSys fs.FileSystem
|
||||||
|
// Used to clone repositories.
|
||||||
|
cloner git.Cloner
|
||||||
|
// Used to clean up, as needed.
|
||||||
|
cleaner func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileLoaderAtCwd returns a loader that loads from ".".
|
||||||
|
func NewFileLoaderAtCwd(fSys fs.FileSystem) *fileLoader {
|
||||||
|
return newLoaderOrDie(fSys, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileLoaderAtRoot returns a loader that loads from "/".
|
||||||
|
func NewFileLoaderAtRoot(fSys fs.FileSystem) *fileLoader {
|
||||||
|
return newLoaderOrDie(fSys, string(filepath.Separator))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root returns the absolute path that is prepended to any
|
||||||
|
// relative paths used in Load.
|
||||||
|
func (l *fileLoader) Root() string {
|
||||||
|
return l.root.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLoaderOrDie(fSys fs.FileSystem, path string) *fileLoader {
|
||||||
|
root, err := demandDirectoryRoot(fSys, path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to make loader at '%s'; %v", path, err)
|
||||||
|
}
|
||||||
|
return newLoaderAtConfirmedDir(
|
||||||
|
root, fSys, nil, git.ClonerUsingGitExec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newLoaderAtConfirmedDir returns a new fileLoader with given root.
|
||||||
|
func newLoaderAtConfirmedDir(
|
||||||
|
root fs.ConfirmedDir, fSys fs.FileSystem,
|
||||||
|
referrer *fileLoader, cloner git.Cloner) *fileLoader {
|
||||||
|
return &fileLoader{
|
||||||
|
root: root,
|
||||||
|
referrer: referrer,
|
||||||
|
fSys: fSys,
|
||||||
|
cloner: cloner,
|
||||||
|
cleaner: func() error { return nil },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assure that the given path is in fact a directory.
|
||||||
|
func demandDirectoryRoot(
|
||||||
|
fSys fs.FileSystem, path string) (fs.ConfirmedDir, error) {
|
||||||
|
if path == "" {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"loader root cannot be empty")
|
||||||
|
}
|
||||||
|
d, f, err := fSys.CleanedAbs(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"absolute path error in '%s' : %v", path, err)
|
||||||
|
}
|
||||||
|
if f != "" {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"got file '%s', but '%s' must be a directory to be a root",
|
||||||
|
f, path)
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Loader, rooted relative to current loader,
|
||||||
|
// or rooted in a temp directory holding a git repo clone.
|
||||||
|
func (l *fileLoader) New(path string) (ifc.Loader, error) {
|
||||||
|
if path == "" {
|
||||||
|
return nil, fmt.Errorf("new root cannot be empty")
|
||||||
|
}
|
||||||
|
repoSpec, err := git.NewRepoSpecFromUrl(path)
|
||||||
|
if err == nil {
|
||||||
|
// Treat this as git repo clone request.
|
||||||
|
if err := l.errIfRepoCycle(repoSpec); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newLoaderAtGitClone(repoSpec, l.fSys, l.referrer, l.cloner)
|
||||||
|
}
|
||||||
|
if filepath.IsAbs(path) {
|
||||||
|
return nil, fmt.Errorf("new root '%s' cannot be absolute", path)
|
||||||
|
}
|
||||||
|
root, err := demandDirectoryRoot(l.fSys, l.root.Join(path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := l.errIfGitContainmentViolation(root); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := l.errIfArgEqualOrHigher(root); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newLoaderAtConfirmedDir(
|
||||||
|
root, l.fSys, l, l.cloner), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newLoaderAtGitClone returns a new Loader pinned to a temporary
|
||||||
|
// directory holding a cloned git repo.
|
||||||
|
func newLoaderAtGitClone(
|
||||||
|
repoSpec *git.RepoSpec, fSys fs.FileSystem,
|
||||||
|
referrer *fileLoader, cloner git.Cloner) (ifc.Loader, error) {
|
||||||
|
err := cloner(repoSpec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
root, f, err := fSys.CleanedAbs(repoSpec.AbsPath())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// We don't know that the path requested in repoSpec
|
||||||
|
// is a directory until we actually clone it and look
|
||||||
|
// inside. That just happened, hence the error check
|
||||||
|
// is here.
|
||||||
|
if f != "" {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"'%s' refers to file '%s'; expecting directory",
|
||||||
|
repoSpec.AbsPath(), f)
|
||||||
|
}
|
||||||
|
return &fileLoader{
|
||||||
|
root: root,
|
||||||
|
referrer: referrer,
|
||||||
|
repoSpec: repoSpec,
|
||||||
|
fSys: fSys,
|
||||||
|
cloner: cloner,
|
||||||
|
cleaner: repoSpec.Cleaner(fSys),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *fileLoader) errIfGitContainmentViolation(
|
||||||
|
base fs.ConfirmedDir) error {
|
||||||
|
containingRepo := l.containingRepo()
|
||||||
|
if containingRepo == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !base.HasPrefix(containingRepo.CloneDir()) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"security; bases in kustomizations found in "+
|
||||||
|
"cloned git repos must be within the repo, "+
|
||||||
|
"but base '%s' is outside '%s'",
|
||||||
|
base, containingRepo.CloneDir())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks back through referrers for a git repo, returning nil
|
||||||
|
// if none found.
|
||||||
|
func (l *fileLoader) containingRepo() *git.RepoSpec {
|
||||||
|
if l.repoSpec != nil {
|
||||||
|
return l.repoSpec
|
||||||
|
}
|
||||||
|
if l.referrer == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return l.referrer.containingRepo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// errIfArgEqualOrHigher tests whether the argument,
|
||||||
|
// is equal to or above the root of any ancestor.
|
||||||
|
func (l *fileLoader) errIfArgEqualOrHigher(
|
||||||
|
candidateRoot fs.ConfirmedDir) error {
|
||||||
|
if l.root.HasPrefix(candidateRoot) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"cycle detected: candidate root '%s' contains visited root '%s'",
|
||||||
|
candidateRoot, l.root)
|
||||||
|
}
|
||||||
|
if l.referrer == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return l.referrer.errIfArgEqualOrHigher(candidateRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(monopole): Distinguish branches?
|
||||||
|
// I.e. Allow a distinction between git URI with
|
||||||
|
// path foo and tag bar and a git URI with the same
|
||||||
|
// path but a different tag?
|
||||||
|
func (l *fileLoader) errIfRepoCycle(newRepoSpec *git.RepoSpec) error {
|
||||||
|
// TODO(monopole): Use parsed data instead of Raw().
|
||||||
|
if l.repoSpec != nil &&
|
||||||
|
strings.HasPrefix(l.repoSpec.Raw(), newRepoSpec.Raw()) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"cycle detected: URI '%s' referenced by previous URI '%s'",
|
||||||
|
newRepoSpec.Raw(), l.repoSpec.Raw())
|
||||||
|
}
|
||||||
|
if l.referrer == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return l.referrer.errIfRepoCycle(newRepoSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load returns content of file at the given relative path,
|
||||||
|
// else an error. The path must refer to a file in or
|
||||||
|
// below the current root.
|
||||||
|
func (l *fileLoader) Load(path string) ([]byte, error) {
|
||||||
|
if filepath.IsAbs(path) {
|
||||||
|
return nil, l.loadOutOfBounds(path)
|
||||||
|
}
|
||||||
|
d, f, err := l.fSys.CleanedAbs(l.root.Join(path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if f == "" {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"'%s' must be a file (got d='%s')", path, d)
|
||||||
|
}
|
||||||
|
if !d.HasPrefix(l.root) {
|
||||||
|
return nil, l.loadOutOfBounds(path)
|
||||||
|
}
|
||||||
|
return l.fSys.ReadFile(d.Join(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *fileLoader) loadOutOfBounds(path string) error {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"security; file '%s' is not in or below '%s'",
|
||||||
|
path, l.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup runs the cleaner.
|
||||||
|
func (l *fileLoader) Cleanup() error {
|
||||||
|
return l.cleaner()
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package loader has a data loading interface and various implementations.
|
||||||
|
package loader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/pkg/fs"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/git"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLoader returns a Loader.
|
||||||
|
func NewLoader(path string, fSys fs.FileSystem) (ifc.Loader, error) {
|
||||||
|
repoSpec, err := git.NewRepoSpecFromUrl(path)
|
||||||
|
if err == nil {
|
||||||
|
return newLoaderAtGitClone(
|
||||||
|
repoSpec, fSys, nil, git.ClonerUsingGitExec)
|
||||||
|
}
|
||||||
|
root, err := demandDirectoryRoot(fSys, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newLoaderAtConfirmedDir(
|
||||||
|
root, fSys, nil, git.ClonerUsingGitExec), nil
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"json6902.go",
|
||||||
|
"strategicmerge.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/patch",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/patch",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/patch/transformer:all-srcs",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package patch
|
||||||
|
|
||||||
|
import "sigs.k8s.io/kustomize/pkg/gvk"
|
||||||
|
|
||||||
|
// Json6902 represents a json patch for an object
|
||||||
|
// with format documented https://tools.ietf.org/html/rfc6902.
|
||||||
|
type Json6902 struct {
|
||||||
|
// Target refers to a Kubernetes object that the json patch will be
|
||||||
|
// applied to. It must refer to a Kubernetes resource under the
|
||||||
|
// purview of this kustomization. Target should use the
|
||||||
|
// raw name of the object (the name specified in its YAML,
|
||||||
|
// before addition of a namePrefix and a nameSuffix).
|
||||||
|
Target *Target `json:"target" yaml:"target"`
|
||||||
|
|
||||||
|
// relative file path for a json patch file inside a kustomization
|
||||||
|
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target represents the kubernetes object that the patch is applied to
|
||||||
|
type Target struct {
|
||||||
|
gvk.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
|
||||||
|
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||||
|
Name string `json:"name" yaml:"name"`
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package patch
|
||||||
|
|
||||||
|
// StrategicMerge represents a relative path to a
|
||||||
|
// stategic merge patch with the format
|
||||||
|
// https://github.com/kubernetes/community/blob/master/contributors/devel/strategic-merge-patch.md
|
||||||
|
type StrategicMerge string
|
||||||
|
|
||||||
|
// Append appends a slice of patch paths to a StrategicMerge slice
|
||||||
|
func Append(patches []StrategicMerge, paths ...string) []StrategicMerge {
|
||||||
|
for _, p := range paths {
|
||||||
|
patches = append(patches, StrategicMerge(p))
|
||||||
|
}
|
||||||
|
return patches
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exist determines if a patch path exists in a slice of StrategicMerge
|
||||||
|
func Exist(patches []StrategicMerge, path string) bool {
|
||||||
|
for _, p := range patches {
|
||||||
|
if p == StrategicMerge(path) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"factory.go",
|
||||||
|
"patchjson6902json.go",
|
||||||
|
"util.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/patch/transformer",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/patch/transformer",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/evanphx/json-patch:go_default_library",
|
||||||
|
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/patch:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resid:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resmap:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/transformers:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resid"
|
||||||
|
|
||||||
|
"github.com/evanphx/json-patch"
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/patch"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PatchJson6902Factory makes Json6902 transformers
|
||||||
|
type PatchJson6902Factory struct {
|
||||||
|
loader ifc.Loader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPatchJson6902Factory returns a new PatchJson6902Factory.
|
||||||
|
func NewPatchJson6902Factory(l ifc.Loader) PatchJson6902Factory {
|
||||||
|
return PatchJson6902Factory{loader: l}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakePatchJson6902Transformer returns a transformer for applying Json6902 patch
|
||||||
|
func (f PatchJson6902Factory) MakePatchJson6902Transformer(patches []patch.Json6902) (transformers.Transformer, error) {
|
||||||
|
var ts []transformers.Transformer
|
||||||
|
for _, p := range patches {
|
||||||
|
t, err := f.makeOnePatchJson6902Transformer(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
ts = append(ts, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return transformers.NewMultiTransformerWithConflictCheck(ts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f PatchJson6902Factory) makeOnePatchJson6902Transformer(p patch.Json6902) (transformers.Transformer, error) {
|
||||||
|
if p.Target == nil {
|
||||||
|
return nil, fmt.Errorf("must specify the target field in patchesJson6902")
|
||||||
|
}
|
||||||
|
if p.Path == "" {
|
||||||
|
return nil, fmt.Errorf("must specify the path for a json patch file")
|
||||||
|
}
|
||||||
|
|
||||||
|
targetId := resid.NewResIdWithPrefixNamespace(
|
||||||
|
gvk.Gvk{
|
||||||
|
Group: p.Target.Group,
|
||||||
|
Version: p.Target.Version,
|
||||||
|
Kind: p.Target.Kind,
|
||||||
|
},
|
||||||
|
p.Target.Name,
|
||||||
|
"",
|
||||||
|
p.Target.Namespace,
|
||||||
|
)
|
||||||
|
|
||||||
|
rawOp, err := f.loader.Load(p.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isJsonFormat(rawOp) {
|
||||||
|
// if it isn't JSON, try to parse it as YAML
|
||||||
|
rawOp, err = yaml.YAMLToJSON(rawOp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedPatch, err := jsonpatch.DecodePatch(rawOp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newPatchJson6902JSONTransformer(targetId, decodedPatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isJsonFormat(data []byte) bool {
|
||||||
|
return data[0] == '['
|
||||||
|
}
|
61
vendor/sigs.k8s.io/kustomize/pkg/patch/transformer/patchjson6902json.go
generated
vendored
Normal file
61
vendor/sigs.k8s.io/kustomize/pkg/patch/transformer/patchjson6902json.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/evanphx/json-patch"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resid"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// patchJson6902JSONTransformer applies patches.
|
||||||
|
type patchJson6902JSONTransformer struct {
|
||||||
|
target resid.ResId
|
||||||
|
patch jsonpatch.Patch
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ transformers.Transformer = &patchJson6902JSONTransformer{}
|
||||||
|
|
||||||
|
// newPatchJson6902JSONTransformer constructs a PatchJson6902 transformer.
|
||||||
|
func newPatchJson6902JSONTransformer(t resid.ResId, p jsonpatch.Patch) (transformers.Transformer, error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return transformers.NewNoOpTransformer(), nil
|
||||||
|
}
|
||||||
|
return &patchJson6902JSONTransformer{target: t, patch: p}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform apply the json patches on top of the base resources.
|
||||||
|
func (t *patchJson6902JSONTransformer) Transform(baseResourceMap resmap.ResMap) error {
|
||||||
|
obj, err := findTargetObj(baseResourceMap, t.target)
|
||||||
|
if obj == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rawObj, err := obj.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
modifiedObj, err := t.patch.Apply(rawObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = obj.UnmarshalJSON(modifiedObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package transformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resid"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findTargetObj(m resmap.ResMap, targetId resid.ResId) (*resource.Resource, error) {
|
||||||
|
matchedIds := m.FindByGVKN(targetId)
|
||||||
|
if targetId.Namespace() != "" {
|
||||||
|
var ids []resid.ResId
|
||||||
|
for _, id := range matchedIds {
|
||||||
|
if id.Namespace() == targetId.Namespace() {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matchedIds = ids
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matchedIds) == 0 {
|
||||||
|
return nil, fmt.Errorf("couldn't find any object to apply the json patch %v", targetId)
|
||||||
|
}
|
||||||
|
if len(matchedIds) > 1 {
|
||||||
|
return nil, fmt.Errorf("found multiple objects that the patch can apply %v", matchedIds)
|
||||||
|
}
|
||||||
|
return m[matchedIds[0]], nil
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["resid.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/resid",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/resid",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//vendor/sigs.k8s.io/kustomize/pkg/gvk:go_default_library"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,194 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package resid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResId conflates GroupVersionKind with a textual name to uniquely identify a kubernetes resource (object).
|
||||||
|
type ResId struct {
|
||||||
|
// Gvk of the resource.
|
||||||
|
gvKind gvk.Gvk
|
||||||
|
// original name of the resource before transformation.
|
||||||
|
name string
|
||||||
|
// namePrefix of the resource
|
||||||
|
// an untransformed resource has no prefix, fully transformed resource has an arbitrary number of prefixes
|
||||||
|
// concatenated together.
|
||||||
|
prefix string
|
||||||
|
// nameSuffix of the resource
|
||||||
|
// an untransformed resource has no suffix, fully transformed resource has an arbitrary number of suffixes
|
||||||
|
// concatenated together.
|
||||||
|
suffix string
|
||||||
|
// namespace the resource belongs to
|
||||||
|
// an untransformed resource has no namespace, fully transformed resource has the namespace from
|
||||||
|
// the top most overlay
|
||||||
|
namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResIdWithPrefixSuffixNamespace creates new resource identifier with a prefix, suffix and a namespace
|
||||||
|
func NewResIdWithPrefixSuffixNamespace(k gvk.Gvk, n, p, s, ns string) ResId {
|
||||||
|
return ResId{gvKind: k, name: n, prefix: p, suffix: s, namespace: ns}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResIdWithPrefixNamespace creates new resource identifier with a prefix and a namespace
|
||||||
|
func NewResIdWithPrefixNamespace(k gvk.Gvk, n, p, ns string) ResId {
|
||||||
|
return ResId{gvKind: k, name: n, prefix: p, namespace: ns}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResIdWithSuffixNamespace creates new resource identifier with a suffix and a namespace
|
||||||
|
func NewResIdWithSuffixNamespace(k gvk.Gvk, n, s, ns string) ResId {
|
||||||
|
return ResId{gvKind: k, name: n, suffix: s, namespace: ns}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResIdWithPrefixSuffix creates new resource identifier with a prefix and suffix
|
||||||
|
func NewResIdWithPrefixSuffix(k gvk.Gvk, n, p, s string) ResId {
|
||||||
|
return ResId{gvKind: k, name: n, prefix: p, suffix: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResId creates new resource identifier
|
||||||
|
func NewResId(k gvk.Gvk, n string) ResId {
|
||||||
|
return ResId{gvKind: k, name: n}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResIdKindOnly creates new resource identifier
|
||||||
|
func NewResIdKindOnly(k string, n string) ResId {
|
||||||
|
return ResId{gvKind: gvk.FromKind(k), name: n}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
noNamespace = "~X"
|
||||||
|
noPrefix = "~P"
|
||||||
|
noName = "~N"
|
||||||
|
noSuffix = "~S"
|
||||||
|
separator = "|"
|
||||||
|
)
|
||||||
|
|
||||||
|
// String of ResId based on GVK, name and prefix
|
||||||
|
func (n ResId) String() string {
|
||||||
|
ns := n.namespace
|
||||||
|
if ns == "" {
|
||||||
|
ns = noNamespace
|
||||||
|
}
|
||||||
|
p := n.prefix
|
||||||
|
if p == "" {
|
||||||
|
p = noPrefix
|
||||||
|
}
|
||||||
|
nm := n.name
|
||||||
|
if nm == "" {
|
||||||
|
nm = noName
|
||||||
|
}
|
||||||
|
s := n.suffix
|
||||||
|
if s == "" {
|
||||||
|
s = noSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(
|
||||||
|
[]string{n.gvKind.String(), ns, p, nm, s}, separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GvknString of ResId based on GVK and name
|
||||||
|
func (n ResId) GvknString() string {
|
||||||
|
return n.gvKind.String() + separator + n.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// GvknEquals return if two ResId have the same Group/Version/Kind and name
|
||||||
|
// The comparison excludes prefix and suffix
|
||||||
|
func (n ResId) GvknEquals(id ResId) bool {
|
||||||
|
return n.gvKind.Equals(id.gvKind) && n.name == id.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gvk returns Group/Version/Kind of the resource.
|
||||||
|
func (n ResId) Gvk() gvk.Gvk {
|
||||||
|
return n.gvKind
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns resource name.
|
||||||
|
func (n ResId) Name() string {
|
||||||
|
return n.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace returns resource namespace.
|
||||||
|
func (n ResId) Namespace() string {
|
||||||
|
return n.namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyWithNewPrefixSuffix make a new copy from current ResId
|
||||||
|
// and append a new prefix and suffix
|
||||||
|
func (n ResId) CopyWithNewPrefixSuffix(p, s string) ResId {
|
||||||
|
result := n
|
||||||
|
if p != "" {
|
||||||
|
result.prefix = n.concatPrefix(p)
|
||||||
|
}
|
||||||
|
if s != "" {
|
||||||
|
result.suffix = n.concatSuffix(s)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyWithNewNamespace make a new copy from current ResId and set a new namespace
|
||||||
|
func (n ResId) CopyWithNewNamespace(ns string) ResId {
|
||||||
|
result := n
|
||||||
|
result.namespace = ns
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasSameLeftmostPrefix check if two ResIds have the same
|
||||||
|
// left most prefix.
|
||||||
|
func (n ResId) HasSameLeftmostPrefix(id ResId) bool {
|
||||||
|
prefixes1 := n.prefixList()
|
||||||
|
prefixes2 := id.prefixList()
|
||||||
|
return prefixes1[0] == prefixes2[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasSameRightmostSuffix check if two ResIds have the same
|
||||||
|
// right most suffix.
|
||||||
|
func (n ResId) HasSameRightmostSuffix(id ResId) bool {
|
||||||
|
suffixes1 := n.suffixList()
|
||||||
|
suffixes2 := id.suffixList()
|
||||||
|
return suffixes1[len(suffixes1)-1] == suffixes2[len(suffixes2)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n ResId) concatPrefix(p string) string {
|
||||||
|
if p == "" {
|
||||||
|
return n.prefix
|
||||||
|
}
|
||||||
|
if n.prefix == "" {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return p + ":" + n.prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n ResId) concatSuffix(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return n.suffix
|
||||||
|
}
|
||||||
|
if n.suffix == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return n.suffix + ":" + s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n ResId) prefixList() []string {
|
||||||
|
return strings.Split(n.prefix, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n ResId) suffixList() []string {
|
||||||
|
return strings.Split(n.suffix, ":")
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"factory.go",
|
||||||
|
"idslice.go",
|
||||||
|
"resmap.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/resmap",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/resmap",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||||
|
"//vendor/github.com/pkg/errors:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/internal/error:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resid:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resource:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package resmap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
internal "sigs.k8s.io/kustomize/pkg/internal/error"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resource"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Factory makes instances of ResMap.
|
||||||
|
type Factory struct {
|
||||||
|
resF *resource.Factory
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFactory returns a new resmap.Factory.
|
||||||
|
func NewFactory(rf *resource.Factory) *Factory {
|
||||||
|
return &Factory{resF: rf}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RF returns a resource.Factory.
|
||||||
|
func (rmF *Factory) RF() *resource.Factory {
|
||||||
|
return rmF.resF
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromFiles returns a ResMap given a resource path slice.
|
||||||
|
func (rmF *Factory) FromFiles(
|
||||||
|
loader ifc.Loader, paths []string) (ResMap, error) {
|
||||||
|
var result []ResMap
|
||||||
|
for _, path := range paths {
|
||||||
|
content, err := loader.Load(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Load from path "+path+" failed")
|
||||||
|
}
|
||||||
|
res, err := rmF.newResMapFromBytes(content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, internal.Handler(err, path)
|
||||||
|
}
|
||||||
|
result = append(result, res)
|
||||||
|
}
|
||||||
|
return MergeWithErrorOnIdCollision(result...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newResMapFromBytes decodes a list of objects in byte array format.
|
||||||
|
func (rmF *Factory) newResMapFromBytes(b []byte) (ResMap, error) {
|
||||||
|
resources, err := rmF.resF.SliceFromBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ResMap{}
|
||||||
|
for _, res := range resources {
|
||||||
|
id := res.Id()
|
||||||
|
if _, found := result[id]; found {
|
||||||
|
return result, fmt.Errorf("GroupVersionKindName: %#v already exists b the map", id)
|
||||||
|
}
|
||||||
|
result[id] = res
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResMapFromConfigMapArgs returns a Resource slice given
|
||||||
|
// a configmap metadata slice from kustomization file.
|
||||||
|
func (rmF *Factory) NewResMapFromConfigMapArgs(argList []types.ConfigMapArgs, options *types.GeneratorOptions) (ResMap, error) {
|
||||||
|
var resources []*resource.Resource
|
||||||
|
for _, args := range argList {
|
||||||
|
res, err := rmF.resF.MakeConfigMap(&args, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "NewResMapFromConfigMapArgs")
|
||||||
|
}
|
||||||
|
resources = append(resources, res)
|
||||||
|
}
|
||||||
|
return newResMapFromResourceSlice(resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewResMapFromSecretArgs takes a SecretArgs slice, generates
|
||||||
|
// secrets from each entry, and accumulates them in a ResMap.
|
||||||
|
func (rmF *Factory) NewResMapFromSecretArgs(argsList []types.SecretArgs, options *types.GeneratorOptions) (ResMap, error) {
|
||||||
|
var resources []*resource.Resource
|
||||||
|
for _, args := range argsList {
|
||||||
|
res, err := rmF.resF.MakeSecret(&args, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "NewResMapFromSecretArgs")
|
||||||
|
}
|
||||||
|
resources = append(resources, res)
|
||||||
|
}
|
||||||
|
return newResMapFromResourceSlice(resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the loader for the underlying factory
|
||||||
|
func (rmF *Factory) Set(ldr ifc.Loader) {
|
||||||
|
rmF.resF.Set(ldr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResMapFromResourceSlice(resources []*resource.Resource) (ResMap, error) {
|
||||||
|
result := ResMap{}
|
||||||
|
for _, res := range resources {
|
||||||
|
id := res.Id()
|
||||||
|
if _, found := result[id]; found {
|
||||||
|
return nil, fmt.Errorf("duplicated %#v is not allowed", id)
|
||||||
|
}
|
||||||
|
result[id] = res
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package resmap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IdSlice implements the sort interface.
|
||||||
|
type IdSlice []resid.ResId
|
||||||
|
|
||||||
|
var _ sort.Interface = IdSlice{}
|
||||||
|
|
||||||
|
func (a IdSlice) Len() int { return len(a) }
|
||||||
|
func (a IdSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a IdSlice) Less(i, j int) bool {
|
||||||
|
if !a[i].Gvk().Equals(a[j].Gvk()) {
|
||||||
|
return a[i].Gvk().IsLessThan(a[j].Gvk())
|
||||||
|
}
|
||||||
|
return a[i].String() < a[j].String()
|
||||||
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package resmap implements a map from ResId to Resource that tracks all resources in a kustomization.
|
||||||
|
package resmap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resid"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resource"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResMap is a map from ResId to Resource.
|
||||||
|
type ResMap map[resid.ResId]*resource.Resource
|
||||||
|
|
||||||
|
// FindByGVKN find the matched ResIds by Group/Version/Kind and Name
|
||||||
|
func (m ResMap) FindByGVKN(inputId resid.ResId) []resid.ResId {
|
||||||
|
var result []resid.ResId
|
||||||
|
for id := range m {
|
||||||
|
if id.GvknEquals(inputId) {
|
||||||
|
result = append(result, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// DemandOneMatchForId find the matched resource by Group/Version/Kind and Name
|
||||||
|
func (m ResMap) DemandOneMatchForId(inputId resid.ResId) (*resource.Resource, bool) {
|
||||||
|
result := m.FindByGVKN(inputId)
|
||||||
|
if len(result) == 1 {
|
||||||
|
return m[result[0]], true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`.
|
||||||
|
func (m ResMap) EncodeAsYaml() ([]byte, error) {
|
||||||
|
var ids []resid.ResId
|
||||||
|
for id := range m {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
sort.Sort(IdSlice(ids))
|
||||||
|
|
||||||
|
firstObj := true
|
||||||
|
var b []byte
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
for _, id := range ids {
|
||||||
|
obj := m[id]
|
||||||
|
out, err := yaml.Marshal(obj.Map())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if firstObj {
|
||||||
|
firstObj = false
|
||||||
|
} else {
|
||||||
|
_, err = buf.WriteString("---\n")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = buf.Write(out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorIfNotEqual returns error if maps are not equal.
|
||||||
|
func (m ResMap) ErrorIfNotEqual(m2 ResMap) error {
|
||||||
|
if len(m) != len(m2) {
|
||||||
|
var keySet1 []resid.ResId
|
||||||
|
var keySet2 []resid.ResId
|
||||||
|
for id := range m {
|
||||||
|
keySet1 = append(keySet1, id)
|
||||||
|
}
|
||||||
|
for id := range m2 {
|
||||||
|
keySet2 = append(keySet2, id)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("maps has different number of entries: %#v doesn't equals %#v", keySet1, keySet2)
|
||||||
|
}
|
||||||
|
for id, obj1 := range m {
|
||||||
|
obj2, found := m2[id]
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("%#v doesn't exist in %#v", id, m2)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(obj1, obj2) {
|
||||||
|
return fmt.Errorf("%#v doesn't deep equal %#v", obj1, obj2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy clone the resmap into a new one
|
||||||
|
func (m ResMap) DeepCopy(rf *resource.Factory) ResMap {
|
||||||
|
mcopy := make(ResMap)
|
||||||
|
for id, obj := range m {
|
||||||
|
mcopy[id] = obj.DeepCopy()
|
||||||
|
}
|
||||||
|
return mcopy
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterBy returns a subset ResMap containing ResIds with
|
||||||
|
// the same namespace and leftmost name prefix and rightmost name
|
||||||
|
// as the inputId. If inputId is a cluster level resource, this
|
||||||
|
// returns the original ResMap.
|
||||||
|
func (m ResMap) FilterBy(inputId resid.ResId) ResMap {
|
||||||
|
if inputId.Gvk().IsClusterKind() {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
result := ResMap{}
|
||||||
|
for id, res := range m {
|
||||||
|
if id.Gvk().IsClusterKind() || id.Namespace() == inputId.Namespace() &&
|
||||||
|
id.HasSameLeftmostPrefix(inputId) &&
|
||||||
|
id.HasSameRightmostSuffix(inputId) {
|
||||||
|
result[id] = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeWithErrorOnIdCollision combines multiple ResMap instances, failing on
|
||||||
|
// key collision and skipping nil maps.
|
||||||
|
// If all of the maps are nil, an empty ResMap is returned.
|
||||||
|
func MergeWithErrorOnIdCollision(maps ...ResMap) (ResMap, error) {
|
||||||
|
result := ResMap{}
|
||||||
|
for _, m := range maps {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for id, res := range m {
|
||||||
|
if _, found := result[id]; found {
|
||||||
|
return nil, fmt.Errorf("id '%q' already used", id)
|
||||||
|
}
|
||||||
|
result[id] = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeWithOverride combines multiple ResMap instances, allowing and sometimes
|
||||||
|
// demanding certain collisions and skipping nil maps.
|
||||||
|
// A collision would be demanded, say, when a generated ConfigMap has the
|
||||||
|
// "replace" option in its generation instructions, meaning it is supposed
|
||||||
|
// to replace something from the raw resources list.
|
||||||
|
// If all of the maps are nil, an empty ResMap is returned.
|
||||||
|
// When looping over the instances to combine them, if a resource id for
|
||||||
|
// resource X is found to be already in the combined map, then the behavior
|
||||||
|
// field for X must be BehaviorMerge or BehaviorReplace. If X is not in the
|
||||||
|
// map, then it's behavior cannot be merge or replace.
|
||||||
|
func MergeWithOverride(maps ...ResMap) (ResMap, error) {
|
||||||
|
result := maps[0]
|
||||||
|
if result == nil {
|
||||||
|
result = ResMap{}
|
||||||
|
}
|
||||||
|
for _, m := range maps[1:] {
|
||||||
|
if m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for id, r := range m {
|
||||||
|
matchedId := result.FindByGVKN(id)
|
||||||
|
if len(matchedId) == 1 {
|
||||||
|
id = matchedId[0]
|
||||||
|
switch r.Behavior() {
|
||||||
|
case types.BehaviorReplace:
|
||||||
|
r.Replace(result[id])
|
||||||
|
result[id] = r
|
||||||
|
case types.BehaviorMerge:
|
||||||
|
r.Merge(result[id])
|
||||||
|
result[id] = r
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("id %#v exists; must merge or replace", id)
|
||||||
|
}
|
||||||
|
} else if len(matchedId) == 0 {
|
||||||
|
switch r.Behavior() {
|
||||||
|
case types.BehaviorMerge, types.BehaviorReplace:
|
||||||
|
return nil, fmt.Errorf("id %#v does not exist; cannot merge or replace", id)
|
||||||
|
default:
|
||||||
|
result[id] = r
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("merge conflict, found multiple objects %v the Resmap %v can merge into", matchedId, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"factory.go",
|
||||||
|
"resource.go",
|
||||||
|
],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/kustomize/pkg/resource",
|
||||||
|
importpath = "sigs.k8s.io/kustomize/pkg/resource",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/ifc:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/internal/error:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/patch:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/resid:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/kustomize/pkg/types:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
internal "sigs.k8s.io/kustomize/pkg/internal/error"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/patch"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Factory makes instances of Resource.
|
||||||
|
type Factory struct {
|
||||||
|
kf ifc.KunstructuredFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFactory makes an instance of Factory.
|
||||||
|
func NewFactory(kf ifc.KunstructuredFactory) *Factory {
|
||||||
|
return &Factory{kf: kf}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromMap returns a new instance of Resource.
|
||||||
|
func (rf *Factory) FromMap(m map[string]interface{}) *Resource {
|
||||||
|
return &Resource{
|
||||||
|
Kunstructured: rf.kf.FromMap(m),
|
||||||
|
options: types.NewGenArgs(nil, nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromMapAndOption returns a new instance of Resource with given options.
|
||||||
|
func (rf *Factory) FromMapAndOption(m map[string]interface{}, args *types.GeneratorArgs, option *types.GeneratorOptions) *Resource {
|
||||||
|
return &Resource{
|
||||||
|
Kunstructured: rf.kf.FromMap(m),
|
||||||
|
options: types.NewGenArgs(args, option),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromKunstructured returns a new instance of Resource.
|
||||||
|
func (rf *Factory) FromKunstructured(
|
||||||
|
u ifc.Kunstructured) *Resource {
|
||||||
|
if u == nil {
|
||||||
|
log.Fatal("unstruct ifc must not be null")
|
||||||
|
}
|
||||||
|
return &Resource{
|
||||||
|
Kunstructured: u,
|
||||||
|
options: types.NewGenArgs(nil, nil),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceFromPatches returns a slice of resources given a patch path
|
||||||
|
// slice from a kustomization file.
|
||||||
|
func (rf *Factory) SliceFromPatches(
|
||||||
|
ldr ifc.Loader, paths []patch.StrategicMerge) ([]*Resource, error) {
|
||||||
|
var result []*Resource
|
||||||
|
for _, path := range paths {
|
||||||
|
content, err := ldr.Load(string(path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res, err := rf.SliceFromBytes(content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, internal.Handler(err, string(path))
|
||||||
|
}
|
||||||
|
result = append(result, res...)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceFromBytes unmarshalls bytes into a Resource slice.
|
||||||
|
func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) {
|
||||||
|
kunStructs, err := rf.kf.SliceFromBytes(in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var result []*Resource
|
||||||
|
for len(kunStructs) > 0 {
|
||||||
|
u := kunStructs[0]
|
||||||
|
kunStructs = kunStructs[1:]
|
||||||
|
if u.GetKind() == "List" {
|
||||||
|
items := u.Map()["items"]
|
||||||
|
itemsSlice, ok := items.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("items in List is type %T, expected array", items)
|
||||||
|
}
|
||||||
|
for _, item := range itemsSlice {
|
||||||
|
itemJSON, err := json.Marshal(item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
innerU, err := rf.kf.SliceFromBytes(itemJSON)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// append innerU to kunStructs so nested Lists can be handled
|
||||||
|
kunStructs = append(kunStructs, innerU...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = append(result, rf.FromKunstructured(u))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the loader for the underlying factory
|
||||||
|
func (rf *Factory) Set(ldr ifc.Loader) {
|
||||||
|
rf.kf.Set(ldr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeConfigMap makes an instance of Resource for ConfigMap
|
||||||
|
func (rf *Factory) MakeConfigMap(args *types.ConfigMapArgs, options *types.GeneratorOptions) (*Resource, error) {
|
||||||
|
u, err := rf.kf.MakeConfigMap(args, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Resource{Kunstructured: u, options: types.NewGenArgs(&types.GeneratorArgs{Behavior: args.Behavior}, options)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeSecret makes an instance of Resource for Secret
|
||||||
|
func (rf *Factory) MakeSecret(args *types.SecretArgs, options *types.GeneratorOptions) (*Resource, error) {
|
||||||
|
u, err := rf.kf.MakeSecret(args, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Resource{Kunstructured: u, options: types.NewGenArgs(&types.GeneratorArgs{Behavior: args.Behavior}, options)}, nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue