From ab053a224d27aa48ea4b34ba7591cfd72c3f567d Mon Sep 17 00:00:00 2001 From: Chao Xu Date: Fri, 3 Nov 2017 16:49:56 -0700 Subject: [PATCH] let validation webhook convert objects to the external version before sending them --- hack/.golint_failures | 2 + hack/local-up-cluster.sh | 2 +- hack/update-generated-protobuf-dockerized.sh | 1 + pkg/generated/openapi/BUILD | 1 + staging/BUILD | 1 + .../test/integration/testserver/resources.go | 12 +- .../pkg/admission/plugin/webhook/BUILD | 6 + .../pkg/admission/plugin/webhook/admission.go | 112 ++- .../plugin/webhook/conversion_test.go | 135 ++++ .../pkg/apis/example/install/install.go | 2 +- .../apiserver/pkg/apis/example/register.go | 1 + .../apiserver/pkg/apis/example/types.go | 32 + .../pkg/apis/example/zz_generated.deepcopy.go | 61 ++ .../k8s.io/apiserver/pkg/apis/example2/BUILD | 38 + .../k8s.io/apiserver/pkg/apis/example2/doc.go | 24 + .../apiserver/pkg/apis/example2/install/BUILD | 45 ++ .../pkg/apis/example2/install/install.go | 44 ++ .../apis/example2/install/roundtrip_test.go | 28 + .../apiserver/pkg/apis/example2/register.go | 52 ++ .../apiserver/pkg/apis/example2/v1/BUILD | 50 ++ .../pkg/apis/example2/v1/conversion.go | 51 ++ .../pkg/apis/example2/v1/defaults.go | 26 + .../apiserver/pkg/apis/example2/v1/doc.go | 24 + .../pkg/apis/example2/v1/generated.pb.go | 682 ++++++++++++++++++ .../pkg/apis/example2/v1/generated.proto | 71 ++ .../pkg/apis/example2/v1/register.go | 63 ++ .../apiserver/pkg/apis/example2/v1/types.go | 64 ++ .../v1/types_swagger_doc_generated.go | 17 + .../example2/v1/zz_generated.conversion.go | 111 +++ .../apis/example2/v1/zz_generated.deepcopy.go | 95 +++ .../apis/example2/v1/zz_generated.defaults.go | 32 + .../apis/example2/zz_generated.deepcopy.go | 21 + test/e2e/apimachinery/BUILD | 1 + test/e2e/apimachinery/webhook.go | 171 ++++- test/images/webhook/BUILD | 4 + test/images/webhook/Makefile | 5 +- test/images/webhook/main.go | 108 ++- test/images/webhook/scheme.go | 36 + 38 files changed, 2161 insertions(+), 70 deletions(-) create mode 100644 staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/conversion_test.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/BUILD create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/doc.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/install/BUILD create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/install/install.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/install/roundtrip_test.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/register.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/BUILD create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/conversion.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/defaults.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/doc.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.pb.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/register.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types_swagger_doc_generated.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.conversion.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.deepcopy.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.defaults.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/example2/zz_generated.deepcopy.go create mode 100644 test/images/webhook/scheme.go diff --git a/hack/.golint_failures b/hack/.golint_failures index 4a31103c218..f17022a5c9c 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -551,6 +551,8 @@ staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1 staging/src/k8s.io/apiserver/pkg/apis/audit/validation staging/src/k8s.io/apiserver/pkg/apis/example staging/src/k8s.io/apiserver/pkg/apis/example/v1 +staging/src/k8s.io/apiserver/pkg/apis/example2 +staging/src/k8s.io/apiserver/pkg/apis/example2/v1 staging/src/k8s.io/apiserver/pkg/audit staging/src/k8s.io/apiserver/pkg/audit/policy staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory diff --git a/hack/local-up-cluster.sh b/hack/local-up-cluster.sh index 94dd5d742a5..6c602226ebc 100755 --- a/hack/local-up-cluster.sh +++ b/hack/local-up-cluster.sh @@ -419,7 +419,7 @@ function start_apiserver { fi # Admission Controllers to invoke prior to persisting objects in cluster - ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount${security_admission},DefaultStorageClass,DefaultTolerationSeconds,ResourceQuota,GenericAdmissionWebhook + ADMISSION_CONTROL=Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount${security_admission},DefaultStorageClass,DefaultTolerationSeconds,GenericAdmissionWebhook,ResourceQuota # This is the default dir and filename where the apiserver will generate a self-signed cert # which should be able to be used as the CA to verify itself diff --git a/hack/update-generated-protobuf-dockerized.sh b/hack/update-generated-protobuf-dockerized.sh index 5b45586a1ed..08c9e48d613 100755 --- a/hack/update-generated-protobuf-dockerized.sh +++ b/hack/update-generated-protobuf-dockerized.sh @@ -77,6 +77,7 @@ PACKAGES=( k8s.io/metrics/pkg/apis/custom_metrics/v1beta1 k8s.io/apiserver/pkg/apis/audit/v1alpha1 k8s.io/apiserver/pkg/apis/audit/v1beta1 + k8s.io/apiserver/pkg/apis/example2/v1 ) # requires the 'proto' tag to build (will remove when ready) diff --git a/pkg/generated/openapi/BUILD b/pkg/generated/openapi/BUILD index 05542444590..f76158b4fca 100644 --- a/pkg/generated/openapi/BUILD +++ b/pkg/generated/openapi/BUILD @@ -59,6 +59,7 @@ openapi_library( "k8s.io/apiserver/pkg/apis/audit/v1alpha1", "k8s.io/apiserver/pkg/apis/audit/v1beta1", "k8s.io/apiserver/pkg/apis/example/v1", + "k8s.io/apiserver/pkg/apis/example2/v1", "k8s.io/client-go/pkg/version", "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1", "k8s.io/metrics/pkg/apis/custom_metrics/v1beta1", diff --git a/staging/BUILD b/staging/BUILD index 14506a8831d..71c38a89954 100644 --- a/staging/BUILD +++ b/staging/BUILD @@ -96,6 +96,7 @@ filegroup( "//staging/src/k8s.io/apiserver/pkg/apis/apiserver:all-srcs", "//staging/src/k8s.io/apiserver/pkg/apis/audit:all-srcs", "//staging/src/k8s.io/apiserver/pkg/apis/example:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/apis/example2:all-srcs", "//staging/src/k8s.io/apiserver/pkg/audit:all-srcs", "//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:all-srcs", "//staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory:all-srcs", diff --git a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go index d2d739de9be..562281f4c97 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go +++ b/staging/src/k8s.io/apiextensions-apiserver/test/integration/testserver/resources.go @@ -146,7 +146,11 @@ func NewCurletInstance(namespace, name string) *unstructured.Unstructured { } } -func CreateNewCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, clientPool dynamic.ClientPool) (dynamic.Interface, error) { +// CreateNewCustomResourceDefinitionWatchUnsafe creates the CRD and makes sure +// the apiextension apiserver has installed the CRD. But it's not safe to watch +// the created CR. Please call CreateNewCustomResourceDefinition if you need to +// watch the CR. +func CreateNewCustomResourceDefinitionWatchUnsafe(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, clientPool dynamic.ClientPool) (dynamic.Interface, error) { _, err := apiExtensionsClient.Apiextensions().CustomResourceDefinitions().Create(crd) if err != nil { return nil, err @@ -169,7 +173,11 @@ func CreateNewCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceD return nil, err } - dynamicClient, err := clientPool.ClientForGroupVersionResource(schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Spec.Names.Plural}) + return clientPool.ClientForGroupVersionResource(schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Spec.Names.Plural}) +} + +func CreateNewCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefinition, apiExtensionsClient clientset.Interface, clientPool dynamic.ClientPool) (dynamic.Interface, error) { + dynamicClient, err := CreateNewCustomResourceDefinitionWatchUnsafe(crd, apiExtensionsClient, clientPool) if err != nil { return nil, err } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/BUILD index 4d2a9ac4086..792c23bb796 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/BUILD @@ -24,6 +24,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", @@ -46,6 +47,7 @@ go_test( "admission_test.go", "authentication_test.go", "certs_test.go", + "conversion_test.go", "rules_test.go", "serviceresolver_test.go", ], @@ -58,11 +60,15 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example/v1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example2/v1:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/admission.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/admission.go index 7e950132e65..737009068c1 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/admission.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/admission.go @@ -37,6 +37,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" @@ -134,6 +135,8 @@ type GenericAdmissionWebhook struct { negotiatedSerializer runtime.NegotiatedSerializer namespaceLister corelisters.NamespaceLister client clientset.Interface + convertor runtime.ObjectConvertor + creator runtime.ObjectCreater authInfoResolver AuthenticationInfoResolver cache *lru.Cache @@ -169,6 +172,8 @@ func (a *GenericAdmissionWebhook) SetScheme(scheme *runtime.Scheme) { a.negotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{ Serializer: serializer.NewCodecFactory(scheme).LegacyCodec(admissionv1alpha1.SchemeGroupVersion), }) + a.convertor = scheme + a.creator = scheme } } @@ -219,6 +224,37 @@ func (a *GenericAdmissionWebhook) loadConfiguration(attr admission.Attributes) ( return hookConfig, nil } +type versionedAttributes struct { + admission.Attributes + oldObject runtime.Object + object runtime.Object +} + +func (v versionedAttributes) GetObject() runtime.Object { + return v.object +} + +func (v versionedAttributes) GetOldObject() runtime.Object { + return v.oldObject +} + +func (a *GenericAdmissionWebhook) convertToGVK(obj runtime.Object, gvk schema.GroupVersionKind) (runtime.Object, error) { + // Unlike other resources, custom resources do not have internal version, so + // if obj is a custom resource, it should not need conversion. + if obj.GetObjectKind().GroupVersionKind() == gvk { + return obj, nil + } + out, err := a.creator.New(gvk) + if err != nil { + return nil, err + } + err = a.convertor.Convert(obj, out, nil) + if err != nil { + return nil, err + } + return out, nil +} + // Admit makes an admission decision based on the request attributes. func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error { hookConfig, err := a.loadConfiguration(attr) @@ -228,14 +264,49 @@ func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error { hooks := hookConfig.Webhooks ctx := context.TODO() - errCh := make(chan error, len(hooks)) - wg := sync.WaitGroup{} - wg.Add(len(hooks)) + var relevantHooks []*v1alpha1.Webhook for i := range hooks { + call, err := a.shouldCallHook(&hooks[i], attr) + if err != nil { + return err + } + if call { + relevantHooks = append(relevantHooks, &hooks[i]) + } + } + + if len(relevantHooks) == 0 { + // no matching hooks + return nil + } + + // convert the object to the external version before sending it to the webhook + versionedAttr := versionedAttributes{ + Attributes: attr, + } + if oldObj := attr.GetOldObject(); oldObj != nil { + out, err := a.convertToGVK(oldObj, attr.GetKind()) + if err != nil { + return apierrors.NewInternalError(err) + } + versionedAttr.oldObject = out + } + if obj := attr.GetObject(); obj != nil { + out, err := a.convertToGVK(obj, attr.GetKind()) + if err != nil { + return apierrors.NewInternalError(err) + } + versionedAttr.object = out + } + + wg := sync.WaitGroup{} + errCh := make(chan error, len(relevantHooks)) + wg.Add(len(relevantHooks)) + for i := range relevantHooks { go func(hook *v1alpha1.Webhook) { defer wg.Done() - err := a.callHook(ctx, hook, attr) + err := a.callHook(ctx, hook, versionedAttr) if err == nil { return } @@ -256,7 +327,7 @@ func (a *GenericAdmissionWebhook) Admit(attr admission.Attributes) error { glog.Warningf("rejected by webhook %q: %#v", hook.Name, err) errCh <- err - }(&hooks[i]) + }(relevantHooks[i]) } wg.Wait() close(errCh) @@ -313,7 +384,7 @@ func (a *GenericAdmissionWebhook) getNamespaceLabels(attr admission.Attributes) // whether the request is exempted by the webhook because of the // namespaceSelector of the webhook. -func (a *GenericAdmissionWebhook) exemptedByNamespaceSelector(h *v1alpha1.Webhook, attr admission.Attributes) (bool, error) { +func (a *GenericAdmissionWebhook) exemptedByNamespaceSelector(h *v1alpha1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) { namespaceName := attr.GetNamespace() if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" { // If the request is about a cluster scoped resource, and it is not a @@ -323,8 +394,14 @@ func (a *GenericAdmissionWebhook) exemptedByNamespaceSelector(h *v1alpha1.Webhoo return true, nil } namespaceLabels, err := a.getNamespaceLabels(attr) + // this means the namespace is not found, for backwards compatibility, + // return a 404 if apierrors.IsNotFound(err) { - return false, err + status, ok := err.(apierrors.APIStatus) + if !ok { + return false, apierrors.NewInternalError(err) + } + return false, &apierrors.StatusError{status.Status()} } if err != nil { return false, apierrors.NewInternalError(err) @@ -337,15 +414,8 @@ func (a *GenericAdmissionWebhook) exemptedByNamespaceSelector(h *v1alpha1.Webhoo return !selector.Matches(labels.Set(namespaceLabels)), nil } -func (a *GenericAdmissionWebhook) callHook(ctx context.Context, h *v1alpha1.Webhook, attr admission.Attributes) error { - excluded, err := a.exemptedByNamespaceSelector(h, attr) - if err != nil { - return err - } - if excluded { - return nil - } - matches := false +func (a *GenericAdmissionWebhook) shouldCallHook(h *v1alpha1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) { + var matches bool for _, r := range h.Rules { m := RuleMatcher{Rule: r, Attr: attr} if m.Matches() { @@ -354,9 +424,17 @@ func (a *GenericAdmissionWebhook) callHook(ctx context.Context, h *v1alpha1.Webh } } if !matches { - return nil + return false, nil } + excluded, err := a.exemptedByNamespaceSelector(h, attr) + if err != nil { + return false, err + } + return !excluded, nil +} + +func (a *GenericAdmissionWebhook) callHook(ctx context.Context, h *v1alpha1.Webhook, attr admission.Attributes) error { // Make the webhook request request := createAdmissionReview(attr) client, err := a.hookClient(h) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/conversion_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/conversion_test.go new file mode 100644 index 00000000000..684745fd979 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/conversion_test.go @@ -0,0 +1,135 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/apis/example" + examplev1 "k8s.io/apiserver/pkg/apis/example/v1" + example2v1 "k8s.io/apiserver/pkg/apis/example2/v1" +) + +func initiateScheme() *runtime.Scheme { + s := runtime.NewScheme() + example.AddToScheme(s) + examplev1.AddToScheme(s) + example2v1.AddToScheme(s) + return s +} + +func TestConvertToGVK(t *testing.T) { + scheme := initiateScheme() + w := GenericAdmissionWebhook{ + convertor: scheme, + creator: scheme, + } + table := map[string]struct { + obj runtime.Object + gvk schema.GroupVersionKind + expectedObj runtime.Object + }{ + "convert example#Pod to example/v1#Pod": { + obj: &example.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: example.PodSpec{ + RestartPolicy: example.RestartPolicy("never"), + }, + }, + gvk: examplev1.SchemeGroupVersion.WithKind("Pod"), + expectedObj: &examplev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: examplev1.PodSpec{ + RestartPolicy: examplev1.RestartPolicy("never"), + }, + }, + }, + "convert example#replicaset to example2/v1#replicaset": { + obj: &example.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rs1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: example.ReplicaSetSpec{ + Replicas: 1, + }, + }, + gvk: example2v1.SchemeGroupVersion.WithKind("ReplicaSet"), + expectedObj: &example2v1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rs1", + Labels: map[string]string{ + "key": "value", + }, + }, + Spec: example2v1.ReplicaSetSpec{ + Replicas: func() *int32 { var i int32; i = 1; return &i }(), + }, + }, + }, + "no conversion for Unstructured object whose gvk matches the desired gvk": { + obj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "mygroup.k8s.io/v1", + "kind": "Flunder", + "data": map[string]interface{}{ + "Key": "Value", + }, + }, + }, + gvk: schema.GroupVersionKind{Group: "mygroup.k8s.io", Version: "v1", Kind: "Flunder"}, + expectedObj: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "mygroup.k8s.io/v1", + "kind": "Flunder", + "data": map[string]interface{}{ + "Key": "Value", + }, + }, + }, + }, + } + + for name, test := range table { + t.Run(name, func(t *testing.T) { + actual, err := w.convertToGVK(test.obj, test.gvk) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(actual, test.expectedObj) { + t.Errorf("\nexpected:\n%#v\ngot:\n %#v\n", test.expectedObj, actual) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/install/install.go b/staging/src/k8s.io/apiserver/pkg/apis/example/install/install.go index e44a29e5a9c..5352108e789 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/install/install.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/install/install.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package install installs the certificates API group, making it available as +// Package install installs the example API group, making it available as // an option to all of the API encoding/decoding machinery. package install diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/register.go b/staging/src/k8s.io/apiserver/pkg/apis/example/register.go index adaced616c2..d25456b0c54 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/register.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/register.go @@ -46,6 +46,7 @@ func Resource(resource string) schema.GroupResource { func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Pod{}, + &ReplicaSet{}, ) return nil } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/types.go b/staging/src/k8s.io/apiserver/pkg/apis/example/types.go index 6dd3e217242..243c1c03306 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/types.go @@ -136,3 +136,35 @@ type PodList struct { Items []Pod } + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ReplicaSet ensures that a specified number of pod replicas are running at any given time. +type ReplicaSet struct { + metav1.TypeMeta + // +optional + metav1.ObjectMeta + + // Spec defines the desired behavior of this ReplicaSet. + // +optional + Spec ReplicaSetSpec + + // Status is the current status of this ReplicaSet. This data may be + // out of date by some window of time. + // +optional + Status ReplicaSetStatus +} + +// ReplicaSetSpec is the specification of a ReplicaSet. +// As the internal representation of a ReplicaSet, it must have +// a Template set. +type ReplicaSetSpec struct { + // Replicas is the number of desired replicas. + Replicas int32 +} + +// ReplicaSetStatus represents the current status of a ReplicaSet. +type ReplicaSetStatus struct { + // Replicas is the number of actual replicas. + Replicas int32 +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/example/zz_generated.deepcopy.go index c1b5c494a67..7d9cf5f2dbf 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/example/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/example/zz_generated.deepcopy.go @@ -178,3 +178,64 @@ func (in *PodStatus) DeepCopy() *PodStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSet) DeepCopyInto(out *ReplicaSet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSet. +func (in *ReplicaSet) DeepCopy() *ReplicaSet { + if in == nil { + return nil + } + out := new(ReplicaSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReplicaSet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetSpec) DeepCopyInto(out *ReplicaSetSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetSpec. +func (in *ReplicaSetSpec) DeepCopy() *ReplicaSetSpec { + if in == nil { + return nil + } + out := new(ReplicaSetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetStatus) DeepCopyInto(out *ReplicaSetStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetStatus. +func (in *ReplicaSetStatus) DeepCopy() *ReplicaSetStatus { + if in == nil { + return nil + } + out := new(ReplicaSetStatus) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/example2/BUILD new file mode 100644 index 00000000000..d0948caf75b --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/BUILD @@ -0,0 +1,38 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "zz_generated.deepcopy.go", + ], + importpath = "k8s.io/apiserver/pkg/apis/example2", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiserver/pkg/apis/example2/install:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/apis/example2/v1:all-srcs", + ], + tags = ["automanaged"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/doc.go new file mode 100644 index 00000000000..ae0ecc109ea --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/doc.go @@ -0,0 +1,24 @@ +/* +Copyright 2017 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. +*/ + +// +k8s:deepcopy-gen=package +// +groupName=example2.k8s.io +// +// package example2 contains an example API whose internal version is defined in +// another group ("example"). This happens if a type is moved to a different +// group. It's not recommended to move types across groups, though Kubernetes +// have a few cases due to historical reasons. This package is for tests. +package example2 // import "k8s.io/apiserver/pkg/apis/example2" diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/install/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/BUILD new file mode 100644 index 00000000000..30d67d3136c --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/BUILD @@ -0,0 +1,45 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["install.go"], + importpath = "k8s.io/apiserver/pkg/apis/example2/install", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example2:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example2/v1:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["roundtrip_test.go"], + importpath = "k8s.io/apiserver/pkg/apis/example2/install", + library = ":go_default_library", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example/fuzzer:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/install/install.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/install.go new file mode 100644 index 00000000000..8980ecbefd4 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/install.go @@ -0,0 +1,44 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package install installs the example2 API group, making it available as +// an option to all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/apimachinery/announced" + "k8s.io/apimachinery/pkg/apimachinery/registered" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/apis/example" + "k8s.io/apiserver/pkg/apis/example2" + example2v1 "k8s.io/apiserver/pkg/apis/example2/v1" +) + +// Install registers the API group and adds types to a scheme +func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) { + if err := announced.NewGroupMetaFactory( + &announced.GroupMetaFactoryArgs{ + GroupName: example2.GroupName, + VersionPreferenceOrder: []string{example2v1.SchemeGroupVersion.Version}, + AddInternalObjectsToScheme: example.AddToScheme, + }, + announced.VersionToSchemeFunc{ + example2v1.SchemeGroupVersion.Version: example2v1.AddToScheme, + }, + ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { + panic(err) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/install/roundtrip_test.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/roundtrip_test.go new file mode 100644 index 00000000000..7f8d4e82a6a --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/install/roundtrip_test.go @@ -0,0 +1,28 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package install + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/testing/roundtrip" + examplefuzzer "k8s.io/apiserver/pkg/apis/example/fuzzer" +) + +func TestRoundTrip(t *testing.T) { + roundtrip.RoundTripTestForAPIGroup(t, Install, examplefuzzer.Funcs) +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/register.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/register.go new file mode 100644 index 00000000000..c9b2fd79afc --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/register.go @@ -0,0 +1,52 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package example2 + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/apis/example" +) + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// GroupName is the group name use in this package +const GroupName = "example2.apiserver.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +// Adds the list of known types to the given scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &example.ReplicaSet{}, + ) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/BUILD new file mode 100644 index 00000000000..92280a1a1c1 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/BUILD @@ -0,0 +1,50 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "conversion.go", + "defaults.go", + "doc.go", + "generated.pb.go", + "register.go", + "types.go", + "types_swagger_doc_generated.go", + "zz_generated.conversion.go", + "zz_generated.deepcopy.go", + "zz_generated.defaults.go", + ], + importpath = "k8s.io/apiserver/pkg/apis/example2/v1", + deps = [ + "//vendor/github.com/gogo/protobuf/proto:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) + +filegroup( + name = "go_default_library_protos", + srcs = ["generated.proto"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/conversion.go new file mode 100644 index 00000000000..21abdefd5d6 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/conversion.go @@ -0,0 +1,51 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + conversion "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + example "k8s.io/apiserver/pkg/apis/example" +) + +func addConversionFuncs(scheme *runtime.Scheme) error { + // Add non-generated conversion functions to handle the *int32 -> int32 + // conversion. A pointer is useful in the versioned type so we can default + // it, but a plain int32 is more convenient in the internal type. These + // functions are the same as the autogenerated ones in every other way. + err := scheme.AddConversionFuncs( + Convert_example_ReplicaSetSpec_To_v1_ReplicaSetSpec, + Convert_v1_ReplicaSetSpec_To_example_ReplicaSetSpec, + ) + if err != nil { + return err + } + return nil +} + +func Convert_example_ReplicaSetSpec_To_v1_ReplicaSetSpec(in *example.ReplicaSetSpec, out *ReplicaSetSpec, s conversion.Scope) error { + out.Replicas = new(int32) + *out.Replicas = int32(in.Replicas) + return nil +} + +func Convert_v1_ReplicaSetSpec_To_example_ReplicaSetSpec(in *ReplicaSetSpec, out *example.ReplicaSetSpec, s conversion.Scope) error { + if in.Replicas != nil { + out.Replicas = *in.Replicas + } + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/defaults.go new file mode 100644 index 00000000000..436ccde2964 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/defaults.go @@ -0,0 +1,26 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + // return RegisterDefaults(scheme) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/doc.go new file mode 100644 index 00000000000..5784d44f398 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/doc.go @@ -0,0 +1,24 @@ +/* +Copyright 2017 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. +*/ + +// +k8s:deepcopy-gen=package +// +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/example2 +// +k8s:conversion-gen=k8s.io/apiserver/pkg/apis/example +// +k8s:openapi-gen=false +// +k8s:defaulter-gen=TypeMeta + +// +groupName=example2.apiserver.k8s.io +package v1 // import "k8s.io/apiserver/pkg/apis/example2/v1" diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.pb.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.pb.go new file mode 100644 index 00000000000..c65d6f1da23 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.pb.go @@ -0,0 +1,682 @@ +/* +Copyright 2017 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 protoc-gen-gogo. +// source: k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto +// DO NOT EDIT! + +/* + Package v1 is a generated protocol buffer package. + + It is generated from these files: + k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto + + It has these top-level messages: + ReplicaSet + ReplicaSetSpec + ReplicaSetStatus +*/ +package v1 + +import proto "github.com/gogo/protobuf/proto" +import fmt "fmt" +import math "math" + +import strings "strings" +import reflect "reflect" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +func (m *ReplicaSet) Reset() { *m = ReplicaSet{} } +func (*ReplicaSet) ProtoMessage() {} +func (*ReplicaSet) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } + +func (m *ReplicaSetSpec) Reset() { *m = ReplicaSetSpec{} } +func (*ReplicaSetSpec) ProtoMessage() {} +func (*ReplicaSetSpec) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{1} } + +func (m *ReplicaSetStatus) Reset() { *m = ReplicaSetStatus{} } +func (*ReplicaSetStatus) ProtoMessage() {} +func (*ReplicaSetStatus) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } + +func init() { + proto.RegisterType((*ReplicaSet)(nil), "k8s.io.apiserver.pkg.apis.example2.v1.ReplicaSet") + proto.RegisterType((*ReplicaSetSpec)(nil), "k8s.io.apiserver.pkg.apis.example2.v1.ReplicaSetSpec") + proto.RegisterType((*ReplicaSetStatus)(nil), "k8s.io.apiserver.pkg.apis.example2.v1.ReplicaSetStatus") +} +func (m *ReplicaSet) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReplicaSet) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) + n1, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Spec.Size())) + n2, err := m.Spec.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Status.Size())) + n3, err := m.Status.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + return i, nil +} + +func (m *ReplicaSetSpec) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReplicaSetSpec) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Replicas != nil { + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.Replicas)) + } + return i, nil +} + +func (m *ReplicaSetStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReplicaSetStatus) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0x8 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.Replicas)) + return i, nil +} + +func encodeFixed64Generated(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Generated(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *ReplicaSet) Size() (n int) { + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Spec.Size() + n += 1 + l + sovGenerated(uint64(l)) + l = m.Status.Size() + n += 1 + l + sovGenerated(uint64(l)) + return n +} + +func (m *ReplicaSetSpec) Size() (n int) { + var l int + _ = l + if m.Replicas != nil { + n += 1 + sovGenerated(uint64(*m.Replicas)) + } + return n +} + +func (m *ReplicaSetStatus) Size() (n int) { + var l int + _ = l + n += 1 + sovGenerated(uint64(m.Replicas)) + return n +} + +func sovGenerated(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozGenerated(x uint64) (n int) { + return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *ReplicaSet) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ReplicaSet{`, + `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, + `Spec:` + strings.Replace(strings.Replace(this.Spec.String(), "ReplicaSetSpec", "ReplicaSetSpec", 1), `&`, ``, 1) + `,`, + `Status:` + strings.Replace(strings.Replace(this.Status.String(), "ReplicaSetStatus", "ReplicaSetStatus", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func (this *ReplicaSetSpec) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ReplicaSetSpec{`, + `Replicas:` + valueToStringGenerated(this.Replicas) + `,`, + `}`, + }, "") + return s +} +func (this *ReplicaSetStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ReplicaSetStatus{`, + `Replicas:` + fmt.Sprintf("%v", this.Replicas) + `,`, + `}`, + }, "") + return s +} +func valueToStringGenerated(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *ReplicaSet) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ReplicaSet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReplicaSet: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Spec.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReplicaSetSpec) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ReplicaSetSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReplicaSetSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Replicas", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Replicas = &v + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReplicaSetStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ReplicaSetStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReplicaSetStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Replicas", wireType) + } + m.Replicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Replicas |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenerated(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthGenerated + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenerated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipGenerated(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthGenerated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenerated = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto", fileDescriptorGenerated) +} + +var fileDescriptorGenerated = []byte{ + // 421 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0xcf, 0x8e, 0xd3, 0x30, + 0x10, 0x87, 0x93, 0xb2, 0xac, 0x2a, 0xb3, 0x5a, 0xad, 0x72, 0xaa, 0x7a, 0x70, 0x51, 0x24, 0xa4, + 0x1e, 0xc0, 0x26, 0xcb, 0x5f, 0x71, 0x42, 0xb9, 0x03, 0x52, 0xf6, 0x80, 0xc4, 0x05, 0x1c, 0x77, + 0x48, 0x4d, 0x9a, 0xd8, 0xb2, 0x9d, 0x08, 0x6e, 0x3c, 0x02, 0x8f, 0xc1, 0xa3, 0xf4, 0xb8, 0xc7, + 0x3d, 0x55, 0x34, 0xbc, 0x08, 0xaa, 0x13, 0x12, 0xb1, 0xed, 0x0a, 0xb8, 0xe5, 0x67, 0xcf, 0xf7, + 0xcd, 0x64, 0x8c, 0x5e, 0xe7, 0xcf, 0x0d, 0x11, 0x92, 0xe6, 0x55, 0x0a, 0xba, 0x04, 0x0b, 0x86, + 0xd6, 0x50, 0x2e, 0xa4, 0xa6, 0xdd, 0x05, 0x53, 0xc2, 0x80, 0xae, 0x41, 0x53, 0x95, 0x67, 0x2e, + 0x51, 0xf8, 0xcc, 0x0a, 0xb5, 0x82, 0x73, 0x5a, 0x47, 0x34, 0x83, 0x12, 0x34, 0xb3, 0xb0, 0x20, + 0x4a, 0x4b, 0x2b, 0x83, 0x7b, 0x2d, 0x46, 0x7a, 0x8c, 0xa8, 0x3c, 0x73, 0x89, 0xfc, 0xc6, 0x48, + 0x1d, 0x4d, 0x1f, 0x64, 0xc2, 0x2e, 0xab, 0x94, 0x70, 0x59, 0xd0, 0x4c, 0x66, 0x92, 0x3a, 0x3a, + 0xad, 0x3e, 0xba, 0xe4, 0x82, 0xfb, 0x6a, 0xad, 0xd3, 0x70, 0x18, 0x86, 0x72, 0xa9, 0xe1, 0x40, + 0xe7, 0xe9, 0xe3, 0xa1, 0xa6, 0x60, 0x7c, 0x29, 0x4a, 0xd0, 0x5f, 0x86, 0x99, 0x0b, 0xb0, 0xec, + 0x10, 0x45, 0x6f, 0xa2, 0x74, 0x55, 0x5a, 0x51, 0xc0, 0x1e, 0xf0, 0xf4, 0x6f, 0x80, 0xe1, 0x4b, + 0x28, 0xd8, 0x1e, 0xf7, 0xe8, 0x26, 0xae, 0xb2, 0x62, 0x45, 0x45, 0x69, 0x8d, 0xd5, 0xd7, 0xa1, + 0xf0, 0xfb, 0x08, 0xa1, 0x04, 0xd4, 0x4a, 0x70, 0x76, 0x01, 0x36, 0xf8, 0x80, 0xc6, 0xbb, 0xff, + 0x58, 0x30, 0xcb, 0x26, 0xfe, 0x5d, 0x7f, 0x7e, 0xe7, 0xfc, 0x21, 0x19, 0xf6, 0xdd, 0x6b, 0x87, + 0x95, 0xef, 0xaa, 0x49, 0x1d, 0x91, 0x37, 0xe9, 0x27, 0xe0, 0xf6, 0x15, 0x58, 0x16, 0x07, 0xeb, + 0xcd, 0xcc, 0x6b, 0x36, 0x33, 0x34, 0x9c, 0x25, 0xbd, 0x35, 0x78, 0x8b, 0x8e, 0x8c, 0x02, 0x3e, + 0x19, 0x39, 0xfb, 0x13, 0xf2, 0x4f, 0xaf, 0x49, 0x86, 0x11, 0x2f, 0x14, 0xf0, 0xf8, 0xa4, 0x6b, + 0x71, 0xb4, 0x4b, 0x89, 0x13, 0x06, 0xef, 0xd1, 0xb1, 0xb1, 0xcc, 0x56, 0x66, 0x72, 0xcb, 0xa9, + 0x9f, 0xfd, 0xbf, 0xda, 0xe1, 0xf1, 0x69, 0x27, 0x3f, 0x6e, 0x73, 0xd2, 0x69, 0xc3, 0x17, 0xe8, + 0xf4, 0xcf, 0x31, 0x82, 0x39, 0x1a, 0xeb, 0xf6, 0xc4, 0xb8, 0x6d, 0xdd, 0x8e, 0x4f, 0x9a, 0xcd, + 0x6c, 0xdc, 0x55, 0x99, 0xa4, 0xbf, 0x0d, 0x5f, 0xa2, 0xb3, 0xeb, 0x7d, 0x82, 0xfb, 0x7b, 0xf4, + 0x59, 0xd7, 0xf9, 0x80, 0x21, 0x9e, 0xaf, 0xb7, 0xd8, 0xbb, 0xdc, 0x62, 0xef, 0x6a, 0x8b, 0xbd, + 0xaf, 0x0d, 0xf6, 0xd7, 0x0d, 0xf6, 0x2f, 0x1b, 0xec, 0x5f, 0x35, 0xd8, 0xff, 0xd1, 0x60, 0xff, + 0xdb, 0x4f, 0xec, 0xbd, 0x1b, 0xd5, 0xd1, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x69, 0x4a, 0x84, + 0xe4, 0x71, 0x03, 0x00, 0x00, +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto new file mode 100644 index 00000000000..d362291870e --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/generated.proto @@ -0,0 +1,71 @@ +/* +Copyright 2017 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. +*/ + + +// This file was autogenerated by go-to-protobuf. Do not edit it manually! + +syntax = 'proto2'; + +package k8s.io.apiserver.pkg.apis.example2.v1; + +import "k8s.io/api/core/v1/generated.proto"; +import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto"; +import "k8s.io/apimachinery/pkg/runtime/generated.proto"; +import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; +import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; + +// Package-wide variables from generator "generated". +option go_package = "v1"; + +// ReplicaSet ensures that a specified number of pod replicas are running at any given time. +message ReplicaSet { + // If the Labels of a ReplicaSet are empty, they are defaulted to + // be the same as the Pod(s) that the ReplicaSet manages. + // Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Spec defines the specification of the desired behavior of the ReplicaSet. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + optional ReplicaSetSpec spec = 2; + + // Status is the most recently observed status of the ReplicaSet. + // This data may be out of date by some window of time. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + optional ReplicaSetStatus status = 3; +} + +// ReplicaSetSpec is the specification of a ReplicaSet. +message ReplicaSetSpec { + // Replicas is the number of desired replicas. + // This is a pointer to distinguish between explicit zero and unspecified. + // Defaults to 1. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // +optional + optional int32 replicas = 1; +} + +// ReplicaSetStatus represents the current status of a ReplicaSet. +message ReplicaSetStatus { + // Replicas is the most recently oberved number of replicas. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + optional int32 replicas = 1; +} + diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/register.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/register.go new file mode 100644 index 00000000000..1cb0f5eb8d5 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/register.go @@ -0,0 +1,63 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "example2.apiserver.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes, addConversionFuncs, addDefaultingFuncs) +} + +// Adds the list of known types to the given scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &ReplicaSet{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types.go new file mode 100644 index 00000000000..e6e6fb00c19 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types.go @@ -0,0 +1,64 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ReplicaSet ensures that a specified number of pod replicas are running at any given time. +type ReplicaSet struct { + metav1.TypeMeta `json:",inline"` + + // If the Labels of a ReplicaSet are empty, they are defaulted to + // be the same as the Pod(s) that the ReplicaSet manages. + // Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Spec defines the specification of the desired behavior of the ReplicaSet. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + Spec ReplicaSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` + + // Status is the most recently observed status of the ReplicaSet. + // This data may be out of date by some window of time. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + Status ReplicaSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// ReplicaSetSpec is the specification of a ReplicaSet. +type ReplicaSetSpec struct { + // Replicas is the number of desired replicas. + // This is a pointer to distinguish between explicit zero and unspecified. + // Defaults to 1. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + // +optional + Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` +} + +// ReplicaSetStatus represents the current status of a ReplicaSet. +type ReplicaSetStatus struct { + // Replicas is the most recently oberved number of replicas. + // More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/#what-is-a-replicationcontroller + Replicas int32 `json:"replicas" protobuf:"varint,1,opt,name=replicas"` +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types_swagger_doc_generated.go new file mode 100644 index 00000000000..c7be42d5a19 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/types_swagger_doc_generated.go @@ -0,0 +1,17 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.conversion.go new file mode 100644 index 00000000000..609ebef2c2a --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.conversion.go @@ -0,0 +1,111 @@ +// +build !ignore_autogenerated + +/* +Copyright 2017 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. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +package v1 + +import ( + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + example "k8s.io/apiserver/pkg/apis/example" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1_ReplicaSet_To_example_ReplicaSet, + Convert_example_ReplicaSet_To_v1_ReplicaSet, + Convert_v1_ReplicaSetSpec_To_example_ReplicaSetSpec, + Convert_example_ReplicaSetSpec_To_v1_ReplicaSetSpec, + Convert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus, + Convert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus, + ) +} + +func autoConvert_v1_ReplicaSet_To_example_ReplicaSet(in *ReplicaSet, out *example.ReplicaSet, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1_ReplicaSetSpec_To_example_ReplicaSetSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1_ReplicaSet_To_example_ReplicaSet is an autogenerated conversion function. +func Convert_v1_ReplicaSet_To_example_ReplicaSet(in *ReplicaSet, out *example.ReplicaSet, s conversion.Scope) error { + return autoConvert_v1_ReplicaSet_To_example_ReplicaSet(in, out, s) +} + +func autoConvert_example_ReplicaSet_To_v1_ReplicaSet(in *example.ReplicaSet, out *ReplicaSet, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_example_ReplicaSetSpec_To_v1_ReplicaSetSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_example_ReplicaSet_To_v1_ReplicaSet is an autogenerated conversion function. +func Convert_example_ReplicaSet_To_v1_ReplicaSet(in *example.ReplicaSet, out *ReplicaSet, s conversion.Scope) error { + return autoConvert_example_ReplicaSet_To_v1_ReplicaSet(in, out, s) +} + +func autoConvert_v1_ReplicaSetSpec_To_example_ReplicaSetSpec(in *ReplicaSetSpec, out *example.ReplicaSetSpec, s conversion.Scope) error { + if err := meta_v1.Convert_Pointer_int32_To_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + return nil +} + +func autoConvert_example_ReplicaSetSpec_To_v1_ReplicaSetSpec(in *example.ReplicaSetSpec, out *ReplicaSetSpec, s conversion.Scope) error { + if err := meta_v1.Convert_int32_To_Pointer_int32(&in.Replicas, &out.Replicas, s); err != nil { + return err + } + return nil +} + +func autoConvert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus(in *ReplicaSetStatus, out *example.ReplicaSetStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + return nil +} + +// Convert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus is an autogenerated conversion function. +func Convert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus(in *ReplicaSetStatus, out *example.ReplicaSetStatus, s conversion.Scope) error { + return autoConvert_v1_ReplicaSetStatus_To_example_ReplicaSetStatus(in, out, s) +} + +func autoConvert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus(in *example.ReplicaSetStatus, out *ReplicaSetStatus, s conversion.Scope) error { + out.Replicas = in.Replicas + return nil +} + +// Convert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus is an autogenerated conversion function. +func Convert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus(in *example.ReplicaSetStatus, out *ReplicaSetStatus, s conversion.Scope) error { + return autoConvert_example_ReplicaSetStatus_To_v1_ReplicaSetStatus(in, out, s) +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..0208e85669c --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.deepcopy.go @@ -0,0 +1,95 @@ +// +build !ignore_autogenerated + +/* +Copyright 2017 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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSet) DeepCopyInto(out *ReplicaSet) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSet. +func (in *ReplicaSet) DeepCopy() *ReplicaSet { + if in == nil { + return nil + } + out := new(ReplicaSet) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReplicaSet) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetSpec) DeepCopyInto(out *ReplicaSetSpec) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + if *in == nil { + *out = nil + } else { + *out = new(int32) + **out = **in + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetSpec. +func (in *ReplicaSetSpec) DeepCopy() *ReplicaSetSpec { + if in == nil { + return nil + } + out := new(ReplicaSetSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReplicaSetStatus) DeepCopyInto(out *ReplicaSetStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaSetStatus. +func (in *ReplicaSetStatus) DeepCopy() *ReplicaSetStatus { + if in == nil { + return nil + } + out := new(ReplicaSetStatus) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.defaults.go new file mode 100644 index 00000000000..6df448eb9fd --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/v1/zz_generated.defaults.go @@ -0,0 +1,32 @@ +// +build !ignore_autogenerated + +/* +Copyright 2017 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. +*/ + +// This file was autogenerated by defaulter-gen. Do not edit it manually! + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/example2/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/example2/zz_generated.deepcopy.go new file mode 100644 index 00000000000..f100811697f --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/example2/zz_generated.deepcopy.go @@ -0,0 +1,21 @@ +// +build !ignore_autogenerated + +/* +Copyright 2017 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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package example2 diff --git a/test/e2e/apimachinery/BUILD b/test/e2e/apimachinery/BUILD index 7c8a85b7ecd..2dacec44df5 100644 --- a/test/e2e/apimachinery/BUILD +++ b/test/e2e/apimachinery/BUILD @@ -60,6 +60,7 @@ go_library( "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library", "//vendor/k8s.io/client-go/discovery:go_default_library", + "//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/util/cert:go_default_library", "//vendor/k8s.io/client-go/util/retry:go_default_library", diff --git a/test/e2e/apimachinery/webhook.go b/test/e2e/apimachinery/webhook.go index 30febfc2923..6693fac45d2 100644 --- a/test/e2e/apimachinery/webhook.go +++ b/test/e2e/apimachinery/webhook.go @@ -24,10 +24,15 @@ import ( "k8s.io/api/core/v1" extensions "k8s.io/api/extensions/v1beta1" rbacv1beta1 "k8s.io/api/rbac/v1beta1" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + crdclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apiextensions-apiserver/test/integration/testserver" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic" utilversion "k8s.io/kubernetes/pkg/util/version" "k8s.io/kubernetes/test/e2e/framework" @@ -47,6 +52,11 @@ const ( skippedNamespaceName = "exempted-namesapce" disallowedPodName = "disallowed-pod" disallowedConfigMapName = "disallowed-configmap" + crdName = "e2e-test-webhook-crd" + crdKind = "E2e-test-webhook-crd" + crdWebhookConfigName = "e2e-test-webhook-config-crd" + crdAPIGroup = "webhook-crd-test.k8s.io" + crdAPIVersion = "v1" ) var serverWebhookVersion = utilversion.MustParseSemantic("v1.8.0") @@ -60,7 +70,7 @@ var _ = SIGDescribe("AdmissionWebhook", func() { It("Should be able to deny pod and configmap creation", func() { // Make sure the relevant provider supports admission webhook framework.SkipUnlessServerVersionGTE(serverWebhookVersion, f.ClientSet.Discovery()) - framework.SkipUnlessProviderIs("gce", "gke") + framework.SkipUnlessProviderIs("gce", "gke", "local") _, err := f.ClientSet.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().List(metav1.ListOptions{}) if errors.IsNotFound(err) { @@ -74,10 +84,29 @@ var _ = SIGDescribe("AdmissionWebhook", func() { // Note that in 1.9 we will have backwards incompatible change to // admission webhooks, so the image will be updated to 1.9 sometime in // the development 1.9 cycle. - deployWebhookAndService(f, "gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v2", context) + deployWebhookAndService(f, "gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v3", context) registerWebhook(f, context) testWebhook(f) }) + + It("Should be able to deny custom resource creation", func() { + // Make sure the relevant provider supports admission webhook + framework.SkipUnlessServerVersionGTE(serverWebhookVersion, f.ClientSet.Discovery()) + framework.SkipUnlessProviderIs("gce", "gke", "local") + _, err := f.ClientSet.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().List(metav1.ListOptions{}) + if errors.IsNotFound(err) { + framework.Skipf("dynamic configuration of webhooks requires the alpha admissionregistration.k8s.io group to be enabled") + } + By("Setting up server cert") + namespaceName := f.Namespace.Name + context := setupServerCert(namespaceName, serviceName) + createAuthReaderRoleBinding(f, namespaceName) + deployWebhookAndService(f, "gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v3", context) + crdCleanup, dynamicClient := createCRD(f) + defer crdCleanup() + registerWebhookForCRD(f, context) + testCRDWebhook(f, dynamicClient) + }) }) func createAuthReaderRoleBinding(f *framework.Framework, namespace string) { @@ -105,12 +134,15 @@ func createAuthReaderRoleBinding(f *framework.Framework, namespace string) { }, }, }) - framework.ExpectNoError(err, "creating role binding %s:webhook to access configMap", namespace) + if err != nil && errors.IsAlreadyExists(err) { + framework.Logf("role binding %s already exists", roleBindingName) + } else { + framework.ExpectNoError(err, "creating role binding %s:webhook to access configMap", namespace) + } } func deployWebhookAndService(f *framework.Framework, image string, context *certContext) { By("Deploying the webhook pod") - client := f.ClientSet // Creating the secret that contains the webhook's cert. @@ -283,7 +315,7 @@ func registerWebhook(f *framework.Framework, context *certContext) { framework.ExpectNoError(err, "registering webhook config %s with namespace %s", webhookConfigName, namespace) // The webhook configuration is honored in 1s. - time.Sleep(2 * time.Second) + time.Sleep(10 * time.Second) } func testWebhook(f *framework.Framework) { @@ -293,20 +325,21 @@ func testWebhook(f *framework.Framework) { pod := nonCompliantPod(f) _, err := client.CoreV1().Pods(f.Namespace.Name).Create(pod) Expect(err).NotTo(BeNil()) - expectedErrMsg := "the pod contains unwanted container name" - if !strings.Contains(err.Error(), expectedErrMsg) { - framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error()) + expectedErrMsg1 := "the pod contains unwanted container name" + if !strings.Contains(err.Error(), expectedErrMsg1) { + framework.Failf("expect error contains %q, got %q", expectedErrMsg1, err.Error()) + } + expectedErrMsg2 := "the pod contains unwanted label" + if !strings.Contains(err.Error(), expectedErrMsg2) { + framework.Failf("expect error contains %q, got %q", expectedErrMsg2, err.Error()) } - // TODO: Test if webhook can detect pod with non-compliant metadata. - // Currently metadata is lost because webhook uses the external version of - // the objects, and the apiserver sends the internal objects. By("create a configmap that should be denied by the webhook") // Creating the configmap, the request should be rejected configmap := nonCompliantConfigMap(f) _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(configmap) Expect(err).NotTo(BeNil()) - expectedErrMsg = "the configmap contains unwanted key and value" + expectedErrMsg := "the configmap contains unwanted key and value" if !strings.Contains(err.Error(), expectedErrMsg) { framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error()) } @@ -340,7 +373,7 @@ func nonCompliantPod(f *framework.Framework) *v1.Pod { ObjectMeta: metav1.ObjectMeta{ Name: disallowedPodName, Labels: map[string]string{ - "webhook-e2e-test": "disallow", + "webhook-e2e-test": "webhook-disallow", }, }, Spec: v1.PodSpec{ @@ -368,6 +401,7 @@ func nonCompliantConfigMap(f *framework.Framework) *v1.ConfigMap { func cleanWebhookTest(f *framework.Framework) { client := f.ClientSet _ = client.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Delete(webhookConfigName, nil) + _ = client.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Delete(crdWebhookConfigName, nil) namespaceName := f.Namespace.Name _ = client.CoreV1().Services(namespaceName).Delete(serviceName, nil) _ = client.ExtensionsV1beta1().Deployments(namespaceName).Delete(deploymentName, nil) @@ -376,3 +410,114 @@ func cleanWebhookTest(f *framework.Framework) { _ = client.CoreV1().ConfigMaps(skippedNamespaceName).Delete(disallowedConfigMapName, nil) _ = client.CoreV1().Namespaces().Delete(skippedNamespaceName, nil) } + +// newCRDForAdmissionWebhookTest generates a CRD +func newCRDForAdmissionWebhookTest() *apiextensionsv1beta1.CustomResourceDefinition { + return &apiextensionsv1beta1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: crdName + "s." + crdAPIGroup}, + Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ + Group: crdAPIGroup, + Version: crdAPIVersion, + Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ + Plural: crdName + "s", + Singular: crdName, + Kind: crdKind, + ListKind: crdName + "List", + }, + Scope: apiextensionsv1beta1.NamespaceScoped, + }, + } +} + +func createCRD(f *framework.Framework) (func(), dynamic.ResourceInterface) { + config, err := framework.LoadConfig() + if err != nil { + framework.Failf("failed to load config: %v", err) + } + + apiExtensionClient, err := crdclientset.NewForConfig(config) + if err != nil { + framework.Failf("failed to initialize apiExtensionClient: %v", err) + } + + crd := newCRDForAdmissionWebhookTest() + + //create CRD and waits for the resource to be recognized and available. + dynamicClient, err := testserver.CreateNewCustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient, f.ClientPool) + if err != nil { + framework.Failf("failed to create CustomResourceDefinition: %v", err) + } + + resourceClient := dynamicClient.Resource(&metav1.APIResource{ + Name: crd.Spec.Names.Plural, + Namespaced: true, + }, f.Namespace.Name) + + return func() { + err = testserver.DeleteCustomResourceDefinition(crd, apiExtensionClient) + if err != nil { + framework.Failf("failed to delete CustomResourceDefinition: %v", err) + } + }, resourceClient +} + +func registerWebhookForCRD(f *framework.Framework, context *certContext) { + client := f.ClientSet + By("Registering the crd webhook via the AdmissionRegistration API") + + namespace := f.Namespace.Name + _, err := client.AdmissionregistrationV1alpha1().ValidatingWebhookConfigurations().Create(&v1alpha1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: crdWebhookConfigName, + }, + Webhooks: []v1alpha1.Webhook{ + { + Name: "deny-unwanted-crd-data.k8s.io", + Rules: []v1alpha1.RuleWithOperations{{ + Operations: []v1alpha1.OperationType{v1alpha1.Create}, + Rule: v1alpha1.Rule{ + APIGroups: []string{crdAPIGroup}, + APIVersions: []string{crdAPIVersion}, + Resources: []string{crdName + "s"}, + }, + }}, + ClientConfig: v1alpha1.WebhookClientConfig{ + Service: &v1alpha1.ServiceReference{ + Namespace: namespace, + Name: serviceName, + Path: strPtr("/crd"), + }, + CABundle: context.signingCert, + }, + }, + }, + }) + framework.ExpectNoError(err, "registering crd webhook config %s with namespace %s", webhookConfigName, namespace) + + // The webhook configuration is honored in 1s. + time.Sleep(10 * time.Second) +} + +func testCRDWebhook(f *framework.Framework, crdClient dynamic.ResourceInterface) { + By("Creating a custom resource that should be denied by the webhook") + crd := newCRDForAdmissionWebhookTest() + crInstance := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": crd.Spec.Names.Kind, + "apiVersion": crd.Spec.Group + "/" + crd.Spec.Version, + "metadata": map[string]interface{}{ + "name": "cr-instance-1", + "namespace": f.Namespace.Name, + }, + "data": map[string]interface{}{ + "webhook-e2e-test": "webhook-disallow", + }, + }, + } + _, err := crdClient.Create(crInstance) + Expect(err).NotTo(BeNil()) + expectedErrMsg := "the custom resource contains unwanted data" + if !strings.Contains(err.Error(), expectedErrMsg) { + framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error()) + } +} diff --git a/test/images/webhook/BUILD b/test/images/webhook/BUILD index 00067234a85..c174d676ae8 100644 --- a/test/images/webhook/BUILD +++ b/test/images/webhook/BUILD @@ -5,14 +5,18 @@ go_library( srcs = [ "config.go", "main.go", + "scheme.go", ], importpath = "k8s.io/kubernetes/test/images/webhook", visibility = ["//visibility:private"], deps = [ "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/api/admission/v1alpha1:go_default_library", + "//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", ], diff --git a/test/images/webhook/Makefile b/test/images/webhook/Makefile index 84b76aade12..d3e3bb66d70 100644 --- a/test/images/webhook/Makefile +++ b/test/images/webhook/Makefile @@ -14,6 +14,7 @@ build: CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webhook . - docker build --no-cache -t gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v2 . + docker build --no-cache -t gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v3 . + rm -rf webhook push: - gcloud docker -- push gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v2 + gcloud docker -- push gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.8v3 diff --git a/test/images/webhook/main.go b/test/images/webhook/main.go index bb4065196bd..a9ea58eb74a 100644 --- a/test/images/webhook/main.go +++ b/test/images/webhook/main.go @@ -19,13 +19,14 @@ package main import ( "encoding/json" "flag" + "fmt" "io/ioutil" "net/http" "strings" "github.com/golang/glog" "k8s.io/api/admission/v1alpha1" - "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -43,58 +44,56 @@ func (c *Config) addFlags() { "File containing the default x509 private key matching --tls-cert-file.") } -// only allow pods to pull images from specific registry. -func admitPods(data []byte) *v1alpha1.AdmissionReviewStatus { - glog.V(2).Info("admitting pods") - ar := v1alpha1.AdmissionReview{} - if err := json.Unmarshal(data, &ar); err != nil { - glog.Error(err) - return nil +func toAdmissionReviewStatus(err error) *v1alpha1.AdmissionReviewStatus { + return &v1alpha1.AdmissionReviewStatus{ + Result: &metav1.Status{ + Message: err.Error(), + }, } +} + +// only allow pods to pull images from specific registry. +func admitPods(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionReviewStatus { + glog.V(2).Info("admitting pods") podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} if ar.Spec.Resource != podResource { - glog.Errorf("expect resource to be %s", podResource) - return nil + err := fmt.Errorf("expect resource to be %s", podResource) + glog.Error(err) + return toAdmissionReviewStatus(err) } raw := ar.Spec.Object.Raw - pod := v1.Pod{} - if err := json.Unmarshal(raw, &pod); err != nil { + pod := corev1.Pod{} + deserializer := codecs.UniversalDeserializer() + if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil { glog.Error(err) - return nil + return toAdmissionReviewStatus(err) } reviewStatus := v1alpha1.AdmissionReviewStatus{} reviewStatus.Allowed = true - // Note: the apiserver encodes the api.Pod. Decoding it as a v1.Pod will - // lose the metadata. So the following check on labels will not work - // until we let the apiserver encodes the versioned object. + + var msg string for k, v := range pod.Labels { if k == "webhook-e2e-test" && v == "webhook-disallow" { reviewStatus.Allowed = false - reviewStatus.Result = &metav1.Status{ - Reason: "the pod contains unwanted label", - } + msg = msg + "the pod contains unwanted label; " } } for _, container := range pod.Spec.Containers { if strings.Contains(container.Name, "webhook-disallow") { reviewStatus.Allowed = false - reviewStatus.Result = &metav1.Status{ - Message: "the pod contains unwanted container name", - } + msg = msg + "the pod contains unwanted container name; " } } + if !reviewStatus.Allowed { + reviewStatus.Result = &metav1.Status{Message: strings.TrimSpace(msg)} + } return &reviewStatus } // deny configmaps with specific key-value pair. -func admitConfigMaps(data []byte) *v1alpha1.AdmissionReviewStatus { +func admitConfigMaps(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionReviewStatus { glog.V(2).Info("admitting configmaps") - ar := v1alpha1.AdmissionReview{} - if err := json.Unmarshal(data, &ar); err != nil { - glog.Error(err) - return nil - } configMapResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"} if ar.Spec.Resource != configMapResource { glog.Errorf("expect resource to be %s", configMapResource) @@ -102,10 +101,11 @@ func admitConfigMaps(data []byte) *v1alpha1.AdmissionReviewStatus { } raw := ar.Spec.Object.Raw - configmap := v1.ConfigMap{} - if err := json.Unmarshal(raw, &configmap); err != nil { + configmap := corev1.ConfigMap{} + deserializer := codecs.UniversalDeserializer() + if _, _, err := deserializer.Decode(raw, nil, &configmap); err != nil { glog.Error(err) - return nil + return toAdmissionReviewStatus(err) } reviewStatus := v1alpha1.AdmissionReviewStatus{} reviewStatus.Allowed = true @@ -120,7 +120,34 @@ func admitConfigMaps(data []byte) *v1alpha1.AdmissionReviewStatus { return &reviewStatus } -type admitFunc func(data []byte) *v1alpha1.AdmissionReviewStatus +func admitCRD(ar v1alpha1.AdmissionReview) *v1alpha1.AdmissionReviewStatus { + glog.V(2).Info("admitting crd") + cr := struct { + metav1.ObjectMeta + Data map[string]string + }{} + + raw := ar.Spec.Object.Raw + err := json.Unmarshal(raw, &cr) + if err != nil { + glog.Error(err) + return toAdmissionReviewStatus(err) + } + + reviewStatus := v1alpha1.AdmissionReviewStatus{} + reviewStatus.Allowed = true + for k, v := range cr.Data { + if k == "webhook-e2e-test" && v == "webhook-disallow" { + reviewStatus.Allowed = false + reviewStatus.Result = &metav1.Status{ + Reason: "the custom resource contains unwanted data", + } + } + } + return &reviewStatus +} + +type admitFunc func(v1alpha1.AdmissionReview) *v1alpha1.AdmissionReviewStatus func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) { var body []byte @@ -137,9 +164,16 @@ func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) { return } - reviewStatus := admit(body) - + var reviewStatus *v1alpha1.AdmissionReviewStatus ar := v1alpha1.AdmissionReview{} + deserializer := codecs.UniversalDeserializer() + if _, _, err := deserializer.Decode(body, nil, &ar); err != nil { + glog.Error(err) + reviewStatus = toAdmissionReviewStatus(err) + } else { + reviewStatus = admit(ar) + } + if reviewStatus != nil { ar.Status = *reviewStatus } @@ -156,10 +190,15 @@ func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) { func servePods(w http.ResponseWriter, r *http.Request) { serve(w, r, admitPods) } + func serveConfigmaps(w http.ResponseWriter, r *http.Request) { serve(w, r, admitConfigMaps) } +func serveCRD(w http.ResponseWriter, r *http.Request) { + serve(w, r, admitCRD) +} + func main() { var config Config config.addFlags() @@ -167,6 +206,7 @@ func main() { http.HandleFunc("/pods", servePods) http.HandleFunc("/configmaps", serveConfigmaps) + http.HandleFunc("/crd", serveCRD) clientset := getClient() server := &http.Server{ Addr: ":443", diff --git a/test/images/webhook/scheme.go b/test/images/webhook/scheme.go new file mode 100644 index 00000000000..6827b137d76 --- /dev/null +++ b/test/images/webhook/scheme.go @@ -0,0 +1,36 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) + +func init() { + addToScheme(scheme) +} + +func addToScheme(scheme *runtime.Scheme) { + corev1.AddToScheme(scheme) + admissionregistrationv1alpha1.AddToScheme(scheme) +}