diff --git a/go.mod b/go.mod index 4c1419e03..e4012a977 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/onsi/gomega v1.10.1 github.com/openshift/api v0.0.0-20210331193751-3acddb19d360 github.com/openshift/build-machinery-go v0.0.0-20211213093930-7e33a7eb4ce3 - github.com/openshift/library-go v0.0.0-20210406144447-d9cdfbd844ea + github.com/openshift/library-go v0.0.0-20210407110438-80b76d711afb github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 k8s.io/api v0.21.1 diff --git a/go.sum b/go.sum index cb177f932..a690144cd 100644 --- a/go.sum +++ b/go.sum @@ -423,8 +423,8 @@ github.com/openshift/build-machinery-go v0.0.0-20210209125900-0da259a2c359/go.mo github.com/openshift/build-machinery-go v0.0.0-20211213093930-7e33a7eb4ce3 h1:65oBhJYHzYK5VL0gF1eiYY37lLzyLZ47b9y5Kib1nf8= github.com/openshift/build-machinery-go v0.0.0-20211213093930-7e33a7eb4ce3/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE= github.com/openshift/client-go v0.0.0-20210331195552-cf6c2669e01f/go.mod h1:hHaRJ6vp2MRd/CpuZ1oJkqnMGy5eEnoAkQmKPZKcUPI= -github.com/openshift/library-go v0.0.0-20210406144447-d9cdfbd844ea h1:kjj4KeouZS8KsRmSYg0nYHBwPYwrEhbWW0ImsC7XGro= -github.com/openshift/library-go v0.0.0-20210406144447-d9cdfbd844ea/go.mod h1:pnz961veImKsbn7pQcuFbcVpCQosYiC1fUOjzEDeOLU= +github.com/openshift/library-go v0.0.0-20210407110438-80b76d711afb h1:11VU4Ppng9FtJJ5D9eTQZhZjtq0KMTfy5kmvUpVeW68= +github.com/openshift/library-go v0.0.0-20210407110438-80b76d711afb/go.mod h1:pnz961veImKsbn7pQcuFbcVpCQosYiC1fUOjzEDeOLU= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= diff --git a/pkg/helpers/helpers.go b/pkg/helpers/helpers.go index 9d6abb4ac..95bdeeada 100644 --- a/pkg/helpers/helpers.go +++ b/pkg/helpers/helpers.go @@ -194,11 +194,23 @@ func CleanUpStaticObject( case *rbacv1.RoleBinding: err = client.RbacV1().RoleBindings(t.Namespace).Delete(ctx, t.Name, metav1.DeleteOptions{}) case *apiextensionsv1.CustomResourceDefinition: - err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Delete(ctx, t.Name, metav1.DeleteOptions{}) + if apiExtensionClient == nil { + err = fmt.Errorf("apiExtensionClient is nil") + } else { + err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Delete(ctx, t.Name, metav1.DeleteOptions{}) + } case *apiextensionsv1beta1.CustomResourceDefinition: - err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(ctx, t.Name, metav1.DeleteOptions{}) + if apiExtensionClient == nil { + err = fmt.Errorf("apiExtensionClient is nil") + } else { + err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(ctx, t.Name, metav1.DeleteOptions{}) + } case *apiregistrationv1.APIService: - err = apiRegistrationClient.APIServices().Delete(ctx, t.Name, metav1.DeleteOptions{}) + if apiRegistrationClient == nil { + err = fmt.Errorf("apiRegistrationClient is nil") + } else { + err = apiRegistrationClient.APIServices().Delete(ctx, t.Name, metav1.DeleteOptions{}) + } case *admissionv1.ValidatingWebhookConfiguration: err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(ctx, t.Name, metav1.DeleteOptions{}) case *admissionv1.MutatingWebhookConfiguration: @@ -337,10 +349,14 @@ func ApplyDirectly( result.Result, result.Changed, result.Error = ApplyMutatingWebhookConfiguration( client.AdmissionregistrationV1(), t) case *apiregistrationv1.APIService: - t.ObjectMeta.Annotations = make(map[string]string) - checksum := fmt.Sprintf("%x", sha256.Sum256(t.Spec.CABundle)) - t.ObjectMeta.Annotations["caBundle-checksum"] = string(checksum[:]) // to trigger the update when caBundle changed - result.Result, result.Changed, result.Error = resourceapply.ApplyAPIService(apiRegistrationClient, recorder, t) + if apiRegistrationClient == nil { + result.Error = fmt.Errorf("apiRegistrationClient is nil") + } else { + t.ObjectMeta.Annotations = make(map[string]string) + checksum := fmt.Sprintf("%x", sha256.Sum256(t.Spec.CABundle)) + t.ObjectMeta.Annotations["caBundle-checksum"] = string(checksum[:]) // to trigger the update when caBundle changed + result.Result, result.Changed, result.Error = resourceapply.ApplyAPIService(apiRegistrationClient, recorder, t) + } default: genericApplyFiles = append(genericApplyFiles, file) } @@ -348,7 +364,6 @@ func ApplyDirectly( ret = append(ret, result) } } - clientHolder := resourceapply.NewKubeClientHolder(client).WithAPIExtensionsClient(apiExtensionClient) applyResults := resourceapply.ApplyDirectly( clientHolder, diff --git a/pkg/helpers/helpers_test.go b/pkg/helpers/helpers_test.go index aa369c326..688125d9f 100644 --- a/pkg/helpers/helpers_test.go +++ b/pkg/helpers/helpers_test.go @@ -12,6 +12,7 @@ import ( "github.com/openshift/library-go/pkg/assets" "github.com/openshift/library-go/pkg/operator/events" "github.com/openshift/library-go/pkg/operator/events/eventstesting" + "github.com/openshift/library-go/pkg/operator/resource/resourceapply" operatorhelpers "github.com/openshift/library-go/pkg/operator/v1helpers" admissionv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" @@ -321,10 +322,12 @@ func TestApplyMutatingWebhookConfiguration(t *testing.T) { func TestApplyDirectly(t *testing.T) { testcase := []struct { - name string - applyFiles map[string]runtime.Object - applyFileNames []string - expectErr bool + name string + applyFiles map[string]runtime.Object + applyFileNames []string + nilapiExtensionClient bool + nilapiRegistratonClient bool + expectErr bool }{ { name: "Apply webhooks & apiservice & secret", @@ -337,6 +340,45 @@ func TestApplyDirectly(t *testing.T) { applyFileNames: []string{"validatingwebhooks", "mutatingwebhooks", "apiservice", "secret"}, expectErr: false, }, + { + name: "Apply webhooks & apiservice & secret with nil apiRegistrationClient", + applyFiles: map[string]runtime.Object{ + "validatingwebhooks": newUnstructured("admissionregistration.k8s.io/v1", "ValidatingWebhookConfiguration", "", "", map[string]interface{}{"webhooks": []interface{}{}}), + "mutatingwebhooks": newUnstructured("admissionregistration.k8s.io/v1", "MutatingWebhookConfiguration", "", "", map[string]interface{}{"webhooks": []interface{}{}}), + "apiservice": newUnstructured("apiregistration.k8s.io/v1", "APIService", "", "", map[string]interface{}{"spec": map[string]interface{}{"service": map[string]string{"name": "svc1", "namespace": "svc1"}}}), + "secret": newUnstructured("v1", "Secret", "ns1", "n1", map[string]interface{}{"data": map[string]interface{}{"key1": []byte("key1")}}), + }, + applyFileNames: []string{"validatingwebhooks", "mutatingwebhooks", "apiservice", "secret"}, + nilapiRegistratonClient: true, + expectErr: true, + }, + { + name: "Apply generic resources with nil apiExtensionclient & nil apiRegistrationClient", + applyFiles: map[string]runtime.Object{ + "secret": newUnstructured("v1", "Secret", "ns1", "n1", map[string]interface{}{"data": map[string]interface{}{"key1": []byte("key1")}}), + }, + nilapiExtensionClient: true, + nilapiRegistratonClient: true, + applyFileNames: []string{"secret"}, + expectErr: false, + }, + { + name: "Apply CRD", + applyFiles: map[string]runtime.Object{ + "crd": newUnstructured("apiextensions.k8s.io/v1beta1", "CustomResourceDefinition", "", "", map[string]interface{}{}), + }, + applyFileNames: []string{"crd"}, + expectErr: false, + }, + { + name: "Apply CRD with nil apiExtensionClient", + applyFiles: map[string]runtime.Object{ + "crd": newUnstructured("apiextensions.k8s.io/v1beta1", "CustomResourceDefinition", "", "", map[string]interface{}{}), + }, + applyFileNames: []string{"crd"}, + nilapiExtensionClient: true, + expectErr: true, + }, { name: "Apply unhandled object", applyFiles: map[string]runtime.Object{ @@ -350,20 +392,47 @@ func TestApplyDirectly(t *testing.T) { for _, c := range testcase { t.Run(c.name, func(t *testing.T) { fakeKubeClient := fakekube.NewSimpleClientset() - fakeResgistrationClient := fakeapiregistration.NewSimpleClientset() fakeExtensionClient := fakeapiextensions.NewSimpleClientset() - results := ApplyDirectly( - fakeKubeClient, fakeExtensionClient, fakeResgistrationClient.ApiregistrationV1(), - eventstesting.NewTestingEventRecorder(t), - func(name string) ([]byte, error) { - if c.applyFiles[name] == nil { - return nil, fmt.Errorf("Failed to find file") - } + fakeResgistrationClient := fakeapiregistration.NewSimpleClientset().ApiregistrationV1() + fakeApplyFunc := func(name string) ([]byte, error) { + if c.applyFiles[name] == nil { + return nil, fmt.Errorf("Failed to find file") + } + + return json.Marshal(c.applyFiles[name]) + } + var results []resourceapply.ApplyResult + switch { + case c.nilapiExtensionClient && c.nilapiRegistratonClient: + results = ApplyDirectly( + fakeKubeClient, nil, nil, + eventstesting.NewTestingEventRecorder(t), + fakeApplyFunc, + c.applyFileNames..., + ) + case c.nilapiExtensionClient: + results = ApplyDirectly( + fakeKubeClient, nil, fakeResgistrationClient, + eventstesting.NewTestingEventRecorder(t), + fakeApplyFunc, + c.applyFileNames..., + ) + case c.nilapiRegistratonClient: + results = ApplyDirectly( + fakeKubeClient, fakeExtensionClient, nil, + eventstesting.NewTestingEventRecorder(t), + fakeApplyFunc, + c.applyFileNames..., + ) + default: + results = ApplyDirectly( + fakeKubeClient, fakeExtensionClient, fakeResgistrationClient, + eventstesting.NewTestingEventRecorder(t), + fakeApplyFunc, + c.applyFileNames..., + ) + } - return json.Marshal(c.applyFiles[name]) - }, - c.applyFileNames..., - ) aggregatedErr := []error{} for _, r := range results { if r.Error != nil { @@ -372,7 +441,7 @@ func TestApplyDirectly(t *testing.T) { } if len(aggregatedErr) == 0 && c.expectErr { - t.Errorf("Expect an apply error") + t.Errorf("Expect an apply error: %s", c.name) } if len(aggregatedErr) != 0 && !c.expectErr { t.Errorf("Expect no apply error, %v", operatorhelpers.NewMultiLineAggregate(aggregatedErr)) @@ -391,9 +460,11 @@ func TestDeleteStaticObject(t *testing.T) { "kind1": newUnstructured("v1", "Kind1", "ns1", "n1", map[string]interface{}{"spec": map[string]interface{}{"key1": []byte("key1")}}), } testcase := []struct { - name string - applyFileName string - expectErr bool + name string + applyFileName string + expectErr bool + nilapiExtensionClient bool + nilapiRegistrationClient bool }{ { name: "Delete validating webhooks", @@ -410,6 +481,12 @@ func TestDeleteStaticObject(t *testing.T) { applyFileName: "apiservice", expectErr: false, }, + { + name: "Delete apiservice with nil apiRegistrationClient", + applyFileName: "apiservice", + nilapiRegistrationClient: true, + expectErr: true, + }, { name: "Delete secret", applyFileName: "secret", @@ -420,6 +497,12 @@ func TestDeleteStaticObject(t *testing.T) { applyFileName: "crd", expectErr: false, }, + { + name: "Delete crd with nil apiExtensionClient", + applyFileName: "crd", + nilapiExtensionClient: true, + expectErr: true, + }, { name: "Delete unhandled object", applyFileName: "kind1", @@ -430,20 +513,47 @@ func TestDeleteStaticObject(t *testing.T) { for _, c := range testcase { t.Run(c.name, func(t *testing.T) { fakeKubeClient := fakekube.NewSimpleClientset() - fakeResgistrationClient := fakeapiregistration.NewSimpleClientset() + fakeResgistrationClient := fakeapiregistration.NewSimpleClientset().ApiregistrationV1() fakeExtensionClient := fakeapiextensions.NewSimpleClientset() - err := CleanUpStaticObject( - context.TODO(), - fakeKubeClient, fakeExtensionClient, fakeResgistrationClient.ApiregistrationV1(), - func(name string) ([]byte, error) { - if applyFiles[name] == nil { - return nil, fmt.Errorf("Failed to find file") - } + fakeAssetFunc := func(name string) ([]byte, error) { + if applyFiles[name] == nil { + return nil, fmt.Errorf("Failed to find file") + } - return json.Marshal(applyFiles[name]) - }, - c.applyFileName, - ) + return json.Marshal(applyFiles[name]) + } + + var err error + switch { + case c.nilapiExtensionClient && c.nilapiRegistrationClient: + err = CleanUpStaticObject( + context.TODO(), + fakeKubeClient, nil, nil, + fakeAssetFunc, + c.applyFileName, + ) + case c.nilapiExtensionClient: + err = CleanUpStaticObject( + context.TODO(), + fakeKubeClient, nil, fakeResgistrationClient, + fakeAssetFunc, + c.applyFileName, + ) + case c.nilapiRegistrationClient: + err = CleanUpStaticObject( + context.TODO(), + fakeKubeClient, fakeExtensionClient, nil, + fakeAssetFunc, + c.applyFileName, + ) + default: + err = CleanUpStaticObject( + context.TODO(), + fakeKubeClient, fakeExtensionClient, fakeResgistrationClient, + fakeAssetFunc, + c.applyFileName, + ) + } if err == nil && c.expectErr { t.Errorf("Expect an apply error") diff --git a/vendor/github.com/openshift/library-go/pkg/config/client/transport.go b/vendor/github.com/openshift/library-go/pkg/config/client/transport.go index 65980c9af..7f2534fdf 100644 --- a/vendor/github.com/openshift/library-go/pkg/config/client/transport.go +++ b/vendor/github.com/openshift/library-go/pkg/config/client/transport.go @@ -52,14 +52,40 @@ type preferredHostRT struct { preferredHostFn func() string } -func (t *preferredHostRT) RoundTrip(r *http.Request) (*http.Response, error) { - preferredHost := t.preferredHostFn() +func (rt *preferredHostRT) RoundTrip(r *http.Request) (*http.Response, error) { + preferredHost := rt.preferredHostFn() if len(preferredHost) == 0 { - return t.baseRT.RoundTrip(r) + return rt.baseRT.RoundTrip(r) } r.Host = preferredHost r.URL.Host = preferredHost - return t.baseRT.RoundTrip(r) + return rt.baseRT.RoundTrip(r) +} + +// CancelRequest exists to facilitate cancellation. +// +// In general there are at least three ways of cancelling a request by an HTTP client: +// 1. Transport.CancelRequest (depreciated) +// 2. Request.Cancel +// 3. Request.Context (preferred) +// +// While using client-go callers can specify a timeout value that gets passed directly to an http.Client. +// The HTTP client cancels requests to the underlying Transport as if the Request's Context ended. +// For compatibility, the Client will also use the deprecated CancelRequest method on Transport if found. +// New RoundTripper implementations should use the Request's Context for cancellation instead of implementing CancelRequest. +// +// Because this wrapper might be the first or might be actually wrapped with already existing wrappers that already implement CancelRequest we need to simply conform. +// +// See for more details: +// https://github.com/kubernetes/kubernetes/blob/442a69c3bdf6fe8e525b05887e57d89db1e2f3a5/staging/src/k8s.io/client-go/transport/transport.go#L257 +// https://github.com/kubernetes/kubernetes/blob/e29c568c4a9cd45d15665345aa015e21bcff52dd/staging/src/k8s.io/client-go/rest/config.go#L328 +// https://github.com/kubernetes/kubernetes/blob/3b2746c9ea9e0fa247b01dca27634e509b385eda/staging/src/k8s.io/client-go/transport/round_trippers.go#L302 +func (rt *preferredHostRT) CancelRequest(req *http.Request) { + type canceler interface{ CancelRequest(*http.Request) } + + if rtCanceller, ok := rt.baseRT.(canceler); ok { + rtCanceller.CancelRequest(req) + } } diff --git a/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/generic.go b/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/generic.go index bee9d7dbd..b13453923 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/generic.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/generic.go @@ -2,22 +2,25 @@ package resourceapply import ( "fmt" - corev1client "k8s.io/client-go/kubernetes/typed/core/v1" - "github.com/openshift/library-go/pkg/operator/v1helpers" - - "github.com/openshift/api" - "github.com/openshift/library-go/pkg/operator/events" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" storagev1 "k8s.io/api/storage/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + + "github.com/openshift/api" + "github.com/openshift/library-go/pkg/operator/events" + "github.com/openshift/library-go/pkg/operator/v1helpers" ) var ( @@ -47,6 +50,7 @@ type ClientHolder struct { kubeClient kubernetes.Interface apiExtensionsClient apiextensionsclient.Interface kubeInformers v1helpers.KubeInformersForNamespaces + dynamicClient dynamic.Interface } func NewClientHolder() *ClientHolder { @@ -72,6 +76,11 @@ func (c *ClientHolder) WithAPIExtensionsClient(client apiextensionsclient.Interf return c } +func (c *ClientHolder) WithDynamicClient(client dynamic.Interface) *ClientHolder { + c.dynamicClient = client + return c +} + // ApplyDirectly applies the given manifest files to API server. func ApplyDirectly(clients *ClientHolder, recorder events.Recorder, manifests AssetFunc, files ...string) []ApplyResult { ret := []ApplyResult{} @@ -84,7 +93,7 @@ func ApplyDirectly(clients *ClientHolder, recorder events.Recorder, manifests As ret = append(ret, result) continue } - requiredObj, _, err := genericCodec.Decode(objBytes, nil, nil) + requiredObj, err := decode(objBytes) if err != nil { result.Error = fmt.Errorf("cannot decode %q: %v", file, err) ret = append(ret, result) @@ -97,75 +106,95 @@ func ApplyDirectly(clients *ClientHolder, recorder events.Recorder, manifests As case *corev1.Namespace: if clients.kubeClient == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplyNamespace(clients.kubeClient.CoreV1(), recorder, t) } - result.Result, result.Changed, result.Error = ApplyNamespace(clients.kubeClient.CoreV1(), recorder, t) case *corev1.Service: if clients.kubeClient == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplyService(clients.kubeClient.CoreV1(), recorder, t) } - result.Result, result.Changed, result.Error = ApplyService(clients.kubeClient.CoreV1(), recorder, t) case *corev1.Pod: if clients.kubeClient == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplyPod(clients.kubeClient.CoreV1(), recorder, t) } - result.Result, result.Changed, result.Error = ApplyPod(clients.kubeClient.CoreV1(), recorder, t) case *corev1.ServiceAccount: if clients.kubeClient == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplyServiceAccount(clients.kubeClient.CoreV1(), recorder, t) } - result.Result, result.Changed, result.Error = ApplyServiceAccount(clients.kubeClient.CoreV1(), recorder, t) case *corev1.ConfigMap: client := clients.configMapsGetter() if client == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplyConfigMap(client, recorder, t) } - result.Result, result.Changed, result.Error = ApplyConfigMap(client, recorder, t) case *corev1.Secret: client := clients.secretsGetter() if client == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplySecret(client, recorder, t) } - result.Result, result.Changed, result.Error = ApplySecret(client, recorder, t) case *rbacv1.ClusterRole: if clients.kubeClient == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplyClusterRole(clients.kubeClient.RbacV1(), recorder, t) } - result.Result, result.Changed, result.Error = ApplyClusterRole(clients.kubeClient.RbacV1(), recorder, t) case *rbacv1.ClusterRoleBinding: if clients.kubeClient == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplyClusterRoleBinding(clients.kubeClient.RbacV1(), recorder, t) } - result.Result, result.Changed, result.Error = ApplyClusterRoleBinding(clients.kubeClient.RbacV1(), recorder, t) case *rbacv1.Role: if clients.kubeClient == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplyRole(clients.kubeClient.RbacV1(), recorder, t) } - result.Result, result.Changed, result.Error = ApplyRole(clients.kubeClient.RbacV1(), recorder, t) case *rbacv1.RoleBinding: if clients.kubeClient == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplyRoleBinding(clients.kubeClient.RbacV1(), recorder, t) } - result.Result, result.Changed, result.Error = ApplyRoleBinding(clients.kubeClient.RbacV1(), recorder, t) case *apiextensionsv1beta1.CustomResourceDefinition: if clients.apiExtensionsClient == nil { result.Error = fmt.Errorf("missing apiExtensionsClient") + } else { + result.Result, result.Changed, result.Error = ApplyCustomResourceDefinitionV1Beta1(clients.apiExtensionsClient.ApiextensionsV1beta1(), recorder, t) } - result.Result, result.Changed, result.Error = ApplyCustomResourceDefinitionV1Beta1(clients.apiExtensionsClient.ApiextensionsV1beta1(), recorder, t) case *apiextensionsv1.CustomResourceDefinition: if clients.apiExtensionsClient == nil { result.Error = fmt.Errorf("missing apiExtensionsClient") + } else { + result.Result, result.Changed, result.Error = ApplyCustomResourceDefinitionV1(clients.apiExtensionsClient.ApiextensionsV1(), recorder, t) } - result.Result, result.Changed, result.Error = ApplyCustomResourceDefinitionV1(clients.apiExtensionsClient.ApiextensionsV1(), recorder, t) case *storagev1.StorageClass: if clients.kubeClient == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplyStorageClass(clients.kubeClient.StorageV1(), recorder, t) } - result.Result, result.Changed, result.Error = ApplyStorageClass(clients.kubeClient.StorageV1(), recorder, t) case *storagev1.CSIDriver: if clients.kubeClient == nil { result.Error = fmt.Errorf("missing kubeClient") + } else { + result.Result, result.Changed, result.Error = ApplyCSIDriver(clients.kubeClient.StorageV1(), recorder, t) + } + case *unstructured.Unstructured: + if clients.dynamicClient == nil { + result.Error = fmt.Errorf("missing dynamicClient") + } else { + result.Result, result.Changed, result.Error = ApplyKnownUnstructured(clients.dynamicClient, recorder, t) } - result.Result, result.Changed, result.Error = ApplyCSIDriver(clients.kubeClient.StorageV1(), recorder, t) default: result.Error = fmt.Errorf("unhandled type %T", requiredObj) } @@ -195,3 +224,19 @@ func (c *ClientHolder) secretsGetter() corev1client.SecretsGetter { } return v1helpers.CachedSecretGetter(c.kubeClient.CoreV1(), c.kubeInformers) } + +func decode(objBytes []byte) (runtime.Object, error) { + // Try to get a typed object first + typedObj, _, decodeErr := genericCodec.Decode(objBytes, nil, nil) + if decodeErr == nil { + return typedObj, nil + } + + // Try unstructured, hoping to recover from "no kind XXX is registered for version YYY" + unstructuredObj, _, err := scheme.Codecs.UniversalDecoder().Decode(objBytes, nil, &unstructured.Unstructured{}) + if err != nil { + // Return the original error + return nil, decodeErr + } + return unstructuredObj, nil +} diff --git a/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/monitoring.go b/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/monitoring.go index 5b297eb7f..ae05022f4 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/monitoring.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/monitoring.go @@ -2,9 +2,7 @@ package resourceapply import ( "context" - "fmt" - "github.com/ghodss/yaml" "github.com/imdario/mergo" "k8s.io/klog/v2" @@ -12,7 +10,6 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" @@ -48,58 +45,44 @@ func ensureServiceMonitorSpec(required, existing *unstructured.Unstructured) (*u } // ApplyServiceMonitor applies the Prometheus service monitor. -func ApplyServiceMonitor(client dynamic.Interface, recorder events.Recorder, serviceMonitorBytes []byte) (bool, error) { - monitorJSON, err := yaml.YAMLToJSON(serviceMonitorBytes) - if err != nil { - return false, err - } - - monitorObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, monitorJSON) - if err != nil { - return false, err - } - - required, ok := monitorObj.(*unstructured.Unstructured) - if !ok { - return false, fmt.Errorf("unexpected object in %t", monitorObj) - } - +func ApplyServiceMonitor(client dynamic.Interface, recorder events.Recorder, required *unstructured.Unstructured) (*unstructured.Unstructured, bool, error) { namespace := required.GetNamespace() existing, err := client.Resource(serviceMonitorGVR).Namespace(namespace).Get(context.TODO(), required.GetName(), metav1.GetOptions{}) if errors.IsNotFound(err) { - _, createErr := client.Resource(serviceMonitorGVR).Namespace(namespace).Create(context.TODO(), required, metav1.CreateOptions{}) + newObj, createErr := client.Resource(serviceMonitorGVR).Namespace(namespace).Create(context.TODO(), required, metav1.CreateOptions{}) if createErr != nil { recorder.Warningf("ServiceMonitorCreateFailed", "Failed to create ServiceMonitor.monitoring.coreos.com/v1: %v", createErr) - return true, createErr + return nil, true, createErr } recorder.Eventf("ServiceMonitorCreated", "Created ServiceMonitor.monitoring.coreos.com/v1 because it was missing") - return true, nil + return newObj, true, nil } if err != nil { - return false, err + return nil, false, err } existingCopy := existing.DeepCopy() updated, endpointsModified, err := ensureServiceMonitorSpec(required, existingCopy) if err != nil { - return false, err + return nil, false, err } if !endpointsModified { - return false, nil + return nil, false, nil } if klog.V(4).Enabled() { klog.Infof("ServiceMonitor %q changes: %v", namespace+"/"+required.GetName(), JSONPatchNoError(existing, existingCopy)) } - if _, err = client.Resource(serviceMonitorGVR).Namespace(namespace).Update(context.TODO(), updated, metav1.UpdateOptions{}); err != nil { + newObj, err := client.Resource(serviceMonitorGVR).Namespace(namespace).Update(context.TODO(), updated, metav1.UpdateOptions{}) + if err != nil { recorder.Warningf("ServiceMonitorUpdateFailed", "Failed to update ServiceMonitor.monitoring.coreos.com/v1: %v", err) - return true, err + return nil, true, err } recorder.Eventf("ServiceMonitorUpdated", "Updated ServiceMonitor.monitoring.coreos.com/v1 because it changed") - return true, err + return newObj, true, err } diff --git a/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/unstructured.go b/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/unstructured.go new file mode 100644 index 000000000..404557860 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/unstructured.go @@ -0,0 +1,21 @@ +package resourceapply + +import ( + "fmt" + + "github.com/openshift/library-go/pkg/operator/events" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" +) + +// ApplyKnownUnstructured applies few selected Unstructured types, where it semantic knowledge +// to merge existing & required objects intelligently. Feel free to add more. +func ApplyKnownUnstructured(client dynamic.Interface, recorder events.Recorder, obj *unstructured.Unstructured) (*unstructured.Unstructured, bool, error) { + serviceMonitorGK := schema.GroupKind{Group: "monitoring.coreos.com", Kind: "ServiceMonitor"} + if obj.GetObjectKind().GroupVersionKind().GroupKind() == serviceMonitorGK { + return ApplyServiceMonitor(client, recorder, obj) + } + + return nil, false, fmt.Errorf("unsupported object type: %s", obj.GetKind()) +} diff --git a/vendor/github.com/openshift/library-go/pkg/operator/v1helpers/args.go b/vendor/github.com/openshift/library-go/pkg/operator/v1helpers/args.go new file mode 100644 index 000000000..e1a165e63 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/operator/v1helpers/args.go @@ -0,0 +1,61 @@ +package v1helpers + +import ( + "fmt" + "sort" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// FlagsFromUnstructured process the unstructured arguments usually retrieved from an operator's configuration file under a specific key. +// There are only two supported/valid types for arguments, that is []sting and/or string. +// Passing a different type yield an error. +// +// Use ToFlagSlice function to get a slice of string flags. +func FlagsFromUnstructured(unstructuredArgs map[string]interface{}) (map[string][]string, error) { + return flagsFromUnstructured(unstructuredArgs) +} + +// ToFlagSlice transforms the provided arguments to a slice of string flags. +// A flag name is taken directly from the key and the value is simply attached. +// A flag is repeated iff it has more than one value. +func ToFlagSlice(args map[string][]string) []string { + var keys []string + for key := range args { + keys = append(keys, key) + } + sort.Strings(keys) + + var flags []string + for _, key := range keys { + for _, token := range args[key] { + flags = append(flags, fmt.Sprintf("--%s=%s", key, token)) + } + } + return flags +} + +// flagsFromUnstructured process the unstructured arguments (interface{}) to a map of strings. +// There are only two supported/valid types for arguments, that is []sting and/or string. +// Passing a different type yield an error. +func flagsFromUnstructured(unstructuredArgs map[string]interface{}) (map[string][]string, error) { + ret := map[string][]string{} + for argName, argRawValue := range unstructuredArgs { + var argsSlice []string + var found bool + var err error + + argsSlice, found, err = unstructured.NestedStringSlice(unstructuredArgs, argName) + if !found || err != nil { + str, found, err := unstructured.NestedString(unstructuredArgs, argName) + if !found || err != nil { + return nil, fmt.Errorf("unable to process an argument, incorrect value %v under %v key, expected []string or string", argRawValue, argName) + } + argsSlice = append(argsSlice, str) + } + + ret[argName] = argsSlice + } + + return ret, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0bf05bc5d..e5a3be4ac 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -199,7 +199,7 @@ github.com/openshift/build-machinery-go/make/targets/golang github.com/openshift/build-machinery-go/make/targets/openshift github.com/openshift/build-machinery-go/make/targets/openshift/operator github.com/openshift/build-machinery-go/scripts -# github.com/openshift/library-go v0.0.0-20210406144447-d9cdfbd844ea +# github.com/openshift/library-go v0.0.0-20210407110438-80b76d711afb ## explicit github.com/openshift/library-go/pkg/assets github.com/openshift/library-go/pkg/authorization/hardcodedauthorizer