Switch ephemeralcontainers SR to Pod Kind

This changes the `/ephemeralcontainers` subresource of `/pods` to use
the `Pod` kind rather than `EphemeralContainers`.

When designing this API initially it seemed preferable to create a new
kind containing only the pod's ephemeral containers, similar to how
binding and scaling work.

It later became clear that this made admission control more difficult
because the controller wouldn't be presented with the entire Pod, so we
updated this to operate on the entire Pod, similar to how `/status`
works.
This commit is contained in:
Lee Verberne 2021-04-09 13:53:13 +02:00
parent dd72c4534c
commit d22dc5cb72
10 changed files with 164 additions and 213 deletions

View File

@ -93,7 +93,6 @@ API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommo
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,Ports
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,VolumeDevices
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,VolumeMounts
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainers,EphemeralContainers
API rule violation: list_type_missing,k8s.io/api/core/v1,ExecAction,Command
API rule violation: list_type_missing,k8s.io/api/core/v1,FCVolumeSource,TargetWWNs
API rule violation: list_type_missing,k8s.io/api/core/v1,FCVolumeSource,WWIDs

View File

@ -96,7 +96,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&RangeAllocation{},
&ConfigMap{},
&ConfigMapList{},
&EphemeralContainers{},
)
return nil

View File

@ -4439,20 +4439,6 @@ type Binding struct {
Target ObjectReference
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// EphemeralContainers is a list of ephemeral containers used with the Pod ephemeralcontainers subresource.
type EphemeralContainers struct {
metav1.TypeMeta
// +optional
metav1.ObjectMeta
// A list of ephemeral containers associated with this pod. New ephemeral containers
// may be appended to this list, but existing ephemeral containers may not be removed
// or modified.
EphemeralContainers []EphemeralContainer
}
// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
type Preconditions struct {
// Specifies the target UID.

View File

@ -293,23 +293,18 @@ type EphemeralContainersREST struct {
var _ = rest.Patcher(&EphemeralContainersREST{})
// Get of this endpoint will return the list of ephemeral containers in this pod
// Get retrieves the object from the storage. It is required to support Patch.
func (r *EphemeralContainersREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
if !utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
return nil, errors.NewBadRequest("feature EphemeralContainers disabled")
}
obj, err := r.store.Get(ctx, name, options)
if err != nil {
return nil, err
}
return ephemeralContainersInPod(obj.(*api.Pod)), nil
return r.store.Get(ctx, name, options)
}
// New creates a new EphemeralContainers resource
// New creates a new pod resource
func (r *EphemeralContainersREST) New() runtime.Object {
return &api.EphemeralContainers{}
return &api.Pod{}
}
// Update alters the EphemeralContainers field in PodSpec
@ -318,76 +313,7 @@ func (r *EphemeralContainersREST) Update(ctx context.Context, name string, objIn
return nil, false, errors.NewBadRequest("feature EphemeralContainers disabled")
}
obj, err := r.store.Get(ctx, name, &metav1.GetOptions{})
if err != nil {
return nil, false, err
}
pod := obj.(*api.Pod)
// Build an UpdatedObjectInfo to pass to the pod store.
// It is given the currently stored v1.Pod and transforms it to the new pod that should be stored.
updatedPodInfo := rest.DefaultUpdatedObjectInfo(pod, func(ctx context.Context, oldObject, _ runtime.Object) (newObject runtime.Object, err error) {
oldPod, ok := oldObject.(*api.Pod)
if !ok {
return nil, fmt.Errorf("unexpected type for Pod %T", oldObject)
}
newEphemeralContainersObj, err := objInfo.UpdatedObject(ctx, ephemeralContainersInPod(oldPod))
if err != nil {
return nil, err
}
newEphemeralContainers, ok := newEphemeralContainersObj.(*api.EphemeralContainers)
if !ok {
return nil, fmt.Errorf("unexpected type for EphemeralContainers %T", newEphemeralContainersObj)
}
// avoid mutating
newPod := oldPod.DeepCopy()
// identity, version (make sure we're working with the right object, instance, and version)
newPod.Name = newEphemeralContainers.Name
newPod.Namespace = newEphemeralContainers.Namespace
newPod.UID = newEphemeralContainers.UID
newPod.ResourceVersion = newEphemeralContainers.ResourceVersion
// ephemeral containers
newPod.Spec.EphemeralContainers = newEphemeralContainers.EphemeralContainers
return newPod, nil
})
// Validation should be passed the API kind (EphemeralContainers) rather than the storage kind.
obj, _, err = r.store.Update(ctx, name, updatedPodInfo, toEphemeralContainersCreateValidation(createValidation), toEphemeralContainersUpdateValidation(updateValidation), false, options)
if err != nil {
return nil, false, err
}
return ephemeralContainersInPod(obj.(*api.Pod)), false, err
}
func toEphemeralContainersCreateValidation(f rest.ValidateObjectFunc) rest.ValidateObjectFunc {
return func(ctx context.Context, obj runtime.Object) error {
return f(ctx, ephemeralContainersInPod(obj.(*api.Pod)))
}
}
func toEphemeralContainersUpdateValidation(f rest.ValidateObjectUpdateFunc) rest.ValidateObjectUpdateFunc {
return func(ctx context.Context, obj, old runtime.Object) error {
return f(ctx, ephemeralContainersInPod(obj.(*api.Pod)), ephemeralContainersInPod(old.(*api.Pod)))
}
}
// Extract the list of Ephemeral Containers from a Pod
func ephemeralContainersInPod(pod *api.Pod) *api.EphemeralContainers {
ephemeralContainers := pod.Spec.EphemeralContainers
if ephemeralContainers == nil {
ephemeralContainers = []api.EphemeralContainer{}
}
return &api.EphemeralContainers{
ObjectMeta: metav1.ObjectMeta{
Name: pod.Name,
Namespace: pod.Namespace,
UID: pod.UID,
ResourceVersion: pod.ResourceVersion,
CreationTimestamp: pod.CreationTimestamp,
},
EphemeralContainers: ephemeralContainers,
}
// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}

View File

@ -205,6 +205,18 @@ type podEphemeralContainersStrategy struct {
// EphemeralContainersStrategy wraps and exports the used podStrategy for the storage package.
var EphemeralContainersStrategy = podEphemeralContainersStrategy{Strategy}
func (podEphemeralContainersStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newPod := obj.(*api.Pod)
oldPod := old.(*api.Pod)
// Drop all changes except for pod.Spec.EphemeralContainers
ec := newPod.Spec.EphemeralContainers
*newPod = *oldPod
newPod.Spec.EphemeralContainers = ec
podutil.DropDisabledPodFields(newPod, oldPod)
}
func (podEphemeralContainersStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
newPod := obj.(*api.Pod)
oldPod := old.(*api.Pod)

View File

@ -88,7 +88,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&RangeAllocation{},
&ConfigMap{},
&ConfigMapList{},
&EphemeralContainers{},
)
// Add common types

View File

@ -3689,8 +3689,7 @@ type PodStatusResult struct {
}
// +genclient
// +genclient:method=GetEphemeralContainers,verb=get,subresource=ephemeralcontainers,result=EphemeralContainers
// +genclient:method=UpdateEphemeralContainers,verb=update,subresource=ephemeralcontainers,input=EphemeralContainers,result=EphemeralContainers
// +genclient:method=UpdateEphemeralContainers,verb=update,subresource=ephemeralcontainers
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Pod is a collection of containers that can run on a host. This resource is created
@ -5150,22 +5149,6 @@ type Binding struct {
Target ObjectReference `json:"target" protobuf:"bytes,2,opt,name=target"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// A list of ephemeral containers used with the Pod ephemeralcontainers subresource.
type EphemeralContainers struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// A list of ephemeral containers associated with this pod. New ephemeral containers
// may be appended to this list, but existing ephemeral containers may not be removed
// or modified.
// +patchMergeKey=name
// +patchStrategy=merge
EphemeralContainers []EphemeralContainer `json:"ephemeralContainers" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=ephemeralContainers"`
}
// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
// +k8s:openapi-gen=false
type Preconditions struct {

View File

@ -258,31 +258,72 @@ func (c *pods) ApplyStatus(ctx context.Context, pod *corev1.PodApplyConfiguratio
return
}
func ephemeralContainersInPod(pod *v1.Pod) *v1.EphemeralContainers {
ephemeralContainers := pod.Spec.EphemeralContainers
if ephemeralContainers == nil {
ephemeralContainers = []v1.EphemeralContainer{}
}
return &v1.EphemeralContainers{
ObjectMeta: metav1.ObjectMeta{
Name: pod.Name,
Namespace: pod.Namespace,
UID: pod.UID,
ResourceVersion: pod.ResourceVersion,
CreationTimestamp: pod.CreationTimestamp,
},
EphemeralContainers: ephemeralContainers,
}
}
// GetEphemeralContainers takes name of the pod, and returns the corresponding v1.EphemeralContainers object, and an error if there is any.
func (c *pods) GetEphemeralContainers(ctx context.Context, podName string, options metav1.GetOptions) (result *v1.EphemeralContainers, err error) {
result = &v1.EphemeralContainers{}
err = c.client.Get().
func (c *pods) GetEphemeralContainers(ctx context.Context, podName string, options metav1.GetOptions) (*v1.EphemeralContainers, error) {
pod := &v1.Pod{}
err := c.client.Get().
Namespace(c.ns).
Resource("pods").
Name(podName).
SubResource("ephemeralcontainers").
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
Into(pod)
if err != nil {
return nil, err
}
return ephemeralContainersInPod(pod), nil
}
// UpdateEphemeralContainers takes the top resource name and the representation of a ephemeralContainers and updates it. Returns the server's representation of the ephemeralContainers, and an error, if there is any.
func (c *pods) UpdateEphemeralContainers(ctx context.Context, podName string, ephemeralContainers *v1.EphemeralContainers, opts metav1.UpdateOptions) (result *v1.EphemeralContainers, err error) {
result = &v1.EphemeralContainers{}
func (c *pods) UpdateEphemeralContainers(ctx context.Context, podName string, ephemeralContainers *v1.EphemeralContainers, opts metav1.UpdateOptions) (*v1.EphemeralContainers, error) {
pod := &v1.Pod{}
err := c.client.Get().
Namespace(c.ns).
Resource("pods").
Name(podName).
SubResource("ephemeralcontainers").
VersionedParams(&metav1.GetOptions{}, scheme.ParameterCodec).
Do(ctx).
Into(pod)
if err != nil {
return nil, err
}
result := &v1.Pod{}
pod.Spec.EphemeralContainers = ephemeralContainers.EphemeralContainers
err = c.client.Put().
Namespace(c.ns).
Resource("pods").
Name(podName).
SubResource("ephemeralcontainers").
VersionedParams(&opts, scheme.ParameterCodec).
Body(ephemeralContainers).
Body(pod).
Do(ctx).
Into(result)
return
if err != nil {
return nil, err
}
return ephemeralContainersInPod(pod), nil
}

View File

@ -382,25 +382,22 @@ func (o *DebugOptions) visitPod(ctx context.Context, pod *corev1.Pod) (*corev1.P
// debugByEphemeralContainer runs an EphemeralContainer in the target Pod for use as a debug container
func (o *DebugOptions) debugByEphemeralContainer(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, string, error) {
pods := o.podClient.Pods(pod.Namespace)
ec, err := pods.GetEphemeralContainers(ctx, pod.Name, metav1.GetOptions{})
klog.V(2).Infof("existing ephemeral containers: %v", pod.Spec.EphemeralContainers)
debugContainer := o.generateDebugContainer(pod)
klog.V(2).Infof("new ephemeral container: %#v", debugContainer)
pod.Spec.EphemeralContainers = append(pod.Spec.EphemeralContainers, *debugContainer)
result, err := pods.UpdateEphemeralContainers(ctx, pod.Name, pod, metav1.UpdateOptions{})
if err != nil {
// The pod has already been fetched at this point, so a NotFound error indicates the ephemeralcontainers subresource wasn't found.
if serr, ok := err.(*errors.StatusError); ok && serr.Status().Reason == metav1.StatusReasonNotFound {
return nil, "", fmt.Errorf("ephemeral containers are disabled for this cluster (error from server: %q).", err)
}
return nil, "", err
}
klog.V(2).Infof("existing ephemeral containers: %v", ec.EphemeralContainers)
debugContainer := o.generateDebugContainer(pod)
klog.V(2).Infof("new ephemeral container: %#v", debugContainer)
ec.EphemeralContainers = append(ec.EphemeralContainers, *debugContainer)
_, err = pods.UpdateEphemeralContainers(ctx, pod.Name, ec, metav1.UpdateOptions{})
if err != nil {
return nil, "", fmt.Errorf("error updating ephemeral containers: %v", err)
}
return pod, debugContainer.Name, nil
return result, debugContainer.Name, nil
}
// debugByCopy runs a copy of the target Pod with a debug container added or an original container modified

View File

@ -235,31 +235,27 @@ func TestPodCreateEphemeralContainers(t *testing.T) {
// setUpEphemeralContainers creates a pod that has Ephemeral Containers. This is a two step
// process because Ephemeral Containers are not allowed during pod creation.
func setUpEphemeralContainers(podsClient typedv1.PodInterface, pod *v1.Pod, containers []v1.EphemeralContainer) error {
if _, err := podsClient.Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil {
return fmt.Errorf("failed to create pod: %v", err)
func setUpEphemeralContainers(podsClient typedv1.PodInterface, pod *v1.Pod, containers []v1.EphemeralContainer) (*v1.Pod, error) {
result, err := podsClient.Create(context.TODO(), pod, metav1.CreateOptions{})
if err != nil {
return nil, fmt.Errorf("failed to create pod: %v", err)
}
if len(containers) == 0 {
return nil
return result, nil
}
pod.Spec.EphemeralContainers = containers
if _, err := podsClient.Update(context.TODO(), pod, metav1.UpdateOptions{}); err == nil {
return fmt.Errorf("unexpected allowed direct update of ephemeral containers during set up: %v", err)
return nil, fmt.Errorf("unexpected allowed direct update of ephemeral containers during set up: %v", err)
}
ec, err := podsClient.GetEphemeralContainers(context.TODO(), pod.Name, metav1.GetOptions{})
result, err = podsClient.UpdateEphemeralContainers(context.TODO(), pod.Name, pod, metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("unable to get ephemeral containers for test case set up: %v", err)
return nil, fmt.Errorf("failed to update ephemeral containers for test case set up: %v", err)
}
ec.EphemeralContainers = containers
if _, err = podsClient.UpdateEphemeralContainers(context.TODO(), pod.Name, ec, metav1.UpdateOptions{}); err != nil {
return fmt.Errorf("failed to update ephemeral containers for test case set up: %v", err)
}
return nil
return result, nil
}
func TestPodPatchEphemeralContainers(t *testing.T) {
@ -303,12 +299,14 @@ func TestPodPatchEphemeralContainers(t *testing.T) {
original: nil,
patchType: types.StrategicMergePatchType,
patchBody: []byte(`{
"ephemeralContainers": [{
"name": "debugger1",
"image": "debugimage",
"imagePullPolicy": "Always",
"terminationMessagePolicy": "File"
}]
"spec": {
"ephemeralContainers": [{
"name": "debugger1",
"image": "debugimage",
"imagePullPolicy": "Always",
"terminationMessagePolicy": "File"
}]
}
}`),
valid: true,
},
@ -317,12 +315,14 @@ func TestPodPatchEphemeralContainers(t *testing.T) {
original: nil,
patchType: types.MergePatchType,
patchBody: []byte(`{
"ephemeralContainers":[{
"name": "debugger1",
"image": "debugimage",
"imagePullPolicy": "Always",
"terminationMessagePolicy": "File"
}]
"spec": {
"ephemeralContainers":[{
"name": "debugger1",
"image": "debugimage",
"imagePullPolicy": "Always",
"terminationMessagePolicy": "File"
}]
}
}`),
valid: true,
},
@ -330,15 +330,17 @@ func TestPodPatchEphemeralContainers(t *testing.T) {
name: "create single container (JSON)",
original: nil,
patchType: types.JSONPatchType,
// Because ephemeralContainers is optional, a JSON patch of an empty ephemeralContainers must add the
// list rather than simply appending to it.
patchBody: []byte(`[{
"op":"add",
"path":"/ephemeralContainers/-",
"value":{
"path":"/spec/ephemeralContainers",
"value":[{
"name":"debugger1",
"image":"debugimage",
"imagePullPolicy": "Always",
"terminationMessagePolicy": "File"
}
}]
}]`),
valid: true,
},
@ -356,12 +358,14 @@ func TestPodPatchEphemeralContainers(t *testing.T) {
},
patchType: types.StrategicMergePatchType,
patchBody: []byte(`{
"ephemeralContainers":[{
"name": "debugger2",
"image": "debugimage",
"imagePullPolicy": "Always",
"terminationMessagePolicy": "File"
}]
"spec": {
"ephemeralContainers":[{
"name": "debugger2",
"image": "debugimage",
"imagePullPolicy": "Always",
"terminationMessagePolicy": "File"
}]
}
}`),
valid: true,
},
@ -379,17 +383,19 @@ func TestPodPatchEphemeralContainers(t *testing.T) {
},
patchType: types.MergePatchType,
patchBody: []byte(`{
"ephemeralContainers":[{
"name": "debugger1",
"image": "debugimage",
"imagePullPolicy": "Always",
"terminationMessagePolicy": "File"
},{
"name": "debugger2",
"image": "debugimage",
"imagePullPolicy": "Always",
"terminationMessagePolicy": "File"
}]
"spec": {
"ephemeralContainers":[{
"name": "debugger1",
"image": "debugimage",
"imagePullPolicy": "Always",
"terminationMessagePolicy": "File"
},{
"name": "debugger2",
"image": "debugimage",
"imagePullPolicy": "Always",
"terminationMessagePolicy": "File"
}]
}
}`),
valid: true,
},
@ -408,7 +414,7 @@ func TestPodPatchEphemeralContainers(t *testing.T) {
patchType: types.JSONPatchType,
patchBody: []byte(`[{
"op":"add",
"path":"/ephemeralContainers/-",
"path":"/spec/ephemeralContainers/-",
"value":{
"name":"debugger2",
"image":"debugimage",
@ -431,9 +437,25 @@ func TestPodPatchEphemeralContainers(t *testing.T) {
},
},
patchType: types.MergePatchType,
patchBody: []byte(`{"ephemeralContainers":[]}`),
patchBody: []byte(`{"spec": {"ephemeralContainers":[]}}`),
valid: false,
},
{
name: "remove the single container (JSON)",
original: []v1.EphemeralContainer{
{
EphemeralContainerCommon: v1.EphemeralContainerCommon{
Name: "debugger1",
Image: "debugimage",
ImagePullPolicy: "Always",
TerminationMessagePolicy: "File",
},
},
},
patchType: types.JSONPatchType,
patchBody: []byte(`[{"op":"remove","path":"/ephemeralContainers/0"}]`),
valid: false, // disallowed by policy rather than patch semantics
},
{
name: "remove all containers (JSON)",
original: []v1.EphemeralContainer{
@ -447,14 +469,14 @@ func TestPodPatchEphemeralContainers(t *testing.T) {
},
},
patchType: types.JSONPatchType,
patchBody: []byte(`[{"op":"remove","path":"/ephemeralContainers/0"}]`),
valid: false,
patchBody: []byte(`[{"op":"remove","path":"/ephemeralContainers"}]`),
valid: false, // disallowed by policy rather than patch semantics
},
}
for i, tc := range cases {
pod := testPod(fmt.Sprintf("ephemeral-container-test-%v", i))
if err := setUpEphemeralContainers(client.CoreV1().Pods(ns.Name), pod, tc.original); err != nil {
if _, err := setUpEphemeralContainers(client.CoreV1().Pods(ns.Name), pod, tc.original); err != nil {
t.Errorf("%v: %v", tc.name, err)
}
@ -643,18 +665,13 @@ func TestPodUpdateEphemeralContainers(t *testing.T) {
}
for i, tc := range cases {
pod := testPod(fmt.Sprintf("ephemeral-container-test-%v", i))
if err := setUpEphemeralContainers(client.CoreV1().Pods(ns.Name), pod, tc.original); err != nil {
pod, err := setUpEphemeralContainers(client.CoreV1().Pods(ns.Name), testPod(fmt.Sprintf("ephemeral-container-test-%v", i)), tc.original)
if err != nil {
t.Errorf("%v: %v", tc.name, err)
}
ec, err := client.CoreV1().Pods(ns.Name).GetEphemeralContainers(context.TODO(), pod.Name, metav1.GetOptions{})
if err != nil {
t.Errorf("%v: unable to get ephemeral containers: %v", tc.name, err)
}
ec.EphemeralContainers = tc.update
if _, err := client.CoreV1().Pods(ns.Name).UpdateEphemeralContainers(context.TODO(), pod.Name, ec, metav1.UpdateOptions{}); tc.valid && err != nil {
pod.Spec.EphemeralContainers = tc.update
if _, err := client.CoreV1().Pods(ns.Name).UpdateEphemeralContainers(context.TODO(), pod.Name, pod, metav1.UpdateOptions{}); tc.valid && err != nil {
t.Errorf("%v: failed to update ephemeral containers: %v", tc.name, err)
} else if !tc.valid && err == nil {
t.Errorf("%v: unexpected allowed update to ephemeral containers", tc.name)
@ -690,29 +707,21 @@ func TestPodEphemeralContainersDisabled(t *testing.T) {
},
},
}
if err := setUpEphemeralContainers(client.CoreV1().Pods(ns.Name), pod, nil); err != nil {
pod, err := setUpEphemeralContainers(client.CoreV1().Pods(ns.Name), pod, nil)
if err != nil {
t.Error(err)
}
ec, err := client.CoreV1().Pods(ns.Name).GetEphemeralContainers(context.TODO(), pod.Name, metav1.GetOptions{})
if err == nil {
t.Errorf("got nil error when getting ephemeral containers with feature disabled, wanted %q", metav1.StatusReasonNotFound)
} else if se := err.(*errors.StatusError); se.ErrStatus.Reason != metav1.StatusReasonNotFound {
t.Errorf("got error reason %q when getting ephemeral containers with feature disabled, want %q: %#v", se.ErrStatus.Reason, metav1.StatusReasonNotFound, se)
}
ec.EphemeralContainers = []v1.EphemeralContainer{
{
EphemeralContainerCommon: v1.EphemeralContainerCommon{
Name: "debugger",
Image: "debugimage",
ImagePullPolicy: "Always",
TerminationMessagePolicy: "File",
},
pod.Spec.EphemeralContainers = append(pod.Spec.EphemeralContainers, v1.EphemeralContainer{
EphemeralContainerCommon: v1.EphemeralContainerCommon{
Name: "debugger",
Image: "debugimage",
ImagePullPolicy: "Always",
TerminationMessagePolicy: "File",
},
}
})
if _, err := client.CoreV1().Pods(ns.Name).UpdateEphemeralContainers(context.TODO(), pod.Name, ec, metav1.UpdateOptions{}); err == nil {
if _, err := client.CoreV1().Pods(ns.Name).UpdateEphemeralContainers(context.TODO(), pod.Name, pod, metav1.UpdateOptions{}); err == nil {
t.Errorf("got nil error when updating ephemeral containers with feature disabled, wanted %q", metav1.StatusReasonNotFound)
} else if se := err.(*errors.StatusError); se.ErrStatus.Reason != metav1.StatusReasonNotFound {
t.Errorf("got error reason %q when updating ephemeral containers with feature disabled, want %q: %#v", se.ErrStatus.Reason, metav1.StatusReasonNotFound, se)