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