Merge pull request #16 from qiujian16/webhook

add webhook
This commit is contained in:
OpenShift Merge Robot
2020-05-29 17:28:50 -04:00
committed by GitHub
45 changed files with 2509 additions and 275 deletions
+2 -2
View File
@@ -84,7 +84,7 @@ deploy-hub: install-olm munge-hub-csv
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:
@@ -110,7 +110,7 @@ deploy-spoke: install-olm munge-spoke-csv 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:
+9 -1
View File
@@ -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"]
@@ -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:
@@ -66,6 +66,7 @@ spec:
resources:
- namespaces
verbs:
- create
- get
- list
- watch
+1
View File
@@ -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
)
+2
View File
@@ -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=
-12
View File
@@ -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
@@ -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"]
@@ -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
@@ -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:
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .HubCoreName }}-registration-controller-sa
namespace: {{ .HubCoreNamespace }}
@@ -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
@@ -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"]
@@ -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 }}
@@ -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 }}
@@ -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
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .HubCoreWebhookRegistrationService }}
namespace: {{ .HubCoreNamespace }}
spec:
selector:
app: {{ .HubCoreName }}-registration-webhook
ports:
- port: 443
targetPort: 6443
@@ -1,5 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .HubCoreName }}-sa
name: {{ .HubCoreName }}-registration-webhook-sa
namespace: {{ .HubCoreNamespace }}
@@ -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
+142 -5
View File
@@ -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
}
+227 -9
View File
@@ -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)
}
})
}
}
+369 -69
View File
@@ -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{}},
}},
}},
}}
+128 -85
View File
@@ -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
}
+125 -36
View File
@@ -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")
}
}
+6
View File
@@ -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)
+25 -47
View File
@@ -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
}
+218
View File
@@ -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())
})
})
})
+20 -1
View File
@@ -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)
+45
View File
@@ -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...))
}
@@ -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
}
@@ -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
@@ -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}
}
@@ -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
@@ -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))
}
@@ -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
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
@@ -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
@@ -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
}
@@ -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
}
@@ -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{}
+5
View File
@@ -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