mirror of https://github.com/k3s-io/k3s
Merge pull request #76849 from liggitt/crd_webhook_integration_tests
Fix scale and rollback subresources with admission webhooks, add integration testsk3s-v1.15.3
commit
716344fd7d
|
@ -158,24 +158,27 @@ func (r *RollbackREST) New() runtime.Object {
|
|||
return &apps.DeploymentRollback{}
|
||||
}
|
||||
|
||||
var _ = rest.Creater(&RollbackREST{})
|
||||
var _ = rest.NamedCreater(&RollbackREST{})
|
||||
|
||||
func (r *RollbackREST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||
func (r *RollbackREST) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||
rollback, ok := obj.(*apps.DeploymentRollback)
|
||||
if !ok {
|
||||
return nil, errors.NewBadRequest(fmt.Sprintf("not a DeploymentRollback: %#v", obj))
|
||||
}
|
||||
|
||||
if errs := appsvalidation.ValidateDeploymentRollback(rollback); len(errs) != 0 {
|
||||
return nil, errors.NewInvalid(apps.Kind("DeploymentRollback"), rollback.Name, errs)
|
||||
}
|
||||
if name != rollback.Name {
|
||||
return nil, errors.NewBadRequest("name in URL does not match name in DeploymentRollback object")
|
||||
}
|
||||
|
||||
if createValidation != nil {
|
||||
if err := createValidation(obj.DeepCopyObject()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if errs := appsvalidation.ValidateDeploymentRollback(rollback); len(errs) != 0 {
|
||||
return nil, errors.NewInvalid(apps.Kind("DeploymentRollback"), rollback.Name, errs)
|
||||
}
|
||||
|
||||
// Update the Deployment with information in DeploymentRollback to trigger rollback
|
||||
err := r.rollbackDeployment(ctx, rollback.Name, &rollback.RollbackTo, rollback.UpdatedAnnotations, dryrun.IsDryRun(options.DryRun))
|
||||
if err != nil {
|
||||
|
@ -293,7 +296,15 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
|
||||
deployment.Spec.Replicas = scale.Spec.Replicas
|
||||
deployment.ResourceVersion = scale.ResourceVersion
|
||||
obj, _, err = r.store.Update(ctx, deployment.Name, rest.DefaultUpdatedObjectInfo(deployment), createValidation, updateValidation, false, options)
|
||||
obj, _, err = r.store.Update(
|
||||
ctx,
|
||||
deployment.Name,
|
||||
rest.DefaultUpdatedObjectInfo(deployment),
|
||||
toScaleCreateValidation(createValidation),
|
||||
toScaleUpdateValidation(updateValidation),
|
||||
false,
|
||||
options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
@ -305,6 +316,30 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
return newScale, false, nil
|
||||
}
|
||||
|
||||
func toScaleCreateValidation(f rest.ValidateObjectFunc) rest.ValidateObjectFunc {
|
||||
return func(obj runtime.Object) error {
|
||||
scale, err := scaleFromDeployment(obj.(*apps.Deployment))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f(scale)
|
||||
}
|
||||
}
|
||||
|
||||
func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc) rest.ValidateObjectUpdateFunc {
|
||||
return func(obj, old runtime.Object) error {
|
||||
newScale, err := scaleFromDeployment(obj.(*apps.Deployment))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldScale, err := scaleFromDeployment(old.(*apps.Deployment))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f(newScale, oldScale)
|
||||
}
|
||||
}
|
||||
|
||||
// scaleFromDeployment returns a scale subresource for a deployment.
|
||||
func scaleFromDeployment(deployment *apps.Deployment) (*autoscaling.Scale, error) {
|
||||
selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
|
||||
|
|
|
@ -351,7 +351,7 @@ func TestEtcdCreateDeploymentRollback(t *testing.T) {
|
|||
if _, err := storage.Deployment.Create(ctx, validNewDeployment(), rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
|
||||
t.Fatalf("%s: unexpected error: %v", k, err)
|
||||
}
|
||||
rollbackRespStatus, err := rollbackStorage.Create(ctx, &test.rollback, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
|
||||
rollbackRespStatus, err := rollbackStorage.Create(ctx, test.rollback.Name, &test.rollback, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
|
||||
if !test.errOK(err) {
|
||||
t.Errorf("%s: unexpected error: %v", k, err)
|
||||
} else if err == nil {
|
||||
|
@ -392,7 +392,7 @@ func TestCreateDeploymentRollbackValidation(t *testing.T) {
|
|||
|
||||
validationError := fmt.Errorf("admission deny")
|
||||
alwaysDenyValidationFunc := func(obj runtime.Object) error { return validationError }
|
||||
_, err := rollbackStorage.Create(ctx, &rollback, alwaysDenyValidationFunc, &metav1.CreateOptions{})
|
||||
_, err := rollbackStorage.Create(ctx, rollback.Name, &rollback, alwaysDenyValidationFunc, &metav1.CreateOptions{})
|
||||
|
||||
if err == nil || validationError != err {
|
||||
t.Errorf("expected: %v, got: %v", validationError, err)
|
||||
|
@ -411,7 +411,7 @@ func TestEtcdCreateDeploymentRollbackNoDeployment(t *testing.T) {
|
|||
rollbackStorage := storage.Rollback
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
|
||||
|
||||
_, err := rollbackStorage.Create(ctx, &apps.DeploymentRollback{
|
||||
_, err := rollbackStorage.Create(ctx, name, &apps.DeploymentRollback{
|
||||
Name: name,
|
||||
UpdatedAnnotations: map[string]string{},
|
||||
RollbackTo: apps.RollbackConfig{Revision: 1},
|
||||
|
|
|
@ -201,7 +201,15 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
|
||||
rs.Spec.Replicas = scale.Spec.Replicas
|
||||
rs.ResourceVersion = scale.ResourceVersion
|
||||
obj, _, err = r.store.Update(ctx, rs.Name, rest.DefaultUpdatedObjectInfo(rs), createValidation, updateValidation, false, options)
|
||||
obj, _, err = r.store.Update(
|
||||
ctx,
|
||||
rs.Name,
|
||||
rest.DefaultUpdatedObjectInfo(rs),
|
||||
toScaleCreateValidation(createValidation),
|
||||
toScaleUpdateValidation(updateValidation),
|
||||
false,
|
||||
options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
@ -213,6 +221,30 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
return newScale, false, err
|
||||
}
|
||||
|
||||
func toScaleCreateValidation(f rest.ValidateObjectFunc) rest.ValidateObjectFunc {
|
||||
return func(obj runtime.Object) error {
|
||||
scale, err := scaleFromReplicaSet(obj.(*apps.ReplicaSet))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f(scale)
|
||||
}
|
||||
}
|
||||
|
||||
func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc) rest.ValidateObjectUpdateFunc {
|
||||
return func(obj, old runtime.Object) error {
|
||||
newScale, err := scaleFromReplicaSet(obj.(*apps.ReplicaSet))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldScale, err := scaleFromReplicaSet(old.(*apps.ReplicaSet))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f(newScale, oldScale)
|
||||
}
|
||||
}
|
||||
|
||||
// scaleFromReplicaSet returns a scale subresource for a replica set.
|
||||
func scaleFromReplicaSet(rs *apps.ReplicaSet) (*autoscaling.Scale, error) {
|
||||
selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
|
||||
|
|
|
@ -188,7 +188,15 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
|
||||
ss.Spec.Replicas = scale.Spec.Replicas
|
||||
ss.ResourceVersion = scale.ResourceVersion
|
||||
obj, _, err = r.store.Update(ctx, ss.Name, rest.DefaultUpdatedObjectInfo(ss), createValidation, updateValidation, false, options)
|
||||
obj, _, err = r.store.Update(
|
||||
ctx,
|
||||
ss.Name,
|
||||
rest.DefaultUpdatedObjectInfo(ss),
|
||||
toScaleCreateValidation(createValidation),
|
||||
toScaleUpdateValidation(updateValidation),
|
||||
false,
|
||||
options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
@ -200,6 +208,30 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
return newScale, false, err
|
||||
}
|
||||
|
||||
func toScaleCreateValidation(f rest.ValidateObjectFunc) rest.ValidateObjectFunc {
|
||||
return func(obj runtime.Object) error {
|
||||
scale, err := scaleFromStatefulSet(obj.(*apps.StatefulSet))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f(scale)
|
||||
}
|
||||
}
|
||||
|
||||
func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc) rest.ValidateObjectUpdateFunc {
|
||||
return func(obj, old runtime.Object) error {
|
||||
newScale, err := scaleFromStatefulSet(obj.(*apps.StatefulSet))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldScale, err := scaleFromStatefulSet(old.(*apps.StatefulSet))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f(newScale, oldScale)
|
||||
}
|
||||
}
|
||||
|
||||
// scaleFromStatefulSet returns a scale subresource for a statefulset.
|
||||
func scaleFromStatefulSet(ss *apps.StatefulSet) (*autoscaling.Scale, error) {
|
||||
selector, err := metav1.LabelSelectorAsSelector(ss.Spec.Selector)
|
||||
|
|
|
@ -183,7 +183,15 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
|
||||
rc.Spec.Replicas = scale.Spec.Replicas
|
||||
rc.ResourceVersion = scale.ResourceVersion
|
||||
obj, _, err = r.store.Update(ctx, rc.Name, rest.DefaultUpdatedObjectInfo(rc), createValidation, updateValidation, false, options)
|
||||
obj, _, err = r.store.Update(
|
||||
ctx,
|
||||
rc.Name,
|
||||
rest.DefaultUpdatedObjectInfo(rc),
|
||||
toScaleCreateValidation(createValidation),
|
||||
toScaleUpdateValidation(updateValidation),
|
||||
false,
|
||||
options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
@ -191,6 +199,21 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
return scaleFromRC(rc), false, nil
|
||||
}
|
||||
|
||||
func toScaleCreateValidation(f rest.ValidateObjectFunc) rest.ValidateObjectFunc {
|
||||
return func(obj runtime.Object) error {
|
||||
return f(scaleFromRC(obj.(*api.ReplicationController)))
|
||||
}
|
||||
}
|
||||
|
||||
func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc) rest.ValidateObjectUpdateFunc {
|
||||
return func(obj, old runtime.Object) error {
|
||||
return f(
|
||||
scaleFromRC(obj.(*api.ReplicationController)),
|
||||
scaleFromRC(old.(*api.ReplicationController)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// scaleFromRC returns a scale subresource for a replication controller.
|
||||
func scaleFromRC(rc *api.ReplicationController) *autoscaling.Scale {
|
||||
return &autoscaling.Scale{
|
||||
|
|
|
@ -95,7 +95,15 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
|
||||
rc.Spec.Replicas = scale.Spec.Replicas
|
||||
rc.ResourceVersion = scale.ResourceVersion
|
||||
obj, _, err = r.store.Update(ctx, rc.Name, rest.DefaultUpdatedObjectInfo(rc), createValidation, updateValidation, false, options)
|
||||
obj, _, err = r.store.Update(
|
||||
ctx,
|
||||
rc.Name,
|
||||
rest.DefaultUpdatedObjectInfo(rc),
|
||||
toScaleCreateValidation(createValidation),
|
||||
toScaleUpdateValidation(updateValidation),
|
||||
false,
|
||||
options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, errors.NewConflict(extensions.Resource("replicationcontrollers/scale"), scale.Name, err)
|
||||
}
|
||||
|
@ -103,6 +111,21 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
return scaleFromRC(rc), false, nil
|
||||
}
|
||||
|
||||
func toScaleCreateValidation(f rest.ValidateObjectFunc) rest.ValidateObjectFunc {
|
||||
return func(obj runtime.Object) error {
|
||||
return f(scaleFromRC(obj.(*api.ReplicationController)))
|
||||
}
|
||||
}
|
||||
|
||||
func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc) rest.ValidateObjectUpdateFunc {
|
||||
return func(obj, old runtime.Object) error {
|
||||
return f(
|
||||
scaleFromRC(obj.(*api.ReplicationController)),
|
||||
scaleFromRC(old.(*api.ReplicationController)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// scaleFromRC returns a scale subresource for a replication controller.
|
||||
func scaleFromRC(rc *api.ReplicationController) *autoscaling.Scale {
|
||||
return &autoscaling.Scale{
|
||||
|
|
|
@ -190,9 +190,9 @@ func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc {
|
|||
case result.Protected && result.Operation != reconciliation.ReconcileNone:
|
||||
klog.Warningf("skipped reconcile-protected clusterrole.%s/%s with missing permissions: %v", rbac.GroupName, clusterRole.Name, result.MissingRules)
|
||||
case result.Operation == reconciliation.ReconcileUpdate:
|
||||
klog.Infof("updated clusterrole.%s/%s with additional permissions: %v", rbac.GroupName, clusterRole.Name, result.MissingRules)
|
||||
klog.V(2).Infof("updated clusterrole.%s/%s with additional permissions: %v", rbac.GroupName, clusterRole.Name, result.MissingRules)
|
||||
case result.Operation == reconciliation.ReconcileCreate:
|
||||
klog.Infof("created clusterrole.%s/%s", rbac.GroupName, clusterRole.Name)
|
||||
klog.V(2).Infof("created clusterrole.%s/%s", rbac.GroupName, clusterRole.Name)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
@ -218,11 +218,11 @@ func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc {
|
|||
case result.Protected && result.Operation != reconciliation.ReconcileNone:
|
||||
klog.Warningf("skipped reconcile-protected clusterrolebinding.%s/%s with missing subjects: %v", rbac.GroupName, clusterRoleBinding.Name, result.MissingSubjects)
|
||||
case result.Operation == reconciliation.ReconcileUpdate:
|
||||
klog.Infof("updated clusterrolebinding.%s/%s with additional subjects: %v", rbac.GroupName, clusterRoleBinding.Name, result.MissingSubjects)
|
||||
klog.V(2).Infof("updated clusterrolebinding.%s/%s with additional subjects: %v", rbac.GroupName, clusterRoleBinding.Name, result.MissingSubjects)
|
||||
case result.Operation == reconciliation.ReconcileCreate:
|
||||
klog.Infof("created clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name)
|
||||
klog.V(2).Infof("created clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name)
|
||||
case result.Operation == reconciliation.ReconcileRecreate:
|
||||
klog.Infof("recreated clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name)
|
||||
klog.V(2).Infof("recreated clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
@ -249,9 +249,9 @@ func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc {
|
|||
case result.Protected && result.Operation != reconciliation.ReconcileNone:
|
||||
klog.Warningf("skipped reconcile-protected role.%s/%s in %v with missing permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules)
|
||||
case result.Operation == reconciliation.ReconcileUpdate:
|
||||
klog.Infof("updated role.%s/%s in %v with additional permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules)
|
||||
klog.V(2).Infof("updated role.%s/%s in %v with additional permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules)
|
||||
case result.Operation == reconciliation.ReconcileCreate:
|
||||
klog.Infof("created role.%s/%s in %v", rbac.GroupName, role.Name, namespace)
|
||||
klog.V(2).Infof("created role.%s/%s in %v", rbac.GroupName, role.Name, namespace)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
@ -279,11 +279,11 @@ func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc {
|
|||
case result.Protected && result.Operation != reconciliation.ReconcileNone:
|
||||
klog.Warningf("skipped reconcile-protected rolebinding.%s/%s in %v with missing subjects: %v", rbac.GroupName, roleBinding.Name, namespace, result.MissingSubjects)
|
||||
case result.Operation == reconciliation.ReconcileUpdate:
|
||||
klog.Infof("updated rolebinding.%s/%s in %v with additional subjects: %v", rbac.GroupName, roleBinding.Name, namespace, result.MissingSubjects)
|
||||
klog.V(2).Infof("updated rolebinding.%s/%s in %v with additional subjects: %v", rbac.GroupName, roleBinding.Name, namespace, result.MissingSubjects)
|
||||
case result.Operation == reconciliation.ReconcileCreate:
|
||||
klog.Infof("created rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace)
|
||||
klog.V(2).Infof("created rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace)
|
||||
case result.Operation == reconciliation.ReconcileRecreate:
|
||||
klog.Infof("recreated rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace)
|
||||
klog.V(2).Infof("recreated rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
|
|
@ -12,6 +12,7 @@ go_library(
|
|||
importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver/conversion",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//staging/src/k8s.io/api/autoscaling/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
|
||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
|
||||
|
@ -22,6 +23,7 @@ go_library(
|
|||
"//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
||||
],
|
||||
|
|
|
@ -19,6 +19,7 @@ package conversion
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
@ -26,6 +27,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
typedscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
// CRConverterFactory is the factory for all CR converters.
|
||||
|
@ -77,7 +79,20 @@ func (m *CRConverterFactory) NewConverter(crd *apiextensions.CustomResourceDefin
|
|||
default:
|
||||
return nil, nil, fmt.Errorf("unknown conversion strategy %q for CRD %s", crd.Spec.Conversion.Strategy, crd.Name)
|
||||
}
|
||||
|
||||
// Determine whether we should expect to be asked to "convert" autoscaling/v1 Scale types
|
||||
convertScale := false
|
||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) {
|
||||
convertScale = crd.Spec.Subresources != nil && crd.Spec.Subresources.Scale != nil
|
||||
for _, version := range crd.Spec.Versions {
|
||||
if version.Subresources != nil && version.Subresources.Scale != nil {
|
||||
convertScale = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe = &crConverter{
|
||||
convertScale: convertScale,
|
||||
validVersions: validVersions,
|
||||
clusterScoped: crd.Spec.Scope == apiextensions.ClusterScoped,
|
||||
converter: converter,
|
||||
|
@ -96,6 +111,7 @@ type crConverterInterface interface {
|
|||
// crConverter extends the delegate converter with generic CR conversion behaviour. The delegate will implement the
|
||||
// user defined conversion strategy given in the CustomResourceDefinition.
|
||||
type crConverter struct {
|
||||
convertScale bool
|
||||
converter crConverterInterface
|
||||
validVersions map[schema.GroupVersion]bool
|
||||
clusterScoped bool
|
||||
|
@ -114,14 +130,23 @@ func (c *crConverter) ConvertFieldLabel(gvk schema.GroupVersionKind, label, valu
|
|||
}
|
||||
|
||||
func (c *crConverter) Convert(in, out, context interface{}) error {
|
||||
// Special-case typed scale conversion if this custom resource supports a scale endpoint
|
||||
if c.convertScale {
|
||||
_, isInScale := in.(*autoscalingv1.Scale)
|
||||
_, isOutScale := out.(*autoscalingv1.Scale)
|
||||
if isInScale || isOutScale {
|
||||
return typedscheme.Scheme.Convert(in, out, context)
|
||||
}
|
||||
}
|
||||
|
||||
unstructIn, ok := in.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("input type %T in not valid for unstructured conversion", in)
|
||||
return fmt.Errorf("input type %T in not valid for unstructured conversion to %T", in, out)
|
||||
}
|
||||
|
||||
unstructOut, ok := out.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("output type %T in not valid for unstructured conversion", out)
|
||||
return fmt.Errorf("output type %T in not valid for unstructured conversion from %T", out, in)
|
||||
}
|
||||
|
||||
outGVK := unstructOut.GroupVersionKind()
|
||||
|
|
|
@ -268,7 +268,15 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
}
|
||||
cr.SetResourceVersion(scale.ResourceVersion)
|
||||
|
||||
obj, _, err = r.store.Update(ctx, cr.GetName(), rest.DefaultUpdatedObjectInfo(cr), createValidation, updateValidation, false, options)
|
||||
obj, _, err = r.store.Update(
|
||||
ctx,
|
||||
cr.GetName(),
|
||||
rest.DefaultUpdatedObjectInfo(cr),
|
||||
toScaleCreateValidation(createValidation, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath),
|
||||
toScaleUpdateValidation(updateValidation, r.specReplicasPath, r.statusReplicasPath, r.labelSelectorPath),
|
||||
false,
|
||||
options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
@ -281,6 +289,30 @@ func (r *ScaleREST) Update(ctx context.Context, name string, objInfo rest.Update
|
|||
return newScale, false, err
|
||||
}
|
||||
|
||||
func toScaleCreateValidation(f rest.ValidateObjectFunc, specReplicasPath, statusReplicasPath, labelSelectorPath string) rest.ValidateObjectFunc {
|
||||
return func(obj runtime.Object) error {
|
||||
scale, _, err := scaleFromCustomResource(obj.(*unstructured.Unstructured), specReplicasPath, statusReplicasPath, labelSelectorPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f(scale)
|
||||
}
|
||||
}
|
||||
|
||||
func toScaleUpdateValidation(f rest.ValidateObjectUpdateFunc, specReplicasPath, statusReplicasPath, labelSelectorPath string) rest.ValidateObjectUpdateFunc {
|
||||
return func(obj, old runtime.Object) error {
|
||||
newScale, _, err := scaleFromCustomResource(obj.(*unstructured.Unstructured), specReplicasPath, statusReplicasPath, labelSelectorPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldScale, _, err := scaleFromCustomResource(old.(*unstructured.Unstructured), specReplicasPath, statusReplicasPath, labelSelectorPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f(newScale, oldScale)
|
||||
}
|
||||
}
|
||||
|
||||
// scaleFromCustomResource returns a scale subresource for a customresource and a bool signalling wether
|
||||
// the specReplicas value was found.
|
||||
func scaleFromCustomResource(cr *unstructured.Unstructured, specReplicasPath, statusReplicasPath, labelSelectorPath string) (*autoscalingv1.Scale, bool, error) {
|
||||
|
@ -310,6 +342,11 @@ func scaleFromCustomResource(cr *unstructured.Unstructured, specReplicasPath, st
|
|||
}
|
||||
|
||||
scale := &autoscalingv1.Scale{
|
||||
// Populate apiVersion and kind so conversion recognizes we are already in the desired GVK and doesn't try to convert
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "autoscaling/v1",
|
||||
Kind: "Scale",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: cr.GetName(),
|
||||
Namespace: cr.GetNamespace(),
|
||||
|
|
|
@ -381,6 +381,10 @@ func TestScaleGet(t *testing.T) {
|
|||
}
|
||||
|
||||
want := &autoscalingv1.Scale{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Scale",
|
||||
APIVersion: "autoscaling/v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: cr.GetName(),
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
|
|
|
@ -6,6 +6,10 @@ go_test(
|
|||
"main_test.go",
|
||||
"watch_restart_test.go",
|
||||
],
|
||||
tags = [
|
||||
"etcd",
|
||||
"integration",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
|
|
|
@ -83,6 +83,7 @@ filegroup(
|
|||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//test/integration/apiserver/admissionwebhook:all-srcs",
|
||||
"//test/integration/apiserver/apply:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_test")
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"admission_test.go",
|
||||
"main_test.go",
|
||||
],
|
||||
tags = [
|
||||
"etcd",
|
||||
"integration",
|
||||
],
|
||||
deps = [
|
||||
"//cmd/kube-apiserver/app/options:go_default_library",
|
||||
"//staging/src/k8s.io/api/admission/v1beta1:go_default_library",
|
||||
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
|
||||
"//staging/src/k8s.io/api/apps/v1beta1:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/dynamic:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/util/retry:go_default_library",
|
||||
"//test/integration/etcd:go_default_library",
|
||||
"//test/integration/framework:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
|
@ -0,0 +1,947 @@
|
|||
/*
|
||||
Copyright 2019 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 admissionwebhook
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||
appsv1beta1 "k8s.io/api/apps/v1beta1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
dynamic "k8s.io/client-go/dynamic"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
||||
"k8s.io/kubernetes/test/integration/etcd"
|
||||
)
|
||||
|
||||
const (
|
||||
testNamespace = "webhook-integration"
|
||||
|
||||
mutation = "mutation"
|
||||
validation = "validation"
|
||||
)
|
||||
|
||||
type testContext struct {
|
||||
t *testing.T
|
||||
|
||||
admissionHolder *holder
|
||||
|
||||
client dynamic.Interface
|
||||
gvr schema.GroupVersionResource
|
||||
resource metav1.APIResource
|
||||
resources map[schema.GroupVersionResource]metav1.APIResource
|
||||
}
|
||||
|
||||
type testFunc func(*testContext)
|
||||
|
||||
var (
|
||||
// defaultResourceFuncs holds the default test functions.
|
||||
// may be overridden for specific resources by customTestFuncs.
|
||||
defaultResourceFuncs = map[string]testFunc{
|
||||
"create": testResourceCreate,
|
||||
"update": testResourceUpdate,
|
||||
"patch": testResourcePatch,
|
||||
"delete": testResourceDelete,
|
||||
"deletecollection": testResourceDeletecollection,
|
||||
}
|
||||
|
||||
// defaultSubresourceFuncs holds default subresource test functions.
|
||||
// may be overridden for specific resources by customTestFuncs.
|
||||
defaultSubresourceFuncs = map[string]testFunc{
|
||||
"update": testSubresourceUpdate,
|
||||
"patch": testSubresourcePatch,
|
||||
}
|
||||
|
||||
// customTestFuncs holds custom test functions by resource and verb.
|
||||
customTestFuncs = map[schema.GroupVersionResource]map[string]testFunc{
|
||||
gvr("", "v1", "namespaces"): {"delete": testNamespaceDelete},
|
||||
gvr("apps", "v1beta1", "deployments/rollback"): {"create": testDeploymentRollback},
|
||||
gvr("extensions", "v1beta1", "deployments/rollback"): {"create": testDeploymentRollback},
|
||||
}
|
||||
|
||||
// excludedResources lists resources / verb combinations that are not yet tested. this set should trend to zero.
|
||||
excludedResources = map[schema.GroupVersionResource]sets.String{
|
||||
// TODO: verify non-persisted review objects work with webhook admission in place (and determine whether they should be sent to admission)
|
||||
gvr("authentication.k8s.io", "v1", "tokenreviews"): sets.NewString("*"),
|
||||
gvr("authentication.k8s.io", "v1beta1", "tokenreviews"): sets.NewString("*"),
|
||||
gvr("authorization.k8s.io", "v1", "localsubjectaccessreviews"): sets.NewString("*"),
|
||||
gvr("authorization.k8s.io", "v1", "subjectaccessreviews"): sets.NewString("*"),
|
||||
gvr("authorization.k8s.io", "v1", "selfsubjectaccessreviews"): sets.NewString("*"),
|
||||
gvr("authorization.k8s.io", "v1", "selfsubjectrulesreviews"): sets.NewString("*"),
|
||||
gvr("authorization.k8s.io", "v1beta1", "localsubjectaccessreviews"): sets.NewString("*"),
|
||||
gvr("authorization.k8s.io", "v1beta1", "subjectaccessreviews"): sets.NewString("*"),
|
||||
gvr("authorization.k8s.io", "v1beta1", "selfsubjectaccessreviews"): sets.NewString("*"),
|
||||
gvr("authorization.k8s.io", "v1beta1", "selfsubjectrulesreviews"): sets.NewString("*"),
|
||||
|
||||
// TODO: webhook config objects are not subject to admission, verify CRUD works and webhooks do not observe them
|
||||
gvr("admissionregistration.k8s.io", "v1beta1", "mutatingwebhookconfigurations"): sets.NewString("*"),
|
||||
gvr("admissionregistration.k8s.io", "v1beta1", "validatingwebhookconfigurations"): sets.NewString("*"),
|
||||
|
||||
// TODO: implement custom subresource tests (requires special states or requests)
|
||||
gvr("", "v1", "bindings"): sets.NewString("create"),
|
||||
gvr("", "v1", "nodes/proxy"): sets.NewString("*"),
|
||||
gvr("", "v1", "pods/attach"): sets.NewString("create"),
|
||||
gvr("", "v1", "pods/binding"): sets.NewString("create"),
|
||||
gvr("", "v1", "pods/eviction"): sets.NewString("create"),
|
||||
gvr("", "v1", "pods/exec"): sets.NewString("create"),
|
||||
gvr("", "v1", "pods/portforward"): sets.NewString("create"),
|
||||
gvr("", "v1", "pods/proxy"): sets.NewString("*"),
|
||||
gvr("", "v1", "services/proxy"): sets.NewString("*"),
|
||||
}
|
||||
|
||||
parentResources = map[schema.GroupVersionResource]schema.GroupVersionResource{
|
||||
gvr("extensions", "v1beta1", "replicationcontrollers/scale"): gvr("", "v1", "replicationcontrollers"),
|
||||
}
|
||||
)
|
||||
|
||||
type holder struct {
|
||||
lock sync.RWMutex
|
||||
|
||||
t *testing.T
|
||||
|
||||
expectGVR metav1.GroupVersionResource
|
||||
expectGVK schema.GroupVersionKind
|
||||
expectOperation v1beta1.Operation
|
||||
expectNamespace string
|
||||
expectName string
|
||||
expectObject bool
|
||||
expectOldObject bool
|
||||
|
||||
recorded map[string]*v1beta1.AdmissionRequest
|
||||
}
|
||||
|
||||
func (h *holder) reset(t *testing.T) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
h.t = t
|
||||
h.expectGVR = metav1.GroupVersionResource{}
|
||||
h.expectGVK = schema.GroupVersionKind{}
|
||||
h.expectOperation = ""
|
||||
h.expectName = ""
|
||||
h.expectNamespace = ""
|
||||
h.expectObject = false
|
||||
h.expectOldObject = false
|
||||
h.recorded = map[string]*v1beta1.AdmissionRequest{
|
||||
mutation: nil,
|
||||
validation: nil,
|
||||
}
|
||||
}
|
||||
func (h *holder) expect(gvr schema.GroupVersionResource, gvk schema.GroupVersionKind, operation v1beta1.Operation, name, namespace string, object, oldObject bool) {
|
||||
// Special-case namespaces, since the object name shows up in request attributes for update/delete requests
|
||||
if len(namespace) == 0 && gvk.Group == "" && gvk.Version == "v1" && gvk.Kind == "Namespace" && operation != v1beta1.Create {
|
||||
namespace = name
|
||||
}
|
||||
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
h.expectGVR = metav1.GroupVersionResource{Group: gvr.Group, Version: gvr.Version, Resource: gvr.Resource}
|
||||
h.expectGVK = gvk
|
||||
h.expectOperation = operation
|
||||
h.expectName = name
|
||||
h.expectNamespace = namespace
|
||||
h.expectObject = object
|
||||
h.expectOldObject = oldObject
|
||||
h.recorded = map[string]*v1beta1.AdmissionRequest{
|
||||
mutation: nil,
|
||||
validation: nil,
|
||||
}
|
||||
}
|
||||
func (h *holder) record(phase string, request *v1beta1.AdmissionRequest) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
// this is useful to turn on if items aren't getting recorded and you need to figure out why
|
||||
debug := false
|
||||
if debug {
|
||||
h.t.Logf("%s %#v %v", request.Operation, request.Resource, request.SubResource)
|
||||
}
|
||||
|
||||
resource := request.Resource
|
||||
if len(request.SubResource) > 0 {
|
||||
resource.Resource += "/" + request.SubResource
|
||||
}
|
||||
if resource != h.expectGVR {
|
||||
if debug {
|
||||
h.t.Log(resource, "!=", h.expectGVR)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if request.Operation != h.expectOperation {
|
||||
if debug {
|
||||
h.t.Log(request.Operation, "!=", h.expectOperation)
|
||||
}
|
||||
return
|
||||
}
|
||||
if request.Namespace != h.expectNamespace {
|
||||
if debug {
|
||||
h.t.Log(request.Namespace, "!=", h.expectNamespace)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
name := request.Name
|
||||
if name == "" && request.Object.Object != nil {
|
||||
name = request.Object.Object.(*unstructured.Unstructured).GetName()
|
||||
}
|
||||
if name != h.expectName {
|
||||
if debug {
|
||||
h.t.Log(name, "!=", h.expectName)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
h.recorded[phase] = request
|
||||
}
|
||||
|
||||
func (h *holder) verify(t *testing.T) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
if err := h.verifyRequest(h.recorded[mutation]); err != nil {
|
||||
t.Errorf("mutation error: %v", err)
|
||||
}
|
||||
if err := h.verifyRequest(h.recorded[validation]); err != nil {
|
||||
t.Errorf("validation error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *holder) verifyRequest(request *v1beta1.AdmissionRequest) error {
|
||||
if request == nil {
|
||||
return fmt.Errorf("no request received")
|
||||
}
|
||||
|
||||
if h.expectObject {
|
||||
if err := h.verifyObject(request.Object.Object); err != nil {
|
||||
return fmt.Errorf("object error: %v", err)
|
||||
}
|
||||
} else if request.Object.Object != nil {
|
||||
return fmt.Errorf("unexpected object: %#v", request.Object.Object)
|
||||
}
|
||||
|
||||
if h.expectOldObject {
|
||||
if err := h.verifyObject(request.OldObject.Object); err != nil {
|
||||
return fmt.Errorf("old object error: %v", err)
|
||||
}
|
||||
} else if request.OldObject.Object != nil {
|
||||
return fmt.Errorf("unexpected old object: %#v", request.OldObject.Object)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *holder) verifyObject(obj runtime.Object) error {
|
||||
if obj == nil {
|
||||
return fmt.Errorf("no object sent")
|
||||
}
|
||||
if obj.GetObjectKind().GroupVersionKind() != h.expectGVK {
|
||||
return fmt.Errorf("expected %#v, got %#v", h.expectGVK, obj.GetObjectKind().GroupVersionKind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestWebhookV1beta1 tests communication between API server and webhook process.
|
||||
func TestWebhookV1beta1(t *testing.T) {
|
||||
// holder communicates expectations to webhooks, and results from webhooks
|
||||
holder := &holder{t: t}
|
||||
|
||||
// set up webhook server
|
||||
roots := x509.NewCertPool()
|
||||
if !roots.AppendCertsFromPEM(localhostCert) {
|
||||
t.Fatal("Failed to append Cert from PEM")
|
||||
}
|
||||
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build cert with error: %+v", err)
|
||||
}
|
||||
|
||||
webhookMux := http.NewServeMux()
|
||||
webhookMux.Handle("/"+mutation, newWebhookHandler(t, holder, mutation))
|
||||
webhookMux.Handle("/"+validation, newWebhookHandler(t, holder, validation))
|
||||
webhookServer := httptest.NewUnstartedServer(webhookMux)
|
||||
webhookServer.TLS = &tls.Config{
|
||||
RootCAs: roots,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
webhookServer.StartTLS()
|
||||
defer webhookServer.Close()
|
||||
|
||||
// start API server
|
||||
master := etcd.StartRealMasterOrDie(t, func(opts *options.ServerRunOptions) {
|
||||
// turn off admission plugins that add finalizers
|
||||
opts.Admission.GenericAdmission.DisablePlugins = []string{"ServiceAccount", "StorageObjectInUseProtection"}
|
||||
|
||||
// force enable all resources so we can check storage.
|
||||
// TODO: drop these once we stop allowing them to be served.
|
||||
opts.APIEnablement.RuntimeConfig["extensions/v1beta1/deployments"] = "true"
|
||||
opts.APIEnablement.RuntimeConfig["extensions/v1beta1/daemonsets"] = "true"
|
||||
opts.APIEnablement.RuntimeConfig["extensions/v1beta1/replicasets"] = "true"
|
||||
opts.APIEnablement.RuntimeConfig["extensions/v1beta1/podsecuritypolicies"] = "true"
|
||||
opts.APIEnablement.RuntimeConfig["extensions/v1beta1/networkpolicies"] = "true"
|
||||
})
|
||||
defer master.Cleanup()
|
||||
|
||||
if _, err := master.Client.CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := createV1beta1MutationWebhook(master.Client, webhookServer.URL+"/"+mutation); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := createV1beta1ValidationWebhook(master.Client, webhookServer.URL+"/"+validation); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// gather resources to test
|
||||
dynamicClient := master.Dynamic
|
||||
_, resources, err := master.Client.Discovery().ServerGroupsAndResources()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get ServerGroupsAndResources with error: %+v", err)
|
||||
}
|
||||
|
||||
gvrsToTest := []schema.GroupVersionResource{}
|
||||
resourcesByGVR := map[schema.GroupVersionResource]metav1.APIResource{}
|
||||
|
||||
for _, list := range resources {
|
||||
defaultGroupVersion, err := schema.ParseGroupVersion(list.GroupVersion)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get GroupVersion for: %+v", list)
|
||||
continue
|
||||
}
|
||||
for _, resource := range list.APIResources {
|
||||
if resource.Group == "" {
|
||||
resource.Group = defaultGroupVersion.Group
|
||||
}
|
||||
if resource.Version == "" {
|
||||
resource.Version = defaultGroupVersion.Version
|
||||
}
|
||||
gvr := defaultGroupVersion.WithResource(resource.Name)
|
||||
resourcesByGVR[gvr] = resource
|
||||
if shouldTestResource(gvr, resource) {
|
||||
gvrsToTest = append(gvrsToTest, gvr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(gvrsToTest, func(i, j int) bool {
|
||||
if gvrsToTest[i].Group < gvrsToTest[j].Group {
|
||||
return true
|
||||
}
|
||||
if gvrsToTest[i].Group > gvrsToTest[j].Group {
|
||||
return false
|
||||
}
|
||||
if gvrsToTest[i].Version < gvrsToTest[j].Version {
|
||||
return true
|
||||
}
|
||||
if gvrsToTest[i].Version > gvrsToTest[j].Version {
|
||||
return false
|
||||
}
|
||||
if gvrsToTest[i].Resource < gvrsToTest[j].Resource {
|
||||
return true
|
||||
}
|
||||
if gvrsToTest[i].Resource > gvrsToTest[j].Resource {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// Test admission on all resources, subresources, and verbs
|
||||
for _, gvr := range gvrsToTest {
|
||||
resource := resourcesByGVR[gvr]
|
||||
t.Run(gvr.Group+"."+gvr.Version+"."+strings.ReplaceAll(resource.Name, "/", "."), func(t *testing.T) {
|
||||
for _, verb := range []string{"create", "update", "patch", "connect", "delete", "deletecollection"} {
|
||||
if shouldTestResourceVerb(gvr, resource, verb) {
|
||||
t.Run(verb, func(t *testing.T) {
|
||||
holder.reset(t)
|
||||
testFunc := getTestFunc(gvr, verb)
|
||||
testFunc(&testContext{
|
||||
t: t,
|
||||
admissionHolder: holder,
|
||||
client: dynamicClient,
|
||||
gvr: gvr,
|
||||
resource: resource,
|
||||
resources: resourcesByGVR,
|
||||
})
|
||||
holder.verify(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// generic resource testing
|
||||
//
|
||||
|
||||
func testResourceCreate(c *testContext) {
|
||||
stubObj, err := getStubObj(c.gvr, c.resource)
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
ns := ""
|
||||
if c.resource.Namespaced {
|
||||
ns = testNamespace
|
||||
}
|
||||
c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), v1beta1.Create, stubObj.GetName(), ns, true, false)
|
||||
_, err = c.client.Resource(c.gvr).Namespace(ns).Create(stubObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testResourceUpdate(c *testContext) {
|
||||
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
obj, err := createOrGetResource(c.client, c.gvr, c.resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.SetAnnotations(map[string]string{"update": "true"})
|
||||
c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true)
|
||||
_, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Update(obj, metav1.UpdateOptions{})
|
||||
return err
|
||||
}); err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testResourcePatch(c *testContext) {
|
||||
obj, err := createOrGetResource(c.client, c.gvr, c.resource)
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true)
|
||||
_, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Patch(
|
||||
obj.GetName(),
|
||||
types.MergePatchType,
|
||||
[]byte(`{"metadata":{"annotations":{"patch":"true"}}}`),
|
||||
metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testResourceDelete(c *testContext) {
|
||||
obj, err := createOrGetResource(c.client, c.gvr, c.resource)
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
background := metav1.DeletePropagationBackground
|
||||
zero := int64(0)
|
||||
c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), v1beta1.Delete, obj.GetName(), obj.GetNamespace(), false, false)
|
||||
err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Delete(obj.GetName(), &metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background})
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// wait for the item to be gone
|
||||
err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
|
||||
obj, err := c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
return true, nil
|
||||
}
|
||||
if err == nil {
|
||||
c.t.Logf("waiting for %#v to be deleted (name: %s, finalizers: %v)...\n", c.gvr, obj.GetName(), obj.GetFinalizers())
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
})
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testResourceDeletecollection(c *testContext) {
|
||||
obj, err := createOrGetResource(c.client, c.gvr, c.resource)
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
background := metav1.DeletePropagationBackground
|
||||
zero := int64(0)
|
||||
|
||||
// update the object with a label that matches our selector
|
||||
_, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Patch(
|
||||
obj.GetName(),
|
||||
types.MergePatchType,
|
||||
[]byte(`{"metadata":{"labels":{"webhooktest":"true"}}}`),
|
||||
metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// set expectations
|
||||
c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), v1beta1.Delete, "", obj.GetNamespace(), false, false)
|
||||
|
||||
// delete
|
||||
err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).DeleteCollection(&metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background}, metav1.ListOptions{LabelSelector: "webhooktest=true"})
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// wait for the item to be gone
|
||||
err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (bool, error) {
|
||||
obj, err := c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
return true, nil
|
||||
}
|
||||
if err == nil {
|
||||
c.t.Logf("waiting for %#v to be deleted (name: %s, finalizers: %v)...\n", c.gvr, obj.GetName(), obj.GetFinalizers())
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
})
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getParentGVR(gvr schema.GroupVersionResource) schema.GroupVersionResource {
|
||||
parentGVR, found := parentResources[gvr]
|
||||
// if no special override is found, just drop the subresource
|
||||
if !found {
|
||||
parentGVR = gvr
|
||||
parentGVR.Resource = strings.Split(parentGVR.Resource, "/")[0]
|
||||
}
|
||||
return parentGVR
|
||||
}
|
||||
|
||||
func testSubresourceUpdate(c *testContext) {
|
||||
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
parentGVR := getParentGVR(c.gvr)
|
||||
parentResource := c.resources[parentGVR]
|
||||
obj, err := createOrGetResource(c.client, parentGVR, parentResource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the parent object as what we submit
|
||||
submitObj := obj
|
||||
|
||||
gvrWithoutSubresources := c.gvr
|
||||
gvrWithoutSubresources.Resource = strings.Split(gvrWithoutSubresources.Resource, "/")[0]
|
||||
subresources := strings.Split(c.gvr.Resource, "/")[1:]
|
||||
|
||||
// If the subresource supports get, fetch that as the object to submit (namespaces/finalize, */scale, etc)
|
||||
if sets.NewString(c.resource.Verbs...).Has("get") {
|
||||
submitObj, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{}, subresources...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Modify the object
|
||||
submitObj.SetAnnotations(map[string]string{"subresourceupdate": "true"})
|
||||
|
||||
// set expectations
|
||||
c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true)
|
||||
|
||||
_, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Update(
|
||||
submitObj,
|
||||
metav1.UpdateOptions{},
|
||||
subresources...,
|
||||
)
|
||||
return err
|
||||
}); err != nil {
|
||||
c.t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testSubresourcePatch(c *testContext) {
|
||||
parentGVR := getParentGVR(c.gvr)
|
||||
parentResource := c.resources[parentGVR]
|
||||
obj, err := createOrGetResource(c.client, parentGVR, parentResource)
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
gvrWithoutSubresources := c.gvr
|
||||
gvrWithoutSubresources.Resource = strings.Split(gvrWithoutSubresources.Resource, "/")[0]
|
||||
subresources := strings.Split(c.gvr.Resource, "/")[1:]
|
||||
|
||||
// set expectations
|
||||
c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), v1beta1.Update, obj.GetName(), obj.GetNamespace(), true, true)
|
||||
|
||||
_, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Patch(
|
||||
obj.GetName(),
|
||||
types.MergePatchType,
|
||||
[]byte(`{"metadata":{"annotations":{"subresourcepatch":"true"}}}`),
|
||||
metav1.PatchOptions{},
|
||||
subresources...,
|
||||
)
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func unimplemented(c *testContext) {
|
||||
c.t.Errorf("Test function for %+v has not been implemented...", c.gvr)
|
||||
}
|
||||
|
||||
//
|
||||
// custom methods
|
||||
//
|
||||
|
||||
// testNamespaceDelete verifies namespace-specific delete behavior:
|
||||
// - ensures admission is called on first delete (which only sets deletionTimestamp and terminating state)
|
||||
// - removes finalizer from namespace
|
||||
// - ensures admission is called on final delete once finalizers are removed
|
||||
func testNamespaceDelete(c *testContext) {
|
||||
obj, err := createOrGetResource(c.client, c.gvr, c.resource)
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
background := metav1.DeletePropagationBackground
|
||||
zero := int64(0)
|
||||
|
||||
c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), v1beta1.Delete, obj.GetName(), obj.GetNamespace(), false, false)
|
||||
err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Delete(obj.GetName(), &metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background})
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
c.admissionHolder.verify(c.t)
|
||||
|
||||
// do the finalization so the namespace can be deleted
|
||||
obj, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
err = unstructured.SetNestedField(obj.Object, nil, "spec", "finalizers")
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
_, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Update(obj, metav1.UpdateOptions{}, "finalize")
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// then run the final delete and make sure admission is called again
|
||||
c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), v1beta1.Delete, obj.GetName(), obj.GetNamespace(), false, false)
|
||||
err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Delete(obj.GetName(), &metav1.DeleteOptions{GracePeriodSeconds: &zero, PropagationPolicy: &background})
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
c.admissionHolder.verify(c.t)
|
||||
|
||||
// verify namespace is gone
|
||||
obj, err = c.client.Resource(c.gvr).Namespace(obj.GetNamespace()).Get(obj.GetName(), metav1.GetOptions{})
|
||||
if err == nil || !errors.IsNotFound(err) {
|
||||
c.t.Errorf("expected namespace to be gone, got %#v, %v", obj, err)
|
||||
}
|
||||
}
|
||||
|
||||
// testDeploymentRollback verifies rollback-specific behavior:
|
||||
// - creates a parent deployment
|
||||
// - creates a rollback object and posts it
|
||||
func testDeploymentRollback(c *testContext) {
|
||||
deploymentGVR := gvr("apps", "v1", "deployments")
|
||||
obj, err := createOrGetResource(c.client, deploymentGVR, c.resources[deploymentGVR])
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
gvrWithoutSubresources := c.gvr
|
||||
gvrWithoutSubresources.Resource = strings.Split(gvrWithoutSubresources.Resource, "/")[0]
|
||||
subresources := strings.Split(c.gvr.Resource, "/")[1:]
|
||||
|
||||
c.admissionHolder.expect(c.gvr, gvk(c.resource.Group, c.resource.Version, c.resource.Kind), v1beta1.Create, obj.GetName(), obj.GetNamespace(), true, false)
|
||||
|
||||
var rollbackObj runtime.Object
|
||||
switch c.gvr {
|
||||
case gvr("apps", "v1beta1", "deployments/rollback"):
|
||||
rollbackObj = &appsv1beta1.DeploymentRollback{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1beta1", Kind: "DeploymentRollback"},
|
||||
Name: obj.GetName(),
|
||||
RollbackTo: appsv1beta1.RollbackConfig{Revision: 0},
|
||||
}
|
||||
case gvr("extensions", "v1beta1", "deployments/rollback"):
|
||||
rollbackObj = &extensionsv1beta1.DeploymentRollback{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "extensions/v1beta1", Kind: "DeploymentRollback"},
|
||||
Name: obj.GetName(),
|
||||
RollbackTo: extensionsv1beta1.RollbackConfig{Revision: 0},
|
||||
}
|
||||
default:
|
||||
c.t.Errorf("unknown rollback resource %#v", c.gvr)
|
||||
return
|
||||
}
|
||||
|
||||
rollbackUnstructuredBody, err := runtime.DefaultUnstructuredConverter.ToUnstructured(rollbackObj)
|
||||
if err != nil {
|
||||
c.t.Errorf("ToUnstructured failed: %v", err)
|
||||
return
|
||||
}
|
||||
rollbackUnstructuredObj := &unstructured.Unstructured{Object: rollbackUnstructuredBody}
|
||||
rollbackUnstructuredObj.SetName(obj.GetName())
|
||||
|
||||
_, err = c.client.Resource(gvrWithoutSubresources).Namespace(obj.GetNamespace()).Create(rollbackUnstructuredObj, metav1.CreateOptions{}, subresources...)
|
||||
if err != nil {
|
||||
c.t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// utility methods
|
||||
//
|
||||
|
||||
func newWebhookHandler(t *testing.T, holder *holder, phase string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
|
||||
t.Errorf("contentType=%s, expect application/json", contentType)
|
||||
return
|
||||
}
|
||||
|
||||
review := v1beta1.AdmissionReview{}
|
||||
if err := json.Unmarshal(data, &review); err != nil {
|
||||
t.Errorf("Fail to deserialize object: %s with error: %v", string(data), err)
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
}
|
||||
|
||||
if review.GetObjectKind().GroupVersionKind() != gvk("admission.k8s.io", "v1beta1", "AdmissionReview") {
|
||||
t.Errorf("Invalid admission review kind: %#v", review.GetObjectKind().GroupVersionKind())
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
}
|
||||
|
||||
if len(review.Request.Object.Raw) > 0 {
|
||||
u := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
if err := json.Unmarshal(review.Request.Object.Raw, u); err != nil {
|
||||
t.Errorf("Fail to deserialize object: %s with error: %v", string(review.Request.Object.Raw), err)
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
}
|
||||
review.Request.Object.Object = u
|
||||
}
|
||||
if len(review.Request.OldObject.Raw) > 0 {
|
||||
u := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
if err := json.Unmarshal(review.Request.OldObject.Raw, u); err != nil {
|
||||
t.Errorf("Fail to deserialize object: %s with error: %v", string(review.Request.OldObject.Raw), err)
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
}
|
||||
review.Request.OldObject.Object = u
|
||||
}
|
||||
holder.record(phase, review.Request)
|
||||
|
||||
review.Response = &v1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
UID: review.Request.UID,
|
||||
Result: &metav1.Status{Message: "admitted"},
|
||||
}
|
||||
// If we're mutating, and have an object, return a patch to exercise conversion
|
||||
if phase == mutation && len(review.Request.Object.Raw) > 0 {
|
||||
review.Response.Patch = []byte(`[{"op":"add","path":"/foo","value":"test"}]`)
|
||||
jsonPatch := v1beta1.PatchTypeJSONPatch
|
||||
review.Response.PatchType = &jsonPatch
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(review); err != nil {
|
||||
t.Errorf("Marshal of response failed with error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func getTestFunc(gvr schema.GroupVersionResource, verb string) testFunc {
|
||||
if f, found := customTestFuncs[gvr][verb]; found {
|
||||
return f
|
||||
}
|
||||
if strings.Contains(gvr.Resource, "/") {
|
||||
if f, found := defaultSubresourceFuncs[verb]; found {
|
||||
return f
|
||||
}
|
||||
return unimplemented
|
||||
}
|
||||
if f, found := defaultResourceFuncs[verb]; found {
|
||||
return f
|
||||
}
|
||||
return unimplemented
|
||||
}
|
||||
|
||||
func getStubObj(gvr schema.GroupVersionResource, resource metav1.APIResource) (*unstructured.Unstructured, error) {
|
||||
data, ok := etcd.GetEtcdStorageDataForNamespace(testNamespace)[gvr]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no stub data for %#v", gvr)
|
||||
}
|
||||
|
||||
stubObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||
if err := json.Unmarshal([]byte(data.Stub), &stubObj.Object); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling stub for %#v: %v", gvr, err)
|
||||
}
|
||||
return stubObj, nil
|
||||
}
|
||||
|
||||
func createOrGetResource(client dynamic.Interface, gvr schema.GroupVersionResource, resource metav1.APIResource) (*unstructured.Unstructured, error) {
|
||||
stubObj, err := getStubObj(gvr, resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns := ""
|
||||
if resource.Namespaced {
|
||||
ns = testNamespace
|
||||
}
|
||||
obj, err := client.Resource(gvr).Namespace(ns).Get(stubObj.GetName(), metav1.GetOptions{})
|
||||
if err == nil {
|
||||
return obj, nil
|
||||
}
|
||||
if !errors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
return client.Resource(gvr).Namespace(ns).Create(stubObj, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
func gvr(group, version, resource string) schema.GroupVersionResource {
|
||||
return schema.GroupVersionResource{Group: group, Version: version, Resource: resource}
|
||||
}
|
||||
func gvk(group, version, kind string) schema.GroupVersionKind {
|
||||
return schema.GroupVersionKind{Group: group, Version: version, Kind: kind}
|
||||
}
|
||||
|
||||
func shouldTestResource(gvr schema.GroupVersionResource, resource metav1.APIResource) bool {
|
||||
if !sets.NewString(resource.Verbs...).HasAny("create", "update", "patch", "connect", "delete", "deletecollection") {
|
||||
return false
|
||||
}
|
||||
return !excludedResources[gvr].Has("*")
|
||||
}
|
||||
|
||||
func shouldTestResourceVerb(gvr schema.GroupVersionResource, resource metav1.APIResource, verb string) bool {
|
||||
if !sets.NewString(resource.Verbs...).Has(verb) {
|
||||
return false
|
||||
}
|
||||
return !excludedResources[gvr].Has(verb)
|
||||
}
|
||||
|
||||
//
|
||||
// webhook registration helpers
|
||||
//
|
||||
|
||||
func createV1beta1ValidationWebhook(client clientset.Interface, endpoint string) error {
|
||||
fail := admissionv1beta1.Fail
|
||||
// Attaching Admission webhook to API server
|
||||
_, err := client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(&admissionv1beta1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "admission.integration.test"},
|
||||
Webhooks: []admissionv1beta1.Webhook{{
|
||||
Name: "admission.integration.test",
|
||||
ClientConfig: admissionv1beta1.WebhookClientConfig{
|
||||
URL: &endpoint,
|
||||
CABundle: localhostCert,
|
||||
},
|
||||
Rules: []admissionv1beta1.RuleWithOperations{{
|
||||
Operations: []admissionv1beta1.OperationType{admissionv1beta1.OperationAll},
|
||||
Rule: admissionv1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}},
|
||||
}},
|
||||
FailurePolicy: &fail,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func createV1beta1MutationWebhook(client clientset.Interface, endpoint string) error {
|
||||
fail := admissionv1beta1.Fail
|
||||
// Attaching Mutation webhook to API server
|
||||
_, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mutation.integration.test"},
|
||||
Webhooks: []admissionv1beta1.Webhook{{
|
||||
Name: "mutation.integration.test",
|
||||
ClientConfig: admissionv1beta1.WebhookClientConfig{
|
||||
URL: &endpoint,
|
||||
CABundle: localhostCert,
|
||||
},
|
||||
Rules: []admissionv1beta1.RuleWithOperations{{
|
||||
Operations: []admissionv1beta1.OperationType{admissionv1beta1.OperationAll},
|
||||
Rule: admissionv1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}},
|
||||
}},
|
||||
FailurePolicy: &fail,
|
||||
AdmissionReviewVersions: []string{"v1beta1"},
|
||||
}},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// localhostCert was generated from crypto/tls/generate_cert.go with the following command:
|
||||
// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||
var localhostCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIBjzCCATmgAwIBAgIRAKpi2WmTcFrVjxrl5n5YDUEwDQYJKoZIhvcNAQELBQAw
|
||||
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
|
||||
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC
|
||||
QQC9fEbRszP3t14Gr4oahV7zFObBI4TfA5i7YnlMXeLinb7MnvT4bkfOJzE6zktn
|
||||
59zP7UiHs3l4YOuqrjiwM413AgMBAAGjaDBmMA4GA1UdDwEB/wQEAwICpDATBgNV
|
||||
HSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MC4GA1UdEQQnMCWCC2V4
|
||||
YW1wbGUuY29thwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUA
|
||||
A0EAUsVE6KMnza/ZbodLlyeMzdo7EM/5nb5ywyOxgIOCf0OOLHsPS9ueGLQX9HEG
|
||||
//yjTXuhNcUugExIjM/AIwAZPQ==
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
// localhostKey is the private key for localhostCert.
|
||||
var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIBOwIBAAJBAL18RtGzM/e3XgavihqFXvMU5sEjhN8DmLtieUxd4uKdvsye9Phu
|
||||
R84nMTrOS2fn3M/tSIezeXhg66quOLAzjXcCAwEAAQJBAKcRxH9wuglYLBdI/0OT
|
||||
BLzfWPZCEw1vZmMR2FF1Fm8nkNOVDPleeVGTWoOEcYYlQbpTmkGSxJ6ya+hqRi6x
|
||||
goECIQDx3+X49fwpL6B5qpJIJMyZBSCuMhH4B7JevhGGFENi3wIhAMiNJN5Q3UkL
|
||||
IuSvv03kaPR5XVQ99/UeEetUgGvBcABpAiBJSBzVITIVCGkGc7d+RCf49KTCIklv
|
||||
bGWObufAR8Ni4QIgWpILjW8dkGg8GOUZ0zaNA6Nvt6TIv2UWGJ4v5PoV98kCIQDx
|
||||
rIiZs5QbKdycsv9gQJzwQAogC8o04X3Zz3dsoX+h4A==
|
||||
-----END RSA PRIVATE KEY-----`)
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
Copyright 2019 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 admissionwebhook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
framework.EtcdMain(m.Run)
|
||||
}
|
|
@ -28,35 +28,43 @@ import (
|
|||
// It is exported so that it can be reused across multiple tests.
|
||||
// It returns a new map on every invocation to prevent different tests from mutating shared state.
|
||||
func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
||||
return GetEtcdStorageDataForNamespace("etcdstoragepathtestnamespace")
|
||||
}
|
||||
|
||||
// GetEtcdStorageDataForNamespace returns etcd data for all persisted objects.
|
||||
// It is exported so that it can be reused across multiple tests.
|
||||
// It returns a new map on every invocation to prevent different tests from mutating shared state.
|
||||
// Namespaced objects keys are computed for the specified namespace.
|
||||
func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionResource]StorageData {
|
||||
etcdStorageData := map[schema.GroupVersionResource]StorageData{
|
||||
// k8s.io/kubernetes/pkg/api/v1
|
||||
gvr("", "v1", "configmaps"): {
|
||||
Stub: `{"data": {"foo": "bar"}, "metadata": {"name": "cm1"}}`,
|
||||
ExpectedEtcdPath: "/registry/configmaps/etcdstoragepathtestnamespace/cm1",
|
||||
ExpectedEtcdPath: "/registry/configmaps/" + namespace + "/cm1",
|
||||
},
|
||||
gvr("", "v1", "services"): {
|
||||
Stub: `{"metadata": {"name": "service1"}, "spec": {"externalName": "service1name", "ports": [{"port": 10000, "targetPort": 11000}], "selector": {"test": "data"}}}`,
|
||||
ExpectedEtcdPath: "/registry/services/specs/etcdstoragepathtestnamespace/service1",
|
||||
ExpectedEtcdPath: "/registry/services/specs/" + namespace + "/service1",
|
||||
},
|
||||
gvr("", "v1", "podtemplates"): {
|
||||
Stub: `{"metadata": {"name": "pt1name"}, "template": {"metadata": {"labels": {"pt": "01"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container9"}]}}}`,
|
||||
ExpectedEtcdPath: "/registry/podtemplates/etcdstoragepathtestnamespace/pt1name",
|
||||
ExpectedEtcdPath: "/registry/podtemplates/" + namespace + "/pt1name",
|
||||
},
|
||||
gvr("", "v1", "pods"): {
|
||||
Stub: `{"metadata": {"name": "pod1"}, "spec": {"containers": [{"image": "fedora:latest", "name": "container7", "resources": {"limits": {"cpu": "1M"}, "requests": {"cpu": "1M"}}}]}}`,
|
||||
ExpectedEtcdPath: "/registry/pods/etcdstoragepathtestnamespace/pod1",
|
||||
ExpectedEtcdPath: "/registry/pods/" + namespace + "/pod1",
|
||||
},
|
||||
gvr("", "v1", "endpoints"): {
|
||||
Stub: `{"metadata": {"name": "ep1name"}, "subsets": [{"addresses": [{"hostname": "bar-001", "ip": "192.168.3.1"}], "ports": [{"port": 8000}]}]}`,
|
||||
ExpectedEtcdPath: "/registry/services/endpoints/etcdstoragepathtestnamespace/ep1name",
|
||||
ExpectedEtcdPath: "/registry/services/endpoints/" + namespace + "/ep1name",
|
||||
},
|
||||
gvr("", "v1", "resourcequotas"): {
|
||||
Stub: `{"metadata": {"name": "rq1name"}, "spec": {"hard": {"cpu": "5M"}}}`,
|
||||
ExpectedEtcdPath: "/registry/resourcequotas/etcdstoragepathtestnamespace/rq1name",
|
||||
ExpectedEtcdPath: "/registry/resourcequotas/" + namespace + "/rq1name",
|
||||
},
|
||||
gvr("", "v1", "limitranges"): {
|
||||
Stub: `{"metadata": {"name": "lr1name"}, "spec": {"limits": [{"type": "Pod"}]}}`,
|
||||
ExpectedEtcdPath: "/registry/limitranges/etcdstoragepathtestnamespace/lr1name",
|
||||
ExpectedEtcdPath: "/registry/limitranges/" + namespace + "/lr1name",
|
||||
},
|
||||
gvr("", "v1", "namespaces"): {
|
||||
Stub: `{"metadata": {"name": "namespace1"}, "spec": {"finalizers": ["kubernetes"]}}`,
|
||||
|
@ -71,41 +79,41 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
ExpectedEtcdPath: "/registry/persistentvolumes/pv1name",
|
||||
},
|
||||
gvr("", "v1", "events"): {
|
||||
Stub: `{"involvedObject": {"namespace": "etcdstoragepathtestnamespace"}, "message": "some data here", "metadata": {"name": "event1"}}`,
|
||||
ExpectedEtcdPath: "/registry/events/etcdstoragepathtestnamespace/event1",
|
||||
Stub: `{"involvedObject": {"namespace": "` + namespace + `"}, "message": "some data here", "metadata": {"name": "event1"}}`,
|
||||
ExpectedEtcdPath: "/registry/events/" + namespace + "/event1",
|
||||
},
|
||||
gvr("", "v1", "persistentvolumeclaims"): {
|
||||
Stub: `{"metadata": {"name": "pvc1"}, "spec": {"accessModes": ["ReadWriteOnce"], "resources": {"limits": {"storage": "1M"}, "requests": {"storage": "2M"}}, "selector": {"matchLabels": {"pvc": "stuff"}}}}`,
|
||||
ExpectedEtcdPath: "/registry/persistentvolumeclaims/etcdstoragepathtestnamespace/pvc1",
|
||||
ExpectedEtcdPath: "/registry/persistentvolumeclaims/" + namespace + "/pvc1",
|
||||
},
|
||||
gvr("", "v1", "serviceaccounts"): {
|
||||
Stub: `{"metadata": {"name": "sa1name"}, "secrets": [{"name": "secret00"}]}`,
|
||||
ExpectedEtcdPath: "/registry/serviceaccounts/etcdstoragepathtestnamespace/sa1name",
|
||||
ExpectedEtcdPath: "/registry/serviceaccounts/" + namespace + "/sa1name",
|
||||
},
|
||||
gvr("", "v1", "secrets"): {
|
||||
Stub: `{"data": {"key": "ZGF0YSBmaWxl"}, "metadata": {"name": "secret1"}}`,
|
||||
ExpectedEtcdPath: "/registry/secrets/etcdstoragepathtestnamespace/secret1",
|
||||
ExpectedEtcdPath: "/registry/secrets/" + namespace + "/secret1",
|
||||
},
|
||||
gvr("", "v1", "replicationcontrollers"): {
|
||||
Stub: `{"metadata": {"name": "rc1"}, "spec": {"selector": {"new": "stuff"}, "template": {"metadata": {"labels": {"new": "stuff"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container8"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/controllers/etcdstoragepathtestnamespace/rc1",
|
||||
ExpectedEtcdPath: "/registry/controllers/" + namespace + "/rc1",
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/kubernetes/pkg/apis/apps/v1beta1
|
||||
gvr("apps", "v1beta1", "statefulsets"): {
|
||||
Stub: `{"metadata": {"name": "ss1"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}}}}`,
|
||||
ExpectedEtcdPath: "/registry/statefulsets/etcdstoragepathtestnamespace/ss1",
|
||||
ExpectedEtcdPath: "/registry/statefulsets/" + namespace + "/ss1",
|
||||
ExpectedGVK: gvkP("apps", "v1", "StatefulSet"),
|
||||
},
|
||||
gvr("apps", "v1beta1", "deployments"): {
|
||||
Stub: `{"metadata": {"name": "deployment2"}, "spec": {"selector": {"matchLabels": {"f": "z"}}, "template": {"metadata": {"labels": {"f": "z"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container6"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/deployments/etcdstoragepathtestnamespace/deployment2",
|
||||
ExpectedEtcdPath: "/registry/deployments/" + namespace + "/deployment2",
|
||||
ExpectedGVK: gvkP("apps", "v1", "Deployment"),
|
||||
},
|
||||
gvr("apps", "v1beta1", "controllerrevisions"): {
|
||||
Stub: `{"metadata":{"name":"crs1"},"data":{"name":"abc","namespace":"default","creationTimestamp":null,"Spec":{"Replicas":0,"Selector":{"matchLabels":{"foo":"bar"}},"Template":{"creationTimestamp":null,"labels":{"foo":"bar"},"Spec":{"Volumes":null,"InitContainers":null,"Containers":null,"RestartPolicy":"Always","TerminationGracePeriodSeconds":null,"ActiveDeadlineSeconds":null,"DNSPolicy":"ClusterFirst","NodeSelector":null,"ServiceAccountName":"","AutomountServiceAccountToken":null,"NodeName":"","SecurityContext":null,"ImagePullSecrets":null,"Hostname":"","Subdomain":"","Affinity":null,"SchedulerName":"","Tolerations":null,"HostAliases":null}},"VolumeClaimTemplates":null,"ServiceName":""},"Status":{"ObservedGeneration":null,"Replicas":0}},"revision":0}`,
|
||||
ExpectedEtcdPath: "/registry/controllerrevisions/etcdstoragepathtestnamespace/crs1",
|
||||
ExpectedEtcdPath: "/registry/controllerrevisions/" + namespace + "/crs1",
|
||||
ExpectedGVK: gvkP("apps", "v1", "ControllerRevision"),
|
||||
},
|
||||
// --
|
||||
|
@ -113,27 +121,27 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/apps/v1beta2
|
||||
gvr("apps", "v1beta2", "statefulsets"): {
|
||||
Stub: `{"metadata": {"name": "ss2"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}}}}`,
|
||||
ExpectedEtcdPath: "/registry/statefulsets/etcdstoragepathtestnamespace/ss2",
|
||||
ExpectedEtcdPath: "/registry/statefulsets/" + namespace + "/ss2",
|
||||
ExpectedGVK: gvkP("apps", "v1", "StatefulSet"),
|
||||
},
|
||||
gvr("apps", "v1beta2", "deployments"): {
|
||||
Stub: `{"metadata": {"name": "deployment3"}, "spec": {"selector": {"matchLabels": {"f": "z"}}, "template": {"metadata": {"labels": {"f": "z"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container6"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/deployments/etcdstoragepathtestnamespace/deployment3",
|
||||
ExpectedEtcdPath: "/registry/deployments/" + namespace + "/deployment3",
|
||||
ExpectedGVK: gvkP("apps", "v1", "Deployment"),
|
||||
},
|
||||
gvr("apps", "v1beta2", "daemonsets"): {
|
||||
Stub: `{"metadata": {"name": "ds5"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container6"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/daemonsets/etcdstoragepathtestnamespace/ds5",
|
||||
ExpectedEtcdPath: "/registry/daemonsets/" + namespace + "/ds5",
|
||||
ExpectedGVK: gvkP("apps", "v1", "DaemonSet"),
|
||||
},
|
||||
gvr("apps", "v1beta2", "replicasets"): {
|
||||
Stub: `{"metadata": {"name": "rs2"}, "spec": {"selector": {"matchLabels": {"g": "h"}}, "template": {"metadata": {"labels": {"g": "h"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container4"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/replicasets/etcdstoragepathtestnamespace/rs2",
|
||||
ExpectedEtcdPath: "/registry/replicasets/" + namespace + "/rs2",
|
||||
ExpectedGVK: gvkP("apps", "v1", "ReplicaSet"),
|
||||
},
|
||||
gvr("apps", "v1beta2", "controllerrevisions"): {
|
||||
Stub: `{"metadata":{"name":"crs2"},"data":{"name":"abc","namespace":"default","creationTimestamp":null,"Spec":{"Replicas":0,"Selector":{"matchLabels":{"foo":"bar"}},"Template":{"creationTimestamp":null,"labels":{"foo":"bar"},"Spec":{"Volumes":null,"InitContainers":null,"Containers":null,"RestartPolicy":"Always","TerminationGracePeriodSeconds":null,"ActiveDeadlineSeconds":null,"DNSPolicy":"ClusterFirst","NodeSelector":null,"ServiceAccountName":"","AutomountServiceAccountToken":null,"NodeName":"","SecurityContext":null,"ImagePullSecrets":null,"Hostname":"","Subdomain":"","Affinity":null,"SchedulerName":"","Tolerations":null,"HostAliases":null}},"VolumeClaimTemplates":null,"ServiceName":""},"Status":{"ObservedGeneration":null,"Replicas":0}},"revision":0}`,
|
||||
ExpectedEtcdPath: "/registry/controllerrevisions/etcdstoragepathtestnamespace/crs2",
|
||||
ExpectedEtcdPath: "/registry/controllerrevisions/" + namespace + "/crs2",
|
||||
ExpectedGVK: gvkP("apps", "v1", "ControllerRevision"),
|
||||
},
|
||||
// --
|
||||
|
@ -141,37 +149,37 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/apps/v1
|
||||
gvr("apps", "v1", "daemonsets"): {
|
||||
Stub: `{"metadata": {"name": "ds6"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container6"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/daemonsets/etcdstoragepathtestnamespace/ds6",
|
||||
ExpectedEtcdPath: "/registry/daemonsets/" + namespace + "/ds6",
|
||||
},
|
||||
gvr("apps", "v1", "deployments"): {
|
||||
Stub: `{"metadata": {"name": "deployment4"}, "spec": {"selector": {"matchLabels": {"f": "z"}}, "template": {"metadata": {"labels": {"f": "z"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container6"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/deployments/etcdstoragepathtestnamespace/deployment4",
|
||||
ExpectedEtcdPath: "/registry/deployments/" + namespace + "/deployment4",
|
||||
},
|
||||
gvr("apps", "v1", "statefulsets"): {
|
||||
Stub: `{"metadata": {"name": "ss3"}, "spec": {"selector": {"matchLabels": {"a": "b"}}, "template": {"metadata": {"labels": {"a": "b"}}}}}`,
|
||||
ExpectedEtcdPath: "/registry/statefulsets/etcdstoragepathtestnamespace/ss3",
|
||||
ExpectedEtcdPath: "/registry/statefulsets/" + namespace + "/ss3",
|
||||
},
|
||||
gvr("apps", "v1", "replicasets"): {
|
||||
Stub: `{"metadata": {"name": "rs3"}, "spec": {"selector": {"matchLabels": {"g": "h"}}, "template": {"metadata": {"labels": {"g": "h"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container4"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/replicasets/etcdstoragepathtestnamespace/rs3",
|
||||
ExpectedEtcdPath: "/registry/replicasets/" + namespace + "/rs3",
|
||||
},
|
||||
gvr("apps", "v1", "controllerrevisions"): {
|
||||
Stub: `{"metadata":{"name":"crs3"},"data":{"name":"abc","namespace":"default","creationTimestamp":null,"Spec":{"Replicas":0,"Selector":{"matchLabels":{"foo":"bar"}},"Template":{"creationTimestamp":null,"labels":{"foo":"bar"},"Spec":{"Volumes":null,"InitContainers":null,"Containers":null,"RestartPolicy":"Always","TerminationGracePeriodSeconds":null,"ActiveDeadlineSeconds":null,"DNSPolicy":"ClusterFirst","NodeSelector":null,"ServiceAccountName":"","AutomountServiceAccountToken":null,"NodeName":"","SecurityContext":null,"ImagePullSecrets":null,"Hostname":"","Subdomain":"","Affinity":null,"SchedulerName":"","Tolerations":null,"HostAliases":null}},"VolumeClaimTemplates":null,"ServiceName":""},"Status":{"ObservedGeneration":null,"Replicas":0}},"revision":0}`,
|
||||
ExpectedEtcdPath: "/registry/controllerrevisions/etcdstoragepathtestnamespace/crs3",
|
||||
ExpectedEtcdPath: "/registry/controllerrevisions/" + namespace + "/crs3",
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/kubernetes/pkg/apis/autoscaling/v1
|
||||
gvr("autoscaling", "v1", "horizontalpodautoscalers"): {
|
||||
Stub: `{"metadata": {"name": "hpa2"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
|
||||
ExpectedEtcdPath: "/registry/horizontalpodautoscalers/etcdstoragepathtestnamespace/hpa2",
|
||||
ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa2",
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/kubernetes/pkg/apis/autoscaling/v2beta1
|
||||
gvr("autoscaling", "v2beta1", "horizontalpodautoscalers"): {
|
||||
Stub: `{"metadata": {"name": "hpa1"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
|
||||
ExpectedEtcdPath: "/registry/horizontalpodautoscalers/etcdstoragepathtestnamespace/hpa1",
|
||||
ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa1",
|
||||
ExpectedGVK: gvkP("autoscaling", "v1", "HorizontalPodAutoscaler"),
|
||||
},
|
||||
// --
|
||||
|
@ -179,7 +187,7 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/autoscaling/v2beta2
|
||||
gvr("autoscaling", "v2beta2", "horizontalpodautoscalers"): {
|
||||
Stub: `{"metadata": {"name": "hpa3"}, "spec": {"maxReplicas": 3, "scaleTargetRef": {"kind": "something", "name": "cross"}}}`,
|
||||
ExpectedEtcdPath: "/registry/horizontalpodautoscalers/etcdstoragepathtestnamespace/hpa3",
|
||||
ExpectedEtcdPath: "/registry/horizontalpodautoscalers/" + namespace + "/hpa3",
|
||||
ExpectedGVK: gvkP("autoscaling", "v1", "HorizontalPodAutoscaler"),
|
||||
},
|
||||
// --
|
||||
|
@ -187,21 +195,21 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/batch/v1
|
||||
gvr("batch", "v1", "jobs"): {
|
||||
Stub: `{"metadata": {"name": "job1"}, "spec": {"manualSelector": true, "selector": {"matchLabels": {"controller-uid": "uid1"}}, "template": {"metadata": {"labels": {"controller-uid": "uid1"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container1"}], "dnsPolicy": "ClusterFirst", "restartPolicy": "Never"}}}}`,
|
||||
ExpectedEtcdPath: "/registry/jobs/etcdstoragepathtestnamespace/job1",
|
||||
ExpectedEtcdPath: "/registry/jobs/" + namespace + "/job1",
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/kubernetes/pkg/apis/batch/v1beta1
|
||||
gvr("batch", "v1beta1", "cronjobs"): {
|
||||
Stub: `{"metadata": {"name": "cjv1beta1"}, "spec": {"jobTemplate": {"spec": {"template": {"metadata": {"labels": {"controller-uid": "uid0"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container0"}], "dnsPolicy": "ClusterFirst", "restartPolicy": "Never"}}}}, "schedule": "* * * * *"}}`,
|
||||
ExpectedEtcdPath: "/registry/cronjobs/etcdstoragepathtestnamespace/cjv1beta1",
|
||||
ExpectedEtcdPath: "/registry/cronjobs/" + namespace + "/cjv1beta1",
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/kubernetes/pkg/apis/batch/v2alpha1
|
||||
gvr("batch", "v2alpha1", "cronjobs"): {
|
||||
Stub: `{"metadata": {"name": "cjv2alpha1"}, "spec": {"jobTemplate": {"spec": {"template": {"metadata": {"labels": {"controller-uid": "uid0"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container0"}], "dnsPolicy": "ClusterFirst", "restartPolicy": "Never"}}}}, "schedule": "* * * * *"}}`,
|
||||
ExpectedEtcdPath: "/registry/cronjobs/etcdstoragepathtestnamespace/cjv2alpha1",
|
||||
ExpectedEtcdPath: "/registry/cronjobs/" + namespace + "/cjv2alpha1",
|
||||
ExpectedGVK: gvkP("batch", "v1beta1", "CronJob"),
|
||||
},
|
||||
// --
|
||||
|
@ -216,7 +224,7 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/coordination/v1
|
||||
gvr("coordination.k8s.io", "v1", "leases"): {
|
||||
Stub: `{"metadata": {"name": "leasev1"}, "spec": {"holderIdentity": "holder", "leaseDurationSeconds": 5}}`,
|
||||
ExpectedEtcdPath: "/registry/leases/etcdstoragepathtestnamespace/leasev1",
|
||||
ExpectedEtcdPath: "/registry/leases/" + namespace + "/leasev1",
|
||||
ExpectedGVK: gvkP("coordination.k8s.io", "v1beta1", "Lease"),
|
||||
},
|
||||
// --
|
||||
|
@ -224,14 +232,14 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/coordination/v1beta1
|
||||
gvr("coordination.k8s.io", "v1beta1", "leases"): {
|
||||
Stub: `{"metadata": {"name": "leasev1beta1"}, "spec": {"holderIdentity": "holder", "leaseDurationSeconds": 5}}`,
|
||||
ExpectedEtcdPath: "/registry/leases/etcdstoragepathtestnamespace/leasev1beta1",
|
||||
ExpectedEtcdPath: "/registry/leases/" + namespace + "/leasev1beta1",
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/kubernetes/pkg/apis/events/v1beta1
|
||||
gvr("events.k8s.io", "v1beta1", "events"): {
|
||||
Stub: `{"metadata": {"name": "event2"}, "regarding": {"namespace": "etcdstoragepathtestnamespace"}, "note": "some data here", "eventTime": "2017-08-09T15:04:05.000000Z", "reportingInstance": "node-xyz", "reportingController": "k8s.io/my-controller", "action": "DidNothing", "reason": "Laziness"}`,
|
||||
ExpectedEtcdPath: "/registry/events/etcdstoragepathtestnamespace/event2",
|
||||
Stub: `{"metadata": {"name": "event2"}, "regarding": {"namespace": "` + namespace + `"}, "note": "some data here", "eventTime": "2017-08-09T15:04:05.000000Z", "reportingInstance": "node-xyz", "reportingController": "k8s.io/my-controller", "action": "DidNothing", "reason": "Laziness"}`,
|
||||
ExpectedEtcdPath: "/registry/events/" + namespace + "/event2",
|
||||
ExpectedGVK: gvkP("", "v1", "Event"),
|
||||
},
|
||||
// --
|
||||
|
@ -239,7 +247,7 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/extensions/v1beta1
|
||||
gvr("extensions", "v1beta1", "daemonsets"): {
|
||||
Stub: `{"metadata": {"name": "ds1"}, "spec": {"selector": {"matchLabels": {"u": "t"}}, "template": {"metadata": {"labels": {"u": "t"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container5"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/daemonsets/etcdstoragepathtestnamespace/ds1",
|
||||
ExpectedEtcdPath: "/registry/daemonsets/" + namespace + "/ds1",
|
||||
ExpectedGVK: gvkP("apps", "v1", "DaemonSet"),
|
||||
},
|
||||
gvr("extensions", "v1beta1", "podsecuritypolicies"): {
|
||||
|
@ -249,21 +257,21 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
},
|
||||
gvr("extensions", "v1beta1", "ingresses"): {
|
||||
Stub: `{"metadata": {"name": "ingress1"}, "spec": {"backend": {"serviceName": "service", "servicePort": 5000}}}`,
|
||||
ExpectedEtcdPath: "/registry/ingress/etcdstoragepathtestnamespace/ingress1",
|
||||
ExpectedEtcdPath: "/registry/ingress/" + namespace + "/ingress1",
|
||||
},
|
||||
gvr("extensions", "v1beta1", "networkpolicies"): {
|
||||
Stub: `{"metadata": {"name": "np1"}, "spec": {"podSelector": {"matchLabels": {"e": "f"}}}}`,
|
||||
ExpectedEtcdPath: "/registry/networkpolicies/etcdstoragepathtestnamespace/np1",
|
||||
ExpectedEtcdPath: "/registry/networkpolicies/" + namespace + "/np1",
|
||||
ExpectedGVK: gvkP("networking.k8s.io", "v1", "NetworkPolicy"),
|
||||
},
|
||||
gvr("extensions", "v1beta1", "deployments"): {
|
||||
Stub: `{"metadata": {"name": "deployment1"}, "spec": {"selector": {"matchLabels": {"f": "z"}}, "template": {"metadata": {"labels": {"f": "z"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container6"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/deployments/etcdstoragepathtestnamespace/deployment1",
|
||||
ExpectedEtcdPath: "/registry/deployments/" + namespace + "/deployment1",
|
||||
ExpectedGVK: gvkP("apps", "v1", "Deployment"),
|
||||
},
|
||||
gvr("extensions", "v1beta1", "replicasets"): {
|
||||
Stub: `{"metadata": {"name": "rs1"}, "spec": {"selector": {"matchLabels": {"g": "h"}}, "template": {"metadata": {"labels": {"g": "h"}}, "spec": {"containers": [{"image": "fedora:latest", "name": "container4"}]}}}}`,
|
||||
ExpectedEtcdPath: "/registry/replicasets/etcdstoragepathtestnamespace/rs1",
|
||||
ExpectedEtcdPath: "/registry/replicasets/" + namespace + "/rs1",
|
||||
ExpectedGVK: gvkP("apps", "v1", "ReplicaSet"),
|
||||
},
|
||||
// --
|
||||
|
@ -271,7 +279,7 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/networking/v1beta1
|
||||
gvr("networking.k8s.io", "v1beta1", "ingresses"): {
|
||||
Stub: `{"metadata": {"name": "ingress2"}, "spec": {"backend": {"serviceName": "service", "servicePort": 5000}}}`,
|
||||
ExpectedEtcdPath: "/registry/ingress/etcdstoragepathtestnamespace/ingress2",
|
||||
ExpectedEtcdPath: "/registry/ingress/" + namespace + "/ingress2",
|
||||
ExpectedGVK: gvkP("extensions", "v1beta1", "Ingress"),
|
||||
},
|
||||
// --
|
||||
|
@ -279,14 +287,14 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/networking/v1
|
||||
gvr("networking.k8s.io", "v1", "networkpolicies"): {
|
||||
Stub: `{"metadata": {"name": "np2"}, "spec": {"podSelector": {"matchLabels": {"e": "f"}}}}`,
|
||||
ExpectedEtcdPath: "/registry/networkpolicies/etcdstoragepathtestnamespace/np2",
|
||||
ExpectedEtcdPath: "/registry/networkpolicies/" + namespace + "/np2",
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/kubernetes/pkg/apis/policy/v1beta1
|
||||
gvr("policy", "v1beta1", "poddisruptionbudgets"): {
|
||||
Stub: `{"metadata": {"name": "pdb1"}, "spec": {"selector": {"matchLabels": {"anokkey": "anokvalue"}}}}`,
|
||||
ExpectedEtcdPath: "/registry/poddisruptionbudgets/etcdstoragepathtestnamespace/pdb1",
|
||||
ExpectedEtcdPath: "/registry/poddisruptionbudgets/" + namespace + "/pdb1",
|
||||
},
|
||||
gvr("policy", "v1beta1", "podsecuritypolicies"): {
|
||||
Stub: `{"metadata": {"name": "psp2"}, "spec": {"fsGroup": {"rule": "RunAsAny"}, "privileged": true, "runAsUser": {"rule": "RunAsAny"}, "seLinux": {"rule": "MustRunAs"}, "supplementalGroups": {"rule": "RunAsAny"}}}`,
|
||||
|
@ -335,14 +343,14 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/settings/v1alpha1
|
||||
gvr("settings.k8s.io", "v1alpha1", "podpresets"): {
|
||||
Stub: `{"metadata": {"name": "podpre1"}, "spec": {"env": [{"name": "FOO"}]}}`,
|
||||
ExpectedEtcdPath: "/registry/podpresets/etcdstoragepathtestnamespace/podpre1",
|
||||
ExpectedEtcdPath: "/registry/podpresets/" + namespace + "/podpre1",
|
||||
},
|
||||
// --
|
||||
|
||||
// k8s.io/kubernetes/pkg/apis/rbac/v1alpha1
|
||||
gvr("rbac.authorization.k8s.io", "v1alpha1", "roles"): {
|
||||
Stub: `{"metadata": {"name": "role1"}, "rules": [{"apiGroups": ["v1"], "resources": ["events"], "verbs": ["watch"]}]}`,
|
||||
ExpectedEtcdPath: "/registry/roles/etcdstoragepathtestnamespace/role1",
|
||||
ExpectedEtcdPath: "/registry/roles/" + namespace + "/role1",
|
||||
ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "Role"),
|
||||
},
|
||||
gvr("rbac.authorization.k8s.io", "v1alpha1", "clusterroles"): {
|
||||
|
@ -352,7 +360,7 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
},
|
||||
gvr("rbac.authorization.k8s.io", "v1alpha1", "rolebindings"): {
|
||||
Stub: `{"metadata": {"name": "roleb1"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
|
||||
ExpectedEtcdPath: "/registry/rolebindings/etcdstoragepathtestnamespace/roleb1",
|
||||
ExpectedEtcdPath: "/registry/rolebindings/" + namespace + "/roleb1",
|
||||
ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "RoleBinding"),
|
||||
},
|
||||
gvr("rbac.authorization.k8s.io", "v1alpha1", "clusterrolebindings"): {
|
||||
|
@ -365,7 +373,7 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/rbac/v1beta1
|
||||
gvr("rbac.authorization.k8s.io", "v1beta1", "roles"): {
|
||||
Stub: `{"metadata": {"name": "role2"}, "rules": [{"apiGroups": ["v1"], "resources": ["events"], "verbs": ["watch"]}]}`,
|
||||
ExpectedEtcdPath: "/registry/roles/etcdstoragepathtestnamespace/role2",
|
||||
ExpectedEtcdPath: "/registry/roles/" + namespace + "/role2",
|
||||
ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "Role"),
|
||||
},
|
||||
gvr("rbac.authorization.k8s.io", "v1beta1", "clusterroles"): {
|
||||
|
@ -375,7 +383,7 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
},
|
||||
gvr("rbac.authorization.k8s.io", "v1beta1", "rolebindings"): {
|
||||
Stub: `{"metadata": {"name": "roleb2"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
|
||||
ExpectedEtcdPath: "/registry/rolebindings/etcdstoragepathtestnamespace/roleb2",
|
||||
ExpectedEtcdPath: "/registry/rolebindings/" + namespace + "/roleb2",
|
||||
ExpectedGVK: gvkP("rbac.authorization.k8s.io", "v1", "RoleBinding"),
|
||||
},
|
||||
gvr("rbac.authorization.k8s.io", "v1beta1", "clusterrolebindings"): {
|
||||
|
@ -388,7 +396,7 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
// k8s.io/kubernetes/pkg/apis/rbac/v1
|
||||
gvr("rbac.authorization.k8s.io", "v1", "roles"): {
|
||||
Stub: `{"metadata": {"name": "role3"}, "rules": [{"apiGroups": ["v1"], "resources": ["events"], "verbs": ["watch"]}]}`,
|
||||
ExpectedEtcdPath: "/registry/roles/etcdstoragepathtestnamespace/role3",
|
||||
ExpectedEtcdPath: "/registry/roles/" + namespace + "/role3",
|
||||
},
|
||||
gvr("rbac.authorization.k8s.io", "v1", "clusterroles"): {
|
||||
Stub: `{"metadata": {"name": "crole3"}, "rules": [{"nonResourceURLs": ["/version"], "verbs": ["get"]}]}`,
|
||||
|
@ -396,7 +404,7 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
},
|
||||
gvr("rbac.authorization.k8s.io", "v1", "rolebindings"): {
|
||||
Stub: `{"metadata": {"name": "roleb3"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
|
||||
ExpectedEtcdPath: "/registry/rolebindings/etcdstoragepathtestnamespace/roleb3",
|
||||
ExpectedEtcdPath: "/registry/rolebindings/" + namespace + "/roleb3",
|
||||
},
|
||||
gvr("rbac.authorization.k8s.io", "v1", "clusterrolebindings"): {
|
||||
Stub: `{"metadata": {"name": "croleb3"}, "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "somecr"}, "subjects": [{"apiVersion": "rbac.authorization.k8s.io/v1alpha1", "kind": "Group", "name": "system:authenticated"}]}`,
|
||||
|
@ -462,18 +470,18 @@ func GetEtcdStorageData() map[schema.GroupVersionResource]StorageData {
|
|||
},
|
||||
gvr("cr.bar.com", "v1", "foos"): {
|
||||
Stub: `{"kind": "Foo", "apiVersion": "cr.bar.com/v1", "metadata": {"name": "cr1foo"}, "color": "blue"}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
||||
ExpectedEtcdPath: "/registry/cr.bar.com/foos/etcdstoragepathtestnamespace/cr1foo",
|
||||
ExpectedEtcdPath: "/registry/cr.bar.com/foos/" + namespace + "/cr1foo",
|
||||
},
|
||||
gvr("custom.fancy.com", "v2", "pants"): {
|
||||
Stub: `{"kind": "Pant", "apiVersion": "custom.fancy.com/v2", "metadata": {"name": "cr2pant"}, "isFancy": true}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
||||
ExpectedEtcdPath: "/registry/custom.fancy.com/pants/cr2pant",
|
||||
},
|
||||
gvr("awesome.bears.com", "v1", "pandas"): {
|
||||
Stub: `{"kind": "Panda", "apiVersion": "awesome.bears.com/v1", "metadata": {"name": "cr3panda"}, "weight": 100}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
||||
Stub: `{"kind": "Panda", "apiVersion": "awesome.bears.com/v1", "metadata": {"name": "cr3panda"}, "spec":{"replicas": 100}}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
||||
ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr3panda",
|
||||
},
|
||||
gvr("awesome.bears.com", "v3", "pandas"): {
|
||||
Stub: `{"kind": "Panda", "apiVersion": "awesome.bears.com/v3", "metadata": {"name": "cr4panda"}, "weight": 300}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
||||
Stub: `{"kind": "Panda", "apiVersion": "awesome.bears.com/v3", "metadata": {"name": "cr4panda"}, "spec":{"replicas": 300}}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
||||
ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr4panda",
|
||||
ExpectedGVK: gvkP("awesome.bears.com", "v1", "Panda"),
|
||||
},
|
||||
|
@ -601,6 +609,14 @@ func GetCustomResourceDefinitionData() []*apiextensionsv1beta1.CustomResourceDef
|
|||
Plural: "pandas",
|
||||
Kind: "Panda",
|
||||
},
|
||||
Subresources: &apiextensionsv1beta1.CustomResourceSubresources{
|
||||
Status: &apiextensionsv1beta1.CustomResourceSubresourceStatus{},
|
||||
Scale: &apiextensionsv1beta1.CustomResourceSubresourceScale{
|
||||
SpecReplicasPath: ".spec.replicas",
|
||||
StatusReplicasPath: ".status.replicas",
|
||||
LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -139,13 +139,19 @@ func StartRealMasterOrDie(t *testing.T, configFuncs ...func(*options.ServerRunOp
|
|||
}()
|
||||
|
||||
lastHealth := ""
|
||||
attempt := 0
|
||||
if err := wait.PollImmediate(time.Second, time.Minute, func() (done bool, err error) {
|
||||
// wait for the server to be healthy
|
||||
result := kubeClient.RESTClient().Get().AbsPath("/healthz").Do()
|
||||
content, _ := result.Raw()
|
||||
lastHealth = string(content)
|
||||
if errResult := result.Error(); errResult != nil {
|
||||
t.Log(errResult)
|
||||
attempt++
|
||||
if attempt < 10 {
|
||||
t.Log("waiting for server to be healthy")
|
||||
} else {
|
||||
t.Log(errResult)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
var status int
|
||||
|
|
|
@ -78,8 +78,10 @@ go_library(
|
|||
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
|
||||
"//test/e2e/framework:go_default_library",
|
||||
"//test/utils:go_default_library",
|
||||
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
|
||||
"//vendor/github.com/go-openapi/spec:go_default_library",
|
||||
"//vendor/github.com/pborman/uuid:go_default_library",
|
||||
"//vendor/google.golang.org/grpc/grpclog:go_default_library",
|
||||
"//vendor/k8s.io/klog:go_default_library",
|
||||
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
|
||||
],
|
||||
|
|
|
@ -27,6 +27,8 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/env"
|
||||
|
@ -109,7 +111,7 @@ func startEtcd() (func(), error) {
|
|||
"--listen-peer-urls",
|
||||
"http://127.0.0.1:0",
|
||||
"--log-package-levels",
|
||||
"*=DEBUG",
|
||||
"*=NOTICE", // set to INFO or DEBUG for more logs
|
||||
)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
@ -123,6 +125,10 @@ func startEtcd() (func(), error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Quiet etcd logs for integration tests
|
||||
// Comment out to get verbose logs if desired
|
||||
clientv3.SetLogger(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, os.Stderr))
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("failed to run etcd: %v", err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue