From ff60fba327d4722ef52120d86a33aea513f7e43f Mon Sep 17 00:00:00 2001 From: Michael Hammer Date: Thu, 7 Mar 2019 11:27:48 -0500 Subject: [PATCH 1/2] core-secret files --- pkg/core/namespace/from_kube.go | 88 +++++++ pkg/core/namespace/namespace.go | 27 +++ pkg/core/namespace/namespace_test.go | 331 +++++++++++++++++++++++++++ pkg/core/namespace/to_kube.go | 106 +++++++++ 4 files changed, 552 insertions(+) create mode 100644 pkg/core/namespace/from_kube.go create mode 100644 pkg/core/namespace/namespace.go create mode 100644 pkg/core/namespace/namespace_test.go create mode 100644 pkg/core/namespace/to_kube.go diff --git a/pkg/core/namespace/from_kube.go b/pkg/core/namespace/from_kube.go new file mode 100644 index 0000000..c2c0741 --- /dev/null +++ b/pkg/core/namespace/from_kube.go @@ -0,0 +1,88 @@ +package namespace + +import ( + "fmt" + "reflect" + + serrors "github.com/koki/structurederrors" + "k8s.io/api/core/v1" +) + +// NewNamespaceFromKubeNamespace will create a new Namespace object with +// the data from a provided kubernetes namespace object +func NewNamespaceFromKubeNamespace(ns interface{}) (*Namespace, error) { + switch reflect.TypeOf(ns) { + case reflect.TypeOf(v1.Namespace{}): + obj := ns.(v1.Namespace) + if obj.APIVersion != "v1" { + return nil, fmt.Errorf("mis-matched versions. Namespace type: %s, APIVersion: %s", reflect.TypeOf(ns), obj.APIVersion) + } + return fromKubeNamespaceV1(&obj) + case reflect.TypeOf(&v1.Namespace{}): + obj := ns.(*v1.Namespace) + if obj.APIVersion != "v1" { + return nil, fmt.Errorf("mis-matched versions. Namespace type: %s, APIVersion: %s", reflect.TypeOf(ns), obj.APIVersion) + } + return fromKubeNamespaceV1(obj) + default: + return nil, fmt.Errorf("unknown Namespace version: %s", reflect.TypeOf(ns)) + } +} + +// fromKubeNamespaceV1 converts to koki namespace for V1 +func fromKubeNamespaceV1(kubeNamespace *v1.Namespace) (*Namespace, error) { + kokiNamespace := Namespace{} + + kokiNamespace.Name = kubeNamespace.Name + kokiNamespace.Namespace = kubeNamespace.Namespace + kokiNamespace.Version = kubeNamespace.APIVersion + kokiNamespace.Cluster = kubeNamespace.ClusterName + kokiNamespace.Labels = kubeNamespace.Labels + kokiNamespace.Annotations = kubeNamespace.Annotations + + finalizers, err := fromKubeNamespaceSpecV1(kubeNamespace.Spec) + if err != nil { + return nil, err + } + kokiNamespace.Finalizers = finalizers + + phase, err := fromKubeNamespaceStatusV1(kubeNamespace.Status) + if err != nil { + return nil, err + } + kokiNamespace.Phase = phase + + return &kokiNamespace, nil +} + +// fromKubeNamespaceSpecV1 changes a kubernetes spec to a kubernetes finalizer +func fromKubeNamespaceSpecV1(kubeSpec v1.NamespaceSpec) ([]FinalizerName, error) { + var kokiFinalizers []FinalizerName + + for i := range kubeSpec.Finalizers { + kubeFinalizer := kubeSpec.Finalizers[i] + + var kokiFinalizer FinalizerName + switch kubeFinalizer { + case v1.FinalizerKubernetes: + kokiFinalizer = FinalizerKubernetes + default: + return nil, serrors.InvalidValueErrorf(kubeFinalizer, "unrecognized finalizer") + } + + kokiFinalizers = append(kokiFinalizers, kokiFinalizer) + } + + return kokiFinalizers, nil +} + +// fromKubeNamespaceStatusV1 changes a kubernetes status to a kubernetes phase +func fromKubeNamespaceStatusV1(kubeStatus v1.NamespaceStatus) (NamespacePhase, error) { + switch kubeStatus.Phase { + case v1.NamespaceActive: + return NamespaceActive, nil + case v1.NamespaceTerminating: + return NamespaceTerminating, nil + } + return NamespaceActive, serrors.InvalidValueErrorf(kubeStatus.Phase, "invalid phase") +} diff --git a/pkg/core/namespace/namespace.go b/pkg/core/namespace/namespace.go new file mode 100644 index 0000000..d3b2531 --- /dev/null +++ b/pkg/core/namespace/namespace.go @@ -0,0 +1,27 @@ +package namespace + +type Namespace struct { + Version string `json:"version,omitempty"` + Cluster string `json:"cluster,omitempty"` + Name string `json:"name,omitempty"` + Namespace string `json:"namespace,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` + + Finalizers []FinalizerName `json:"finalizers,omitempty"` + + Phase NamespacePhase `json:"phase,omitempty"` +} + +type NamespacePhase int + +const ( + NamespaceActive NamespacePhase = iota + NamespaceTerminating +) + +type FinalizerName int + +const ( + FinalizerKubernetes FinalizerName = iota +) \ No newline at end of file diff --git a/pkg/core/namespace/namespace_test.go b/pkg/core/namespace/namespace_test.go new file mode 100644 index 0000000..fac9006 --- /dev/null +++ b/pkg/core/namespace/namespace_test.go @@ -0,0 +1,331 @@ +package namespace + +import ( + "reflect" + "testing" + + v1 "k8s.io/api/core/v1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var metaDataMappings = map[string]string{ + "Name": "Name", + "Namespace": "Namespace", + "Version": "APIVersion", + "Cluster": "ClusterName", + "Labels": "Labels", + "Annotations": "Annotations", +} + +// TestNewNamespaceFromKubeNamespace verifies that the correct version and type are created +func TestNewNamespaceFromKubeNamespace(t *testing.T) { + testcases := []struct { + description string + obj interface{} + }{ + { + description: "v1 namespace object", + obj: v1.Namespace{}, + }, + { + description: "v1 namespace pointer", + obj: &v1.Namespace{}, + }, + } + + for _, tc := range testcases { + obj, _ := NewNamespaceFromKubeNamespace(tc.obj) + expectedObj := reflect.TypeOf(&Namespace{}) + objType := reflect.TypeOf(obj) + if expectedObj != objType { + t.Errorf("incorrect koki namespace, expected %s, got %s", expectedObj, objType) + } + } +} + +// TestFromKubeV1 verifies that the fields were correctly populated +func TestFromKubeV1(t *testing.T) { + v1Ns := v1.Namespace{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "testCM", + Namespace: "testNS", + ClusterName: "testCluster", + Labels: map[string]string{"label1": "test1", "label2": "test2"}, + Annotations: map[string]string{"ann1": "test1", "ann2": "test2"}, + }, + Spec: v1.NamespaceSpec{ + Finalizers: []v1.FinalizerName{ + v1.FinalizerKubernetes, + }, + }, + Status: v1.NamespaceStatus{ + Phase: v1.NamespaceActive, + }, + } + + ns, _ := fromKubeNamespaceV1(&v1Ns) + + // Check Meta Data Fields + for name, v1Name := range metaDataMappings { + value := reflect.ValueOf(ns).Elem().FieldByName(name).Interface() + v1Value := reflect.ValueOf(v1Ns).FieldByName(v1Name).Interface() + if !reflect.DeepEqual(value, v1Value) { + t.Errorf("incorrect koki field %s, expected %s, got %s", name, v1Value, value) + } + } + // Check Finalizers + valueFinalizer := ns.Finalizers + v1ValueFinalizer := v1Ns.Spec.Finalizers + if !finalizersEqual(&valueFinalizer, &v1ValueFinalizer) { + t.Errorf("incorrect koki finalizers, used %+v, got %+v", v1ValueFinalizer, valueFinalizer) + } + // Check NamespacePhase + valuePhase := ns.Phase + if valuePhase != NamespaceActive { + t.Errorf("incorrect koki Phase, expectec %+v, got %+v", NamespaceActive, valuePhase) + } +} + +// TestToKube verifies that the correct version and type were returned +func TestToKube(t *testing.T) { + testcases := []struct { + description string + version string + expectedObj interface{} + }{ + { + description: "v1 api version", + version: "v1", + expectedObj: &v1.Namespace{}, + }, + { + description: "empty api version", + version: "", + expectedObj: &v1.Namespace{}, + }, + { + description: "unknown api version", + version: "unknown", + expectedObj: nil, + }, + } + + for _, tc := range testcases { + ns := Namespace{ + Version: tc.version, + } + kubeObj, err := ns.ToKube() + kubeType := reflect.TypeOf(kubeObj) + expectedType := reflect.TypeOf(tc.expectedObj) + if kubeType != expectedType { + t.Errorf("wrong api version, got %s expected %s", kubeType, expectedType) + } + if tc.expectedObj == nil && err == nil { + t.Errorf("no error returned") + } + } +} + +// TestToKubeV1 verifies that the fields were correctly populated +func TestToKubeV1(t *testing.T) { + ns := Namespace{ + Version: "v1", + Name: "testNS", + Namespace: "testNS", + Cluster: "testCluster", + Labels: map[string]string{"label1": "test1", "label2": "test2"}, + Annotations: map[string]string{"ann1": "test1", "ann2": "test2"}, + Finalizers: []FinalizerName{FinalizerKubernetes}, + Phase: NamespaceTerminating, + } + + kubeObj, _ := ns.toKubeV1() + + // Check Meta Data Fields + for name, v1Name := range metaDataMappings { + value := reflect.ValueOf(ns).FieldByName(name).Interface() + v1Value := reflect.ValueOf(kubeObj).Elem().FieldByName(v1Name).Interface() + if !reflect.DeepEqual(value, v1Value) { + t.Errorf("incorrect %s, expected %s, got %s", v1Name, value, v1Value) + } + } + // Check Finalizers + valueFinalizer := ns.Finalizers + v1ValueFinalizer := kubeObj.Spec.Finalizers + if !finalizersEqual(&valueFinalizer, &v1ValueFinalizer) { + t.Errorf("incorrect converting to kube finalizers, used %+v, got %+v", valueFinalizer, v1ValueFinalizer) + } + // Check NamespacePhase + v1ValuePhase := kubeObj.Status.Phase + if v1ValuePhase != v1.NamespaceTerminating { + t.Errorf("incorrect converting to kube Phase, expected %+v, got %+v", v1.NamespaceTerminating, v1ValuePhase) + } +} + +// TestFromKubeNamespaceSpecV1 verifies the correct type and values were returned +func TestFromKubeNamespaceSpecV1(t *testing.T) { + kubeSpec1 := v1.NamespaceSpec{Finalizers: []v1.FinalizerName{v1.FinalizerKubernetes}} + kubeSpec2 := v1.NamespaceSpec{Finalizers: []v1.FinalizerName{}} + kubeSpecMap := map[*v1.NamespaceSpec][]FinalizerName{ + &kubeSpec1: []FinalizerName{FinalizerKubernetes}, + &kubeSpec2: nil, + } + for kubeSpec, kokiFinalizers := range kubeSpecMap { + gotFinalizers, err := fromKubeNamespaceSpecV1(*kubeSpec) + if err != nil { + t.Errorf("Error from v1 spec: %s", err) + } + // Check the Type Received + expectedType := reflect.TypeOf([]FinalizerName{}) + gotType := reflect.TypeOf(gotFinalizers) + if expectedType != gotType { + t.Errorf("wrong koki finalizers type, got %s expected %s", gotType, expectedType) + } + // Check the Value Received + if !reflect.DeepEqual(gotFinalizers, kokiFinalizers) { + t.Errorf("wrong finalizer values, expected %+v, got %+v", kokiFinalizers, gotFinalizers) + } + } + +} + +// TestFromKubeNamespaceStatusV1 verifies the correct type and values were returned +func TestFromKubeNamespaceStatusV1(t *testing.T) { + kubeStatus1 := v1.NamespaceStatus{Phase: v1.NamespaceActive} + kubeStatus2 := v1.NamespaceStatus{Phase: v1.NamespaceTerminating} + kubeStatusMap := map[*v1.NamespaceStatus]NamespacePhase{ + &kubeStatus1: NamespaceActive, + &kubeStatus2: NamespaceTerminating, + } + for kubeStatus, kokiPhase := range kubeStatusMap { + gotKokiPhase, err := fromKubeNamespaceStatusV1(*kubeStatus) + if err != nil { + t.Errorf("Error from v1 status: %s", err) + } + // Check the Type Received + expectedType := reflect.TypeOf(NamespaceActive) + gotType := reflect.TypeOf(gotKokiPhase) + if expectedType != gotType { + t.Errorf("wrong koki Phase type, got %+v expected %+v", gotType, expectedType) + } + // Check the Value Received + if gotKokiPhase != kokiPhase { + t.Errorf("wrong phase value, expected %+v, got %+v", kokiPhase, gotKokiPhase) + } + } +} + // TestToKubeNamespaceStatusV1 verifies the correct type and values were returned +func TestToKubeNamespaceStatusV1(t *testing.T) { + kokiNamespaces := map[*Namespace]v1.NamespacePhase{ + &Namespace{Phase: NamespaceActive}: v1.NamespaceActive, + &Namespace{Phase: NamespaceTerminating}: v1.NamespaceTerminating, + } + for kokiNamespace, kubePhase := range kokiNamespaces { + gotKubeStatus, err := kokiNamespace.toKubeNamespaceStatusV1() + if err != nil { + t.Errorf("Error to v1 status: %s", err) + } + // Check the Type Received + expectedType := reflect.TypeOf(v1.NamespaceStatus{}) + gotType := reflect.TypeOf(gotKubeStatus) + if expectedType != gotType { + t.Errorf("wrong kube NamespaceStatus type, got %s expected %s", gotType, expectedType) + } + // Check the Value Received + if string(gotKubeStatus.Phase) != string(kubePhase) { + t.Errorf("wrong finalizers in spec") + } + } +} + +// TestToKubeNamespaceSpecV1 verifies the correct type and values were returned +func TestToKubeNamespaceSpecV1(t *testing.T) { + kokiNamespaces := []Namespace{ + Namespace{Finalizers: []FinalizerName{}}, // no finalizers + Namespace{Finalizers: []FinalizerName{FinalizerKubernetes}}, // one finalizer + } + for _, kokiNamespace := range kokiNamespaces { + gotKubeNamespaceSpec, err := kokiNamespace.toKubeNamespaceSpecV1() + if err != nil { + t.Errorf("Error to v1 spec: %s", err) + } + // Check the Type Received + expectedType := reflect.TypeOf(v1.NamespaceSpec{}) + gotType := reflect.TypeOf(gotKubeNamespaceSpec) + if expectedType != gotType { + t.Errorf("wrong kube NamespaceSpec type, got %s expected %s", gotType, expectedType) + } + // Check the Values Received + if !finalizersEqual(&kokiNamespace.Finalizers, &gotKubeNamespaceSpec.Finalizers) { + t.Errorf("wrong finalizers in spec") + } + } +} + +// TestToKubeFinalizerV1 +func TestToKubeFinalizerV1(t *testing.T) { + finalizers := map[FinalizerName]v1.FinalizerName{ + FinalizerKubernetes: v1.FinalizerKubernetes, + } + for kokiFinalizer, kubeFinalizer := range finalizers { + gotKubeFinalizer, err := toKubeFinalizerV1(kokiFinalizer) + if err != nil { + t.Errorf("%Error to v1 finalizer: s", err) + } + // Check the Type Received + expectedType := reflect.TypeOf(kubeFinalizer) + gotType := reflect.TypeOf(gotKubeFinalizer) + if expectedType != gotType { + t.Errorf("wrong kube finalizer type, got %s, expected %s", gotType, expectedType) + } + // Check the Value Received + if !reflect.DeepEqual(gotKubeFinalizer, kubeFinalizer) { + t.Errorf("incorrect finalizer, expected %s, got %s", kubeFinalizer, gotKubeFinalizer) + } + } +} + +// TestFinalizersEqual verifies a list of koki finalizers can be compared to a list of kube finalizers +func TestFinalizersEqual(t *testing.T) { + kokiFinalizers1 := []FinalizerName{} + kokiFinalizers2 := []FinalizerName{FinalizerKubernetes} + kubeFinalizers1 := []v1.FinalizerName{} + kubeFinalizers2 := []v1.FinalizerName{v1.FinalizerKubernetes} + if !finalizersEqual(&kokiFinalizers1, &kubeFinalizers1) { + t.Errorf("incorrect finalizer equality for empty, expected %+v, got %+v", true, false) + } + if !finalizersEqual(&kokiFinalizers2, &kubeFinalizers2) { + t.Errorf("incorrect finalizer equality for 1 finalizer each, expected %+v, got %+v", true, false) + } + if finalizersEqual(&kokiFinalizers1, &kubeFinalizers2) { + t.Errorf("incorrect finalizer equality for different number of finalizers, expected %+v, got %+v", false, true) + } +} + + +// finalizersEqual checks that lists of finalizers are equal, handles if out of order +func finalizersEqual(kokiFinalizers *[]FinalizerName, kubeFinalizers *[]v1.FinalizerName) bool { + if ((kokiFinalizers == nil) != (kubeFinalizers == nil)) || (len(*kokiFinalizers) != len(*kubeFinalizers)) { + return false + } + kubeFinalizersMap := make([]bool, len(*kubeFinalizers), len(*kubeFinalizers)) + for _, kokiFinalizer := range *kokiFinalizers { + found := false + for i, kubeFinalizer := range *kubeFinalizers { + convertedKokiFinalizer, _ := toKubeFinalizerV1(kokiFinalizer) + if reflect.DeepEqual(convertedKokiFinalizer, kubeFinalizer) && !kubeFinalizersMap[i] { + kubeFinalizersMap[i] = true // mark as found + found = true + break + } + } + if !found { + return false + } + } + return true +} diff --git a/pkg/core/namespace/to_kube.go b/pkg/core/namespace/to_kube.go new file mode 100644 index 0000000..00f78db --- /dev/null +++ b/pkg/core/namespace/to_kube.go @@ -0,0 +1,106 @@ +package namespace + +import ( + "fmt" + "strings" + + serrors "github.com/koki/structurederrors" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// ToKube will return a kubernetes namespace object of the api version +// type defined in the namespace +func (ns *Namespace) ToKube() (runtime.Object, error) { + switch strings.ToLower(ns.Version) { + case "v1": + return ns.toKubeV1() + case "": + return ns.toKubeV1() + default: + return nil, fmt.Errorf("unsupported api version for Namespace: %s", ns.Version) + } +} + +// toKubev1 converts to a kubernetes namespace for V1 +func (ns *Namespace) toKubeV1() (*v1.Namespace, error) { + var err error + kubeNamespace := &v1.Namespace{} + + kubeNamespace.Name = ns.Name + kubeNamespace.Namespace = ns.Namespace + if len(ns.Version) == 0 { + kubeNamespace.APIVersion = "v1" + } else { + kubeNamespace.APIVersion = ns.Version + } + kubeNamespace.Kind = "Namespace" + kubeNamespace.ClusterName = ns.Cluster + kubeNamespace.Labels = ns.Labels + kubeNamespace.Annotations = ns.Annotations + + spec, err := ns.toKubeNamespaceSpecV1() + if err != nil { + return nil, serrors.ContextualizeErrorf(err, "Namespace spec") + } + kubeNamespace.Spec = spec + + status, err := ns.toKubeNamespaceStatusV1() + if err != nil { + return nil, serrors.ContextualizeErrorf(err, "Namespace status") + } + kubeNamespace.Status = status + + return kubeNamespace, nil +} + +// toKubeNamespaceStatusV1 changes a koki status to a kubernetes status +func (ns *Namespace) toKubeNamespaceStatusV1() (v1.NamespaceStatus, error) { + var kubeStatus v1.NamespaceStatus + + if &ns.Phase == nil { + return kubeStatus, nil + } + + var phase v1.NamespacePhase + switch ns.Phase { + case NamespaceActive: + phase = v1.NamespaceActive + case NamespaceTerminating: + phase = v1.NamespaceTerminating + default: + return kubeStatus, serrors.InvalidValueErrorf(ns.Phase, "Invalid namespace phase") + } + kubeStatus.Phase = phase + + return kubeStatus, nil +} + +// toKubeNamespaceSpecV1 changes a koki spec to a kubernetes spec +func (ns *Namespace) toKubeNamespaceSpecV1() (v1.NamespaceSpec, error) { + var kubeSpec v1.NamespaceSpec + var kubeFinalizers []v1.FinalizerName + + for i := range ns.Finalizers { + kokiFinalizer := ns.Finalizers[i] + + kubeFinalizer, err := toKubeFinalizerV1(kokiFinalizer) + if err != nil { + return kubeSpec, err + } + + kubeFinalizers = append(kubeFinalizers, kubeFinalizer) + } + kubeSpec.Finalizers = kubeFinalizers + + return kubeSpec, nil +} + +// toKubeFinalizerV1 changes a koki finalizer to a kubernetes finalizer +func toKubeFinalizerV1(kokiFinalizer FinalizerName) (v1.FinalizerName, error) { + switch kokiFinalizer { + case FinalizerKubernetes: + return v1.FinalizerKubernetes, nil + } + return v1.FinalizerKubernetes, serrors.InvalidValueErrorf(kokiFinalizer, "unrecognized value") +} From 6d4885c5186bc070d838f5d4e99f9166a8715002 Mon Sep 17 00:00:00 2001 From: Michael Hammer Date: Thu, 7 Mar 2019 11:28:41 -0500 Subject: [PATCH 2/2] updated go mod and sum --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 4050b88..311733e 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/imdario/mergo v0.3.6 github.com/json-iterator/go v1.1.5 // indirect github.com/koki/json v0.0.0-20180412040528-e521cbda08e3 + github.com/koki/short v0.4.0 github.com/koki/structurederrors v0.0.0-20180506174113-6b997eb5e2ca github.com/kr/pretty v0.1.0 // indirect github.com/kr/pty v1.1.3 // indirect diff --git a/go.sum b/go.sum index 356364b..2307ef8 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,8 @@ github.com/koki/json v0.0.0-20180412040528-e521cbda08e3 h1:9t+domN30eqyAq73D7kBk github.com/koki/json v0.0.0-20180412040528-e521cbda08e3/go.mod h1:GxoZSBQJ3PAJ3f6MqwA4YHzjqA8AjalvkQEhrBVq094= github.com/koki/mantle v0.0.0-20181120192352-ca65c4d23a14 h1:rbH1TZeVAUNG+hP6ThJs8dcqZLoZbkHMWBpQMlkGHoE= github.com/koki/mantle v0.0.0-20190122212551-738f1eadde62 h1:DBwEton1c8BWEmkgv3h+vc7rZx52lEsNtw8BbpJi0ZY= +github.com/koki/short v0.4.0 h1:eSc3KkBjo0oMOFVpwzYYBQtmg99/oXbjjN/nZ8FFc2A= +github.com/koki/short v0.4.0/go.mod h1:+fOE/2zGvDljKx9c0jCYtD2MNKKXq5fA3Qiyj8tVZEs= github.com/koki/structurederrors v0.0.0-20180506174113-6b997eb5e2ca h1:KmXUVzyPjXzd3kY0feNFsWOGVDYFT4MjjgG8QJx0m6k= github.com/koki/structurederrors v0.0.0-20180506174113-6b997eb5e2ca/go.mod h1:9ovSGYUTbfN7zFRU4ouJ0kbVhAs/6qO8EQkqRWQkvO8= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=