mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 12:15:52 +00:00
Merge pull request #66466 from apelisse/dry-run-struct
Automatic merge from submit-queue (batch tested with PRs 64815, 66823, 66473, 66466). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. dry-run: Use dry-runnable structure Creates a structures that decides to either by-pass persistence and tries to reproduce the same behavior (without persistence), or just pass along to storage. This is obviously not finished, I'm would like to get feedback on the direction, is this the direction we'd like to go? **What this PR does / why we need it**: **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes # **Special notes for your reviewer**: **Release note**: ```release-note NONE ```
This commit is contained in:
commit
929c8459c3
@ -59,6 +59,7 @@ go_library(
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
|
||||
appsv1beta2 "k8s.io/kubernetes/pkg/apis/apps/v1beta2"
|
||||
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
||||
@ -171,7 +172,7 @@ func (r *RollbackREST) Create(ctx context.Context, obj runtime.Object, createVal
|
||||
}
|
||||
|
||||
// Update the Deployment with information in DeploymentRollback to trigger rollback
|
||||
err := r.rollbackDeployment(ctx, rollback.Name, &rollback.RollbackTo, rollback.UpdatedAnnotations)
|
||||
err := r.rollbackDeployment(ctx, rollback.Name, &rollback.RollbackTo, rollback.UpdatedAnnotations, dryrun.IsDryRun(options.DryRun))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -182,8 +183,8 @@ func (r *RollbackREST) Create(ctx context.Context, obj runtime.Object, createVal
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *RollbackREST) rollbackDeployment(ctx context.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string) error {
|
||||
if _, err := r.setDeploymentRollback(ctx, deploymentID, config, annotations); err != nil {
|
||||
func (r *RollbackREST) rollbackDeployment(ctx context.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string, dryRun bool) error {
|
||||
if _, err := r.setDeploymentRollback(ctx, deploymentID, config, annotations, dryRun); err != nil {
|
||||
err = storeerr.InterpretGetError(err, extensions.Resource("deployments"), deploymentID)
|
||||
err = storeerr.InterpretUpdateError(err, extensions.Resource("deployments"), deploymentID)
|
||||
if _, ok := err.(*errors.StatusError); !ok {
|
||||
@ -194,7 +195,7 @@ func (r *RollbackREST) rollbackDeployment(ctx context.Context, deploymentID stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RollbackREST) setDeploymentRollback(ctx context.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string) (*extensions.Deployment, error) {
|
||||
func (r *RollbackREST) setDeploymentRollback(ctx context.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string, dryRun bool) (*extensions.Deployment, error) {
|
||||
dKey, err := r.store.KeyFunc(ctx, deploymentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -214,7 +215,7 @@ func (r *RollbackREST) setDeploymentRollback(ctx context.Context, deploymentID s
|
||||
d.Spec.RollbackTo = config
|
||||
finalDeployment = d
|
||||
return d, nil
|
||||
}))
|
||||
}), dryRun)
|
||||
return finalDeployment, err
|
||||
}
|
||||
|
||||
|
@ -205,7 +205,7 @@ func TestScaleGet(t *testing.T) {
|
||||
var deployment extensions.Deployment
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
|
||||
key := "/deployments/" + namespace + "/" + name
|
||||
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0); err != nil {
|
||||
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0, false); err != nil {
|
||||
t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
|
||||
}
|
||||
|
||||
@ -246,7 +246,7 @@ func TestScaleUpdate(t *testing.T) {
|
||||
var deployment extensions.Deployment
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
|
||||
key := "/deployments/" + namespace + "/" + name
|
||||
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0); err != nil {
|
||||
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0, false); err != nil {
|
||||
t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
|
||||
}
|
||||
replicas := int32(12)
|
||||
@ -283,7 +283,7 @@ func TestStatusUpdate(t *testing.T) {
|
||||
defer storage.Deployment.Store.DestroyFunc()
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
|
||||
key := "/deployments/" + namespace + "/" + name
|
||||
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, nil, 0); err != nil {
|
||||
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, nil, 0, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
update := extensions.Deployment{
|
||||
|
@ -260,7 +260,7 @@ func TestScaleGet(t *testing.T) {
|
||||
var rs extensions.ReplicaSet
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/replicasets/" + metav1.NamespaceDefault + "/" + name
|
||||
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, &rs, 0); err != nil {
|
||||
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, &rs, 0, false); err != nil {
|
||||
t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err)
|
||||
}
|
||||
|
||||
@ -305,7 +305,7 @@ func TestScaleUpdate(t *testing.T) {
|
||||
var rs extensions.ReplicaSet
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/replicasets/" + metav1.NamespaceDefault + "/" + name
|
||||
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, &rs, 0); err != nil {
|
||||
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, &rs, 0, false); err != nil {
|
||||
t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err)
|
||||
}
|
||||
replicas := 12
|
||||
@ -347,7 +347,7 @@ func TestStatusUpdate(t *testing.T) {
|
||||
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/replicasets/" + metav1.NamespaceDefault + "/foo"
|
||||
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, nil, 0); err != nil {
|
||||
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, nil, 0, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
update := extensions.ReplicaSet{
|
||||
|
@ -102,7 +102,7 @@ func TestStatusUpdate(t *testing.T) {
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/statefulsets/" + metav1.NamespaceDefault + "/foo"
|
||||
validStatefulSet := validNewStatefulSet()
|
||||
if err := storage.StatefulSet.Storage.Create(ctx, key, validStatefulSet, nil, 0); err != nil {
|
||||
if err := storage.StatefulSet.Storage.Create(ctx, key, validStatefulSet, nil, 0, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
update := apps.StatefulSet{
|
||||
@ -210,7 +210,7 @@ func TestScaleGet(t *testing.T) {
|
||||
var sts apps.StatefulSet
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/statefulsets/" + metav1.NamespaceDefault + "/" + name
|
||||
if err := storage.StatefulSet.Storage.Create(ctx, key, &validStatefulSet, &sts, 0); err != nil {
|
||||
if err := storage.StatefulSet.Storage.Create(ctx, key, &validStatefulSet, &sts, 0, false); err != nil {
|
||||
t.Fatalf("error setting new statefulset (key: %s) %v: %v", key, validStatefulSet, err)
|
||||
}
|
||||
|
||||
@ -254,7 +254,7 @@ func TestScaleUpdate(t *testing.T) {
|
||||
var sts apps.StatefulSet
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/statefulsets/" + metav1.NamespaceDefault + "/" + name
|
||||
if err := storage.StatefulSet.Storage.Create(ctx, key, &validStatefulSet, &sts, 0); err != nil {
|
||||
if err := storage.StatefulSet.Storage.Create(ctx, key, &validStatefulSet, &sts, 0, false); err != nil {
|
||||
t.Fatalf("error setting new statefulset (key: %s) %v: %v", key, validStatefulSet, err)
|
||||
}
|
||||
replicas := 12
|
||||
|
@ -177,7 +177,7 @@ func TestUpdateStatus(t *testing.T) {
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
key, _ := storage.KeyFunc(ctx, "foo")
|
||||
autoscalerStart := validNewHorizontalPodAutoscaler("foo")
|
||||
err := storage.Storage.Create(ctx, key, autoscalerStart, nil, 0)
|
||||
err := storage.Storage.Create(ctx, key, autoscalerStart, nil, 0, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ go_library(
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
storageerr "k8s.io/apiserver/pkg/storage/errors"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
@ -190,6 +191,7 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
|
||||
}
|
||||
return existingNamespace, nil
|
||||
}),
|
||||
dryrun.IsDryRun(options.DryRun),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
@ -156,7 +156,7 @@ func TestDeleteNamespaceWithIncompleteFinalizers(t *testing.T) {
|
||||
},
|
||||
Status: api.NamespaceStatus{Phase: api.NamespaceActive},
|
||||
}
|
||||
if err := storage.store.Storage.Create(ctx, key, namespace, nil, 0); err != nil {
|
||||
if err := storage.store.Storage.Create(ctx, key, namespace, nil, 0, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if _, _, err := storage.Delete(ctx, "foo", nil); err == nil {
|
||||
@ -181,7 +181,7 @@ func TestDeleteNamespaceWithCompleteFinalizers(t *testing.T) {
|
||||
},
|
||||
Status: api.NamespaceStatus{Phase: api.NamespaceActive},
|
||||
}
|
||||
if err := storage.store.Storage.Create(ctx, key, namespace, nil, 0); err != nil {
|
||||
if err := storage.store.Storage.Create(ctx, key, namespace, nil, 0, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if _, _, err := storage.Delete(ctx, "foo", nil); err != nil {
|
||||
|
@ -169,7 +169,7 @@ func TestUpdateStatus(t *testing.T) {
|
||||
ctx := genericapirequest.NewContext()
|
||||
key, _ := storage.KeyFunc(ctx, "foo")
|
||||
pvStart := validNewPersistentVolume("foo")
|
||||
err := storage.Storage.Create(ctx, key, pvStart, nil, 0)
|
||||
err := storage.Storage.Create(ctx, key, pvStart, nil, 0, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ func TestUpdateStatus(t *testing.T) {
|
||||
|
||||
key, _ := storage.KeyFunc(ctx, "foo")
|
||||
pvcStart := validNewPersistentVolumeClaim("foo", metav1.NamespaceDefault)
|
||||
err := storage.Storage.Create(ctx, key, pvcStart, nil, 0)
|
||||
err := storage.Storage.Create(ctx, key, pvcStart, nil, 0, false)
|
||||
|
||||
pvc := &api.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
@ -33,7 +33,7 @@ func TestPodLogValidates(t *testing.T) {
|
||||
s, destroyFunc := generic.NewRawStorage(config)
|
||||
defer destroyFunc()
|
||||
store := &genericregistry.Store{
|
||||
Storage: s,
|
||||
Storage: genericregistry.DryRunnableStorage{Storage: s},
|
||||
}
|
||||
logRest := &LogREST{Store: store, KubeletConn: nil}
|
||||
|
||||
|
@ -26,6 +26,7 @@ go_test(
|
||||
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/features:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||
@ -66,6 +67,7 @@ go_library(
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/util/retry:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -30,6 +30,7 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
@ -148,7 +149,7 @@ func (r *BindingREST) Create(ctx context.Context, obj runtime.Object, createVali
|
||||
return nil, errs.ToAggregate()
|
||||
}
|
||||
|
||||
err = r.assignPod(ctx, binding.Name, binding.Target.Name, binding.Annotations)
|
||||
err = r.assignPod(ctx, binding.Name, binding.Target.Name, binding.Annotations, dryrun.IsDryRun(options.DryRun))
|
||||
out = &metav1.Status{Status: metav1.StatusSuccess}
|
||||
return
|
||||
}
|
||||
@ -156,7 +157,7 @@ func (r *BindingREST) Create(ctx context.Context, obj runtime.Object, createVali
|
||||
// setPodHostAndAnnotations sets the given pod's host to 'machine' if and only if it was
|
||||
// previously 'oldMachine' and merges the provided annotations with those of the pod.
|
||||
// Returns the current state of the pod, or an error.
|
||||
func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podID, oldMachine, machine string, annotations map[string]string) (finalPod *api.Pod, err error) {
|
||||
func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podID, oldMachine, machine string, annotations map[string]string, dryRun bool) (finalPod *api.Pod, err error) {
|
||||
podKey, err := r.store.KeyFunc(ctx, podID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -185,13 +186,13 @@ func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podID, oldMa
|
||||
})
|
||||
finalPod = pod
|
||||
return pod, nil
|
||||
}))
|
||||
}), dryRun)
|
||||
return finalPod, err
|
||||
}
|
||||
|
||||
// assignPod assigns the given pod to the given machine.
|
||||
func (r *BindingREST) assignPod(ctx context.Context, podID string, machine string, annotations map[string]string) (err error) {
|
||||
if _, err = r.setPodHostAndAnnotations(ctx, podID, "", machine, annotations); err != nil {
|
||||
func (r *BindingREST) assignPod(ctx context.Context, podID string, machine string, annotations map[string]string, dryRun bool) (err error) {
|
||||
if _, err = r.setPodHostAndAnnotations(ctx, podID, "", machine, annotations, dryRun); err != nil {
|
||||
err = storeerr.InterpretGetError(err, api.Resource("pods"), podID)
|
||||
err = storeerr.InterpretUpdateError(err, api.Resource("pods"), podID)
|
||||
if _, ok := err.(*errors.StatusError); !ok {
|
||||
|
@ -34,6 +34,7 @@ import (
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
@ -170,7 +171,7 @@ func newFailDeleteStorage(t *testing.T, called *bool) (*REST, *etcdtesting.EtcdT
|
||||
ResourcePrefix: "pods",
|
||||
}
|
||||
storage := NewStorage(restOptions, nil, nil, nil)
|
||||
storage.Pod.Store.Storage = FailDeletionStorage{storage.Pod.Store.Storage, called}
|
||||
storage.Pod.Store.Storage = genericregistry.DryRunnableStorage{Storage: FailDeletionStorage{storage.Pod.Store.Storage.Storage, called}}
|
||||
return storage.Pod, server
|
||||
}
|
||||
|
||||
@ -340,7 +341,7 @@ func TestResourceLocation(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
storage, _, _, server := newStorage(t)
|
||||
key, _ := storage.KeyFunc(ctx, tc.pod.Name)
|
||||
if err := storage.Storage.Create(ctx, key, &tc.pod, nil, 0); err != nil {
|
||||
if err := storage.Storage.Create(ctx, key, &tc.pod, nil, 0, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
@ -825,7 +826,7 @@ func TestEtcdUpdateScheduled(t *testing.T) {
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
},
|
||||
}, nil, 1)
|
||||
}, nil, 1, false)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@ -897,7 +898,7 @@ func TestEtcdUpdateStatus(t *testing.T) {
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
},
|
||||
}
|
||||
err := storage.Storage.Create(ctx, key, &podStart, nil, 0)
|
||||
err := storage.Storage.Create(ctx, key, &podStart, nil, 0, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ func TestUpdateStatus(t *testing.T) {
|
||||
|
||||
key, _ := storage.KeyFunc(ctx, "foo")
|
||||
resourcequotaStart := validNewResourceQuota()
|
||||
err := storage.Storage.Create(ctx, key, resourcequotaStart, nil, 0)
|
||||
err := storage.Storage.Create(ctx, key, resourcequotaStart, nil, 0, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ func NewTestRESTWithPods(t *testing.T, endpoints *api.EndpointsList, pods *api.P
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
for ix := range pods.Items {
|
||||
key, _ := podStorage.Pod.KeyFunc(ctx, pods.Items[ix].Name)
|
||||
if err := podStorage.Pod.Storage.Create(ctx, key, &pods.Items[ix], nil, 0); err != nil {
|
||||
if err := podStorage.Pod.Storage.Create(ctx, key, &pods.Items[ix], nil, 0, false); err != nil {
|
||||
t.Fatalf("Couldn't create pod: %v", err)
|
||||
}
|
||||
}
|
||||
@ -188,7 +188,7 @@ func NewTestRESTWithPods(t *testing.T, endpoints *api.EndpointsList, pods *api.P
|
||||
ctx := genericapirequest.NewDefaultContext()
|
||||
for ix := range endpoints.Items {
|
||||
key, _ := endpointStorage.KeyFunc(ctx, endpoints.Items[ix].Name)
|
||||
if err := endpointStorage.Store.Storage.Create(ctx, key, &endpoints.Items[ix], nil, 0); err != nil {
|
||||
if err := endpointStorage.Store.Storage.Create(ctx, key, &endpoints.Items[ix], nil, 0, false); err != nil {
|
||||
t.Fatalf("Couldn't create endpoint: %v", err)
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ func TestStatusUpdate(t *testing.T) {
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/poddisruptionbudgets/" + metav1.NamespaceDefault + "/foo"
|
||||
validPodDisruptionBudget := validNewPodDisruptionBudget()
|
||||
if err := storage.Storage.Create(ctx, key, validPodDisruptionBudget, nil, 0); err != nil {
|
||||
if err := storage.Storage.Create(ctx, key, validPodDisruptionBudget, nil, 0, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,7 @@ func TestDeleteSystemPriorityClass(t *testing.T) {
|
||||
key := "test/system-node-critical"
|
||||
ctx := genericapirequest.NewContext()
|
||||
pc := scheduling.SystemPriorityClasses()[0]
|
||||
if err := storage.Store.Storage.Create(ctx, key, pc, nil, 0); err != nil {
|
||||
if err := storage.Store.Storage.Create(ctx, key, pc, nil, 0, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if _, _, err := storage.Delete(ctx, pc.Name, nil); err == nil {
|
||||
|
@ -93,6 +93,7 @@ filegroup(
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/server:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/flag:all-srcs",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/flushwriter:all-srcs",
|
||||
|
@ -1462,6 +1462,10 @@
|
||||
"ImportPath": "k8s.io/apiserver/pkg/storage/value",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apiserver/pkg/util/dryrun",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apiserver/pkg/util/feature",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
@ -2282,6 +2286,10 @@
|
||||
"ImportPath": "k8s.io/apiserver/pkg/storage/storagebackend",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apiserver/pkg/util/dryrun",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apiserver/pkg/util/feature",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
@ -254,7 +254,7 @@ func TestColumns(t *testing.T) {
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/foo"
|
||||
validCustomResource := validNewCustomResource()
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0); err != nil {
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
@ -324,7 +324,7 @@ func TestStatusUpdate(t *testing.T) {
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/foo"
|
||||
validCustomResource := validNewCustomResource()
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0); err != nil {
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0, false); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
@ -375,7 +375,7 @@ func TestScaleGet(t *testing.T) {
|
||||
var cr unstructured.Unstructured
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0); err != nil {
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0, false); err != nil {
|
||||
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
|
||||
}
|
||||
|
||||
@ -415,7 +415,7 @@ func TestScaleGetWithoutSpecReplicas(t *testing.T) {
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
||||
withoutSpecReplicas := validCustomResource.DeepCopy()
|
||||
unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas")
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0); err != nil {
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0, false); err != nil {
|
||||
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err)
|
||||
}
|
||||
|
||||
@ -438,7 +438,7 @@ func TestScaleUpdate(t *testing.T) {
|
||||
var cr unstructured.Unstructured
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0); err != nil {
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0, false); err != nil {
|
||||
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
|
||||
}
|
||||
|
||||
@ -492,7 +492,7 @@ func TestScaleUpdateWithoutSpecReplicas(t *testing.T) {
|
||||
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
||||
withoutSpecReplicas := validCustomResource.DeepCopy()
|
||||
unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas")
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0); err != nil {
|
||||
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0, false); err != nil {
|
||||
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err)
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ go_library(
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
storageerr "k8s.io/apiserver/pkg/storage/errors"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
)
|
||||
|
||||
// rest implements a RESTStorage for API services against etcd
|
||||
@ -129,6 +130,7 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
|
||||
})
|
||||
return existingCRD, nil
|
||||
}),
|
||||
dryrun.IsDryRun(options.DryRun),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
@ -10,6 +10,7 @@ go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"decorated_watcher_test.go",
|
||||
"dryrun_test.go",
|
||||
"store_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
@ -20,12 +21,14 @@ go_test(
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion: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/fields:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/labels: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/runtime/serializer:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/selection:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
@ -53,6 +56,7 @@ go_library(
|
||||
srcs = [
|
||||
"decorated_watcher.go",
|
||||
"doc.go",
|
||||
"dryrun.go",
|
||||
"storage_factory.go",
|
||||
"store.go",
|
||||
],
|
||||
@ -84,6 +88,7 @@ go_library(
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/etcd/metrics:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright 2018 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 registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
)
|
||||
|
||||
type DryRunnableStorage struct {
|
||||
Storage storage.Interface
|
||||
Codec runtime.Codec
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) Versioner() storage.Versioner {
|
||||
return s.Storage.Versioner()
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64, dryRun bool) error {
|
||||
if dryRun {
|
||||
if err := s.Storage.Get(ctx, key, "", out, false); err == nil {
|
||||
return storage.NewKeyExistsError(key, 0)
|
||||
}
|
||||
s.copyInto(obj, out)
|
||||
return nil
|
||||
}
|
||||
return s.Storage.Create(ctx, key, obj, out, ttl)
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, dryRun bool) error {
|
||||
if dryRun {
|
||||
if err := s.Storage.Get(ctx, key, "", out, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return preconditions.Check(key, out)
|
||||
}
|
||||
return s.Storage.Delete(ctx, key, out, preconditions)
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) Watch(ctx context.Context, key string, resourceVersion string, p storage.SelectionPredicate) (watch.Interface, error) {
|
||||
return s.Storage.Watch(ctx, key, resourceVersion, p)
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) WatchList(ctx context.Context, key string, resourceVersion string, p storage.SelectionPredicate) (watch.Interface, error) {
|
||||
return s.Storage.WatchList(ctx, key, resourceVersion, p)
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) Get(ctx context.Context, key string, resourceVersion string, objPtr runtime.Object, ignoreNotFound bool) error {
|
||||
return s.Storage.Get(ctx, key, resourceVersion, objPtr, ignoreNotFound)
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) GetToList(ctx context.Context, key string, resourceVersion string, p storage.SelectionPredicate, listObj runtime.Object) error {
|
||||
return s.Storage.GetToList(ctx, key, resourceVersion, p, listObj)
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) List(ctx context.Context, key string, resourceVersion string, p storage.SelectionPredicate, listObj runtime.Object) error {
|
||||
return s.Storage.List(ctx, key, resourceVersion, p, listObj)
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) GuaranteedUpdate(
|
||||
ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool,
|
||||
preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc, dryRun bool, suggestion ...runtime.Object) error {
|
||||
if dryRun {
|
||||
err := s.Storage.Get(ctx, key, "", ptrToType, ignoreNotFound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = preconditions.Check(key, ptrToType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rev, err := s.Versioner().ObjectResourceVersion(ptrToType)
|
||||
out, _, err := tryUpdate(ptrToType, storage.ResponseMeta{ResourceVersion: rev})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.copyInto(out, ptrToType)
|
||||
return nil
|
||||
}
|
||||
return s.Storage.GuaranteedUpdate(ctx, key, ptrToType, ignoreNotFound, preconditions, tryUpdate, suggestion...)
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) Count(key string) (int64, error) {
|
||||
return s.Storage.Count(key)
|
||||
}
|
||||
|
||||
func (s *DryRunnableStorage) copyInto(in, out runtime.Object) error {
|
||||
var data []byte
|
||||
|
||||
data, err := runtime.Encode(s.Codec, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err = s.Codec.Decode(data, nil, out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
@ -0,0 +1,304 @@
|
||||
/*
|
||||
Copyright 2018 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 registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/apitesting"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
|
||||
"k8s.io/apiserver/pkg/storage/storagebackend/factory"
|
||||
)
|
||||
|
||||
func NewDryRunnableTestStorage(t *testing.T) (DryRunnableStorage, func()) {
|
||||
server, sc := etcdtesting.NewUnsecuredEtcd3TestClientServer(t)
|
||||
sc.Codec = apitesting.TestStorageCodec(codecs, examplev1.SchemeGroupVersion)
|
||||
s, destroy, err := factory.Create(*sc)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating storage: %v", err)
|
||||
}
|
||||
return DryRunnableStorage{Storage: s, Codec: sc.Codec}, func() {
|
||||
destroy()
|
||||
server.Terminate(t)
|
||||
}
|
||||
}
|
||||
|
||||
func UnstructuredOrDie(j string) *unstructured.Unstructured {
|
||||
m := map[string]interface{}{}
|
||||
err := json.Unmarshal([]byte(j), &m)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to unmarshal into Unstructured: %v", err))
|
||||
}
|
||||
return &unstructured.Unstructured{Object: m}
|
||||
}
|
||||
|
||||
func TestDryRunCreateDoesntCreate(t *testing.T) {
|
||||
s, destroy := NewDryRunnableTestStorage(t)
|
||||
defer destroy()
|
||||
|
||||
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
|
||||
out := UnstructuredOrDie(`{}`)
|
||||
|
||||
err := s.Create(context.Background(), "key", obj, out, 0, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new dry-run object: %v", err)
|
||||
}
|
||||
|
||||
err = s.Get(context.Background(), "key", "", out, false)
|
||||
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeKeyNotFound {
|
||||
t.Errorf("Expected key to be not found, error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDryRunCreateReturnsObject(t *testing.T) {
|
||||
s, destroy := NewDryRunnableTestStorage(t)
|
||||
defer destroy()
|
||||
|
||||
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
|
||||
out := UnstructuredOrDie(`{}`)
|
||||
|
||||
err := s.Create(context.Background(), "key", obj, out, 0, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new dry-run object: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(obj, out) {
|
||||
t.Errorf("Returned object different from input object:\nExpected: %v\nGot: %v", obj, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDryRunCreateExistingObjectFails(t *testing.T) {
|
||||
s, destroy := NewDryRunnableTestStorage(t)
|
||||
defer destroy()
|
||||
|
||||
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
|
||||
out := UnstructuredOrDie(`{}`)
|
||||
|
||||
err := s.Create(context.Background(), "key", obj, out, 0, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new object: %v", err)
|
||||
}
|
||||
|
||||
err = s.Create(context.Background(), "key", obj, out, 0, true)
|
||||
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeKeyExists {
|
||||
t.Errorf("Expected KeyExists error: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDryRunUpdateMissingObjectFails(t *testing.T) {
|
||||
s, destroy := NewDryRunnableTestStorage(t)
|
||||
defer destroy()
|
||||
|
||||
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
|
||||
|
||||
updateFunc := func(input runtime.Object, res storage.ResponseMeta) (output runtime.Object, ttl *uint64, err error) {
|
||||
return input, nil, errors.New("UpdateFunction shouldn't be called")
|
||||
}
|
||||
|
||||
err := s.GuaranteedUpdate(context.Background(), "key", obj, false, nil, updateFunc, true)
|
||||
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeKeyNotFound {
|
||||
t.Errorf("Expected key to be not found, error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDryRunUpdatePreconditions(t *testing.T) {
|
||||
s, destroy := NewDryRunnableTestStorage(t)
|
||||
defer destroy()
|
||||
|
||||
obj := UnstructuredOrDie(`{"kind": "Pod", "metadata": {"uid": "my-uid"}}`)
|
||||
out := UnstructuredOrDie(`{}`)
|
||||
err := s.Create(context.Background(), "key", obj, out, 0, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new object: %v", err)
|
||||
}
|
||||
|
||||
updateFunc := func(input runtime.Object, res storage.ResponseMeta) (output runtime.Object, ttl *uint64, err error) {
|
||||
u, ok := input.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return input, nil, errors.New("Input object is not unstructured")
|
||||
}
|
||||
unstructured.SetNestedField(u.Object, "value", "field")
|
||||
return u, nil, nil
|
||||
}
|
||||
wrongID := types.UID("wrong-uid")
|
||||
myID := types.UID("my-uid")
|
||||
err = s.GuaranteedUpdate(context.Background(), "key", obj, false, &storage.Preconditions{UID: &wrongID}, updateFunc, true)
|
||||
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeInvalidObj {
|
||||
t.Errorf("Expected invalid object, error: %v", err)
|
||||
}
|
||||
|
||||
err = s.GuaranteedUpdate(context.Background(), "key", obj, false, &storage.Preconditions{UID: &myID}, updateFunc, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to update with valid precondition: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDryRunUpdateDoesntUpdate(t *testing.T) {
|
||||
s, destroy := NewDryRunnableTestStorage(t)
|
||||
defer destroy()
|
||||
|
||||
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
|
||||
created := UnstructuredOrDie(`{}`)
|
||||
|
||||
err := s.Create(context.Background(), "key", obj, created, 0, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new object: %v", err)
|
||||
}
|
||||
|
||||
updateFunc := func(input runtime.Object, res storage.ResponseMeta) (output runtime.Object, ttl *uint64, err error) {
|
||||
u, ok := input.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return input, nil, errors.New("Input object is not unstructured")
|
||||
}
|
||||
unstructured.SetNestedField(u.Object, "value", "field")
|
||||
return u, nil, nil
|
||||
}
|
||||
|
||||
err = s.GuaranteedUpdate(context.Background(), "key", obj, false, nil, updateFunc, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to dry-run update: %v", err)
|
||||
}
|
||||
out := UnstructuredOrDie(`{}`)
|
||||
err = s.Get(context.Background(), "key", "", out, false)
|
||||
if !reflect.DeepEqual(created, out) {
|
||||
t.Fatalf("Returned object %q different from expected %q", created, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDryRunUpdateReturnsObject(t *testing.T) {
|
||||
s, destroy := NewDryRunnableTestStorage(t)
|
||||
defer destroy()
|
||||
|
||||
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
|
||||
out := UnstructuredOrDie(`{}`)
|
||||
|
||||
err := s.Create(context.Background(), "key", obj, out, 0, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new object: %v", err)
|
||||
}
|
||||
|
||||
updateFunc := func(input runtime.Object, res storage.ResponseMeta) (output runtime.Object, ttl *uint64, err error) {
|
||||
u, ok := input.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return input, nil, errors.New("Input object is not unstructured")
|
||||
}
|
||||
unstructured.SetNestedField(u.Object, "value", "field")
|
||||
return u, nil, nil
|
||||
}
|
||||
|
||||
err = s.GuaranteedUpdate(context.Background(), "key", obj, false, nil, updateFunc, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to dry-run update: %v", err)
|
||||
}
|
||||
out = UnstructuredOrDie(`{"field": "value", "kind": "Pod", "metadata": {"resourceVersion": "2", "selfLink": ""}}`)
|
||||
if !reflect.DeepEqual(obj, out) {
|
||||
t.Fatalf("Returned object %#v different from expected %#v", obj, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDryRunDeleteDoesntDelete(t *testing.T) {
|
||||
s, destroy := NewDryRunnableTestStorage(t)
|
||||
defer destroy()
|
||||
|
||||
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
|
||||
out := UnstructuredOrDie(`{}`)
|
||||
|
||||
err := s.Create(context.Background(), "key", obj, out, 0, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new object: %v", err)
|
||||
}
|
||||
|
||||
err = s.Delete(context.Background(), "key", out, nil, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to dry-run delete the object: %v", err)
|
||||
}
|
||||
|
||||
err = s.Get(context.Background(), "key", "", out, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve dry-run deleted object: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDryRunDeleteMissingObjectFails(t *testing.T) {
|
||||
s, destroy := NewDryRunnableTestStorage(t)
|
||||
defer destroy()
|
||||
|
||||
out := UnstructuredOrDie(`{}`)
|
||||
err := s.Delete(context.Background(), "key", out, nil, true)
|
||||
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeKeyNotFound {
|
||||
t.Errorf("Expected key to be not found, error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDryRunDeleteReturnsObject(t *testing.T) {
|
||||
s, destroy := NewDryRunnableTestStorage(t)
|
||||
defer destroy()
|
||||
|
||||
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
|
||||
out := UnstructuredOrDie(`{}`)
|
||||
|
||||
err := s.Create(context.Background(), "key", obj, out, 0, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new object: %v", err)
|
||||
}
|
||||
|
||||
out = UnstructuredOrDie(`{}`)
|
||||
expected := UnstructuredOrDie(`{"kind": "Pod", "metadata": {"resourceVersion": "2", "selfLink": ""}}`)
|
||||
err = s.Delete(context.Background(), "key", out, nil, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete with valid precondition: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, out) {
|
||||
t.Fatalf("Returned object %q doesn't match expected: %q", out, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDryRunDeletePreconditions(t *testing.T) {
|
||||
s, destroy := NewDryRunnableTestStorage(t)
|
||||
defer destroy()
|
||||
|
||||
obj := UnstructuredOrDie(`{"kind": "Pod", "metadata": {"uid": "my-uid"}}`)
|
||||
out := UnstructuredOrDie(`{}`)
|
||||
|
||||
err := s.Create(context.Background(), "key", obj, out, 0, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new object: %v", err)
|
||||
}
|
||||
|
||||
wrongID := types.UID("wrong-uid")
|
||||
myID := types.UID("my-uid")
|
||||
err = s.Delete(context.Background(), "key", out, &storage.Preconditions{UID: &wrongID}, true)
|
||||
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeInvalidObj {
|
||||
t.Errorf("Expected invalid object, error: %v", err)
|
||||
}
|
||||
|
||||
err = s.Delete(context.Background(), "key", out, &storage.Preconditions{UID: &myID}, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete with valid precondition: %v", err)
|
||||
}
|
||||
}
|
@ -45,6 +45,7 @@ import (
|
||||
"k8s.io/apiserver/pkg/storage"
|
||||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||
"k8s.io/apiserver/pkg/storage/etcd/metrics"
|
||||
"k8s.io/apiserver/pkg/util/dryrun"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
@ -172,8 +173,10 @@ type Store struct {
|
||||
// of items into tabular output. If unset, the default will be used.
|
||||
TableConvertor rest.TableConvertor
|
||||
|
||||
// Storage is the interface for the underlying storage for the resource.
|
||||
Storage storage.Interface
|
||||
// Storage is the interface for the underlying storage for the
|
||||
// resource. It is wrapped into a "DryRunnableStorage" that will
|
||||
// either pass-through or simply dry-run.
|
||||
Storage DryRunnableStorage
|
||||
// Called to cleanup clients used by the underlying Storage; optional.
|
||||
DestroyFunc func()
|
||||
}
|
||||
@ -348,7 +351,7 @@ func (e *Store) Create(ctx context.Context, obj runtime.Object, createValidation
|
||||
return nil, err
|
||||
}
|
||||
out := e.NewFunc()
|
||||
if err := e.Storage.Create(ctx, key, obj, out, ttl); err != nil {
|
||||
if err := e.Storage.Create(ctx, key, obj, out, ttl, dryrun.IsDryRun(options.DryRun)); err != nil {
|
||||
err = storeerr.InterpretCreateError(err, qualifiedResource, name)
|
||||
err = rest.CheckGeneratedNameError(e.CreateStrategy, err, obj)
|
||||
if !kubeerr.IsAlreadyExists(err) {
|
||||
@ -496,10 +499,10 @@ func (e *Store) shouldDeleteForFailedInitialization(ctx context.Context, obj run
|
||||
|
||||
// deleteWithoutFinalizers handles deleting an object ignoring its finalizer list.
|
||||
// Used for objects that are either been finalized or have never initialized.
|
||||
func (e *Store) deleteWithoutFinalizers(ctx context.Context, name, key string, obj runtime.Object, preconditions *storage.Preconditions) (runtime.Object, bool, error) {
|
||||
func (e *Store) deleteWithoutFinalizers(ctx context.Context, name, key string, obj runtime.Object, preconditions *storage.Preconditions, dryRun bool) (runtime.Object, bool, error) {
|
||||
out := e.NewFunc()
|
||||
glog.V(6).Infof("going to delete %s from registry, triggered by update", name)
|
||||
if err := e.Storage.Delete(ctx, key, out, preconditions); err != nil {
|
||||
if err := e.Storage.Delete(ctx, key, out, preconditions, dryRun); err != nil {
|
||||
// Deletion is racy, i.e., there could be multiple update
|
||||
// requests to remove all finalizers from the object, so we
|
||||
// ignore the NotFound error.
|
||||
@ -633,12 +636,12 @@ func (e *Store) Update(ctx context.Context, name string, objInfo rest.UpdatedObj
|
||||
return obj, &ttl, nil
|
||||
}
|
||||
return obj, nil, nil
|
||||
})
|
||||
}, dryrun.IsDryRun(options.DryRun))
|
||||
|
||||
if err != nil {
|
||||
// delete the object
|
||||
if err == errEmptiedFinalizers {
|
||||
return e.deleteWithoutFinalizers(ctx, name, key, deleteObj, storagePreconditions)
|
||||
return e.deleteWithoutFinalizers(ctx, name, key, deleteObj, storagePreconditions, dryrun.IsDryRun(options.DryRun))
|
||||
}
|
||||
if creating {
|
||||
err = storeerr.InterpretCreateError(err, qualifiedResource, name)
|
||||
@ -650,7 +653,7 @@ func (e *Store) Update(ctx context.Context, name string, objInfo rest.UpdatedObj
|
||||
}
|
||||
|
||||
if e.shouldDeleteForFailedInitialization(ctx, out) {
|
||||
return e.deleteWithoutFinalizers(ctx, name, key, out, storagePreconditions)
|
||||
return e.deleteWithoutFinalizers(ctx, name, key, out, storagePreconditions, dryrun.IsDryRun(options.DryRun))
|
||||
}
|
||||
|
||||
if creating {
|
||||
@ -919,6 +922,7 @@ func (e *Store) updateForGracefulDeletionAndFinalizers(ctx context.Context, name
|
||||
lastExisting = existing
|
||||
return existing, nil
|
||||
}),
|
||||
dryrun.IsDryRun(options.DryRun),
|
||||
)
|
||||
switch err {
|
||||
case nil:
|
||||
@ -1000,10 +1004,15 @@ func (e *Store) Delete(ctx context.Context, name string, options *metav1.DeleteO
|
||||
return out, false, err
|
||||
}
|
||||
|
||||
// If dry-run, then just return the object as just saved.
|
||||
if dryrun.IsDryRun(options.DryRun) {
|
||||
return out, true, nil
|
||||
}
|
||||
|
||||
// delete immediately, or no graceful deletion supported
|
||||
glog.V(6).Infof("going to delete %s from registry: ", name)
|
||||
out = e.NewFunc()
|
||||
if err := e.Storage.Delete(ctx, key, out, &preconditions); err != nil {
|
||||
if err := e.Storage.Delete(ctx, key, out, &preconditions, dryrun.IsDryRun(options.DryRun)); err != nil {
|
||||
// Please refer to the place where we set ignoreNotFound for the reason
|
||||
// why we ignore the NotFound error .
|
||||
if storage.IsNotFound(err) && ignoreNotFound && lastExisting != nil {
|
||||
@ -1364,8 +1373,9 @@ func (e *Store) CompleteWithOptions(options *generic.StoreOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
if e.Storage == nil {
|
||||
e.Storage, e.DestroyFunc = opts.Decorator(
|
||||
if e.Storage.Storage == nil {
|
||||
e.Storage.Codec = opts.StorageConfig.Codec
|
||||
e.Storage.Storage, e.DestroyFunc = opts.Decorator(
|
||||
opts.StorageConfig,
|
||||
e.NewFunc(),
|
||||
prefix,
|
||||
|
@ -213,7 +213,7 @@ func TestStoreList(t *testing.T) {
|
||||
destroyFunc, registry := NewTestGenericStoreRegistry(t)
|
||||
|
||||
if item.in != nil {
|
||||
if err := storagetesting.CreateList("/pods", registry.Storage, item.in); err != nil {
|
||||
if err := storagetesting.CreateList("/pods", registry.Storage.Storage, item.in); err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
}
|
||||
@ -1901,7 +1901,7 @@ func newTestGenericStoreRegistry(t *testing.T, scheme *runtime.Scheme, hasCacheE
|
||||
},
|
||||
}
|
||||
},
|
||||
Storage: s,
|
||||
Storage: DryRunnableStorage{Storage: s},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,7 +164,7 @@ func (t *Tester) createObject(ctx context.Context, obj runtime.Object) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.storage.Storage.Create(ctx, key, obj, nil, 0)
|
||||
return t.storage.Storage.Create(ctx, key, obj, nil, 0, false)
|
||||
}
|
||||
|
||||
func (t *Tester) setObjectsForList(objects []runtime.Object) []runtime.Object {
|
||||
@ -173,7 +173,7 @@ func (t *Tester) setObjectsForList(objects []runtime.Object) []runtime.Object {
|
||||
t.tester.Errorf("unable to clear collection: %v", err)
|
||||
return nil
|
||||
}
|
||||
if err := storagetesting.CreateObjList(key, t.storage.Storage, objects); err != nil {
|
||||
if err := storagetesting.CreateObjList(key, t.storage.Storage.Storage, objects); err != nil {
|
||||
t.tester.Errorf("unexpected error: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
@ -153,21 +153,6 @@ func (h *etcdHelper) Create(ctx context.Context, key string, obj, out runtime.Ob
|
||||
return err
|
||||
}
|
||||
|
||||
func checkPreconditions(key string, preconditions *storage.Preconditions, out runtime.Object) error {
|
||||
if preconditions == nil {
|
||||
return nil
|
||||
}
|
||||
objMeta, err := meta.Accessor(out)
|
||||
if err != nil {
|
||||
return storage.NewInternalErrorf("can't enforce preconditions %v on un-introspectable object %v, got error: %v", *preconditions, out, err)
|
||||
}
|
||||
if preconditions.UID != nil && *preconditions.UID != objMeta.GetUID() {
|
||||
errMsg := fmt.Sprintf("Precondition failed: UID in precondition: %v, UID in object meta: %v", preconditions.UID, objMeta.GetUID())
|
||||
return storage.NewInvalidObjError(key, errMsg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements storage.Interface.
|
||||
func (h *etcdHelper) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions) error {
|
||||
if ctx == nil {
|
||||
@ -199,7 +184,7 @@ func (h *etcdHelper) Delete(ctx context.Context, key string, out runtime.Object,
|
||||
if err != nil {
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
if err := checkPreconditions(key, preconditions, obj); err != nil {
|
||||
if err := preconditions.Check(key, obj); err != nil {
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
index := uint64(0)
|
||||
@ -493,7 +478,7 @@ func (h *etcdHelper) GuaranteedUpdate(
|
||||
if err != nil {
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
if err := checkPreconditions(key, preconditions, obj); err != nil {
|
||||
if err := preconditions.Check(key, obj); err != nil {
|
||||
return toStorageErr(err, key, 0)
|
||||
}
|
||||
meta := storage.ResponseMeta{}
|
||||
|
@ -233,7 +233,7 @@ func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.O
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkPreconditions(key, preconditions, origState.obj); err != nil {
|
||||
if err := preconditions.Check(key, origState.obj); err != nil {
|
||||
return err
|
||||
}
|
||||
txnResp, err := s.client.KV.Txn(ctx).If(
|
||||
@ -294,7 +294,7 @@ func (s *store) GuaranteedUpdate(
|
||||
|
||||
transformContext := authenticatedDataString(key)
|
||||
for {
|
||||
if err := checkPreconditions(key, preconditions, origState.obj); err != nil {
|
||||
if err := preconditions.Check(key, origState.obj); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -791,21 +791,6 @@ func appendListItem(v reflect.Value, data []byte, rev uint64, pred storage.Selec
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPreconditions(key string, preconditions *storage.Preconditions, out runtime.Object) error {
|
||||
if preconditions == nil {
|
||||
return nil
|
||||
}
|
||||
objMeta, err := meta.Accessor(out)
|
||||
if err != nil {
|
||||
return storage.NewInternalErrorf("can't enforce preconditions %v on un-introspectable object %v, got error: %v", *preconditions, out, err)
|
||||
}
|
||||
if preconditions.UID != nil && *preconditions.UID != objMeta.GetUID() {
|
||||
errMsg := fmt.Sprintf("Precondition failed: UID in precondition: %v, UID in object meta: %v", *preconditions.UID, objMeta.GetUID())
|
||||
return storage.NewInvalidObjError(key, errMsg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func notFound(key string) clientv3.Cmp {
|
||||
return clientv3.Compare(clientv3.ModRevision(key), "=", 0)
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@ -106,6 +108,29 @@ func NewUIDPreconditions(uid string) *Preconditions {
|
||||
return &Preconditions{UID: &u}
|
||||
}
|
||||
|
||||
func (p *Preconditions) Check(key string, obj runtime.Object) error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
objMeta, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return NewInternalErrorf(
|
||||
"can't enforce preconditions %v on un-introspectable object %v, got error: %v",
|
||||
*p,
|
||||
obj,
|
||||
err)
|
||||
}
|
||||
if p.UID != nil && *p.UID != objMeta.GetUID() {
|
||||
err := fmt.Sprintf(
|
||||
"Precondition failed: UID in precondition: %v, UID in object meta: %v",
|
||||
*p.UID,
|
||||
objMeta.GetUID())
|
||||
return NewInvalidObjError(key, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// Interface offers a common interface for object marshaling/unmarshaling operations and
|
||||
// hides all the storage-related operations behind it.
|
||||
type Interface interface {
|
||||
|
23
staging/src/k8s.io/apiserver/pkg/util/dryrun/BUILD
Normal file
23
staging/src/k8s.io/apiserver/pkg/util/dryrun/BUILD
Normal file
@ -0,0 +1,23 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["dryrun.go"],
|
||||
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/util/dryrun",
|
||||
importpath = "k8s.io/apiserver/pkg/util/dryrun",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
22
staging/src/k8s.io/apiserver/pkg/util/dryrun/dryrun.go
Normal file
22
staging/src/k8s.io/apiserver/pkg/util/dryrun/dryrun.go
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
Copyright 2018 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 dryrun
|
||||
|
||||
// IsDryRun returns true if the DryRun flag is an actual dry-run.
|
||||
func IsDryRun(flag []string) bool {
|
||||
return len(flag) > 0
|
||||
}
|
@ -1134,6 +1134,10 @@
|
||||
"ImportPath": "k8s.io/apiserver/pkg/storage/value",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apiserver/pkg/util/dryrun",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apiserver/pkg/util/feature",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
@ -1106,6 +1106,10 @@
|
||||
"ImportPath": "k8s.io/apiserver/pkg/storage/value",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apiserver/pkg/util/dryrun",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apiserver/pkg/util/feature",
|
||||
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
Loading…
Reference in New Issue
Block a user