From aa4e3bf5c714a2d1cb82d5701cdf9271046ef45d Mon Sep 17 00:00:00 2001 From: Fabian von Feilitzsch Date: Fri, 16 Jul 2021 13:49:03 -0400 Subject: [PATCH 1/3] Namespace will now be extracted from request URL when not provided in the body Signed-off-by: Fabian von Feilitzsch --- internal/ansible/proxy/inject_owner.go | 2 +- internal/ansible/proxy/inject_owner_test.go | 125 ++++++++++++++++ .../ansible/proxy/kubeconfig/kubeconfig.go | 13 +- internal/ansible/proxy/proxy_suite_test.go | 128 +++++++++++++++++ internal/ansible/proxy/proxy_test.go | 134 ++++++------------ internal/helm/client/client.go | 2 +- internal/helm/controller/controller.go | 2 +- internal/util/k8sutil/k8sutil.go | 8 +- internal/util/k8sutil/k8sutil_test.go | 63 +++++++- 9 files changed, 374 insertions(+), 103 deletions(-) create mode 100644 internal/ansible/proxy/inject_owner_test.go create mode 100644 internal/ansible/proxy/proxy_suite_test.go diff --git a/internal/ansible/proxy/inject_owner.go b/internal/ansible/proxy/inject_owner.go index 4514c87b302..39842fdd79f 100644 --- a/internal/ansible/proxy/inject_owner.go +++ b/internal/ansible/proxy/inject_owner.go @@ -136,7 +136,7 @@ func (i *injectOwnerReferenceHandler) ServeHTTP(w http.ResponseWriter, req *http ownerObject.SetGroupVersionKind(ownerGVK) ownerObject.SetNamespace(owner.Namespace) ownerObject.SetName(owner.Name) - addOwnerRef, err := k8sutil.SupportsOwnerReference(i.restMapper, ownerObject, data) + addOwnerRef, err := k8sutil.SupportsOwnerReference(i.restMapper, ownerObject, data, r.Namespace) if err != nil { m := "Could not determine if we should add owner ref" log.Error(err, m) diff --git a/internal/ansible/proxy/inject_owner_test.go b/internal/ansible/proxy/inject_owner_test.go new file mode 100644 index 00000000000..42a6ad3f4ae --- /dev/null +++ b/internal/ansible/proxy/inject_owner_test.go @@ -0,0 +1,125 @@ +// Copyright 2021 The Operator-SDK 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 proxy + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/operator-framework/operator-sdk/internal/ansible/proxy/kubeconfig" +) + +var _ = Describe("injectOwnerReferenceHandler", func() { + + Describe("ServeHTTP", func() { + It("Should inject ownerReferences even when namespace is not explicitly set", func() { + if testing.Short() { + Skip("skipping ansible owner reference injection testing in short mode") + } + cm := corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-owner-ref-injection", + }, + Data: map[string]string{ + "hello": "world", + }, + } + + body, err := json.Marshal(cm) + if err != nil { + Fail("Failed to marshal body") + } + + po, err := createTestPod("test-injection", "default", testClient) + if err != nil { + Fail(fmt.Sprintf("Failed to create pod: %v", err)) + } + defer func() { + if err := testClient.Delete(context.Background(), po); err != nil { + Fail(fmt.Sprintf("Failed to delete the pod: %v", err)) + } + }() + + req, err := http.NewRequest("POST", "http://localhost:8888/api/v1/namespaces/default/configmaps", bytes.NewReader(body)) + if err != nil { + Fail(fmt.Sprintf("Failed to create http request: %v", err)) + } + + username, err := kubeconfig.EncodeOwnerRef( + metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Pod", + Name: po.GetName(), + UID: po.GetUID(), + }, "default") + if err != nil { + Fail("Failed to encode owner reference") + } + req.SetBasicAuth(username, "unused") + + httpClient := http.Client{} + + defer func() { + cleanupReq, err := http.NewRequest("DELETE", "http://localhost:8888/api/v1/namespaces/default/configmaps/test-owner-ref-injection", bytes.NewReader([]byte{})) + if err != nil { + Fail(fmt.Sprintf("Failed to delete configmap: %v", err)) + } + _, err = httpClient.Do(cleanupReq) + if err != nil { + Fail(fmt.Sprintf("Failed to delete configmap: %v", err)) + } + }() + + resp, err := httpClient.Do(req) + if err != nil { + Fail(fmt.Sprintf("Failed to create configmap: %v", err)) + } + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + Fail(fmt.Sprintf("Failed to read response body: %v", err)) + } + var modifiedCM corev1.ConfigMap + err = json.Unmarshal(respBody, &modifiedCM) + if err != nil { + Fail(fmt.Sprintf("Failed to unmarshal configmap: %v", err)) + } + ownerRefs := modifiedCM.ObjectMeta.OwnerReferences + + Expect(len(ownerRefs)).To(Equal(1)) + + ownerRef := ownerRefs[0] + + Expect(ownerRef.APIVersion).To(Equal("v1")) + Expect(ownerRef.Kind).To(Equal("Pod")) + Expect(ownerRef.Name).To(Equal(po.GetName())) + Expect(ownerRef.UID).To(Equal(po.GetUID())) + }) + }) +}) diff --git a/internal/ansible/proxy/kubeconfig/kubeconfig.go b/internal/ansible/proxy/kubeconfig/kubeconfig.go index db098b6c507..b4396815bc6 100644 --- a/internal/ansible/proxy/kubeconfig/kubeconfig.go +++ b/internal/ansible/proxy/kubeconfig/kubeconfig.go @@ -68,18 +68,25 @@ type NamespacedOwnerReference struct { Namespace string } +func EncodeOwnerRef(ownerRef metav1.OwnerReference, namespace string) (string, error) { + nsOwnerRef := NamespacedOwnerReference{OwnerReference: ownerRef, Namespace: namespace} + ownerRefJSON, err := json.Marshal(nsOwnerRef) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(ownerRefJSON), nil +} + // Create renders a kubeconfig template and writes it to disk func Create(ownerRef metav1.OwnerReference, proxyURL string, namespace string) (*os.File, error) { - nsOwnerRef := NamespacedOwnerReference{OwnerReference: ownerRef, Namespace: namespace} parsedURL, err := url.Parse(proxyURL) if err != nil { return nil, err } - ownerRefJSON, err := json.Marshal(nsOwnerRef) + username, err := EncodeOwnerRef(ownerRef, namespace) if err != nil { return nil, err } - username := base64.URLEncoding.EncodeToString(ownerRefJSON) parsedURL.User = url.User(username) v := values{ Username: username, diff --git a/internal/ansible/proxy/proxy_suite_test.go b/internal/ansible/proxy/proxy_suite_test.go new file mode 100644 index 00000000000..58c087ec33a --- /dev/null +++ b/internal/ansible/proxy/proxy_suite_test.go @@ -0,0 +1,128 @@ +// Copyright 2021 The Operator-SDK 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 proxy + +import ( + "context" + "fmt" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/operator-framework/operator-sdk/internal/ansible/proxy/controllermap" + kcorev1 "k8s.io/api/core/v1" + kmetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +var testMgr manager.Manager + +var testClient client.Client + +func TestProxy(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Proxy Test Suite") +} + +var _ = BeforeSuite(func() { + if testing.Short() { + return + } + var err error + testMgr, err = manager.New(config.GetConfigOrDie(), manager.Options{Namespace: "default"}) + if err != nil { + Fail(fmt.Sprintf("Failed to instantiate manager: %v", err)) + } + done := make(chan error) + cMap := controllermap.NewControllerMap() + err = Run(done, Options{ + Address: "localhost", + Port: 8888, + KubeConfig: testMgr.GetConfig(), + Cache: nil, + RESTMapper: testMgr.GetRESTMapper(), + ControllerMap: cMap, + WatchedNamespaces: []string{"test-watched-namespace"}, + OwnerInjection: true, + }) + if err != nil { + Fail(fmt.Sprintf("Error starting proxy: %v", err)) + } + testClient, err = client.New(testMgr.GetConfig(), client.Options{}) + if err != nil { + Fail(fmt.Sprintf("Failed to create the client: %v", err)) + } + _, err = createTestNamespace("test-watched-namespace", testClient) + if err != nil { + Fail(fmt.Sprintf("Failed to create watched namespace: %v", err)) + } +}) + +var _ = AfterSuite(func() { + if testing.Short() { + return + } + err := testClient.Delete(context.Background(), &kcorev1.Namespace{ + ObjectMeta: kmetav1.ObjectMeta{ + Name: "test-watched-namespace", + Labels: map[string]string{ + "test-label": "test-watched-namespace", + }, + }, + }) + + if err != nil { + Fail(fmt.Sprintf("Failed to clean up namespace: %v:", err)) + } +}) + +func createTestNamespace(name string, cl client.Client) (client.Object, error) { + ns := &kcorev1.Namespace{ + ObjectMeta: kmetav1.ObjectMeta{ + Name: name, + Labels: map[string]string{ + "test-label": name, + }, + }, + } + if err := cl.Create(context.Background(), ns); err != nil { + return nil, err + } + return ns, nil +} + +func createTestPod(name, namespace string, cl client.Client) (client.Object, error) { + three := int64(3) + pod := &kcorev1.Pod{ + ObjectMeta: kmetav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + "test-label": name, + }, + }, + Spec: kcorev1.PodSpec{ + Containers: []kcorev1.Container{{Name: "nginx", Image: "nginx"}}, + RestartPolicy: "Always", + ActiveDeadlineSeconds: &three, + }, + } + if err := cl.Create(context.Background(), pod); err != nil { + return nil, err + } + return pod, nil +} diff --git a/internal/ansible/proxy/proxy_test.go b/internal/ansible/proxy/proxy_test.go index b9a442f0255..e641454f253 100644 --- a/internal/ansible/proxy/proxy_test.go +++ b/internal/ansible/proxy/proxy_test.go @@ -23,99 +23,55 @@ import ( "os" "testing" - kcorev1 "k8s.io/api/core/v1" - kmetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/config" - "sigs.k8s.io/controller-runtime/pkg/manager" + . "github.com/onsi/ginkgo" - "github.com/operator-framework/operator-sdk/internal/ansible/proxy/controllermap" + kcorev1 "k8s.io/api/core/v1" ) -func TestHandler(t *testing.T) { - if testing.Short() { - t.Skip("skipping ansible proxy testing in short mode") - } - mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{Namespace: "default"}) - if err != nil { - t.Fatalf("Failed to instantiate manager: %v", err) - } - done := make(chan error) - cMap := controllermap.NewControllerMap() - err = Run(done, Options{ - Address: "localhost", - Port: 8888, - KubeConfig: mgr.GetConfig(), - Cache: nil, - RESTMapper: mgr.GetRESTMapper(), - ControllerMap: cMap, - WatchedNamespaces: []string{"default"}, - }) - if err != nil { - t.Fatalf("Error starting proxy: %v", err) - } - - cl, err := client.New(mgr.GetConfig(), client.Options{}) - if err != nil { - t.Fatalf("Failed to create the client: %v", err) - } - - po, err := createPod("test", "default", cl) - if err != nil { - t.Fatalf("Failed to create the pod: %v", err) - } +var _ = Describe("proxyTests", func() { + t := GinkgoT() - resp, err := http.Get("http://localhost:8888/api/v1/namespaces/default/pods/test") - if err != nil { - t.Fatalf("Error getting pod from proxy: %v", err) - } - defer func() { - if err := resp.Body.Close(); err != nil && !errors.Is(err, os.ErrClosed) { - t.Errorf("Failed to close response body: (%v)", err) + It("should retrieve resources from the cache", func() { + if testing.Short() { + Skip("skipping ansible proxy testing in short mode") } - }() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Error reading response body: %v", err) - } - // Should only be one string from 'X-Cache' header (explicitly set to HIT in proxy) - if resp.Header["X-Cache"] == nil { - t.Fatalf("Object was not retrieved from cache") - if resp.Header["X-Cache"][0] != "HIT" { - t.Fatalf("Cache response header found but got [%v], expected [HIT]", resp.Header["X-Cache"][0]) + po, err := createTestPod("test", "test-watched-namespace", testClient) + if err != nil { + t.Fatalf("Failed to create the pod: %v", err) } - } - data := kcorev1.Pod{} - err = json.Unmarshal(body, &data) - if err != nil { - t.Fatalf("Error parsing response: %v", err) - } - if data.Name != "test" { - t.Fatalf("Got unexpected pod name: %#v", data.Name) - } - if err := cl.Delete(context.Background(), po); err != nil { - t.Fatalf("Failed to delete the pod: %v", err) - } -} + defer func() { + if err := testClient.Delete(context.Background(), po); err != nil { + t.Fatalf("Failed to delete the pod: %v", err) + } + }() -func createPod(name, namespace string, cl client.Client) (client.Object, error) { - three := int64(3) - pod := &kcorev1.Pod{ - ObjectMeta: kmetav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: map[string]string{ - "test-label": name, - }, - }, - Spec: kcorev1.PodSpec{ - Containers: []kcorev1.Container{{Name: "nginx", Image: "nginx"}}, - RestartPolicy: "Always", - ActiveDeadlineSeconds: &three, - }, - } - if err := cl.Create(context.Background(), pod); err != nil { - return nil, err - } - return pod, nil -} + resp, err := http.Get("http://localhost:8888/api/v1/namespaces/test-watched-namespace/pods/test") + if err != nil { + t.Fatalf("Error getting pod from proxy: %v", err) + } + defer func() { + if err := resp.Body.Close(); err != nil && !errors.Is(err, os.ErrClosed) { + t.Errorf("Failed to close response body: (%v)", err) + } + }() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Error reading response body: %v", err) + } + // Should only be one string from 'X-Cache' header (explicitly set to HIT in proxy) + if resp.Header["X-Cache"] == nil { + t.Fatalf("Object was not retrieved from cache") + if resp.Header["X-Cache"][0] != "HIT" { + t.Fatalf("Cache response header found but got [%v], expected [HIT]", resp.Header["X-Cache"][0]) + } + } + data := kcorev1.Pod{} + err = json.Unmarshal(body, &data) + if err != nil { + t.Fatalf("Error parsing response: %v", err) + } + if data.Name != "test" { + t.Fatalf("Got unexpected pod name: %#v", data.Name) + } + }) +}) diff --git a/internal/helm/client/client.go b/internal/helm/client/client.go index 615ad882ad9..12bcb155756 100644 --- a/internal/helm/client/client.go +++ b/internal/helm/client/client.go @@ -140,7 +140,7 @@ func (c *ownerRefInjectingClient) Build(reader io.Reader, validate bool) (kube.R return err } u := &unstructured.Unstructured{Object: objMap} - useOwnerRef, err := k8sutil.SupportsOwnerReference(c.restMapper, c.owner, u) + useOwnerRef, err := k8sutil.SupportsOwnerReference(c.restMapper, c.owner, u, "") if err != nil { return err } diff --git a/internal/helm/controller/controller.go b/internal/helm/controller/controller.go index a3f5a5d0467..3870fa782a3 100644 --- a/internal/helm/controller/controller.go +++ b/internal/helm/controller/controller.go @@ -129,7 +129,7 @@ func watchDependentResources(mgr manager.Manager, r *HelmOperatorReconciler, c c } restMapper := mgr.GetRESTMapper() - useOwnerRef, err := k8sutil.SupportsOwnerReference(restMapper, owner, dependent) + useOwnerRef, err := k8sutil.SupportsOwnerReference(restMapper, owner, dependent, "") if err != nil { return err } diff --git a/internal/util/k8sutil/k8sutil.go b/internal/util/k8sutil/k8sutil.go index 297fffa80a2..4559bd69b6a 100644 --- a/internal/util/k8sutil/k8sutil.go +++ b/internal/util/k8sutil/k8sutil.go @@ -112,12 +112,14 @@ func TrimDNS1123Label(label string) string { } // SupportsOwnerReference checks whether a given dependent supports owner references, based on the owner. +// The namespace of the dependent resource can either be passed in explicitly, otherwise it will be +// extracted from the dependent runtime.Object. // This function performs following checks: // -- True: Owner is cluster-scoped. // -- True: Both Owner and dependent are Namespaced with in same namespace. // -- False: Owner is Namespaced and dependent is Cluster-scoped. // -- False: Both Owner and dependent are Namespaced with different namespaces. -func SupportsOwnerReference(restMapper meta.RESTMapper, owner, dependent runtime.Object) (bool, error) { +func SupportsOwnerReference(restMapper meta.RESTMapper, owner, dependent runtime.Object, depNamespace string) (bool, error) { ownerGVK := owner.GetObjectKind().GroupVersionKind() ownerMapping, err := restMapper.RESTMapping(ownerGVK.GroupKind(), ownerGVK.Version) if err != nil { @@ -140,7 +142,9 @@ func SupportsOwnerReference(restMapper meta.RESTMapper, owner, dependent runtime ownerClusterScoped := ownerMapping.Scope.Name() == meta.RESTScopeNameRoot ownerNamespace := mOwner.GetNamespace() depClusterScoped := depMapping.Scope.Name() == meta.RESTScopeNameRoot - depNamespace := mDep.GetNamespace() + if depNamespace == "" { + depNamespace = mDep.GetNamespace() + } if ownerClusterScoped { return true, nil diff --git a/internal/util/k8sutil/k8sutil_test.go b/internal/util/k8sutil/k8sutil_test.go index 21b6690885d..a5410aa65db 100644 --- a/internal/util/k8sutil/k8sutil_test.go +++ b/internal/util/k8sutil/k8sutil_test.go @@ -53,11 +53,12 @@ func TestGetDisplayName(t *testing.T) { func TestSupportsOwnerReference(t *testing.T) { type testcase struct { - name string - restMapper meta.RESTMapper - owner runtime.Object - dependent runtime.Object - result bool + name string + restMapper meta.RESTMapper + owner runtime.Object + dependent runtime.Object + result bool + depNamespace string } var defaultVersion []schema.GroupVersion @@ -228,11 +229,61 @@ func TestSupportsOwnerReference(t *testing.T) { }, result: false, }, + { + name: "Returns true if depNamespace provided and matches.", + restMapper: restMapper, + depNamespace: "ns", + owner: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "MyNamespaceKind", + "apiVersion": "apps/v1alpha1", + "metadata": map[string]interface{}{ + "name": "example-nginx-controller", + "namespace": "ns", + }, + }, + }, + dependent: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "MyNamespaceKind", + "apiVersion": "apps/v1alpha1", + "metadata": map[string]interface{}{ + "name": "example-nginx-role", + }, + }, + }, + result: true, + }, + { + name: "Returns false if depNamespace provided and doesn't match.", + restMapper: restMapper, + depNamespace: "ns1", + owner: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "MyNamespaceKind", + "apiVersion": "apps/v1alpha1", + "metadata": map[string]interface{}{ + "name": "example-nginx-controller", + "namespace": "ns", + }, + }, + }, + dependent: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "MyNamespaceKind", + "apiVersion": "apps/v1alpha1", + "metadata": map[string]interface{}{ + "name": "example-nginx-role", + }, + }, + }, + result: false, + }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - useOwner, err := SupportsOwnerReference(c.restMapper, c.owner, c.dependent) + useOwner, err := SupportsOwnerReference(c.restMapper, c.owner, c.dependent, c.depNamespace) if err != nil { assert.Error(t, err) } From b55a21a3c5d9f71a5e414698f4f9cb2a6d0e1cb6 Mon Sep 17 00:00:00 2001 From: Fabian von Feilitzsch Date: Wed, 21 Jul 2021 11:20:31 -0400 Subject: [PATCH 2/3] Add changelog Signed-off-by: Fabian von Feilitzsch --- changelog/fragments/detect-request-ns.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changelog/fragments/detect-request-ns.yaml diff --git a/changelog/fragments/detect-request-ns.yaml b/changelog/fragments/detect-request-ns.yaml new file mode 100644 index 00000000000..d6cdce2f454 --- /dev/null +++ b/changelog/fragments/detect-request-ns.yaml @@ -0,0 +1,8 @@ +entries: + - description: > + For Ansible-based operators, if a request is sent without a body in + the metadata it will now be extracted from the request URL and properly + set owner references/dependent watches. + kind: "bugfix" + breaking: false + From 0ee520579aa8e671377d6d8e48d5de58e24353ca Mon Sep 17 00:00:00 2001 From: Fabian von Feilitzsch Date: Thu, 22 Jul 2021 13:34:13 -0400 Subject: [PATCH 3/3] cleanup Signed-off-by: Fabian von Feilitzsch --- internal/ansible/proxy/kubeconfig/kubeconfig.go | 3 +++ internal/ansible/proxy/proxy_test.go | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/ansible/proxy/kubeconfig/kubeconfig.go b/internal/ansible/proxy/kubeconfig/kubeconfig.go index b4396815bc6..0453d17f58b 100644 --- a/internal/ansible/proxy/kubeconfig/kubeconfig.go +++ b/internal/ansible/proxy/kubeconfig/kubeconfig.go @@ -68,6 +68,9 @@ type NamespacedOwnerReference struct { Namespace string } +// EncodeOwnerRef takes an ownerReference and a namespace and returns a base64 encoded +// string that can be used in the username field of a request to associate the +// owner with the request being made. func EncodeOwnerRef(ownerRef metav1.OwnerReference, namespace string) (string, error) { nsOwnerRef := NamespacedOwnerReference{OwnerReference: ownerRef, Namespace: namespace} ownerRefJSON, err := json.Marshal(nsOwnerRef) diff --git a/internal/ansible/proxy/proxy_test.go b/internal/ansible/proxy/proxy_test.go index e641454f253..dbcd8f3b0bb 100644 --- a/internal/ansible/proxy/proxy_test.go +++ b/internal/ansible/proxy/proxy_test.go @@ -61,9 +61,9 @@ var _ = Describe("proxyTests", func() { // Should only be one string from 'X-Cache' header (explicitly set to HIT in proxy) if resp.Header["X-Cache"] == nil { t.Fatalf("Object was not retrieved from cache") - if resp.Header["X-Cache"][0] != "HIT" { - t.Fatalf("Cache response header found but got [%v], expected [HIT]", resp.Header["X-Cache"][0]) - } + } + if resp.Header["X-Cache"][0] != "HIT" { + t.Fatalf("Cache response header found but got [%v], expected [HIT]", resp.Header["X-Cache"][0]) } data := kcorev1.Pod{} err = json.Unmarshal(body, &data)