From 8434bc8b1d2c4701e484c53f8e063dc8f03f2e26 Mon Sep 17 00:00:00 2001 From: Jian Qiu Date: Fri, 22 May 2020 10:14:39 +0800 Subject: [PATCH] add webhook --- Makefile | 4 +- deploy/nucleus-hub/cluster_role.yaml | 10 +- .../nucleus-hub.clusterserviceversion.yaml | 26 ++ .../nucleus-spoke.clusterserviceversion.yaml | 1 + go.mod | 1 + go.sum | 2 + manifests/hub/hub-clusterrolebinding.yaml | 12 - ...yaml => hub-registration-clusterrole.yaml} | 2 +- .../hub-registration-clusterrolebinding.yaml | 12 + ....yaml => hub-registration-deployment.yaml} | 12 +- .../hub/hub-registration-serviceaccount.yaml | 5 + .../hub-registration-webhook-apiservice.yaml | 13 + .../hub-registration-webhook-clusterrole.yaml | 13 + ...gistration-webhook-clusterrolebinding.yaml | 12 + .../hub-registration-webhook-deployment.yaml | 50 ++ .../hub/hub-registration-webhook-secret.yaml | 10 + .../hub/hub-registration-webhook-service.yaml | 11 + ...-registration-webhook-serviceaccount.yaml} | 2 +- ...ation-webhook-validatingconfiguration.yaml | 26 ++ pkg/helpers/helpers.go | 147 +++++- pkg/helpers/helpers_test.go | 236 +++++++++- pkg/operators/hub/bindata/bindata.go | 438 +++++++++++++++--- pkg/operators/hub/controller.go | 213 +++++---- pkg/operators/hub/controller_test.go | 161 +++++-- pkg/operators/manager.go | 6 + pkg/operators/spoke/controller.go | 72 +-- test/integration/hub_test.go | 218 +++++++++ test/integration/integration_suite_test.go | 21 +- test/integration/util/util.go | 45 ++ .../clientset/clientset.go | 111 +++++ .../clientset_generated/clientset/doc.go | 20 + .../clientset/fake/clientset_generated.go | 89 ++++ .../clientset_generated/clientset/fake/doc.go | 20 + .../clientset/fake/register.go | 58 +++ .../typed/apiregistration/v1/fake/doc.go | 20 + .../v1/fake/fake_apiregistration_client.go | 40 ++ .../v1/fake/fake_apiservice.go | 133 ++++++ .../v1beta1/apiregistration_client.go | 89 ++++ .../apiregistration/v1beta1/apiservice.go | 184 ++++++++ .../typed/apiregistration/v1beta1/doc.go | 20 + .../typed/apiregistration/v1beta1/fake/doc.go | 20 + .../fake/fake_apiregistration_client.go | 40 ++ .../v1beta1/fake/fake_apiservice.go | 133 ++++++ .../v1beta1/generated_expansion.go | 21 + vendor/modules.txt | 5 + 45 files changed, 2509 insertions(+), 275 deletions(-) delete mode 100644 manifests/hub/hub-clusterrolebinding.yaml rename manifests/hub/{hub-clusterrole.yaml => hub-registration-clusterrole.yaml} (94%) create mode 100644 manifests/hub/hub-registration-clusterrolebinding.yaml rename manifests/hub/{hub-deployment.yaml => hub-registration-deployment.yaml} (69%) create mode 100644 manifests/hub/hub-registration-serviceaccount.yaml create mode 100644 manifests/hub/hub-registration-webhook-apiservice.yaml create mode 100644 manifests/hub/hub-registration-webhook-clusterrole.yaml create mode 100644 manifests/hub/hub-registration-webhook-clusterrolebinding.yaml create mode 100644 manifests/hub/hub-registration-webhook-deployment.yaml create mode 100644 manifests/hub/hub-registration-webhook-secret.yaml create mode 100644 manifests/hub/hub-registration-webhook-service.yaml rename manifests/hub/{hub-serviceaccount.yaml => hub-registration-webhook-serviceaccount.yaml} (61%) create mode 100644 manifests/hub/hub-registration-webhook-validatingconfiguration.yaml create mode 100644 test/integration/hub_test.go create mode 100644 test/integration/util/util.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/clientset.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/doc.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/clientset_generated.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/doc.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/register.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/doc.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/fake_apiregistration_client.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/fake_apiservice.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/apiregistration_client.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/apiservice.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/doc.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/doc.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/fake_apiregistration_client.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/fake_apiservice.go create mode 100644 vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/generated_expansion.go diff --git a/Makefile b/Makefile index 09dacb7e5..7927ec236 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ deploy-hub: install-olm sed -e "s,quay.io/open-cluster-management/registration,$(REGISTRATION_IMAGE)," deploy/nucleus-hub/crds/nucleus_open-cluster-management_hubcores.cr.yaml | $(KUBECTL) apply -f - clean-hub: ensure-operator-sdk - $(KUBECTL) delete -f deploy/nucleus-hub/crds/nucleus_open-cluster-management_hubcores.cr.yaml + $(KUBECTL) delete -f deploy/nucleus-hub/crds/nucleus_open-cluster-management_hubcores.cr.yaml --ignore-not-found $(OPERATOR_SDK) cleanup --olm --operator-namespace open-cluster-management --operator-version 0.1.0 --manifests deploy/nucleus-hub/olm-catalog/nucleus-hub --olm-namespace $(OLM_NAMESPACE) cluster-ip: @@ -108,7 +108,7 @@ deploy-spoke: install-olm bootstrap-secret sed -e "s,quay.io/open-cluster-management/registration,$(REGISTRATION_IMAGE)," -e "s,quay.io/open-cluster-management/work,$(WORK_IMAGE)," deploy/nucleus-spoke/crds/nucleus_open-cluster-management_spokecores.cr.yaml | $(KUBECTL) apply -f - clean-spoke: ensure-operator-sdk - $(KUBECTL) delete -f deploy/nucleus-spoke/crds/nucleus_open-cluster-management_spokecores.cr.yaml + $(KUBECTL) delete -f deploy/nucleus-spoke/crds/nucleus_open-cluster-management_spokecores.cr.yaml --ignore-not-found $(OPERATOR_SDK) cleanup --olm --operator-namespace open-cluster-management --operator-version 0.1.0 --manifests deploy/nucleus-spoke/olm-catalog/nucleus-spoke --olm-namespace $(OLM_NAMESPACE) ensure-operator-sdk: diff --git a/deploy/nucleus-hub/cluster_role.yaml b/deploy/nucleus-hub/cluster_role.yaml index 58beb93ba..cc0f1a80e 100644 --- a/deploy/nucleus-hub/cluster_role.yaml +++ b/deploy/nucleus-hub/cluster_role.yaml @@ -5,7 +5,7 @@ metadata: rules: # Allow nucleus to create workload - apiGroups: [""] - resources: ["configmaps", "namespaces", "serviceaccounts"] + resources: ["configmaps", "namespaces", "serviceaccounts", "services", "secrets"] verbs: ["create", "get", "list", "update", "watch", "patch", "delete"] - apiGroups: ["authorization.k8s.io"] resources: ["subjectaccessreviews"] @@ -26,6 +26,14 @@ rules: - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["create", "get", "list", "update", "watch", "patch", "delete"] +# Allow nucleus to create apiservice +- apiGroups: ["apiregistration.k8s.io"] + resources: ["apiservices"] + verbs: ["create", "get", "list", "update", "watch", "patch", "delete"] +# Allow nucleus to create validatingwebhookconfigurration +- apiGroups: ["admissionregistration.k8s.io"] + resources: ["validatingwebhookconfigurations"] + verbs: ["create", "get", "list", "update", "watch", "patch", "delete"] # Allow nuclues to manage nucleus apis. - apiGroups: ["nucleus.open-cluster-management.io"] resources: ["hubcores"] diff --git a/deploy/nucleus-hub/olm-catalog/nucleus-hub/manifests/nucleus-hub.clusterserviceversion.yaml b/deploy/nucleus-hub/olm-catalog/nucleus-hub/manifests/nucleus-hub.clusterserviceversion.yaml index 40d5d01b7..b72d5dac8 100644 --- a/deploy/nucleus-hub/olm-catalog/nucleus-hub/manifests/nucleus-hub.clusterserviceversion.yaml +++ b/deploy/nucleus-hub/olm-catalog/nucleus-hub/manifests/nucleus-hub.clusterserviceversion.yaml @@ -39,6 +39,8 @@ spec: - configmaps - namespaces - serviceaccounts + - services + - secrets verbs: - create - get @@ -114,6 +116,30 @@ spec: - watch - patch - delete + - apiGroups: + - apiregistration.k8s.io + resources: + - apiservices + verbs: + - create + - get + - list + - update + - watch + - patch + - delete + - apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - create + - get + - list + - update + - watch + - patch + - delete - apiGroups: - nucleus.open-cluster-management.io resources: diff --git a/deploy/nucleus-spoke/olm-catalog/nucleus-spoke/manifests/nucleus-spoke.clusterserviceversion.yaml b/deploy/nucleus-spoke/olm-catalog/nucleus-spoke/manifests/nucleus-spoke.clusterserviceversion.yaml index d5ff09eba..1f08159f8 100644 --- a/deploy/nucleus-spoke/olm-catalog/nucleus-spoke/manifests/nucleus-spoke.clusterserviceversion.yaml +++ b/deploy/nucleus-spoke/olm-catalog/nucleus-spoke/manifests/nucleus-spoke.clusterserviceversion.yaml @@ -66,6 +66,7 @@ spec: resources: - namespaces verbs: + - create - get - list - watch diff --git a/go.mod b/go.mod index 1b56b0c19..5d3304d28 100644 --- a/go.mod +++ b/go.mod @@ -19,5 +19,6 @@ require ( k8s.io/client-go v0.18.2 k8s.io/component-base v0.18.2 k8s.io/klog v1.0.0 + k8s.io/kube-aggregator v0.18.0 sigs.k8s.io/controller-runtime v0.6.0 ) diff --git a/go.sum b/go.sum index 0bbf47d80..117244b1b 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,7 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libnetwork v0.0.0-20190731215715-7f13a5c99f4b/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -276,6 +277,7 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= diff --git a/manifests/hub/hub-clusterrolebinding.yaml b/manifests/hub/hub-clusterrolebinding.yaml deleted file mode 100644 index 2a7ac61c8..000000000 --- a/manifests/hub/hub-clusterrolebinding.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: system:open-cluster-management:{{ .HubCoreName }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:open-cluster-management:{{ .HubCoreName }} -subjects: -- kind: ServiceAccount - namespace: {{ .HubCoreNamespace }} - name: {{ .HubCoreName }}-sa diff --git a/manifests/hub/hub-clusterrole.yaml b/manifests/hub/hub-registration-clusterrole.yaml similarity index 94% rename from manifests/hub/hub-clusterrole.yaml rename to manifests/hub/hub-registration-clusterrole.yaml index 985215e90..8e0818408 100644 --- a/manifests/hub/hub-clusterrole.yaml +++ b/manifests/hub/hub-registration-clusterrole.yaml @@ -1,7 +1,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: system:open-cluster-management:{{ .HubCoreName }} + name: system:open-cluster-management:{{ .HubCoreName }}-registration-controller rules: # Allow hub to monitor and update status of csr - apiGroups: ["certificates.k8s.io"] diff --git a/manifests/hub/hub-registration-clusterrolebinding.yaml b/manifests/hub/hub-registration-clusterrolebinding.yaml new file mode 100644 index 000000000..bd1a3bf61 --- /dev/null +++ b/manifests/hub/hub-registration-clusterrolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: system:open-cluster-management:{{ .HubCoreName }}-registration-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:open-cluster-management:{{ .HubCoreName }}-registration-controller +subjects: +- kind: ServiceAccount + namespace: {{ .HubCoreNamespace }} + name: {{ .HubCoreName }}-registration-controller-sa diff --git a/manifests/hub/hub-deployment.yaml b/manifests/hub/hub-registration-deployment.yaml similarity index 69% rename from manifests/hub/hub-deployment.yaml rename to manifests/hub/hub-registration-deployment.yaml index 198193108..3b9c0a1b3 100644 --- a/manifests/hub/hub-deployment.yaml +++ b/manifests/hub/hub-registration-deployment.yaml @@ -1,23 +1,23 @@ kind: Deployment apiVersion: apps/v1 metadata: - name: {{ .HubCoreName }}-controller + name: {{ .HubCoreName }}-registration-controller namespace: {{ .HubCoreNamespace }} labels: - app: nucleushub-controller + app: nucleushub-registration-controller spec: replicas: 3 selector: matchLabels: - app: nucleushub-controller + app: nucleushub-registration-controller template: metadata: labels: - app: nucleushub-controller + app: nucleushub-registration-controller spec: - serviceAccountName: {{ .HubCoreName }}-sa + serviceAccountName: {{ .HubCoreName }}-registration-controller-sa containers: - - name: hub-controller + - name: hub-registration-controller image: {{ .RegistrationImage }} imagePullPolicy: IfNotPresent args: diff --git a/manifests/hub/hub-registration-serviceaccount.yaml b/manifests/hub/hub-registration-serviceaccount.yaml new file mode 100644 index 000000000..a50e25e07 --- /dev/null +++ b/manifests/hub/hub-registration-serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .HubCoreName }}-registration-controller-sa + namespace: {{ .HubCoreNamespace }} diff --git a/manifests/hub/hub-registration-webhook-apiservice.yaml b/manifests/hub/hub-registration-webhook-apiservice.yaml new file mode 100644 index 000000000..d5fcdaa18 --- /dev/null +++ b/manifests/hub/hub-registration-webhook-apiservice.yaml @@ -0,0 +1,13 @@ +apiVersion: apiregistration.k8s.io/v1 +kind: APIService +metadata: + name: v1.admission.cluster.open-cluster-management.io +spec: + group: admission.cluster.open-cluster-management.io + version: v1 + service: + name: {{ .HubCoreWebhookRegistrationService }} + namespace: {{ .HubCoreNamespace }} + caBundle: {{ .RegistrationAPIServiceCABundle }} + groupPriorityMinimum: 10000 + versionPriority: 20 diff --git a/manifests/hub/hub-registration-webhook-clusterrole.yaml b/manifests/hub/hub-registration-webhook-clusterrole.yaml new file mode 100644 index 000000000..09caee5e6 --- /dev/null +++ b/manifests/hub/hub-registration-webhook-clusterrole.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: system:open-cluster-management:{{ .HubCoreName }}-registration-webhook +rules: +# Allow spokecluster admission to get/list/watch configmaps +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list", "watch"] +# Allow spokecluster admission to create subjectaccessreviews +- apiGroups: ["authorization.k8s.io"] + resources: ["subjectaccessreviews"] + verbs: ["create"] diff --git a/manifests/hub/hub-registration-webhook-clusterrolebinding.yaml b/manifests/hub/hub-registration-webhook-clusterrolebinding.yaml new file mode 100644 index 000000000..91da01ba8 --- /dev/null +++ b/manifests/hub/hub-registration-webhook-clusterrolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: system:open-cluster-management:{{ .HubCoreName }}-registration-webhook +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .HubCoreName }}-registration-webhook +subjects: + - kind: ServiceAccount + name: {{ .HubCoreName }}-registration-webhook-sa + namespace: {{ .HubCoreNamespace }} diff --git a/manifests/hub/hub-registration-webhook-deployment.yaml b/manifests/hub/hub-registration-webhook-deployment.yaml new file mode 100644 index 000000000..6b25926d9 --- /dev/null +++ b/manifests/hub/hub-registration-webhook-deployment.yaml @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .HubCoreName }}-registration-webhook + namespace: {{ .HubCoreNamespace }} + labels: + app: {{ .HubCoreName }}-registration-webhook +spec: + replicas: 3 + selector: + matchLabels: + app: {{ .HubCoreName }}-registration-webhook + template: + metadata: + labels: + app: {{ .HubCoreName }}-registration-webhook + spec: + serviceAccountName: {{ .HubCoreName }}-registration-webhook-sa + containers: + - name: {{ .HubCoreName }}-registration-webhook-sa + image: {{ .RegistrationImage }} + imagePullPolicy: IfNotPresent + args: + - "/registration" + - "webhook" + - "--secure-port=6443" + - "--tls-cert-file=/serving-cert/tls.crt" + - "--tls-private-key-file=/serving-cert/tls.key" + livenessProbe: + httpGet: + path: /healthz + scheme: HTTPS + port: 6443 + initialDelaySeconds: 2 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /healthz + scheme: HTTPS + port: 6443 + initialDelaySeconds: 2 + volumeMounts: + - name: webhook-secret + mountPath: "/serving-cert" + readOnly: true + volumes: + - name: webhook-secret + secret: + secretName: {{ .HubCoreWebhookSecret }} + diff --git a/manifests/hub/hub-registration-webhook-secret.yaml b/manifests/hub/hub-registration-webhook-secret.yaml new file mode 100644 index 000000000..8c05b7f21 --- /dev/null +++ b/manifests/hub/hub-registration-webhook-secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .HubCoreWebhookSecret }} + namespace: {{ .HubCoreNamespace }} +data: + tls.crt: {{ .RegistrationServingCert }} + tls.key: {{ .RegistrationServingKey }} + ca.crt: {{ .RegistrationAPIServiceCABundle }} +type: Opaque diff --git a/manifests/hub/hub-registration-webhook-service.yaml b/manifests/hub/hub-registration-webhook-service.yaml new file mode 100644 index 000000000..31b03f495 --- /dev/null +++ b/manifests/hub/hub-registration-webhook-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .HubCoreWebhookRegistrationService }} + namespace: {{ .HubCoreNamespace }} +spec: + selector: + app: {{ .HubCoreName }}-registration-webhook + ports: + - port: 443 + targetPort: 6443 diff --git a/manifests/hub/hub-serviceaccount.yaml b/manifests/hub/hub-registration-webhook-serviceaccount.yaml similarity index 61% rename from manifests/hub/hub-serviceaccount.yaml rename to manifests/hub/hub-registration-webhook-serviceaccount.yaml index b17e4cab1..bc60dd80c 100644 --- a/manifests/hub/hub-serviceaccount.yaml +++ b/manifests/hub/hub-registration-webhook-serviceaccount.yaml @@ -1,5 +1,5 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: {{ .HubCoreName }}-sa + name: {{ .HubCoreName }}-registration-webhook-sa namespace: {{ .HubCoreNamespace }} diff --git a/manifests/hub/hub-registration-webhook-validatingconfiguration.yaml b/manifests/hub/hub-registration-webhook-validatingconfiguration.yaml new file mode 100644 index 000000000..0dbc67de8 --- /dev/null +++ b/manifests/hub/hub-registration-webhook-validatingconfiguration.yaml @@ -0,0 +1,26 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: spokeclustervalidators.admission.cluster.open-cluster-management.io +webhooks: +- name: spokeclustervalidators.admission.cluster.open-cluster-management.io + failurePolicy: Fail + clientConfig: + service: + # reach the webhook via the registered aggregated API + namespace: default + name: kubernetes + path: /apis/admission.cluster.open-cluster-management.io/v1/spokeclustervalidators + rules: + - operations: + - CREATE + - UPDATE + apiGroups: + - cluster.open-cluster-management.io + apiVersions: + - "*" + resources: + - spokeclusters + admissionReviewVersions: ["v1beta1"] + sideEffects: None + timeoutSeconds: 3 diff --git a/pkg/helpers/helpers.go b/pkg/helpers/helpers.go index 999666c2d..34e84f1d7 100644 --- a/pkg/helpers/helpers.go +++ b/pkg/helpers/helpers.go @@ -7,6 +7,8 @@ import ( nucleusv1client "github.com/open-cluster-management/api/client/nucleus/clientset/versioned/typed/nucleus/v1" nucleusapiv1 "github.com/open-cluster-management/api/nucleus/v1" + admissionv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -16,10 +18,33 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/kubernetes" + admissionclient "k8s.io/client-go/kubernetes/typed/admissionregistration/v1" "k8s.io/client-go/util/retry" + apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1" + + "github.com/openshift/api" + "github.com/openshift/library-go/pkg/operator/events" + "github.com/openshift/library-go/pkg/operator/resource/resourceapply" + "github.com/openshift/library-go/pkg/operator/resource/resourcemerge" ) +var ( + genericScheme = runtime.NewScheme() + genericCodecs = serializer.NewCodecFactory(genericScheme) + genericCodec = genericCodecs.UniversalDeserializer() +) + +func init() { + utilruntime.Must(api.InstallKube(genericScheme)) + utilruntime.Must(apiextensionsv1beta1.AddToScheme(genericScheme)) + utilruntime.Must(apiregistrationv1.AddToScheme(genericScheme)) + utilruntime.Must(admissionv1.AddToScheme(genericScheme)) +} + func IsConditionTrue(condition *nucleusapiv1.StatusCondition) bool { if condition == nil { return false @@ -159,9 +184,18 @@ func UpdateNucleusSpokeConditionFn(conds ...nucleusapiv1.StatusCondition) Update func CleanUpStaticObject( ctx context.Context, client kubernetes.Interface, - apiextensionclient apiextensionsclient.Interface, - object runtime.Object) error { - var err error + apiExtensionClient apiextensionsclient.Interface, + apiRegistrationClient apiregistrationclient.APIServicesGetter, + manifests resourceapply.AssetFunc, + file string) error { + objectRaw, err := manifests(file) + if err != nil { + return err + } + object, _, err := genericCodec.Decode(objectRaw, nil, nil) + if err != nil { + return err + } switch t := object.(type) { case *corev1.Namespace: err = client.CoreV1().Namespaces().Delete(ctx, t.Name, metav1.DeleteOptions{}) @@ -182,9 +216,13 @@ 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{}) + err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Delete(ctx, t.Name, metav1.DeleteOptions{}) case *apiextensionsv1beta1.CustomResourceDefinition: - err = apiextensionclient.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(ctx, t.Name, metav1.DeleteOptions{}) + err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(ctx, t.Name, metav1.DeleteOptions{}) + case *apiregistrationv1.APIService: + err = apiRegistrationClient.APIServices().Delete(ctx, t.Name, metav1.DeleteOptions{}) + case *admissionv1.ValidatingWebhookConfiguration: + err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(ctx, t.Name, metav1.DeleteOptions{}) default: err = fmt.Errorf("unhandled type %T", object) } @@ -193,3 +231,102 @@ func CleanUpStaticObject( } return err } + +func ApplyValidatingWebhookConfiguration( + client admissionclient.ValidatingWebhookConfigurationsGetter, + required *admissionv1.ValidatingWebhookConfiguration) (*admissionv1.ValidatingWebhookConfiguration, bool, error) { + existing, err := client.ValidatingWebhookConfigurations().Get(context.TODO(), required.Name, metav1.GetOptions{}) + if errors.IsNotFound(err) { + actual, err := client.ValidatingWebhookConfigurations().Create(context.TODO(), required, metav1.CreateOptions{}) + return actual, true, err + } + if err != nil { + return nil, false, err + } + + modified := resourcemerge.BoolPtr(false) + existingCopy := existing.DeepCopy() + resourcemerge.EnsureObjectMeta(modified, &existingCopy.ObjectMeta, required.ObjectMeta) + if !equality.Semantic.DeepEqual(existingCopy.Webhooks, required.Webhooks) { + *modified = true + existing.Webhooks = required.Webhooks + } + if !*modified { + return existing, false, nil + } + + actual, err := client.ValidatingWebhookConfigurations().Update(context.TODO(), existingCopy, metav1.UpdateOptions{}) + return actual, true, err +} + +func ApplyDeployment( + client kubernetes.Interface, generation int64, manifests resourceapply.AssetFunc, recorder events.Recorder, file string) (int64, error) { + deploymentBytes, err := manifests(file) + if err != nil { + + } + deployment, _, err := genericCodec.Decode(deploymentBytes, nil, nil) + if err != nil { + return generation, fmt.Errorf("%q: %v", file, err) + } + updatedDeployment, updated, err := resourceapply.ApplyDeployment( + client.AppsV1(), + recorder, + deployment.(*appsv1.Deployment), generation, false) + if err != nil { + return generation, fmt.Errorf("%q (%T): %v", file, deployment, err) + } + + if updated { + generation = updatedDeployment.ObjectMeta.Generation + } + + return generation, nil +} + +func ApplyDirectly( + client kubernetes.Interface, + apiExtensionClient apiextensionsclient.Interface, + apiRegistrationClient apiregistrationclient.APIServicesGetter, + recorder events.Recorder, + manifests resourceapply.AssetFunc, + files ...string) []resourceapply.ApplyResult { + ret := []resourceapply.ApplyResult{} + genericApplyFiles := []string{} + for _, file := range files { + result := resourceapply.ApplyResult{File: file} + objBytes, err := manifests(file) + if err != nil { + result.Error = fmt.Errorf("missing %q: %v", file, err) + ret = append(ret, result) + continue + } + requiredObj, _, err := genericCodec.Decode(objBytes, nil, nil) + if err != nil { + result.Error = fmt.Errorf("cannot decode %q: %v", file, err) + ret = append(ret, result) + continue + } + result.Type = fmt.Sprintf("%T", requiredObj) + switch t := requiredObj.(type) { + case *admissionv1.ValidatingWebhookConfiguration: + result.Result, result.Changed, result.Error = ApplyValidatingWebhookConfiguration( + client.AdmissionregistrationV1(), t) + case *apiregistrationv1.APIService: + result.Result, result.Changed, result.Error = resourceapply.ApplyAPIService(apiRegistrationClient, recorder, t) + default: + genericApplyFiles = append(genericApplyFiles, file) + } + } + + clientHolder := resourceapply.NewKubeClientHolder(client).WithAPIExtensionsClient(apiExtensionClient) + applyResults := resourceapply.ApplyDirectly( + clientHolder, + recorder, + manifests, + genericApplyFiles..., + ) + + ret = append(ret, applyResults...) + return ret +} diff --git a/pkg/helpers/helpers_test.go b/pkg/helpers/helpers_test.go index e693eb00b..6aaaa3a20 100644 --- a/pkg/helpers/helpers_test.go +++ b/pkg/helpers/helpers_test.go @@ -2,14 +2,24 @@ package helpers import ( "context" + "encoding/json" + "fmt" "testing" "time" nucleusfake "github.com/open-cluster-management/api/client/nucleus/clientset/versioned/fake" nucleusapiv1 "github.com/open-cluster-management/api/nucleus/v1" + "github.com/openshift/library-go/pkg/operator/events/eventstesting" + operatorhelpers "github.com/openshift/library-go/pkg/operator/v1helpers" + admissionv1 "k8s.io/api/admissionregistration/v1" + fakeapiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" "k8s.io/apimachinery/pkg/api/equality" 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/util/diff" + fakekube "k8s.io/client-go/kubernetes/fake" + fakeapiregistration "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake" ) func TestUpdateStatusCondition(t *testing.T) { @@ -21,14 +31,14 @@ func TestUpdateStatusCondition(t *testing.T) { name string startingConditions []nucleusapiv1.StatusCondition newCondition nucleusapiv1.StatusCondition - expextedUpdated bool + expectedUpdated bool expectedConditions []nucleusapiv1.StatusCondition }{ { name: "add to empty", startingConditions: []nucleusapiv1.StatusCondition{}, newCondition: newCondition("test", "True", "my-reason", "my-message", nil), - expextedUpdated: true, + expectedUpdated: true, expectedConditions: []nucleusapiv1.StatusCondition{newCondition("test", "True", "my-reason", "my-message", nil)}, }, { @@ -37,7 +47,7 @@ func TestUpdateStatusCondition(t *testing.T) { newCondition("two", "True", "my-reason", "my-message", nil), }, newCondition: newCondition("one", "True", "my-reason", "my-message", nil), - expextedUpdated: true, + expectedUpdated: true, expectedConditions: []nucleusapiv1.StatusCondition{ newCondition("two", "True", "my-reason", "my-message", nil), newCondition("one", "True", "my-reason", "my-message", nil), @@ -50,7 +60,7 @@ func TestUpdateStatusCondition(t *testing.T) { newCondition("one", "True", "my-reason", "my-message", nil), }, newCondition: newCondition("one", "False", "my-different-reason", "my-othermessage", nil), - expextedUpdated: true, + expectedUpdated: true, expectedConditions: []nucleusapiv1.StatusCondition{ newCondition("two", "True", "my-reason", "my-message", nil), newCondition("one", "False", "my-different-reason", "my-othermessage", nil), @@ -63,7 +73,7 @@ func TestUpdateStatusCondition(t *testing.T) { newCondition("one", "True", "my-reason", "my-message", &beforeish), }, newCondition: newCondition("one", "True", "my-reason", "my-message", &afterish), - expextedUpdated: false, + expectedUpdated: false, expectedConditions: []nucleusapiv1.StatusCondition{ newCondition("two", "True", "my-reason", "my-message", nil), newCondition("one", "True", "my-reason", "my-message", &beforeish), @@ -97,8 +107,8 @@ func TestUpdateStatusCondition(t *testing.T) { if err != nil { t.Errorf("unexpected err: %v", err) } - if updated != c.expextedUpdated { - t.Errorf("expected %t, but %t", c.expextedUpdated, updated) + if updated != c.expectedUpdated { + t.Errorf("expected %t, but %t", c.expectedUpdated, updated) } spokestatus, updated, err := UpdateNucleusSpokeStatus( @@ -110,8 +120,8 @@ func TestUpdateStatusCondition(t *testing.T) { if err != nil { t.Errorf("unexpected err: %v", err) } - if updated != c.expextedUpdated { - t.Errorf("expected %t, but %t", c.expextedUpdated, updated) + if updated != c.expectedUpdated { + t.Errorf("expected %t, but %t", c.expectedUpdated, updated) } for i := range c.expectedConditions { @@ -148,3 +158,211 @@ func newCondition(name, status, reason, message string, lastTransition *metav1.T } return ret } + +func newValidatingWebhookConfiguration(name, svc, svcNameSpace string) *admissionv1.ValidatingWebhookConfiguration { + return &admissionv1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Webhooks: []admissionv1.ValidatingWebhook{ + { + ClientConfig: admissionv1.WebhookClientConfig{ + Service: &admissionv1.ServiceReference{ + Name: svc, + Namespace: svcNameSpace, + }, + }, + }, + }, + } +} + +func newUnstructured( + apiVersion, kind, namespace, name string, content map[string]interface{}) *unstructured.Unstructured { + object := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": apiVersion, + "kind": kind, + "metadata": map[string]interface{}{ + "namespace": namespace, + "name": name, + }, + }, + } + for key, val := range content { + object.Object[key] = val + } + + return object +} + +func TestApplyValidatingWebhookConfiguration(t *testing.T) { + testcase := []struct { + name string + existing []runtime.Object + expected *admissionv1.ValidatingWebhookConfiguration + expectUpdated bool + }{ + { + name: "Create a new configuration", + expectUpdated: true, + existing: []runtime.Object{}, + expected: newValidatingWebhookConfiguration("test", "svc1", "svc1"), + }, + { + name: "update an existing configuration", + expectUpdated: true, + existing: []runtime.Object{newValidatingWebhookConfiguration("test", "svc1", "svc1")}, + expected: newValidatingWebhookConfiguration("test", "svc2", "svc2"), + }, + { + name: "skip update", + expectUpdated: false, + existing: []runtime.Object{newValidatingWebhookConfiguration("test", "svc1", "svc1")}, + expected: newValidatingWebhookConfiguration("test", "svc1", "svc1"), + }, + } + + for _, c := range testcase { + t.Run(c.name, func(t *testing.T) { + fakeKubeClient := fakekube.NewSimpleClientset(c.existing...) + _, updated, err := ApplyValidatingWebhookConfiguration(fakeKubeClient.AdmissionregistrationV1(), c.expected) + if err != nil { + t.Errorf("Expected no error when applying: %v", err) + } + + if updated != c.expectUpdated { + t.Errorf("Expect update is %t, but got %t", c.expectUpdated, updated) + } + }) + } +} + +func TestApplyDirectly(t *testing.T) { + testcase := []struct { + name string + applyFiles map[string]runtime.Object + applyFileNames []string + expectErr bool + }{ + { + name: "Apply webhooks & apiservice & secret", + applyFiles: map[string]runtime.Object{ + "webhooks": newUnstructured("admissionregistration.k8s.io/v1", "ValidatingWebhookConfiguration", "", "", 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{"webhooks", "apiservice", "secret"}, + expectErr: false, + }, + { + name: "Apply unhandled object", + applyFiles: map[string]runtime.Object{ + "kind1": newUnstructured("v1", "Kind1", "ns1", "n1", map[string]interface{}{"spec": map[string]interface{}{"key1": []byte("key1")}}), + }, + applyFileNames: []string{"kind1"}, + expectErr: true, + }, + } + + 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") + } + + return json.Marshal(c.applyFiles[name]) + }, + c.applyFileNames..., + ) + aggregatedErr := []error{} + for _, r := range results { + if r.Error != nil { + aggregatedErr = append(aggregatedErr, r.Error) + } + } + + if len(aggregatedErr) == 0 && c.expectErr { + t.Errorf("Expect an apply error") + } + if len(aggregatedErr) != 0 && !c.expectErr { + t.Errorf("Expect no apply error, %v", operatorhelpers.NewMultiLineAggregate(aggregatedErr)) + } + }) + } +} + +func TestDeleteStaticObject(t *testing.T) { + applyFiles := map[string]runtime.Object{ + "webhooks": newUnstructured("admissionregistration.k8s.io/v1", "ValidatingWebhookConfiguration", "", "", 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")}}), + "crd": newUnstructured("apiextensions.k8s.io/v1beta1", "CustomResourceDefinition", "", "", map[string]interface{}{}), + "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: "Delete webhooks", + applyFileName: "webhooks", + expectErr: false, + }, + { + name: "Delete apiservice", + applyFileName: "apiservice", + expectErr: false, + }, + { + name: "Delete secret", + applyFileName: "secret", + expectErr: false, + }, + { + name: "Delete crd", + applyFileName: "crd", + expectErr: false, + }, + { + name: "Delete unhandled object", + applyFileName: "kind1", + expectErr: true, + }, + } + + for _, c := range testcase { + t.Run(c.name, func(t *testing.T) { + fakeKubeClient := fakekube.NewSimpleClientset() + fakeResgistrationClient := fakeapiregistration.NewSimpleClientset() + 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") + } + + return json.Marshal(applyFiles[name]) + }, + c.applyFileName, + ) + + if err == nil && c.expectErr { + t.Errorf("Expect an apply error") + } + if err != nil && !c.expectErr { + t.Errorf("Expect no apply error, %v", err) + } + }) + } +} diff --git a/pkg/operators/hub/bindata/bindata.go b/pkg/operators/hub/bindata/bindata.go index cb9ac3abd..fc7fd3fbb 100644 --- a/pkg/operators/hub/bindata/bindata.go +++ b/pkg/operators/hub/bindata/bindata.go @@ -2,11 +2,19 @@ // sources: // manifests/hub/0000_00_clusters.open-cluster-management.io_spokeclusters.crd.yaml // manifests/hub/0000_00_work.open-cluster-management.io_manifestworks.crd.yaml -// manifests/hub/hub-clusterrole.yaml -// manifests/hub/hub-clusterrolebinding.yaml -// manifests/hub/hub-deployment.yaml // manifests/hub/hub-namespace.yaml -// manifests/hub/hub-serviceaccount.yaml +// manifests/hub/hub-registration-clusterrole.yaml +// manifests/hub/hub-registration-clusterrolebinding.yaml +// manifests/hub/hub-registration-deployment.yaml +// manifests/hub/hub-registration-serviceaccount.yaml +// manifests/hub/hub-registration-webhook-apiservice.yaml +// manifests/hub/hub-registration-webhook-clusterrole.yaml +// manifests/hub/hub-registration-webhook-clusterrolebinding.yaml +// manifests/hub/hub-registration-webhook-deployment.yaml +// manifests/hub/hub-registration-webhook-secret.yaml +// manifests/hub/hub-registration-webhook-service.yaml +// manifests/hub/hub-registration-webhook-serviceaccount.yaml +// manifests/hub/hub-registration-webhook-validatingconfiguration.yaml // DO NOT EDIT! package bindata @@ -411,10 +419,31 @@ func manifestsHub0000_00_workOpenClusterManagementIo_manifestworksCrdYaml() (*as return a, nil } -var _manifestsHubHubClusterroleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +var _manifestsHubHubNamespaceYaml = []byte(`apiVersion: v1 +kind: Namespace +metadata: + name: {{ .HubCoreNamespace }} +`) + +func manifestsHubHubNamespaceYamlBytes() ([]byte, error) { + return _manifestsHubHubNamespaceYaml, nil +} + +func manifestsHubHubNamespaceYaml() (*asset, error) { + bytes, err := manifestsHubHubNamespaceYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/hub/hub-namespace.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _manifestsHubHubRegistrationClusterroleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: system:open-cluster-management:{{ .HubCoreName }} + name: system:open-cluster-management:{{ .HubCoreName }}-registration-controller rules: # Allow hub to monitor and update status of csr - apiGroups: ["certificates.k8s.io"] @@ -449,70 +478,70 @@ rules: verbs: ["update", "patch"] `) -func manifestsHubHubClusterroleYamlBytes() ([]byte, error) { - return _manifestsHubHubClusterroleYaml, nil +func manifestsHubHubRegistrationClusterroleYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationClusterroleYaml, nil } -func manifestsHubHubClusterroleYaml() (*asset, error) { - bytes, err := manifestsHubHubClusterroleYamlBytes() +func manifestsHubHubRegistrationClusterroleYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationClusterroleYamlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "manifests/hub/hub-clusterrole.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + info := bindataFileInfo{name: "manifests/hub/hub-registration-clusterrole.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _manifestsHubHubClusterrolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +var _manifestsHubHubRegistrationClusterrolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: system:open-cluster-management:{{ .HubCoreName }} + name: system:open-cluster-management:{{ .HubCoreName }}-registration-controller roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: system:open-cluster-management:{{ .HubCoreName }} + name: system:open-cluster-management:{{ .HubCoreName }}-registration-controller subjects: - kind: ServiceAccount namespace: {{ .HubCoreNamespace }} - name: {{ .HubCoreName }}-sa + name: {{ .HubCoreName }}-registration-controller-sa `) -func manifestsHubHubClusterrolebindingYamlBytes() ([]byte, error) { - return _manifestsHubHubClusterrolebindingYaml, nil +func manifestsHubHubRegistrationClusterrolebindingYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationClusterrolebindingYaml, nil } -func manifestsHubHubClusterrolebindingYaml() (*asset, error) { - bytes, err := manifestsHubHubClusterrolebindingYamlBytes() +func manifestsHubHubRegistrationClusterrolebindingYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationClusterrolebindingYamlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "manifests/hub/hub-clusterrolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + info := bindataFileInfo{name: "manifests/hub/hub-registration-clusterrolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _manifestsHubHubDeploymentYaml = []byte(`kind: Deployment +var _manifestsHubHubRegistrationDeploymentYaml = []byte(`kind: Deployment apiVersion: apps/v1 metadata: - name: {{ .HubCoreName }}-controller + name: {{ .HubCoreName }}-registration-controller namespace: {{ .HubCoreNamespace }} labels: - app: nucleushub-controller + app: nucleushub-registration-controller spec: replicas: 3 selector: matchLabels: - app: nucleushub-controller + app: nucleushub-registration-controller template: metadata: labels: - app: nucleushub-controller + app: nucleushub-registration-controller spec: - serviceAccountName: {{ .HubCoreName }}-sa + serviceAccountName: {{ .HubCoreName }}-registration-controller-sa containers: - - name: hub-controller + - name: hub-registration-controller image: {{ .RegistrationImage }} imagePullPolicy: IfNotPresent args: @@ -533,60 +562,315 @@ spec: initialDelaySeconds: 2 `) -func manifestsHubHubDeploymentYamlBytes() ([]byte, error) { - return _manifestsHubHubDeploymentYaml, nil +func manifestsHubHubRegistrationDeploymentYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationDeploymentYaml, nil } -func manifestsHubHubDeploymentYaml() (*asset, error) { - bytes, err := manifestsHubHubDeploymentYamlBytes() +func manifestsHubHubRegistrationDeploymentYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationDeploymentYamlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "manifests/hub/hub-deployment.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + info := bindataFileInfo{name: "manifests/hub/hub-registration-deployment.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _manifestsHubHubNamespaceYaml = []byte(`apiVersion: v1 -kind: Namespace -metadata: - name: {{ .HubCoreNamespace }} -`) - -func manifestsHubHubNamespaceYamlBytes() ([]byte, error) { - return _manifestsHubHubNamespaceYaml, nil -} - -func manifestsHubHubNamespaceYaml() (*asset, error) { - bytes, err := manifestsHubHubNamespaceYamlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "manifests/hub/hub-namespace.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -var _manifestsHubHubServiceaccountYaml = []byte(`apiVersion: v1 +var _manifestsHubHubRegistrationServiceaccountYaml = []byte(`apiVersion: v1 kind: ServiceAccount metadata: - name: {{ .HubCoreName }}-sa + name: {{ .HubCoreName }}-registration-controller-sa namespace: {{ .HubCoreNamespace }} `) -func manifestsHubHubServiceaccountYamlBytes() ([]byte, error) { - return _manifestsHubHubServiceaccountYaml, nil +func manifestsHubHubRegistrationServiceaccountYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationServiceaccountYaml, nil } -func manifestsHubHubServiceaccountYaml() (*asset, error) { - bytes, err := manifestsHubHubServiceaccountYamlBytes() +func manifestsHubHubRegistrationServiceaccountYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationServiceaccountYamlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "manifests/hub/hub-serviceaccount.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + info := bindataFileInfo{name: "manifests/hub/hub-registration-serviceaccount.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _manifestsHubHubRegistrationWebhookApiserviceYaml = []byte(`apiVersion: apiregistration.k8s.io/v1 +kind: APIService +metadata: + name: v1.admission.cluster.open-cluster-management.io +spec: + group: admission.cluster.open-cluster-management.io + version: v1 + service: + name: {{ .HubCoreWebhookRegistrationService }} + namespace: {{ .HubCoreNamespace }} + caBundle: {{ .RegistrationAPIServiceCABundle }} + groupPriorityMinimum: 10000 + versionPriority: 20 +`) + +func manifestsHubHubRegistrationWebhookApiserviceYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationWebhookApiserviceYaml, nil +} + +func manifestsHubHubRegistrationWebhookApiserviceYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationWebhookApiserviceYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/hub/hub-registration-webhook-apiservice.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _manifestsHubHubRegistrationWebhookClusterroleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: system:open-cluster-management:{{ .HubCoreName }}-registration-webhook +rules: +# Allow spokecluster admission to get/list/watch configmaps +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list", "watch"] +# Allow spokecluster admission to create subjectaccessreviews +- apiGroups: ["authorization.k8s.io"] + resources: ["subjectaccessreviews"] + verbs: ["create"] +`) + +func manifestsHubHubRegistrationWebhookClusterroleYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationWebhookClusterroleYaml, nil +} + +func manifestsHubHubRegistrationWebhookClusterroleYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationWebhookClusterroleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/hub/hub-registration-webhook-clusterrole.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _manifestsHubHubRegistrationWebhookClusterrolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: system:open-cluster-management:{{ .HubCoreName }}-registration-webhook +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ .HubCoreName }}-registration-webhook +subjects: + - kind: ServiceAccount + name: {{ .HubCoreName }}-registration-webhook-sa + namespace: {{ .HubCoreNamespace }} +`) + +func manifestsHubHubRegistrationWebhookClusterrolebindingYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationWebhookClusterrolebindingYaml, nil +} + +func manifestsHubHubRegistrationWebhookClusterrolebindingYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationWebhookClusterrolebindingYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/hub/hub-registration-webhook-clusterrolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _manifestsHubHubRegistrationWebhookDeploymentYaml = []byte(`apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .HubCoreName }}-registration-webhook + namespace: {{ .HubCoreNamespace }} + labels: + app: {{ .HubCoreName }}-registration-webhook +spec: + replicas: 3 + selector: + matchLabels: + app: {{ .HubCoreName }}-registration-webhook + template: + metadata: + labels: + app: {{ .HubCoreName }}-registration-webhook + spec: + serviceAccountName: {{ .HubCoreName }}-registration-webhook-sa + containers: + - name: {{ .HubCoreName }}-registration-webhook-sa + image: {{ .RegistrationImage }} + imagePullPolicy: IfNotPresent + args: + - "/registration" + - "webhook" + - "--secure-port=6443" + - "--tls-cert-file=/serving-cert/tls.crt" + - "--tls-private-key-file=/serving-cert/tls.key" + livenessProbe: + httpGet: + path: /healthz + scheme: HTTPS + port: 6443 + initialDelaySeconds: 2 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /healthz + scheme: HTTPS + port: 6443 + initialDelaySeconds: 2 + volumeMounts: + - name: webhook-secret + mountPath: "/serving-cert" + readOnly: true + volumes: + - name: webhook-secret + secret: + secretName: {{ .HubCoreWebhookSecret }} + +`) + +func manifestsHubHubRegistrationWebhookDeploymentYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationWebhookDeploymentYaml, nil +} + +func manifestsHubHubRegistrationWebhookDeploymentYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationWebhookDeploymentYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/hub/hub-registration-webhook-deployment.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _manifestsHubHubRegistrationWebhookSecretYaml = []byte(`apiVersion: v1 +kind: Secret +metadata: + name: {{ .HubCoreWebhookSecret }} + namespace: {{ .HubCoreNamespace }} +data: + tls.crt: {{ .RegistrationServingCert }} + tls.key: {{ .RegistrationServingKey }} + ca.crt: {{ .RegistrationAPIServiceCABundle }} +type: Opaque +`) + +func manifestsHubHubRegistrationWebhookSecretYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationWebhookSecretYaml, nil +} + +func manifestsHubHubRegistrationWebhookSecretYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationWebhookSecretYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/hub/hub-registration-webhook-secret.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _manifestsHubHubRegistrationWebhookServiceYaml = []byte(`apiVersion: v1 +kind: Service +metadata: + name: {{ .HubCoreWebhookRegistrationService }} + namespace: {{ .HubCoreNamespace }} +spec: + selector: + app: {{ .HubCoreName }}-registration-webhook + ports: + - port: 443 + targetPort: 6443 +`) + +func manifestsHubHubRegistrationWebhookServiceYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationWebhookServiceYaml, nil +} + +func manifestsHubHubRegistrationWebhookServiceYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationWebhookServiceYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/hub/hub-registration-webhook-service.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _manifestsHubHubRegistrationWebhookServiceaccountYaml = []byte(`apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .HubCoreName }}-registration-webhook-sa + namespace: {{ .HubCoreNamespace }} +`) + +func manifestsHubHubRegistrationWebhookServiceaccountYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationWebhookServiceaccountYaml, nil +} + +func manifestsHubHubRegistrationWebhookServiceaccountYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationWebhookServiceaccountYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/hub/hub-registration-webhook-serviceaccount.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _manifestsHubHubRegistrationWebhookValidatingconfigurationYaml = []byte(`apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: spokeclustervalidators.admission.cluster.open-cluster-management.io +webhooks: +- name: spokeclustervalidators.admission.cluster.open-cluster-management.io + failurePolicy: Fail + clientConfig: + service: + # reach the webhook via the registered aggregated API + namespace: default + name: kubernetes + path: /apis/admission.cluster.open-cluster-management.io/v1/spokeclustervalidators + rules: + - operations: + - CREATE + - UPDATE + apiGroups: + - cluster.open-cluster-management.io + apiVersions: + - "*" + resources: + - spokeclusters + admissionReviewVersions: ["v1beta1"] + sideEffects: None + timeoutSeconds: 3 +`) + +func manifestsHubHubRegistrationWebhookValidatingconfigurationYamlBytes() ([]byte, error) { + return _manifestsHubHubRegistrationWebhookValidatingconfigurationYaml, nil +} + +func manifestsHubHubRegistrationWebhookValidatingconfigurationYaml() (*asset, error) { + bytes, err := manifestsHubHubRegistrationWebhookValidatingconfigurationYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "manifests/hub/hub-registration-webhook-validatingconfiguration.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -645,11 +929,19 @@ func AssetNames() []string { var _bindata = map[string]func() (*asset, error){ "manifests/hub/0000_00_clusters.open-cluster-management.io_spokeclusters.crd.yaml": manifestsHub0000_00_clustersOpenClusterManagementIo_spokeclustersCrdYaml, "manifests/hub/0000_00_work.open-cluster-management.io_manifestworks.crd.yaml": manifestsHub0000_00_workOpenClusterManagementIo_manifestworksCrdYaml, - "manifests/hub/hub-clusterrole.yaml": manifestsHubHubClusterroleYaml, - "manifests/hub/hub-clusterrolebinding.yaml": manifestsHubHubClusterrolebindingYaml, - "manifests/hub/hub-deployment.yaml": manifestsHubHubDeploymentYaml, "manifests/hub/hub-namespace.yaml": manifestsHubHubNamespaceYaml, - "manifests/hub/hub-serviceaccount.yaml": manifestsHubHubServiceaccountYaml, + "manifests/hub/hub-registration-clusterrole.yaml": manifestsHubHubRegistrationClusterroleYaml, + "manifests/hub/hub-registration-clusterrolebinding.yaml": manifestsHubHubRegistrationClusterrolebindingYaml, + "manifests/hub/hub-registration-deployment.yaml": manifestsHubHubRegistrationDeploymentYaml, + "manifests/hub/hub-registration-serviceaccount.yaml": manifestsHubHubRegistrationServiceaccountYaml, + "manifests/hub/hub-registration-webhook-apiservice.yaml": manifestsHubHubRegistrationWebhookApiserviceYaml, + "manifests/hub/hub-registration-webhook-clusterrole.yaml": manifestsHubHubRegistrationWebhookClusterroleYaml, + "manifests/hub/hub-registration-webhook-clusterrolebinding.yaml": manifestsHubHubRegistrationWebhookClusterrolebindingYaml, + "manifests/hub/hub-registration-webhook-deployment.yaml": manifestsHubHubRegistrationWebhookDeploymentYaml, + "manifests/hub/hub-registration-webhook-secret.yaml": manifestsHubHubRegistrationWebhookSecretYaml, + "manifests/hub/hub-registration-webhook-service.yaml": manifestsHubHubRegistrationWebhookServiceYaml, + "manifests/hub/hub-registration-webhook-serviceaccount.yaml": manifestsHubHubRegistrationWebhookServiceaccountYaml, + "manifests/hub/hub-registration-webhook-validatingconfiguration.yaml": manifestsHubHubRegistrationWebhookValidatingconfigurationYaml, } // AssetDir returns the file names below a certain @@ -697,11 +989,19 @@ var _bintree = &bintree{nil, map[string]*bintree{ "hub": {nil, map[string]*bintree{ "0000_00_clusters.open-cluster-management.io_spokeclusters.crd.yaml": {manifestsHub0000_00_clustersOpenClusterManagementIo_spokeclustersCrdYaml, map[string]*bintree{}}, "0000_00_work.open-cluster-management.io_manifestworks.crd.yaml": {manifestsHub0000_00_workOpenClusterManagementIo_manifestworksCrdYaml, map[string]*bintree{}}, - "hub-clusterrole.yaml": {manifestsHubHubClusterroleYaml, map[string]*bintree{}}, - "hub-clusterrolebinding.yaml": {manifestsHubHubClusterrolebindingYaml, map[string]*bintree{}}, - "hub-deployment.yaml": {manifestsHubHubDeploymentYaml, map[string]*bintree{}}, - "hub-namespace.yaml": {manifestsHubHubNamespaceYaml, map[string]*bintree{}}, - "hub-serviceaccount.yaml": {manifestsHubHubServiceaccountYaml, map[string]*bintree{}}, + "hub-namespace.yaml": {manifestsHubHubNamespaceYaml, map[string]*bintree{}}, + "hub-registration-clusterrole.yaml": {manifestsHubHubRegistrationClusterroleYaml, map[string]*bintree{}}, + "hub-registration-clusterrolebinding.yaml": {manifestsHubHubRegistrationClusterrolebindingYaml, map[string]*bintree{}}, + "hub-registration-deployment.yaml": {manifestsHubHubRegistrationDeploymentYaml, map[string]*bintree{}}, + "hub-registration-serviceaccount.yaml": {manifestsHubHubRegistrationServiceaccountYaml, map[string]*bintree{}}, + "hub-registration-webhook-apiservice.yaml": {manifestsHubHubRegistrationWebhookApiserviceYaml, map[string]*bintree{}}, + "hub-registration-webhook-clusterrole.yaml": {manifestsHubHubRegistrationWebhookClusterroleYaml, map[string]*bintree{}}, + "hub-registration-webhook-clusterrolebinding.yaml": {manifestsHubHubRegistrationWebhookClusterrolebindingYaml, map[string]*bintree{}}, + "hub-registration-webhook-deployment.yaml": {manifestsHubHubRegistrationWebhookDeploymentYaml, map[string]*bintree{}}, + "hub-registration-webhook-secret.yaml": {manifestsHubHubRegistrationWebhookSecretYaml, map[string]*bintree{}}, + "hub-registration-webhook-service.yaml": {manifestsHubHubRegistrationWebhookServiceYaml, map[string]*bintree{}}, + "hub-registration-webhook-serviceaccount.yaml": {manifestsHubHubRegistrationWebhookServiceaccountYaml, map[string]*bintree{}}, + "hub-registration-webhook-validatingconfiguration.yaml": {manifestsHubHubRegistrationWebhookValidatingconfigurationYaml, map[string]*bintree{}}, }}, }}, }} diff --git a/pkg/operators/hub/controller.go b/pkg/operators/hub/controller.go index 3bcf34c22..f60637256 100644 --- a/pkg/operators/hub/controller.go +++ b/pkg/operators/hub/controller.go @@ -2,25 +2,23 @@ package hub import ( "context" + "encoding/base64" "fmt" "path/filepath" "time" - appsv1 "k8s.io/api/apps/v1" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/kubernetes" "k8s.io/klog" + apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1" - "github.com/openshift/api" "github.com/openshift/library-go/pkg/assets" "github.com/openshift/library-go/pkg/controller/factory" + "github.com/openshift/library-go/pkg/crypto" "github.com/openshift/library-go/pkg/operator/events" - "github.com/openshift/library-go/pkg/operator/resource/resourceapply" operatorhelpers "github.com/openshift/library-go/pkg/operator/v1helpers" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -34,56 +32,64 @@ import ( ) var ( - genericScheme = runtime.NewScheme() - genericCodecs = serializer.NewCodecFactory(genericScheme) - genericCodec = genericCodecs.UniversalDeserializer() - crdNames = []string{ + crdNames = []string{ "manifestworks.work.open-cluster-management.io", "spokeclusters.cluster.open-cluster-management.io", } staticResourceFiles = []string{ "manifests/hub/0000_00_clusters.open-cluster-management.io_spokeclusters.crd.yaml", "manifests/hub/0000_00_work.open-cluster-management.io_manifestworks.crd.yaml", - "manifests/hub/hub-clusterrole.yaml", - "manifests/hub/hub-clusterrolebinding.yaml", + "manifests/hub/hub-registration-clusterrole.yaml", + "manifests/hub/hub-registration-clusterrolebinding.yaml", "manifests/hub/hub-namespace.yaml", - "manifests/hub/hub-serviceaccount.yaml", + "manifests/hub/hub-registration-serviceaccount.yaml", + "manifests/hub/hub-registration-webhook-clusterrole.yaml", + "manifests/hub/hub-registration-webhook-clusterrolebinding.yaml", + "manifests/hub/hub-registration-webhook-service.yaml", + "manifests/hub/hub-registration-webhook-serviceaccount.yaml", + "manifests/hub/hub-registration-webhook-apiservice.yaml", + "manifests/hub/hub-registration-webhook-secret.yaml", + "manifests/hub/hub-registration-webhook-validatingconfiguration.yaml", + } + + deploymentFiles = []string{ + "manifests/hub/hub-registration-deployment.yaml", + "manifests/hub/hub-registration-webhook-deployment.yaml", } ) const ( - nucleusHubFinalizer = "nucleus.open-cluster-management.io/hub-core-cleanup" - nucluesHubCoreNamespace = "open-cluster-management-hub" - hubCoreApplied = "Applied" - hubCoreAvailable = "Available" + nucleusHubFinalizer = "nucleus.open-cluster-management.io/hub-core-cleanup" + nucleusHubCoreNamespace = "open-cluster-management-hub" + nucleusHubCoreWebhookSecret = "webhook-serving-cert" + hubCoreApplied = "Applied" + hubCoreAvailable = "Available" ) -func init() { - utilruntime.Must(api.InstallKube(genericScheme)) - utilruntime.Must(apiextensionsv1beta1.AddToScheme(genericScheme)) -} - type nucleusHubController struct { - nucleusClient nucleusv1client.HubCoreInterface - nucleusLister nucleuslister.HubCoreLister - kubeClient kubernetes.Interface - apiExtensionClient apiextensionsclient.Interface - currentHubDeploymentGeneration int64 + nucleusClient nucleusv1client.HubCoreInterface + nucleusLister nucleuslister.HubCoreLister + kubeClient kubernetes.Interface + apiExtensionClient apiextensionsclient.Interface + apiRegistrationClient apiregistrationclient.APIServicesGetter + currentGeneration []int64 } // NewNucleusHubController construct nucleus hub controller func NewNucleusHubController( kubeClient kubernetes.Interface, apiExtensionClient apiextensionsclient.Interface, + apiRegistrationClient apiregistrationclient.APIServicesGetter, nucleusClient nucleusv1client.HubCoreInterface, nucleusInformer nucleusinformer.HubCoreInformer, recorder events.Recorder) factory.Controller { controller := &nucleusHubController{ - kubeClient: kubeClient, - apiExtensionClient: apiExtensionClient, - nucleusClient: nucleusClient, - nucleusLister: nucleusInformer.Lister(), - currentHubDeploymentGeneration: 0, + kubeClient: kubeClient, + apiExtensionClient: apiExtensionClient, + apiRegistrationClient: apiRegistrationClient, + nucleusClient: nucleusClient, + nucleusLister: nucleusInformer.Lister(), + currentGeneration: make([]int64, len(deploymentFiles)), } return factory.New().WithSync(controller.sync). @@ -97,9 +103,14 @@ func NewNucleusHubController( // hubConfig is used to render the template of hub manifests type hubConfig struct { - HubCoreName string - HubCoreNamespace string - RegistrationImage string + HubCoreName string + HubCoreNamespace string + RegistrationImage string + HubCoreWebhookSecret string + HubCoreWebhookRegistrationService string + RegistrationAPIServiceCABundle string + RegistrationServingCert string + RegistrationServingKey string } func (n *nucleusHubController) sync(ctx context.Context, controllerContext factory.SyncContext) error { @@ -117,9 +128,11 @@ func (n *nucleusHubController) sync(ctx context.Context, controllerContext facto hubCore = hubCore.DeepCopy() config := hubConfig{ - HubCoreName: hubCore.Name, - HubCoreNamespace: nucluesHubCoreNamespace, - RegistrationImage: hubCore.Spec.RegistrationImagePullSpec, + HubCoreName: hubCore.Name, + HubCoreNamespace: nucleusHubCoreNamespace, + RegistrationImage: hubCore.Spec.RegistrationImagePullSpec, + HubCoreWebhookSecret: nucleusHubCoreWebhookSecret, + HubCoreWebhookRegistrationService: fmt.Sprintf("%s-registration-webhook", hubCore.Name), } // Update finalizer at first @@ -146,10 +159,20 @@ func (n *nucleusHubController) sync(ctx context.Context, controllerContext facto return n.removeWorkFinalizer(ctx, hubCore) } - clientHolder := resourceapply.NewKubeClientHolder(n.kubeClient).WithAPIExtensionsClient(n.apiExtensionClient) + ca, cert, key, err := n.ensureServingCertAndCA( + ctx, config.HubCoreNamespace, config.HubCoreWebhookSecret, config.HubCoreWebhookRegistrationService) + if err != nil { + return err + } + config.RegistrationAPIServiceCABundle = base64.StdEncoding.EncodeToString(ca) + config.RegistrationServingCert = base64.StdEncoding.EncodeToString(cert) + config.RegistrationServingKey = base64.StdEncoding.EncodeToString(key) + // Apply static files - resourceResults := resourceapply.ApplyDirectly( - clientHolder, + resourceResults := helpers.ApplyDirectly( + n.kubeClient, + n.apiExtensionClient, + n.apiRegistrationClient, controllerContext.Recorder(), func(name string) ([]byte, error) { return assets.MustCreateAssetFromTemplate(name, bindata.MustAsset(filepath.Join("", name)), config).Data, nil @@ -164,9 +187,19 @@ func (n *nucleusHubController) sync(ctx context.Context, controllerContext facto } // Render deployment manifest and apply - err = n.applyDeployment(config, controllerContext) - if err != nil { - errs = append(errs, err) + for index, file := range deploymentFiles { + currentGeneration, err := helpers.ApplyDeployment( + n.kubeClient, + n.currentGeneration[index], + func(name string) ([]byte, error) { + return assets.MustCreateAssetFromTemplate(name, bindata.MustAsset(filepath.Join("", name)), config).Data, nil + }, + controllerContext.Recorder(), + file) + if err != nil { + errs = append(errs, err) + } + n.currentGeneration[index] = currentGeneration } conditions := &hubCore.Status.Conditions @@ -197,32 +230,6 @@ func (n *nucleusHubController) sync(ctx context.Context, controllerContext facto return operatorhelpers.NewMultiLineAggregate(errs) } -func (n *nucleusHubController) applyDeployment(config hubConfig, controllerContext factory.SyncContext) error { - deploymentFile := "manifests/hub/hub-deployment.yaml" - deploymentRaw := assets.MustCreateAssetFromTemplate( - deploymentFile, - bindata.MustAsset(filepath.Join("", deploymentFile)), config).Data - deployment, _, err := genericCodec.Decode(deploymentRaw, nil, nil) - if err != nil { - return fmt.Errorf("%q: %v", deploymentFile, err) - } - updatedDeployment, updated, err := resourceapply.ApplyDeployment( - n.kubeClient.AppsV1(), - controllerContext.Recorder(), - deployment.(*appsv1.Deployment), n.currentHubDeploymentGeneration, false) - if err != nil { - klog.Errorf("Failed to apply hub deployment manifest: %v", err) - return fmt.Errorf("%q (%T): %v", deploymentFile, deployment, err) - } - - // Record the generation, so the deployment is only updated when generation is changed. - if updated { - n.currentHubDeploymentGeneration = updatedDeployment.ObjectMeta.Generation - } - - return nil -} - func (n *nucleusHubController) removeWorkFinalizer(ctx context.Context, deploy *nucleusapiv1.HubCore) error { copiedFinalizers := []string{} for i := range deploy.Finalizers { @@ -264,6 +271,48 @@ func (n *nucleusHubController) removeCRD(ctx context.Context, name string) error return fmt.Errorf("CRD %s is still being deleted", name) } +// ensureServingCertAndCA generates self signed CA and server key/cert for webhook server. +// TODO consider ca/cert renewal +func (n *nucleusHubController) ensureServingCertAndCA( + ctx context.Context, namespace, secretName, svcName string) ([]byte, []byte, []byte, error) { + secret, err := n.kubeClient.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) + switch { + case err != nil && !errors.IsNotFound(err): + return nil, nil, nil, err + case err == nil: + if secret.Data["ca.crt"] != nil && secret.Data["tls.crt"] != nil && secret.Data["tls.key"] != nil { + return secret.Data["ca.crt"], secret.Data["tls.crt"], secret.Data["tls.key"], nil + } + } + + caConfig, err := crypto.MakeSelfSignedCAConfig("nucleus-webhook", 365) + if err != nil { + return nil, nil, nil, err + } + + ca := &crypto.CA{ + SerialGenerator: &crypto.RandomSerialGenerator{}, + Config: caConfig, + } + + hostName := fmt.Sprintf("%s.%s.svc", svcName, namespace) + server, err := ca.MakeServerCert(sets.NewString(hostName), 365) + if err != nil { + return nil, nil, nil, err + } + + caData, _, err := caConfig.GetPEMBytes() + if err != nil { + return nil, nil, nil, err + } + certData, keyData, err := server.GetPEMBytes() + if err != nil { + return nil, nil, nil, err + } + + return caData, certData, keyData, nil +} + func (n *nucleusHubController) cleanUp( ctx context.Context, controllerContext factory.SyncContext, config hubConfig) error { // Remove crd @@ -275,24 +324,18 @@ func (n *nucleusHubController) cleanUp( controllerContext.Recorder().Eventf("CRDDeleted", "crd %s is deleted", name) } - // Remove deployment - deploymentName := fmt.Sprintf("%s-controller", config.HubCoreName) - err := n.kubeClient.AppsV1().Deployments(config.HubCoreNamespace).Delete(ctx, deploymentName, metav1.DeleteOptions{}) - if err != nil && !errors.IsNotFound(err) { - return err - } - controllerContext.Recorder().Eventf("DeploymentDeleted", "deployment %s is deleted", deploymentName) - // Remove Static files for _, file := range staticResourceFiles { - objectRaw := assets.MustCreateAssetFromTemplate( + err := helpers.CleanUpStaticObject( + ctx, + n.kubeClient, + n.apiExtensionClient, + n.apiRegistrationClient, + func(name string) ([]byte, error) { + return assets.MustCreateAssetFromTemplate(name, bindata.MustAsset(filepath.Join("", name)), config).Data, nil + }, file, - bindata.MustAsset(filepath.Join("", file)), config).Data - object, _, err := genericCodec.Decode(objectRaw, nil, nil) - if err != nil { - return err - } - err = helpers.CleanUpStaticObject(ctx, n.kubeClient, n.apiExtensionClient, object) + ) if err != nil { return err } diff --git a/pkg/operators/hub/controller_test.go b/pkg/operators/hub/controller_test.go index 8a8ba0f96..dae4e9047 100644 --- a/pkg/operators/hub/controller_test.go +++ b/pkg/operators/hub/controller_test.go @@ -1,7 +1,8 @@ package hub import ( - "fmt" + "context" + "reflect" "testing" "time" @@ -18,17 +19,20 @@ import ( "k8s.io/apimachinery/pkg/runtime" fakekube "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" + "k8s.io/client-go/util/cert" "k8s.io/client-go/util/workqueue" + fakeapiregistration "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake" "github.com/openshift/library-go/pkg/operator/events" "github.com/openshift/library-go/pkg/operator/events/eventstesting" ) type testController struct { - controller *nucleusHubController - kubeClient *fakekube.Clientset - apiExtensionClient *fakeapiextensions.Clientset - nucleusClient *fakenucleusclient.Clientset + controller *nucleusHubController + kubeClient *fakekube.Clientset + apiExtensionClient *fakeapiextensions.Clientset + apiRegistrationClient *fakeapiregistration.Clientset + nucleusClient *fakenucleusclient.Clientset } type fakeSyncContext struct { @@ -66,8 +70,9 @@ func newTestController(hubcore *nucleusapiv1.HubCore) *testController { nucleusInformers := nucleusinformers.NewSharedInformerFactory(fakeNucleusClient, 5*time.Minute) hubController := &nucleusHubController{ - nucleusClient: fakeNucleusClient.NucleusV1().HubCores(), - nucleusLister: nucleusInformers.Nucleus().V1().HubCores().Lister(), + nucleusClient: fakeNucleusClient.NucleusV1().HubCores(), + nucleusLister: nucleusInformers.Nucleus().V1().HubCores().Lister(), + currentGeneration: make([]int64, len(deploymentFiles)), } store := nucleusInformers.Nucleus().V1().HubCores().Informer().GetStore() @@ -93,12 +98,25 @@ func (t *testController) withCRDObject(objects ...runtime.Object) *testControlle return t } +func (t *testController) withAPIServiceObject(objects ...runtime.Object) *testController { + fakeAPIRegistrationClient := fakeapiregistration.NewSimpleClientset(objects...) + t.controller.apiRegistrationClient = fakeAPIRegistrationClient.ApiregistrationV1() + t.apiRegistrationClient = fakeAPIRegistrationClient + return t +} + func assertAction(t *testing.T, actual clienttesting.Action, expected string) { if actual.GetVerb() != expected { t.Errorf("expected %s action but got: %#v", expected, actual) } } +func assertEqualNumber(t *testing.T, actual, expected int) { + if actual != expected { + t.Errorf("expected %d number of actions but got: %d", expected, actual) + } +} + func assertCondition(t *testing.T, actual runtime.Object, expectedCondition string, expectedStatus metav1.ConditionStatus) { hubCore := actual.(*nucleusapiv1.HubCore) conditions := hubCore.Status.Conditions @@ -132,11 +150,8 @@ func ensureObject(t *testing.T, object runtime.Object, hubCore *nucleusapiv1.Hub switch o := object.(type) { case *corev1.Namespace: - ensureNameNamespace(t, access.GetName(), "", nucluesHubCoreNamespace, "") - case *corev1.ServiceAccount: - ensureNameNamespace(t, access.GetName(), access.GetNamespace(), fmt.Sprintf("%s-sa", hubCore.Name), nucluesHubCoreNamespace) + ensureNameNamespace(t, access.GetName(), "", nucleusHubCoreNamespace, "") case *appsv1.Deployment: - ensureNameNamespace(t, access.GetName(), access.GetNamespace(), fmt.Sprintf("%s-controller", hubCore.Name), nucluesHubCoreNamespace) if hubCore.Spec.RegistrationImagePullSpec != o.Spec.Template.Spec.Containers[0].Image { t.Errorf("Image does not match to the expected.") } @@ -146,7 +161,7 @@ func ensureObject(t *testing.T, object runtime.Object, hubCore *nucleusapiv1.Hub // TestSyncDeploy tests sync manifests of hub component func TestSyncDeploy(t *testing.T) { hubCore := newHubCore("testhub") - controller := newTestController(hubCore).withCRDObject().withKubeObject() + controller := newTestController(hubCore).withCRDObject().withKubeObject().withAPIServiceObject() syncContext := newFakeSyncContext(t, "testhub") err := controller.controller.sync(nil, syncContext) @@ -154,28 +169,45 @@ func TestSyncDeploy(t *testing.T) { t.Errorf("Expected non error when sync, %v", err) } - createObjects := []runtime.Object{} + createKubeObjects := []runtime.Object{} kubeActions := controller.kubeClient.Actions() for _, action := range kubeActions { if action.GetVerb() == "create" { object := action.(clienttesting.CreateActionImpl).Object - createObjects = append(createObjects, object) + createKubeObjects = append(createKubeObjects, object) } } // Check if resources are created as expected - if len(createObjects) != 5 { - t.Errorf("Expect 5 objects created in the sync loop, actual %q", len(createObjects)) - } - for _, object := range createObjects { + assertEqualNumber(t, len(createKubeObjects), 12) + for _, object := range createKubeObjects { ensureObject(t, object, hubCore) } - nucleusAction := controller.nucleusClient.Actions() - if len(nucleusAction) != 2 { - t.Errorf("Expect 2 actions in the sync loop") + createCRDObjects := []runtime.Object{} + crdActions := controller.apiExtensionClient.Actions() + for _, action := range crdActions { + if action.GetVerb() == "create" { + object := action.(clienttesting.CreateActionImpl).Object + createCRDObjects = append(createCRDObjects, object) + } } + // Check if resources are created as expected + assertEqualNumber(t, len(createCRDObjects), 2) + createAPIServiceObjects := []runtime.Object{} + apiServiceActions := controller.apiRegistrationClient.Actions() + for _, action := range apiServiceActions { + if action.GetVerb() == "create" { + object := action.(clienttesting.CreateActionImpl).Object + createAPIServiceObjects = append(createAPIServiceObjects, object) + } + } + // Check if resources are created as expected + assertEqualNumber(t, len(createAPIServiceObjects), 1) + + nucleusAction := controller.nucleusClient.Actions() + assertEqualNumber(t, len(nucleusAction), 2) assertAction(t, nucleusAction[1], "update") assertCondition(t, nucleusAction[1].(clienttesting.UpdateActionImpl).Object, hubCoreApplied, metav1.ConditionTrue) } @@ -185,7 +217,7 @@ func TestSyncDelete(t *testing.T) { hubCore := newHubCore("testhub") now := metav1.Now() hubCore.ObjectMeta.SetDeletionTimestamp(&now) - controller := newTestController(hubCore).withCRDObject().withKubeObject() + controller := newTestController(hubCore).withCRDObject().withKubeObject().withAPIServiceObject() syncContext := newFakeSyncContext(t, "testhub") err := controller.controller.sync(nil, syncContext) @@ -193,27 +225,42 @@ func TestSyncDelete(t *testing.T) { t.Errorf("Expected non error when sync, %v", err) } - deleteActions := []clienttesting.DeleteActionImpl{} + deleteKubeActions := []clienttesting.DeleteActionImpl{} kubeActions := controller.kubeClient.Actions() for _, action := range kubeActions { if action.GetVerb() == "delete" { - deleteAction := action.(clienttesting.DeleteActionImpl) - deleteActions = append(deleteActions, deleteAction) + deleteKubeAction := action.(clienttesting.DeleteActionImpl) + deleteKubeActions = append(deleteKubeActions, deleteKubeAction) } } + assertEqualNumber(t, len(deleteKubeActions), 10) - for _, action := range deleteActions { + deleteCRDActions := []clienttesting.DeleteActionImpl{} + crdActions := controller.apiExtensionClient.Actions() + for _, action := range crdActions { + if action.GetVerb() == "delete" { + deleteCRDAction := action.(clienttesting.DeleteActionImpl) + deleteCRDActions = append(deleteCRDActions, deleteCRDAction) + } + } + // Check if resources are created as expected + assertEqualNumber(t, len(deleteCRDActions), 4) + + deleteAPIServiceActions := []clienttesting.DeleteActionImpl{} + apiServiceActions := controller.apiRegistrationClient.Actions() + for _, action := range apiServiceActions { + if action.GetVerb() == "delete" { + deleteAPIServiceAction := action.(clienttesting.DeleteActionImpl) + deleteAPIServiceActions = append(deleteAPIServiceActions, deleteAPIServiceAction) + } + } + // Check if resources are created as expected + assertEqualNumber(t, len(deleteAPIServiceActions), 1) + + for _, action := range deleteKubeActions { switch action.Resource.Resource { - case "clusterroles": - ensureNameNamespace(t, action.Name, "", fmt.Sprintf("system:open-cluster-management:%s", hubCore.Name), "") - case "clusterrolebindings": - ensureNameNamespace(t, action.Name, "", fmt.Sprintf("system:open-cluster-management:%s", hubCore.Name), "") case "namespaces": - ensureNameNamespace(t, action.Name, "", nucluesHubCoreNamespace, "") - case "serviceaccounts": - ensureNameNamespace(t, action.Name, action.Namespace, fmt.Sprintf("%s-sa", hubCore.Name), nucluesHubCoreNamespace) - case "deployments": - ensureNameNamespace(t, action.Name, action.Namespace, fmt.Sprintf("%s-controller", hubCore.Name), nucluesHubCoreNamespace) + ensureNameNamespace(t, action.Name, "", nucleusHubCoreNamespace, "") } } } @@ -228,7 +275,7 @@ func TestDeleteCRD(t *testing.T) { Name: crdNames[0], }, } - controller := newTestController(hubCore).withCRDObject(crd).withKubeObject() + controller := newTestController(hubCore).withCRDObject(crd).withKubeObject().withAPIServiceObject() // Return crd with the first get, and return not found with the 2nd get getCount := 0 @@ -252,3 +299,45 @@ func TestDeleteCRD(t *testing.T) { t.Errorf("Expected no error when sync: %v", err) } } + +func TestEnsureServingCertAndCA(t *testing.T) { + hubCore := newHubCore("testhub") + controller := newTestController(hubCore).withCRDObject().withKubeObject().withAPIServiceObject() + ca, certificate, key, err := controller.controller.ensureServingCertAndCA(context.TODO(), "ns1", "kubeconfig", "webhook") + if err != nil { + t.Errorf("Expect no error when generating serving cert: %v", err) + } + certs, err := cert.ParseCertsPEM(certificate) + if err != nil { + t.Errorf("Expect no error when parsing cert") + } + if len(certs) != 2 { + t.Errorf("Expect 2 cert is parsed, actual %d", len(certs)) + } + for _, cert := range certs { + if cert.Subject.CommonName != "webhook.ns1.svc" && cert.Subject.CommonName != "nucleus-webhook" { + t.Errorf("Common name in cert is not correct, actual %s", cert.Subject.CommonName) + } + } + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubeconfig", + Namespace: "ns1", + }, + Data: map[string][]byte{ + "ca.crt": ca, + "tls.crt": certificate, + "tls.key": key, + }, + } + controller = newTestController(hubCore).withCRDObject().withKubeObject(secret).withAPIServiceObject() + actualCA, actualCert, actualKey, err := controller.controller.ensureServingCertAndCA(context.TODO(), "ns1", "kubeconfig", "webhook") + if err != nil { + t.Errorf("Expect no error when generating serving cert: %v", err) + } + + if !reflect.DeepEqual(ca, actualCA) || !reflect.DeepEqual(certificate, actualCert) || !reflect.DeepEqual(key, actualKey) { + t.Errorf("Expect the cert/key/ca is obtained from secret") + } +} diff --git a/pkg/operators/manager.go b/pkg/operators/manager.go index 2983a266f..e66b135c2 100644 --- a/pkg/operators/manager.go +++ b/pkg/operators/manager.go @@ -6,6 +6,7 @@ import ( apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/client-go/kubernetes" + apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" "github.com/openshift/library-go/pkg/controller/controllercmd" @@ -26,6 +27,10 @@ func RunNucleusHubOperator(ctx context.Context, controllerContext *controllercmd if err != nil { return err } + apiRegistrationClient, err := apiregistrationclient.NewForConfig(controllerContext.KubeConfig) + if err != nil { + return err + } // Build nucleus client and informer nucleusClient, err := nucleusclient.NewForConfig(controllerContext.KubeConfig) @@ -37,6 +42,7 @@ func RunNucleusHubOperator(ctx context.Context, controllerContext *controllercmd hubcontroller := hub.NewNucleusHubController( kubeClient, apiExtensionClient, + apiRegistrationClient.ApiregistrationV1(), nucleusClient.NucleusV1().HubCores(), nucleusInformer.Nucleus().V1().HubCores(), controllerContext.EventRecorder) diff --git a/pkg/operators/spoke/controller.go b/pkg/operators/spoke/controller.go index dba294b6a..918052f5c 100644 --- a/pkg/operators/spoke/controller.go +++ b/pkg/operators/spoke/controller.go @@ -6,20 +6,16 @@ import ( "path/filepath" "strings" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/kubernetes" "k8s.io/klog" - "github.com/openshift/api" "github.com/openshift/library-go/pkg/controller/factory" "github.com/openshift/library-go/pkg/operator/events" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" "github.com/openshift/library-go/pkg/assets" "github.com/openshift/library-go/pkg/operator/resource/resourceapply" @@ -43,9 +39,6 @@ const ( ) var ( - genericScheme = runtime.NewScheme() - genericCodecs = serializer.NewCodecFactory(genericScheme) - genericCodec = genericCodecs.UniversalDeserializer() staticResourceFiles = []string{ "manifests/spoke/spoke-registration-serviceaccount.yaml", "manifests/spoke/spoke-registration-clusterrole.yaml", @@ -67,10 +60,6 @@ type nucleusSpokeController struct { workGeneration int64 } -func init() { - utilruntime.Must(api.InstallKube(genericScheme)) -} - // NewNucleusSpokeController construct nucleus spoke controller func NewNucleusSpokeController( kubeClient kubernetes.Interface, @@ -244,7 +233,14 @@ func (n *nucleusSpokeController) sync(ctx context.Context, controllerContext fac } // Deploy registration agent - generation, err := n.applyDeployment(config, "manifests/spoke/spoke-registration-deployment.yaml", n.registrationGeneration, controllerContext) + generation, err := helpers.ApplyDeployment( + n.kubeClient, + n.registrationGeneration, + func(name string) ([]byte, error) { + return assets.MustCreateAssetFromTemplate(name, bindata.MustAsset(filepath.Join("", name)), config).Data, nil + }, + controllerContext.Recorder(), + "manifests/spoke/spoke-registration-deployment.yaml") if err != nil { helpers.UpdateNucleusSpokeStatus(ctx, n.nucleusClient, spokeCoreName, helpers.UpdateNucleusSpokeConditionFn(nucleusapiv1.StatusCondition{ Type: spokeCoreApplied, Status: metav1.ConditionFalse, Reason: "SpokeCoreApplyFailed", @@ -256,8 +252,14 @@ func (n *nucleusSpokeController) sync(ctx context.Context, controllerContext fac n.registrationGeneration = generation // Deploy work agent - generation, err = n.applyDeployment( - config, "manifests/spoke/spoke-work-deployment.yaml", n.workGeneration, controllerContext) + generation, err = helpers.ApplyDeployment( + n.kubeClient, + n.workGeneration, + func(name string) ([]byte, error) { + return assets.MustCreateAssetFromTemplate(name, bindata.MustAsset(filepath.Join("", name)), config).Data, nil + }, + controllerContext.Recorder(), + "manifests/spoke/spoke-work-deployment.yaml") if err != nil { helpers.UpdateNucleusSpokeStatus(ctx, n.nucleusClient, spokeCoreName, helpers.UpdateNucleusSpokeConditionFn(nucleusapiv1.StatusCondition{ Type: spokeCoreApplied, Status: metav1.ConditionFalse, Reason: "SpokeCoreApplyFailed", @@ -309,32 +311,6 @@ func (n *nucleusSpokeController) sync(ctx context.Context, controllerContext fac return nil } -func (n *nucleusSpokeController) applyDeployment( - config spokeConfig, file string, generation int64, controllerContext factory.SyncContext) (int64, error) { - deploymentRaw := assets.MustCreateAssetFromTemplate( - file, - bindata.MustAsset(filepath.Join("", file)), config).Data - deployment, _, err := genericCodec.Decode(deploymentRaw, nil, nil) - if err != nil { - return 0, fmt.Errorf("%q: %v", file, err) - } - updatedDeployment, updated, err := resourceapply.ApplyDeployment( - n.kubeClient.AppsV1(), - controllerContext.Recorder(), - deployment.(*appsv1.Deployment), generation, false) - if err != nil { - klog.Errorf("Failed to apply hub deployment manifest: %v", err) - return 0, fmt.Errorf("%q (%T): %v", file, deployment, err) - } - - // Record the generation, so the deployment is only updated when generation is changed. - if updated { - generation = updatedDeployment.ObjectMeta.Generation - } - - return generation, nil -} - func (n *nucleusSpokeController) cleanUp(ctx context.Context, controllerContext factory.SyncContext, config spokeConfig) error { // Remove deployment registrationDeployment := fmt.Sprintf("%s-registration-agent", config.SpokeCoreName) @@ -358,14 +334,16 @@ func (n *nucleusSpokeController) cleanUp(ctx context.Context, controllerContext // Remove Static files for _, file := range staticResourceFiles { - objectRaw := assets.MustCreateAssetFromTemplate( + err := helpers.CleanUpStaticObject( + ctx, + n.kubeClient, + nil, + nil, + func(name string) ([]byte, error) { + return assets.MustCreateAssetFromTemplate(name, bindata.MustAsset(filepath.Join("", name)), config).Data, nil + }, file, - bindata.MustAsset(filepath.Join("", file)), config).Data - object, _, err := genericCodec.Decode(objectRaw, nil, nil) - if err != nil { - return err - } - err = helpers.CleanUpStaticObject(ctx, n.kubeClient, nil, object) + ) if err != nil { return err } diff --git a/test/integration/hub_test.go b/test/integration/hub_test.go new file mode 100644 index 000000000..72d1fcd46 --- /dev/null +++ b/test/integration/hub_test.go @@ -0,0 +1,218 @@ +package integration + +import ( + "context" + "fmt" + + "github.com/onsi/ginkgo" + "github.com/onsi/gomega" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/openshift/library-go/pkg/controller/controllercmd" + + "github.com/open-cluster-management/nucleus/pkg/operators" + "github.com/open-cluster-management/nucleus/test/integration/util" + + nucleusapiv1 "github.com/open-cluster-management/api/nucleus/v1" +) + +func startHubOperator(ctx context.Context) { + err := operators.RunNucleusHubOperator(ctx, &controllercmd.ControllerContext{ + KubeConfig: restConfig, + EventRecorder: util.NewIntegrationTestEventRecorder("integration"), + }) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) +} + +var _ = ginkgo.Describe("HubCore", func() { + var cancel context.CancelFunc + var err error + var hubCoreName string + var hubRegistrationClusterRole string + var hubWebhookClusterRole string + var hubRegistrationSA string + var hubWebhookSA string + var hubRegistrationDeployment string + var hubWebhookDeployment string + var webhookSecret string + var validtingWebhook string + + ginkgo.BeforeEach(func() { + var ctx context.Context + ctx, cancel = context.WithCancel(context.Background()) + go startHubOperator(ctx) + }) + + ginkgo.JustBeforeEach(func() { + hubcore := &nucleusapiv1.HubCore{ + ObjectMeta: metav1.ObjectMeta{ + Name: hubCoreName, + }, + Spec: nucleusapiv1.HubCoreSpec{ + RegistrationImagePullSpec: "quay.io/open-cluster-management/registration", + }, + } + hubcore, err = nucleusClient.NucleusV1().HubCores().Create(context.Background(), hubcore, metav1.CreateOptions{}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + }) + + ginkgo.AfterEach(func() { + if cancel != nil { + cancel() + } + }) + + ginkgo.Context("Deploy and clean hub component", func() { + ginkgo.BeforeEach(func() { + hubCoreName = "hub" + hubRegistrationClusterRole = fmt.Sprintf("system:open-cluster-management:%s-registration-controller", hubCoreName) + hubWebhookClusterRole = fmt.Sprintf("system:open-cluster-management:%s-registration-webhook", hubCoreName) + hubRegistrationSA = fmt.Sprintf("%s-registration-controller-sa", hubCoreName) + hubWebhookSA = fmt.Sprintf("%s-registration-webhook-sa", hubCoreName) + hubRegistrationDeployment = fmt.Sprintf("%s-registration-controller", hubCoreName) + hubWebhookDeployment = fmt.Sprintf("%s-registration-webhook", hubCoreName) + webhookSecret = "webhook-serving-cert" + validtingWebhook = "spokeclustervalidators.admission.cluster.open-cluster-management.io" + }) + + ginkgo.It("should have expected resource created successfully", func() { + // Check namespace + gomega.Eventually(func() bool { + if _, err := kubeClient.CoreV1().Namespaces().Get(context.Background(), hubNamespace, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check clusterrole/clusterrolebinding + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoles().Get(context.Background(), hubRegistrationClusterRole, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoles().Get(context.Background(), hubWebhookClusterRole, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoleBindings().Get(context.Background(), hubRegistrationClusterRole, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoleBindings().Get(context.Background(), hubWebhookClusterRole, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check service account + gomega.Eventually(func() bool { + if _, err := kubeClient.CoreV1().ServiceAccounts(hubNamespace).Get(context.Background(), hubRegistrationSA, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.CoreV1().ServiceAccounts(hubNamespace).Get(context.Background(), hubWebhookSA, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check deployment + gomega.Eventually(func() bool { + if _, err := kubeClient.AppsV1().Deployments(hubNamespace).Get(context.Background(), hubRegistrationDeployment, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.AppsV1().Deployments(hubNamespace).Get(context.Background(), hubWebhookDeployment, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check service + gomega.Eventually(func() bool { + if _, err := kubeClient.CoreV1().Services(hubNamespace).Get(context.Background(), hubWebhookDeployment, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check webhook secret + gomega.Eventually(func() bool { + s, err := kubeClient.CoreV1().Secrets(hubNamespace).Get(context.Background(), webhookSecret, metav1.GetOptions{}) + if err != nil { + return false + } + if s.Data == nil || s.Data["ca.crt"] == nil || s.Data["tls.crt"] == nil || s.Data["tls.key"] == nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check validating webhook + gomega.Eventually(func() bool { + if _, err := kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(context.Background(), validtingWebhook, metav1.GetOptions{}); err != nil { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + err := nucleusClient.NucleusV1().HubCores().Delete(context.Background(), hubCoreName, metav1.DeleteOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Check namespace deletion + gomega.Eventually(func() bool { + if _, err := kubeClient.CoreV1().Namespaces().Get(context.Background(), hubNamespace, metav1.GetOptions{}); errors.IsNotFound(err) { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check clusterrole/clusterrolebinding deletion + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoles().Get(context.Background(), hubRegistrationClusterRole, metav1.GetOptions{}); errors.IsNotFound(err) { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoles().Get(context.Background(), hubWebhookClusterRole, metav1.GetOptions{}); errors.IsNotFound(err) { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoleBindings().Get(context.Background(), hubRegistrationClusterRole, metav1.GetOptions{}); errors.IsNotFound(err) { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + gomega.Eventually(func() bool { + if _, err := kubeClient.RbacV1().ClusterRoleBindings().Get(context.Background(), hubWebhookClusterRole, metav1.GetOptions{}); errors.IsNotFound(err) { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + + // Check validating webhook deletion + gomega.Eventually(func() bool { + if _, err := kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(context.Background(), validtingWebhook, metav1.GetOptions{}); errors.IsNotFound(err) { + return false + } + return true + }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) + }) + }) + +}) diff --git a/test/integration/integration_suite_test.go b/test/integration/integration_suite_test.go index 240e987ec..71c66d9de 100644 --- a/test/integration/integration_suite_test.go +++ b/test/integration/integration_suite_test.go @@ -1,4 +1,4 @@ -package integration_test +package integration import ( "path/filepath" @@ -7,12 +7,15 @@ import ( "github.com/onsi/ginkgo" "github.com/onsi/gomega" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/envtest/printer" "sigs.k8s.io/controller-runtime/pkg/log/zap" logf "sigs.k8s.io/controller-runtime/pkg/log" + + nucleusclient "github.com/open-cluster-management/api/client/nucleus/clientset/versioned" ) func TestIntegration(t *testing.T) { @@ -20,10 +23,21 @@ func TestIntegration(t *testing.T) { ginkgo.RunSpecsWithDefaultAndCustomReporters(t, "Integration Suite", []ginkgo.Reporter{printer.NewlineReporter{}}) } +const ( + eventuallyTimeout = 30 // seconds + eventuallyInterval = 1 // seconds + hubNamespace = "open-cluster-management-hub" + spokeNamespace = "open-cluster-management-spoke" +) + var testEnv *envtest.Environment var kubeClient kubernetes.Interface +var restConfig *rest.Config + +var nucleusClient nucleusclient.Interface + var _ = ginkgo.BeforeSuite(func(done ginkgo.Done) { logf.SetLogger(zap.LoggerTo(ginkgo.GinkgoWriter, true)) @@ -46,6 +60,11 @@ var _ = ginkgo.BeforeSuite(func(done ginkgo.Done) { kubeClient, err = kubernetes.NewForConfig(cfg) gomega.Expect(err).ToNot(gomega.HaveOccurred()) gomega.Expect(kubeClient).ToNot(gomega.BeNil()) + nucleusClient, err = nucleusclient.NewForConfig(cfg) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + gomega.Expect(kubeClient).ToNot(gomega.BeNil()) + + restConfig = cfg close(done) }, 60) diff --git a/test/integration/util/util.go b/test/integration/util/util.go new file mode 100644 index 000000000..0406a7cf9 --- /dev/null +++ b/test/integration/util/util.go @@ -0,0 +1,45 @@ +package util + +import ( + "fmt" + + "github.com/onsi/ginkgo" + + "github.com/openshift/library-go/pkg/operator/events" +) + +func NewIntegrationTestEventRecorder(componet string) events.Recorder { + return &IntegrationTestEventRecorder{component: componet} +} + +type IntegrationTestEventRecorder struct { + component string +} + +func (r *IntegrationTestEventRecorder) ComponentName() string { + return r.component +} + +func (r *IntegrationTestEventRecorder) ForComponent(c string) events.Recorder { + return &IntegrationTestEventRecorder{component: c} +} + +func (r *IntegrationTestEventRecorder) WithComponentSuffix(suffix string) events.Recorder { + return r.ForComponent(fmt.Sprintf("%s-%s", r.ComponentName(), suffix)) +} + +func (r *IntegrationTestEventRecorder) Event(reason, message string) { + fmt.Fprintf(ginkgo.GinkgoWriter, "Event: [%s] %v: %v \n", r.component, reason, message) +} + +func (r *IntegrationTestEventRecorder) Eventf(reason, messageFmt string, args ...interface{}) { + r.Event(reason, fmt.Sprintf(messageFmt, args...)) +} + +func (r *IntegrationTestEventRecorder) Warning(reason, message string) { + fmt.Fprintf(ginkgo.GinkgoWriter, "Warning: [%s] %v: %v \n", r.component, reason, message) +} + +func (r *IntegrationTestEventRecorder) Warningf(reason, messageFmt string, args ...interface{}) { + r.Warning(reason, fmt.Sprintf(messageFmt, args...)) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/clientset.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/clientset.go new file mode 100644 index 000000000..0680b9de0 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/clientset.go @@ -0,0 +1,111 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package clientset + +import ( + "fmt" + + discovery "k8s.io/client-go/discovery" + rest "k8s.io/client-go/rest" + flowcontrol "k8s.io/client-go/util/flowcontrol" + apiregistrationv1 "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1" + apiregistrationv1beta1 "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1" +) + +type Interface interface { + Discovery() discovery.DiscoveryInterface + ApiregistrationV1beta1() apiregistrationv1beta1.ApiregistrationV1beta1Interface + ApiregistrationV1() apiregistrationv1.ApiregistrationV1Interface +} + +// Clientset contains the clients for groups. Each group has exactly one +// version included in a Clientset. +type Clientset struct { + *discovery.DiscoveryClient + apiregistrationV1beta1 *apiregistrationv1beta1.ApiregistrationV1beta1Client + apiregistrationV1 *apiregistrationv1.ApiregistrationV1Client +} + +// ApiregistrationV1beta1 retrieves the ApiregistrationV1beta1Client +func (c *Clientset) ApiregistrationV1beta1() apiregistrationv1beta1.ApiregistrationV1beta1Interface { + return c.apiregistrationV1beta1 +} + +// ApiregistrationV1 retrieves the ApiregistrationV1Client +func (c *Clientset) ApiregistrationV1() apiregistrationv1.ApiregistrationV1Interface { + return c.apiregistrationV1 +} + +// Discovery retrieves the DiscoveryClient +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + if c == nil { + return nil + } + return c.DiscoveryClient +} + +// NewForConfig creates a new Clientset for the given config. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfig will generate a rate-limiter in configShallowCopy. +func NewForConfig(c *rest.Config) (*Clientset, error) { + configShallowCopy := *c + if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + if configShallowCopy.Burst <= 0 { + return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } + configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) + } + var cs Clientset + var err error + cs.apiregistrationV1beta1, err = apiregistrationv1beta1.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } + cs.apiregistrationV1, err = apiregistrationv1.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } + + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) + if err != nil { + return nil, err + } + return &cs, nil +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *Clientset { + var cs Clientset + cs.apiregistrationV1beta1 = apiregistrationv1beta1.NewForConfigOrDie(c) + cs.apiregistrationV1 = apiregistrationv1.NewForConfigOrDie(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) + return &cs +} + +// New creates a new Clientset for the given RESTClient. +func New(c rest.Interface) *Clientset { + var cs Clientset + cs.apiregistrationV1beta1 = apiregistrationv1beta1.New(c) + cs.apiregistrationV1 = apiregistrationv1.New(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClient(c) + return &cs +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/doc.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/doc.go new file mode 100644 index 000000000..ee865e56d --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated clientset. +package clientset diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/clientset_generated.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/clientset_generated.go new file mode 100644 index 000000000..18dbdefe7 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/clientset_generated.go @@ -0,0 +1,89 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/discovery" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/testing" + clientset "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" + apiregistrationv1 "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1" + fakeapiregistrationv1 "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake" + apiregistrationv1beta1 "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1" + fakeapiregistrationv1beta1 "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake" +) + +// NewSimpleClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + testing.Fake + discovery *fakediscovery.FakeDiscovery + tracker testing.ObjectTracker +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return c.discovery +} + +func (c *Clientset) Tracker() testing.ObjectTracker { + return c.tracker +} + +var _ clientset.Interface = &Clientset{} + +// ApiregistrationV1beta1 retrieves the ApiregistrationV1beta1Client +func (c *Clientset) ApiregistrationV1beta1() apiregistrationv1beta1.ApiregistrationV1beta1Interface { + return &fakeapiregistrationv1beta1.FakeApiregistrationV1beta1{Fake: &c.Fake} +} + +// ApiregistrationV1 retrieves the ApiregistrationV1Client +func (c *Clientset) ApiregistrationV1() apiregistrationv1.ApiregistrationV1Interface { + return &fakeapiregistrationv1.FakeApiregistrationV1{Fake: &c.Fake} +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/doc.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/doc.go new file mode 100644 index 000000000..9b99e7167 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated fake clientset. +package fake diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/register.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/register.go new file mode 100644 index 000000000..2c66db85d --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake/register.go @@ -0,0 +1,58 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + apiregistrationv1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) +var parameterCodec = runtime.NewParameterCodec(scheme) +var localSchemeBuilder = runtime.SchemeBuilder{ + apiregistrationv1beta1.AddToScheme, + apiregistrationv1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(scheme)) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/doc.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/doc.go new file mode 100644 index 000000000..16f443990 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/fake_apiregistration_client.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/fake_apiregistration_client.go new file mode 100644 index 000000000..3e563e6f0 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/fake_apiregistration_client.go @@ -0,0 +1,40 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1 "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1" +) + +type FakeApiregistrationV1 struct { + *testing.Fake +} + +func (c *FakeApiregistrationV1) APIServices() v1.APIServiceInterface { + return &FakeAPIServices{c} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeApiregistrationV1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/fake_apiservice.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/fake_apiservice.go new file mode 100644 index 000000000..54cd4576d --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake/fake_apiservice.go @@ -0,0 +1,133 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" +) + +// FakeAPIServices implements APIServiceInterface +type FakeAPIServices struct { + Fake *FakeApiregistrationV1 +} + +var apiservicesResource = schema.GroupVersionResource{Group: "apiregistration.k8s.io", Version: "v1", Resource: "apiservices"} + +var apiservicesKind = schema.GroupVersionKind{Group: "apiregistration.k8s.io", Version: "v1", Kind: "APIService"} + +// Get takes name of the aPIService, and returns the corresponding aPIService object, and an error if there is any. +func (c *FakeAPIServices) Get(ctx context.Context, name string, options v1.GetOptions) (result *apiregistrationv1.APIService, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(apiservicesResource, name), &apiregistrationv1.APIService{}) + if obj == nil { + return nil, err + } + return obj.(*apiregistrationv1.APIService), err +} + +// List takes label and field selectors, and returns the list of APIServices that match those selectors. +func (c *FakeAPIServices) List(ctx context.Context, opts v1.ListOptions) (result *apiregistrationv1.APIServiceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(apiservicesResource, apiservicesKind, opts), &apiregistrationv1.APIServiceList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &apiregistrationv1.APIServiceList{ListMeta: obj.(*apiregistrationv1.APIServiceList).ListMeta} + for _, item := range obj.(*apiregistrationv1.APIServiceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested aPIServices. +func (c *FakeAPIServices) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(apiservicesResource, opts)) +} + +// Create takes the representation of a aPIService and creates it. Returns the server's representation of the aPIService, and an error, if there is any. +func (c *FakeAPIServices) Create(ctx context.Context, aPIService *apiregistrationv1.APIService, opts v1.CreateOptions) (result *apiregistrationv1.APIService, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(apiservicesResource, aPIService), &apiregistrationv1.APIService{}) + if obj == nil { + return nil, err + } + return obj.(*apiregistrationv1.APIService), err +} + +// Update takes the representation of a aPIService and updates it. Returns the server's representation of the aPIService, and an error, if there is any. +func (c *FakeAPIServices) Update(ctx context.Context, aPIService *apiregistrationv1.APIService, opts v1.UpdateOptions) (result *apiregistrationv1.APIService, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(apiservicesResource, aPIService), &apiregistrationv1.APIService{}) + if obj == nil { + return nil, err + } + return obj.(*apiregistrationv1.APIService), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeAPIServices) UpdateStatus(ctx context.Context, aPIService *apiregistrationv1.APIService, opts v1.UpdateOptions) (*apiregistrationv1.APIService, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(apiservicesResource, "status", aPIService), &apiregistrationv1.APIService{}) + if obj == nil { + return nil, err + } + return obj.(*apiregistrationv1.APIService), err +} + +// Delete takes name of the aPIService and deletes it. Returns an error if one occurs. +func (c *FakeAPIServices) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteAction(apiservicesResource, name), &apiregistrationv1.APIService{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeAPIServices) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(apiservicesResource, listOpts) + + _, err := c.Fake.Invokes(action, &apiregistrationv1.APIServiceList{}) + return err +} + +// Patch applies the patch and returns the patched aPIService. +func (c *FakeAPIServices) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *apiregistrationv1.APIService, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(apiservicesResource, name, pt, data, subresources...), &apiregistrationv1.APIService{}) + if obj == nil { + return nil, err + } + return obj.(*apiregistrationv1.APIService), err +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/apiregistration_client.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/apiregistration_client.go new file mode 100644 index 000000000..e7a9aa507 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/apiregistration_client.go @@ -0,0 +1,89 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + rest "k8s.io/client-go/rest" + v1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" + "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +) + +type ApiregistrationV1beta1Interface interface { + RESTClient() rest.Interface + APIServicesGetter +} + +// ApiregistrationV1beta1Client is used to interact with features provided by the apiregistration.k8s.io group. +type ApiregistrationV1beta1Client struct { + restClient rest.Interface +} + +func (c *ApiregistrationV1beta1Client) APIServices() APIServiceInterface { + return newAPIServices(c) +} + +// NewForConfig creates a new ApiregistrationV1beta1Client for the given config. +func NewForConfig(c *rest.Config) (*ApiregistrationV1beta1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &ApiregistrationV1beta1Client{client}, nil +} + +// NewForConfigOrDie creates a new ApiregistrationV1beta1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *ApiregistrationV1beta1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new ApiregistrationV1beta1Client for the given RESTClient. +func New(c rest.Interface) *ApiregistrationV1beta1Client { + return &ApiregistrationV1beta1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1beta1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *ApiregistrationV1beta1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/apiservice.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/apiservice.go new file mode 100644 index 000000000..a5e76412e --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/apiservice.go @@ -0,0 +1,184 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "context" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + v1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" + scheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +) + +// APIServicesGetter has a method to return a APIServiceInterface. +// A group's client should implement this interface. +type APIServicesGetter interface { + APIServices() APIServiceInterface +} + +// APIServiceInterface has methods to work with APIService resources. +type APIServiceInterface interface { + Create(ctx context.Context, aPIService *v1beta1.APIService, opts v1.CreateOptions) (*v1beta1.APIService, error) + Update(ctx context.Context, aPIService *v1beta1.APIService, opts v1.UpdateOptions) (*v1beta1.APIService, error) + UpdateStatus(ctx context.Context, aPIService *v1beta1.APIService, opts v1.UpdateOptions) (*v1beta1.APIService, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1beta1.APIService, error) + List(ctx context.Context, opts v1.ListOptions) (*v1beta1.APIServiceList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.APIService, err error) + APIServiceExpansion +} + +// aPIServices implements APIServiceInterface +type aPIServices struct { + client rest.Interface +} + +// newAPIServices returns a APIServices +func newAPIServices(c *ApiregistrationV1beta1Client) *aPIServices { + return &aPIServices{ + client: c.RESTClient(), + } +} + +// Get takes name of the aPIService, and returns the corresponding aPIService object, and an error if there is any. +func (c *aPIServices) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.APIService, err error) { + result = &v1beta1.APIService{} + err = c.client.Get(). + Resource("apiservices"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of APIServices that match those selectors. +func (c *aPIServices) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.APIServiceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1beta1.APIServiceList{} + err = c.client.Get(). + Resource("apiservices"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested aPIServices. +func (c *aPIServices) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Resource("apiservices"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a aPIService and creates it. Returns the server's representation of the aPIService, and an error, if there is any. +func (c *aPIServices) Create(ctx context.Context, aPIService *v1beta1.APIService, opts v1.CreateOptions) (result *v1beta1.APIService, err error) { + result = &v1beta1.APIService{} + err = c.client.Post(). + Resource("apiservices"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(aPIService). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a aPIService and updates it. Returns the server's representation of the aPIService, and an error, if there is any. +func (c *aPIServices) Update(ctx context.Context, aPIService *v1beta1.APIService, opts v1.UpdateOptions) (result *v1beta1.APIService, err error) { + result = &v1beta1.APIService{} + err = c.client.Put(). + Resource("apiservices"). + Name(aPIService.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(aPIService). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *aPIServices) UpdateStatus(ctx context.Context, aPIService *v1beta1.APIService, opts v1.UpdateOptions) (result *v1beta1.APIService, err error) { + result = &v1beta1.APIService{} + err = c.client.Put(). + Resource("apiservices"). + Name(aPIService.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(aPIService). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the aPIService and deletes it. Returns an error if one occurs. +func (c *aPIServices) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Resource("apiservices"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *aPIServices) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Resource("apiservices"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched aPIService. +func (c *aPIServices) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.APIService, err error) { + result = &v1beta1.APIService{} + err = c.client.Patch(pt). + Resource("apiservices"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/doc.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/doc.go new file mode 100644 index 000000000..771101956 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1beta1 diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/doc.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/doc.go new file mode 100644 index 000000000..16f443990 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/fake_apiregistration_client.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/fake_apiregistration_client.go new file mode 100644 index 000000000..9b32316af --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/fake_apiregistration_client.go @@ -0,0 +1,40 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1beta1 "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1" +) + +type FakeApiregistrationV1beta1 struct { + *testing.Fake +} + +func (c *FakeApiregistrationV1beta1) APIServices() v1beta1.APIServiceInterface { + return &FakeAPIServices{c} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeApiregistrationV1beta1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/fake_apiservice.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/fake_apiservice.go new file mode 100644 index 000000000..dbc3037cd --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake/fake_apiservice.go @@ -0,0 +1,133 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + v1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" +) + +// FakeAPIServices implements APIServiceInterface +type FakeAPIServices struct { + Fake *FakeApiregistrationV1beta1 +} + +var apiservicesResource = schema.GroupVersionResource{Group: "apiregistration.k8s.io", Version: "v1beta1", Resource: "apiservices"} + +var apiservicesKind = schema.GroupVersionKind{Group: "apiregistration.k8s.io", Version: "v1beta1", Kind: "APIService"} + +// Get takes name of the aPIService, and returns the corresponding aPIService object, and an error if there is any. +func (c *FakeAPIServices) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.APIService, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(apiservicesResource, name), &v1beta1.APIService{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.APIService), err +} + +// List takes label and field selectors, and returns the list of APIServices that match those selectors. +func (c *FakeAPIServices) List(ctx context.Context, opts v1.ListOptions) (result *v1beta1.APIServiceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(apiservicesResource, apiservicesKind, opts), &v1beta1.APIServiceList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1beta1.APIServiceList{ListMeta: obj.(*v1beta1.APIServiceList).ListMeta} + for _, item := range obj.(*v1beta1.APIServiceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested aPIServices. +func (c *FakeAPIServices) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(apiservicesResource, opts)) +} + +// Create takes the representation of a aPIService and creates it. Returns the server's representation of the aPIService, and an error, if there is any. +func (c *FakeAPIServices) Create(ctx context.Context, aPIService *v1beta1.APIService, opts v1.CreateOptions) (result *v1beta1.APIService, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(apiservicesResource, aPIService), &v1beta1.APIService{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.APIService), err +} + +// Update takes the representation of a aPIService and updates it. Returns the server's representation of the aPIService, and an error, if there is any. +func (c *FakeAPIServices) Update(ctx context.Context, aPIService *v1beta1.APIService, opts v1.UpdateOptions) (result *v1beta1.APIService, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(apiservicesResource, aPIService), &v1beta1.APIService{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.APIService), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeAPIServices) UpdateStatus(ctx context.Context, aPIService *v1beta1.APIService, opts v1.UpdateOptions) (*v1beta1.APIService, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(apiservicesResource, "status", aPIService), &v1beta1.APIService{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.APIService), err +} + +// Delete takes name of the aPIService and deletes it. Returns an error if one occurs. +func (c *FakeAPIServices) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteAction(apiservicesResource, name), &v1beta1.APIService{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeAPIServices) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(apiservicesResource, listOpts) + + _, err := c.Fake.Invokes(action, &v1beta1.APIServiceList{}) + return err +} + +// Patch applies the patch and returns the patched aPIService. +func (c *FakeAPIServices) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1beta1.APIService, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(apiservicesResource, name, pt, data, subresources...), &v1beta1.APIService{}) + if obj == nil { + return nil, err + } + return obj.(*v1beta1.APIService), err +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/generated_expansion.go b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/generated_expansion.go new file mode 100644 index 000000000..e9108c5c8 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +Copyright 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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1beta1 + +type APIServiceExpansion interface{} diff --git a/vendor/modules.txt b/vendor/modules.txt index 00d0610f6..aa2b43d29 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -846,8 +846,13 @@ k8s.io/klog k8s.io/kube-aggregator/pkg/apis/apiregistration k8s.io/kube-aggregator/pkg/apis/apiregistration/v1 k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1 +k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset +k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1 +k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1/fake +k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1 +k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1/fake # k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c k8s.io/kube-openapi/pkg/builder k8s.io/kube-openapi/pkg/common