commit
6da251948c
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
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 util
|
||||||
|
|
||||||
|
type ByGVKN []GroupVersionKindName
|
||||||
|
|
||||||
|
func (a ByGVKN) Len() int { return len(a) }
|
||||||
|
func (a ByGVKN) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a ByGVKN) Less(i, j int) bool {
|
||||||
|
if a[i].gvk.String() != a[j].gvk.String() {
|
||||||
|
return a[i].gvk.String() < a[j].gvk.String()
|
||||||
|
}
|
||||||
|
return a[i].name < a[j].name
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
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 util
|
||||||
|
|
||||||
|
type PathConfig struct {
|
||||||
|
Path []string
|
||||||
|
CreateIfNotPresent bool
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrefixNameOptions contains the prefix and the path config for each field that
|
||||||
|
// the name prefix will be applied.
|
||||||
|
type PrefixNameOptions struct {
|
||||||
|
prefix string
|
||||||
|
pathConfigs []PathConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Transformer = &PrefixNameOptions{}
|
||||||
|
|
||||||
|
var DefaultNamePrefixPathConfigs = []PathConfig{
|
||||||
|
{
|
||||||
|
Path: []string{"metadata", "name"},
|
||||||
|
CreateIfNotPresent: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *PrefixNameOptions) Complete(prefix string, pathConfigs []PathConfig) {
|
||||||
|
o.prefix = prefix
|
||||||
|
if pathConfigs == nil {
|
||||||
|
pathConfigs = DefaultNamePrefixPathConfigs
|
||||||
|
}
|
||||||
|
o.pathConfigs = pathConfigs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *PrefixNameOptions) Transform(m map[GroupVersionKindName]*unstructured.Unstructured) error {
|
||||||
|
for gvkn := range m {
|
||||||
|
obj := m[gvkn]
|
||||||
|
objMap := obj.UnstructuredContent()
|
||||||
|
for _, path := range o.pathConfigs {
|
||||||
|
err := mutateField(objMap, path.Path, path.CreateIfNotPresent, o.addPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *PrefixNameOptions) TransformBytes(in []byte) ([]byte, error) {
|
||||||
|
m, err := Decode(in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.Transform(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Encode(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *PrefixNameOptions) addPrefix(in interface{}) (interface{}, error) {
|
||||||
|
s, ok := in.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%#v is expectd to be %T", in, s)
|
||||||
|
}
|
||||||
|
return o.prefix + s, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
var prefixNameOps = PrefixNameOptions{
|
||||||
|
prefix: "someprefix-",
|
||||||
|
pathConfigs: DefaultNamePrefixPathConfigs,
|
||||||
|
}
|
||||||
|
|
||||||
|
var namePrefixedCm1 = unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "someprefix-cm1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var namePrefixedCm2 = unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "someprefix-cm2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var namePrefixedM = map[GroupVersionKindName]*unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
gvk: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
|
||||||
|
name: "cm1",
|
||||||
|
}: &namePrefixedCm1,
|
||||||
|
{
|
||||||
|
gvk: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
|
||||||
|
name: "cm2",
|
||||||
|
}: &namePrefixedCm2,
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrefixNameRun(t *testing.T) {
|
||||||
|
m := createMap()
|
||||||
|
err := prefixNameOps.Transform(m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(m, namePrefixedM) {
|
||||||
|
t.Fatalf("%s doesn't match expected %s", m, namePrefixedM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Transformer interface {
|
||||||
|
// Transform modifies objects in a map, e.g. add prefixes or additional labels.
|
||||||
|
Transform(m map[GroupVersionKindName]*unstructured.Unstructured) error
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GroupVersionKindName struct {
|
||||||
|
gvk schema.GroupVersionKind
|
||||||
|
// name of the resource.
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decode(in []byte) (map[GroupVersionKindName]*unstructured.Unstructured, error) {
|
||||||
|
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
|
||||||
|
objs := []*unstructured.Unstructured{}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
var out unstructured.Unstructured
|
||||||
|
err = decoder.Decode(&out)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
objs = append(objs, &out)
|
||||||
|
}
|
||||||
|
if err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[GroupVersionKindName]*unstructured.Unstructured{}
|
||||||
|
for i := range objs {
|
||||||
|
metaAccessor, err := meta.Accessor(objs[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
name := metaAccessor.GetName()
|
||||||
|
typeAccessor, err := meta.TypeAccessor(objs[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
apiVersion := typeAccessor.GetAPIVersion()
|
||||||
|
kind := typeAccessor.GetKind()
|
||||||
|
gv, err := schema.ParseGroupVersion(apiVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gvk := gv.WithKind(kind)
|
||||||
|
gvkn := GroupVersionKindName{
|
||||||
|
gvk: gvk,
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
m[gvkn] = objs[i]
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Encode(in map[GroupVersionKindName]*unstructured.Unstructured) ([]byte, error) {
|
||||||
|
gvknList := []GroupVersionKindName{}
|
||||||
|
for gvkn := range in {
|
||||||
|
gvknList = append(gvknList, gvkn)
|
||||||
|
}
|
||||||
|
sort.Sort(ByGVKN(gvknList))
|
||||||
|
|
||||||
|
firstObj := true
|
||||||
|
var b []byte
|
||||||
|
buf := bytes.NewBuffer(b)
|
||||||
|
for _, gvkn := range gvknList {
|
||||||
|
obj := in[gvkn]
|
||||||
|
out, err := yaml.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !firstObj {
|
||||||
|
_, err = buf.WriteString("---\n")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = buf.Write(out)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
firstObj = false
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mutateFunc func(interface{}) (interface{}, error)
|
||||||
|
|
||||||
|
func mutateField(m map[string]interface{}, pathToField []string, createIfNotPresent bool, fns ...mutateFunc) error {
|
||||||
|
if len(pathToField) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, found := m[pathToField[0]]
|
||||||
|
if !found {
|
||||||
|
if !createIfNotPresent {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m[pathToField[0]] = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pathToField) == 1 {
|
||||||
|
var err error
|
||||||
|
for _, fn := range fns {
|
||||||
|
m[pathToField[0]], err = fn(m[pathToField[0]])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v := m[pathToField[0]]
|
||||||
|
newPathToField := pathToField[1:]
|
||||||
|
switch typedV := v.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
return mutateField(typedV, newPathToField, createIfNotPresent, fns...)
|
||||||
|
case []interface{}:
|
||||||
|
for i := range typedV {
|
||||||
|
item := typedV[i]
|
||||||
|
typedItem, ok := item.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%#v is expectd to be %T", item, typedItem)
|
||||||
|
}
|
||||||
|
err := mutateField(typedItem, newPathToField, createIfNotPresent, fns...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%#v is not expected to be a primitive type", typedV)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
var encoded = []byte(`apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: cm2
|
||||||
|
`)
|
||||||
|
|
||||||
|
func createMap() map[GroupVersionKindName]*unstructured.Unstructured {
|
||||||
|
cm1 := unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cm2 := unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "cm2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return map[GroupVersionKindName]*unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
gvk: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
|
||||||
|
name: "cm1",
|
||||||
|
}: &cm1,
|
||||||
|
{
|
||||||
|
gvk: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"},
|
||||||
|
name: "cm2",
|
||||||
|
}: &cm2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecode(t *testing.T) {
|
||||||
|
expected := createMap()
|
||||||
|
m, err := Decode(encoded)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(m, expected) {
|
||||||
|
t.Fatalf("%#v doesn't match expected %#v", m, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncode(t *testing.T) {
|
||||||
|
out, err := Encode(createMap())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(out, encoded) {
|
||||||
|
t.Fatalf("%s doesn't match expected %s", out, encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue