diff --git a/go.mod b/go.mod index 80ee601c8..020d6395d 100644 --- a/go.mod +++ b/go.mod @@ -62,6 +62,7 @@ require ( google.golang.org/genproto v0.0.0-20200312145019-da6875a35672 // indirect google.golang.org/grpc v1.28.0 // indirect gopkg.in/ini.v1 v1.54.0 // indirect + gopkg.in/yaml.v2 v2.2.8 // indirect gotest.tools/v3 v3.0.2 // indirect helm.sh/helm/v3 v3.1.2 k8s.io/api v0.18.0 diff --git a/pkg/components/prometheus-operator/component_conversion_test.go b/pkg/components/prometheus-operator/component_conversion_test.go new file mode 100644 index 000000000..1c45e7fd2 --- /dev/null +++ b/pkg/components/prometheus-operator/component_conversion_test.go @@ -0,0 +1,167 @@ +package prometheus //nolint:testpackage + +import ( + "reflect" + "testing" + + "github.com/hashicorp/hcl/v2" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml" + "k8s.io/client-go/util/jsonpath" + + "github.com/kinvolk/lokomotive/pkg/components/util" +) + +func renderManifests(t *testing.T, configHCL string) map[string]string { + name := "prometheus-operator" + + component := newComponent() + + body, diagnostics := util.GetComponentBody(configHCL, name) + if diagnostics.HasErrors() { + t.Fatalf("Getting component body: %v", diagnostics.Errs()) + } + + diagnostics = component.LoadConfig(body, &hcl.EvalContext{}) + if diagnostics.HasErrors() { + t.Fatalf("Valid config should not return an error, got: %v", diagnostics) + } + + ret, err := component.RenderManifests() + if err != nil { + t.Fatalf("Rendering manifests with valid config should succeed, got: %s", err) + } + + return ret +} + +//nolint:funlen +func TestConversion(t *testing.T) { + testCases := []struct { + Name string + InputConfig string + ExpectedConfigFileName string + Expected reflect.Value + JSONPath string + }{ + { + Name: "use external_url param", + InputConfig: ` +component "prometheus-operator" { + prometheus { + external_url = "https://prometheus.externalurl.net" + } +} +`, + ExpectedConfigFileName: "prometheus-operator/templates/prometheus/prometheus.yaml", + Expected: reflect.ValueOf("https://prometheus.externalurl.net"), + JSONPath: "{.spec.externalUrl}", + }, + { + Name: "no external_url param", + InputConfig: ` + component "prometheus-operator" { + prometheus { + ingress { + host = "prometheus.mydomain.net" + class = "contour" + certmanager_cluster_issuer = "letsencrypt-production" + } + } + } + `, + ExpectedConfigFileName: "prometheus-operator/templates/prometheus/prometheus.yaml", + Expected: reflect.ValueOf("https://prometheus.mydomain.net"), + JSONPath: "{.spec.externalUrl}", + }, + { + Name: "ingress creation for prometheus", + InputConfig: ` + component "prometheus-operator" { + prometheus { + ingress { + host = "prometheus.mydomain.net" + class = "contour" + certmanager_cluster_issuer = "letsencrypt-production" + } + } + } + `, + ExpectedConfigFileName: "prometheus-operator/templates/prometheus/ingress.yaml", + Expected: reflect.ValueOf("prometheus.mydomain.net"), + JSONPath: "{.spec.rules[0].host}", + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + + m := renderManifests(t, tc.InputConfig) + if len(m) == 0 { + t.Fatalf("Rendered manifests shouldn't be empty") + } + + gotConfig, ok := m[tc.ExpectedConfigFileName] + if !ok { + t.Fatalf("Config not found with filename: %q", tc.ExpectedConfigFileName) + } + + u := getUnstructredObj(t, gotConfig) + got := getValFromObject(t, tc.JSONPath, u) + + switch got.Kind() { //nolint:exhaustive + case reflect.Interface: + switch gotVal := got.Interface().(type) { + // Add more cases as the expected values become heterogeneous. + // case bool: + case string: + expVal, ok := tc.Expected.Interface().(string) + if !ok { + t.Fatalf("expected value is not string") + } + + if gotVal != expVal { + t.Fatalf("expected: %s, got: %s", expVal, gotVal) + } + + default: + t.Fatalf("Unknown type of the interface object: %T", got.Interface()) + } + default: + t.Fatalf("Unknown type of the object extracted from the converted YAML: %v", got.Kind()) + } + }) + } +} + +func getUnstructredObj(t *testing.T, yamlObj string) *unstructured.Unstructured { + u := &unstructured.Unstructured{} + + // Decode YAML into `unstructured.Unstructured`. + dec := yamlserializer.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) + if _, _, err := dec.Decode([]byte(yamlObj), nil, u); err != nil { + t.Fatalf("Converting config to unstructured.Unstructured: %v", err) + } + + return u +} + +func getValFromObject(t *testing.T, jp string, obj *unstructured.Unstructured) reflect.Value { + jPath := jsonpath.New("parse") + if err := jPath.Parse(jp); err != nil { + t.Fatalf("Parsing JSONPath: %v", err) + } + + v, err := jPath.FindResults(obj.Object) + if err != nil { + t.Fatalf("Finding results using JSONPath in the YAML file: %v", err) + } + + if len(v) == 0 || len(v[0]) == 0 { + t.Fatalf("No result found") + } + + return v[0][0] +} diff --git a/pkg/components/prometheus-operator/component_test.go b/pkg/components/prometheus-operator/component_test.go index 8de992256..9f996c555 100644 --- a/pkg/components/prometheus-operator/component_test.go +++ b/pkg/components/prometheus-operator/component_test.go @@ -62,6 +62,34 @@ component "prometheus-operator" { }`, wantErr: true, }, + { + desc: "prometheus ingress and external_url given and are different", + hcl: ` +component "prometheus-operator" { + prometheus { + external_url = "https://prometheus.notmydomain.net" + ingress { + host = "prometheus.mydomain.net" + } + } +} +`, + wantErr: true, + }, + { + desc: "prometheus ingress and external_url given and are same", + hcl: ` +component "prometheus-operator" { + prometheus { + external_url = "https://prometheus.mydomain.net" + ingress { + host = "prometheus.mydomain.net" + } + } +} +`, + wantErr: false, + }, } for _, tc := range tests { diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/yaml/yaml.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/yaml/yaml.go new file mode 100644 index 000000000..2fdd1d43d --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/yaml/yaml.go @@ -0,0 +1,46 @@ +/* +Copyright 2014 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 yaml + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/yaml" +) + +// yamlSerializer converts YAML passed to the Decoder methods to JSON. +type yamlSerializer struct { + // the nested serializer + runtime.Serializer +} + +// yamlSerializer implements Serializer +var _ runtime.Serializer = yamlSerializer{} + +// NewDecodingSerializer adds YAML decoding support to a serializer that supports JSON. +func NewDecodingSerializer(jsonSerializer runtime.Serializer) runtime.Serializer { + return &yamlSerializer{jsonSerializer} +} + +func (c yamlSerializer) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + out, err := yaml.ToJSON(data) + if err != nil { + return nil, nil, err + } + data = out + return c.Serializer.Decode(data, gvk, into) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8ae88d736..8067f30a5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -575,6 +575,7 @@ k8s.io/apimachinery/pkg/runtime/serializer/protobuf k8s.io/apimachinery/pkg/runtime/serializer/recognizer k8s.io/apimachinery/pkg/runtime/serializer/streaming k8s.io/apimachinery/pkg/runtime/serializer/versioning +k8s.io/apimachinery/pkg/runtime/serializer/yaml k8s.io/apimachinery/pkg/selection k8s.io/apimachinery/pkg/types k8s.io/apimachinery/pkg/util/cache