mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 03:11:40 +00:00
Add Ephemeral Containers to the Kubernetes core API
This commit is contained in:
parent
c7ffc1cd8c
commit
013f049ce0
@ -125,6 +125,14 @@ API rule violation: list_type_missing,k8s.io/api/core/v1,EndpointSubset,NotReady
|
|||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,EndpointSubset,Ports
|
API rule violation: list_type_missing,k8s.io/api/core/v1,EndpointSubset,Ports
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,Endpoints,Subsets
|
API rule violation: list_type_missing,k8s.io/api/core/v1,Endpoints,Subsets
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,EndpointsList,Items
|
API rule violation: list_type_missing,k8s.io/api/core/v1,EndpointsList,Items
|
||||||
|
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,Args
|
||||||
|
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,Command
|
||||||
|
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,Env
|
||||||
|
API rule violation: list_type_missing,k8s.io/api/core/v1,EphemeralContainerCommon,EnvFrom
|
||||||
|
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,EventList,Items
|
API rule violation: list_type_missing,k8s.io/api/core/v1,EventList,Items
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,ExecAction,Command
|
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,TargetWWNs
|
||||||
@ -173,6 +181,7 @@ API rule violation: list_type_missing,k8s.io/api/core/v1,PodPortForwardOptions,P
|
|||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSecurityContext,SupplementalGroups
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSecurityContext,SupplementalGroups
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSecurityContext,Sysctls
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSecurityContext,Sysctls
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,Containers
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,Containers
|
||||||
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,EphemeralContainers
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,HostAliases
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,HostAliases
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,ImagePullSecrets
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,ImagePullSecrets
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,InitContainers
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,InitContainers
|
||||||
@ -181,6 +190,7 @@ API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,Tolerations
|
|||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,Volumes
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodSpec,Volumes
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodStatus,Conditions
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodStatus,Conditions
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodStatus,ContainerStatuses
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodStatus,ContainerStatuses
|
||||||
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodStatus,EphemeralContainerStatuses
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodStatus,InitContainerStatuses
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodStatus,InitContainerStatuses
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodStatus,PodIPs
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodStatus,PodIPs
|
||||||
API rule violation: list_type_missing,k8s.io/api/core/v1,PodTemplateList,Items
|
API rule violation: list_type_missing,k8s.io/api/core/v1,PodTemplateList,Items
|
||||||
|
@ -45,6 +45,13 @@ func VisitContainers(podSpec *api.PodSpec, visitor ContainerVisitor) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
|
||||||
|
for i := range podSpec.EphemeralContainers {
|
||||||
|
if !visitor((*api.Container)(&podSpec.EphemeralContainers[i].EphemeralContainerCommon)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,6 +369,9 @@ func dropDisabledFields(
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
|
||||||
|
podSpec.EphemeralContainers = nil
|
||||||
|
}
|
||||||
|
|
||||||
if (!utilfeature.DefaultFeatureGate.Enabled(features.VolumeSubpath) || !utilfeature.DefaultFeatureGate.Enabled(features.VolumeSubpathEnvExpansion)) && !subpathExprInUse(oldPodSpec) {
|
if (!utilfeature.DefaultFeatureGate.Enabled(features.VolumeSubpath) || !utilfeature.DefaultFeatureGate.Enabled(features.VolumeSubpathEnvExpansion)) && !subpathExprInUse(oldPodSpec) {
|
||||||
// drop subpath env expansion from the pod if either of the subpath features is disabled and the old spec did not specify subpath env expansion
|
// drop subpath env expansion from the pod if either of the subpath features is disabled and the old spec did not specify subpath env expansion
|
||||||
|
@ -35,6 +35,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestVisitContainers(t *testing.T) {
|
func TestVisitContainers(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
description string
|
||||||
haveSpec *api.PodSpec
|
haveSpec *api.PodSpec
|
||||||
@ -79,6 +81,37 @@ func TestVisitContainers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
[]string{"i1", "i2", "c1", "c2"},
|
[]string{"i1", "i2", "c1", "c2"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ephemeral containers",
|
||||||
|
&api.PodSpec{
|
||||||
|
Containers: []api.Container{
|
||||||
|
{Name: "c1"},
|
||||||
|
{Name: "c2"},
|
||||||
|
},
|
||||||
|
EphemeralContainers: []api.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e1"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]string{"c1", "c2", "e1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"all container types",
|
||||||
|
&api.PodSpec{
|
||||||
|
Containers: []api.Container{
|
||||||
|
{Name: "c1"},
|
||||||
|
{Name: "c2"},
|
||||||
|
},
|
||||||
|
InitContainers: []api.Container{
|
||||||
|
{Name: "i1"},
|
||||||
|
{Name: "i2"},
|
||||||
|
},
|
||||||
|
EphemeralContainers: []api.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e1"}},
|
||||||
|
{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e2"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]string{"i1", "i2", "c1", "c2", "e1", "e2"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"dropping fields",
|
"dropping fields",
|
||||||
&api.PodSpec{
|
&api.PodSpec{
|
||||||
@ -90,8 +123,12 @@ func TestVisitContainers(t *testing.T) {
|
|||||||
{Name: "i1"},
|
{Name: "i1"},
|
||||||
{Name: "i2", SecurityContext: &api.SecurityContext{}},
|
{Name: "i2", SecurityContext: &api.SecurityContext{}},
|
||||||
},
|
},
|
||||||
|
EphemeralContainers: []api.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e1"}},
|
||||||
|
{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e2", SecurityContext: &api.SecurityContext{}}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
[]string{"i1", "i2", "c1", "c2"},
|
[]string{"i1", "i2", "c1", "c2", "e1", "e2"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,10 +154,17 @@ func TestVisitContainers(t *testing.T) {
|
|||||||
t.Errorf("VisitContainers() for test case %q: got SecurityContext %#v for init container %v, wanted nil", tc.description, c.SecurityContext, c.Name)
|
t.Errorf("VisitContainers() for test case %q: got SecurityContext %#v for init container %v, wanted nil", tc.description, c.SecurityContext, c.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, c := range tc.haveSpec.EphemeralContainers {
|
||||||
|
if c.SecurityContext != nil {
|
||||||
|
t.Errorf("VisitContainers() for test case %q: got SecurityContext %#v for ephemeral container %v, wanted nil", tc.description, c.SecurityContext, c.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPodSecrets(t *testing.T) {
|
func TestPodSecrets(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
// Stub containing all possible secret references in a pod.
|
// Stub containing all possible secret references in a pod.
|
||||||
// The names of the referenced secrets match struct paths detected by reflection.
|
// The names of the referenced secrets match struct paths detected by reflection.
|
||||||
pod := &api.Pod{
|
pod := &api.Pod{
|
||||||
@ -195,6 +239,17 @@ func TestPodSecrets(t *testing.T) {
|
|||||||
CSI: &api.CSIVolumeSource{
|
CSI: &api.CSIVolumeSource{
|
||||||
NodePublishSecretRef: &api.LocalObjectReference{
|
NodePublishSecretRef: &api.LocalObjectReference{
|
||||||
Name: "Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef"}}}}},
|
Name: "Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef"}}}}},
|
||||||
|
EphemeralContainers: []api.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: api.EphemeralContainerCommon{
|
||||||
|
EnvFrom: []api.EnvFromSource{{
|
||||||
|
SecretRef: &api.SecretEnvSource{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{
|
||||||
|
Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef"}}}},
|
||||||
|
Env: []api.EnvVar{{
|
||||||
|
ValueFrom: &api.EnvVarSource{
|
||||||
|
SecretKeyRef: &api.SecretKeySelector{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{
|
||||||
|
Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef"}}}}}}}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
extractedNames := sets.NewString()
|
extractedNames := sets.NewString()
|
||||||
@ -212,6 +267,8 @@ func TestPodSecrets(t *testing.T) {
|
|||||||
expectedSecretPaths := sets.NewString(
|
expectedSecretPaths := sets.NewString(
|
||||||
"Spec.Containers[*].EnvFrom[*].SecretRef",
|
"Spec.Containers[*].EnvFrom[*].SecretRef",
|
||||||
"Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef",
|
"Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef",
|
||||||
|
"Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef",
|
||||||
|
"Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef",
|
||||||
"Spec.ImagePullSecrets",
|
"Spec.ImagePullSecrets",
|
||||||
"Spec.InitContainers[*].EnvFrom[*].SecretRef",
|
"Spec.InitContainers[*].EnvFrom[*].SecretRef",
|
||||||
"Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef",
|
"Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef",
|
||||||
@ -290,6 +347,8 @@ func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, n
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPodConfigmaps(t *testing.T) {
|
func TestPodConfigmaps(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
// Stub containing all possible ConfigMap references in a pod.
|
// Stub containing all possible ConfigMap references in a pod.
|
||||||
// The names of the referenced ConfigMaps match struct paths detected by reflection.
|
// The names of the referenced ConfigMaps match struct paths detected by reflection.
|
||||||
pod := &api.Pod{
|
pod := &api.Pod{
|
||||||
@ -304,6 +363,17 @@ func TestPodConfigmaps(t *testing.T) {
|
|||||||
ConfigMapKeyRef: &api.ConfigMapKeySelector{
|
ConfigMapKeyRef: &api.ConfigMapKeySelector{
|
||||||
LocalObjectReference: api.LocalObjectReference{
|
LocalObjectReference: api.LocalObjectReference{
|
||||||
Name: "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}},
|
Name: "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}},
|
||||||
|
EphemeralContainers: []api.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: api.EphemeralContainerCommon{
|
||||||
|
EnvFrom: []api.EnvFromSource{{
|
||||||
|
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{
|
||||||
|
Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef"}}}},
|
||||||
|
Env: []api.EnvVar{{
|
||||||
|
ValueFrom: &api.EnvVarSource{
|
||||||
|
ConfigMapKeyRef: &api.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: api.LocalObjectReference{
|
||||||
|
Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}},
|
||||||
InitContainers: []api.Container{{
|
InitContainers: []api.Container{{
|
||||||
EnvFrom: []api.EnvFromSource{{
|
EnvFrom: []api.EnvFromSource{{
|
||||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||||
@ -338,6 +408,8 @@ func TestPodConfigmaps(t *testing.T) {
|
|||||||
expectedPaths := sets.NewString(
|
expectedPaths := sets.NewString(
|
||||||
"Spec.Containers[*].EnvFrom[*].ConfigMapRef",
|
"Spec.Containers[*].EnvFrom[*].ConfigMapRef",
|
||||||
"Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef",
|
"Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef",
|
||||||
|
"Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef",
|
||||||
|
"Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef",
|
||||||
"Spec.InitContainers[*].EnvFrom[*].ConfigMapRef",
|
"Spec.InitContainers[*].EnvFrom[*].ConfigMapRef",
|
||||||
"Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef",
|
"Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef",
|
||||||
"Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap",
|
"Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap",
|
||||||
|
@ -50,6 +50,7 @@ func TestDefaulting(t *testing.T) {
|
|||||||
{Group: "", Version: "v1", Kind: "ConfigMapList"}: {},
|
{Group: "", Version: "v1", Kind: "ConfigMapList"}: {},
|
||||||
{Group: "", Version: "v1", Kind: "Endpoints"}: {},
|
{Group: "", Version: "v1", Kind: "Endpoints"}: {},
|
||||||
{Group: "", Version: "v1", Kind: "EndpointsList"}: {},
|
{Group: "", Version: "v1", Kind: "EndpointsList"}: {},
|
||||||
|
{Group: "", Version: "v1", Kind: "EphemeralContainers"}: {},
|
||||||
{Group: "", Version: "v1", Kind: "Namespace"}: {},
|
{Group: "", Version: "v1", Kind: "Namespace"}: {},
|
||||||
{Group: "", Version: "v1", Kind: "NamespaceList"}: {},
|
{Group: "", Version: "v1", Kind: "NamespaceList"}: {},
|
||||||
{Group: "", Version: "v1", Kind: "Node"}: {},
|
{Group: "", Version: "v1", Kind: "Node"}: {},
|
||||||
|
@ -23,6 +23,8 @@ import (
|
|||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FindPort locates the container port for the given pod and portName. If the
|
// FindPort locates the container port for the given pod and portName. If the
|
||||||
@ -67,6 +69,13 @@ func VisitContainers(podSpec *v1.PodSpec, visitor ContainerVisitor) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
|
||||||
|
for i := range podSpec.EphemeralContainers {
|
||||||
|
if !visitor((*v1.Container)(&podSpec.EphemeralContainers[i].EphemeralContainerCommon)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,9 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFindPort(t *testing.T) {
|
func TestFindPort(t *testing.T) {
|
||||||
@ -199,6 +202,8 @@ func TestFindPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestVisitContainers(t *testing.T) {
|
func TestVisitContainers(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
description string
|
||||||
haveSpec *v1.PodSpec
|
haveSpec *v1.PodSpec
|
||||||
@ -243,6 +248,37 @@ func TestVisitContainers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
[]string{"i1", "i2", "c1", "c2"},
|
[]string{"i1", "i2", "c1", "c2"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ephemeral containers",
|
||||||
|
&v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{Name: "c1"},
|
||||||
|
{Name: "c2"},
|
||||||
|
},
|
||||||
|
EphemeralContainers: []v1.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]string{"c1", "c2", "e1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"all container types",
|
||||||
|
&v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{Name: "c1"},
|
||||||
|
{Name: "c2"},
|
||||||
|
},
|
||||||
|
InitContainers: []v1.Container{
|
||||||
|
{Name: "i1"},
|
||||||
|
{Name: "i2"},
|
||||||
|
},
|
||||||
|
EphemeralContainers: []v1.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
|
||||||
|
{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]string{"i1", "i2", "c1", "c2", "e1", "e2"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"dropping fields",
|
"dropping fields",
|
||||||
&v1.PodSpec{
|
&v1.PodSpec{
|
||||||
@ -254,8 +290,12 @@ func TestVisitContainers(t *testing.T) {
|
|||||||
{Name: "i1"},
|
{Name: "i1"},
|
||||||
{Name: "i2", SecurityContext: &v1.SecurityContext{}},
|
{Name: "i2", SecurityContext: &v1.SecurityContext{}},
|
||||||
},
|
},
|
||||||
|
EphemeralContainers: []v1.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e1"}},
|
||||||
|
{EphemeralContainerCommon: v1.EphemeralContainerCommon{Name: "e2", SecurityContext: &v1.SecurityContext{}}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
[]string{"i1", "i2", "c1", "c2"},
|
[]string{"i1", "i2", "c1", "c2", "e1", "e2"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,10 +321,17 @@ func TestVisitContainers(t *testing.T) {
|
|||||||
t.Errorf("VisitContainers() for test case %q: got SecurityContext %#v for init container %v, wanted nil", tc.description, c.SecurityContext, c.Name)
|
t.Errorf("VisitContainers() for test case %q: got SecurityContext %#v for init container %v, wanted nil", tc.description, c.SecurityContext, c.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, c := range tc.haveSpec.EphemeralContainers {
|
||||||
|
if c.SecurityContext != nil {
|
||||||
|
t.Errorf("VisitContainers() for test case %q: got SecurityContext %#v for ephemeral container %v, wanted nil", tc.description, c.SecurityContext, c.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPodSecrets(t *testing.T) {
|
func TestPodSecrets(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
// Stub containing all possible secret references in a pod.
|
// Stub containing all possible secret references in a pod.
|
||||||
// The names of the referenced secrets match struct paths detected by reflection.
|
// The names of the referenced secrets match struct paths detected by reflection.
|
||||||
pod := &v1.Pod{
|
pod := &v1.Pod{
|
||||||
@ -359,6 +406,17 @@ func TestPodSecrets(t *testing.T) {
|
|||||||
CSI: &v1.CSIVolumeSource{
|
CSI: &v1.CSIVolumeSource{
|
||||||
NodePublishSecretRef: &v1.LocalObjectReference{
|
NodePublishSecretRef: &v1.LocalObjectReference{
|
||||||
Name: "Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef"}}}}},
|
Name: "Spec.Volumes[*].VolumeSource.CSI.NodePublishSecretRef"}}}}},
|
||||||
|
EphemeralContainers: []v1.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: v1.EphemeralContainerCommon{
|
||||||
|
EnvFrom: []v1.EnvFromSource{{
|
||||||
|
SecretRef: &v1.SecretEnvSource{
|
||||||
|
LocalObjectReference: v1.LocalObjectReference{
|
||||||
|
Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef"}}}},
|
||||||
|
Env: []v1.EnvVar{{
|
||||||
|
ValueFrom: &v1.EnvVarSource{
|
||||||
|
SecretKeyRef: &v1.SecretKeySelector{
|
||||||
|
LocalObjectReference: v1.LocalObjectReference{
|
||||||
|
Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef"}}}}}}}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
extractedNames := sets.NewString()
|
extractedNames := sets.NewString()
|
||||||
@ -376,6 +434,8 @@ func TestPodSecrets(t *testing.T) {
|
|||||||
expectedSecretPaths := sets.NewString(
|
expectedSecretPaths := sets.NewString(
|
||||||
"Spec.Containers[*].EnvFrom[*].SecretRef",
|
"Spec.Containers[*].EnvFrom[*].SecretRef",
|
||||||
"Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef",
|
"Spec.Containers[*].Env[*].ValueFrom.SecretKeyRef",
|
||||||
|
"Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].SecretRef",
|
||||||
|
"Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.SecretKeyRef",
|
||||||
"Spec.ImagePullSecrets",
|
"Spec.ImagePullSecrets",
|
||||||
"Spec.InitContainers[*].EnvFrom[*].SecretRef",
|
"Spec.InitContainers[*].EnvFrom[*].SecretRef",
|
||||||
"Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef",
|
"Spec.InitContainers[*].Env[*].ValueFrom.SecretKeyRef",
|
||||||
@ -454,6 +514,8 @@ func collectResourcePaths(t *testing.T, resourcename string, path *field.Path, n
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPodConfigmaps(t *testing.T) {
|
func TestPodConfigmaps(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
// Stub containing all possible ConfigMap references in a pod.
|
// Stub containing all possible ConfigMap references in a pod.
|
||||||
// The names of the referenced ConfigMaps match struct paths detected by reflection.
|
// The names of the referenced ConfigMaps match struct paths detected by reflection.
|
||||||
pod := &v1.Pod{
|
pod := &v1.Pod{
|
||||||
@ -468,6 +530,17 @@ func TestPodConfigmaps(t *testing.T) {
|
|||||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: v1.LocalObjectReference{
|
LocalObjectReference: v1.LocalObjectReference{
|
||||||
Name: "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}},
|
Name: "Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}},
|
||||||
|
EphemeralContainers: []v1.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: v1.EphemeralContainerCommon{
|
||||||
|
EnvFrom: []v1.EnvFromSource{{
|
||||||
|
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||||
|
LocalObjectReference: v1.LocalObjectReference{
|
||||||
|
Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef"}}}},
|
||||||
|
Env: []v1.EnvVar{{
|
||||||
|
ValueFrom: &v1.EnvVarSource{
|
||||||
|
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: v1.LocalObjectReference{
|
||||||
|
Name: "Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef"}}}}}}}},
|
||||||
InitContainers: []v1.Container{{
|
InitContainers: []v1.Container{{
|
||||||
EnvFrom: []v1.EnvFromSource{{
|
EnvFrom: []v1.EnvFromSource{{
|
||||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||||
@ -502,6 +575,8 @@ func TestPodConfigmaps(t *testing.T) {
|
|||||||
expectedPaths := sets.NewString(
|
expectedPaths := sets.NewString(
|
||||||
"Spec.Containers[*].EnvFrom[*].ConfigMapRef",
|
"Spec.Containers[*].EnvFrom[*].ConfigMapRef",
|
||||||
"Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef",
|
"Spec.Containers[*].Env[*].ValueFrom.ConfigMapKeyRef",
|
||||||
|
"Spec.EphemeralContainers[*].EphemeralContainerCommon.EnvFrom[*].ConfigMapRef",
|
||||||
|
"Spec.EphemeralContainers[*].EphemeralContainerCommon.Env[*].ValueFrom.ConfigMapKeyRef",
|
||||||
"Spec.InitContainers[*].EnvFrom[*].ConfigMapRef",
|
"Spec.InitContainers[*].EnvFrom[*].ConfigMapRef",
|
||||||
"Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef",
|
"Spec.InitContainers[*].Env[*].ValueFrom.ConfigMapKeyRef",
|
||||||
"Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap",
|
"Spec.Volumes[*].VolumeSource.Projected.Sources[*].ConfigMap",
|
||||||
|
@ -26,8 +26,11 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
"k8s.io/kubernetes/pkg/apis/apps"
|
"k8s.io/kubernetes/pkg/apis/apps"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidateStatefulSet(t *testing.T) {
|
func TestValidateStatefulSet(t *testing.T) {
|
||||||
@ -1776,6 +1779,8 @@ func TestValidateDaemonSetUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateDaemonSet(t *testing.T) {
|
func TestValidateDaemonSet(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
validSelector := map[string]string{"a": "b"}
|
validSelector := map[string]string{"a": "b"}
|
||||||
validPodTemplate := api.PodTemplate{
|
validPodTemplate := api.PodTemplate{
|
||||||
Template: api.PodTemplateSpec{
|
Template: api.PodTemplateSpec{
|
||||||
@ -1946,6 +1951,26 @@ func TestValidateDaemonSet(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"template may not contain ephemeral containers": {
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
|
||||||
|
Spec: apps.DaemonSetSpec{
|
||||||
|
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
|
||||||
|
Template: api.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Labels: validSelector,
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
RestartPolicy: api.RestartPolicyAlways,
|
||||||
|
DNSPolicy: api.DNSClusterFirst,
|
||||||
|
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
|
||||||
|
EphemeralContainers: []api.EphemeralContainer{{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UpdateStrategy: apps.DaemonSetUpdateStrategy{
|
||||||
|
Type: apps.OnDeleteDaemonSetStrategyType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for k, v := range errorCases {
|
for k, v := range errorCases {
|
||||||
errs := ValidateDaemonSet(&v)
|
errs := ValidateDaemonSet(&v)
|
||||||
@ -2018,6 +2043,8 @@ func validDeployment() *apps.Deployment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateDeployment(t *testing.T) {
|
func TestValidateDeployment(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
successCases := []*apps.Deployment{
|
successCases := []*apps.Deployment{
|
||||||
validDeployment(),
|
validDeployment(),
|
||||||
}
|
}
|
||||||
@ -2103,6 +2130,17 @@ func TestValidateDeployment(t *testing.T) {
|
|||||||
invalidProgressDeadlineDeployment.Spec.MinReadySeconds = seconds
|
invalidProgressDeadlineDeployment.Spec.MinReadySeconds = seconds
|
||||||
errorCases["must be greater than minReadySeconds"] = invalidProgressDeadlineDeployment
|
errorCases["must be greater than minReadySeconds"] = invalidProgressDeadlineDeployment
|
||||||
|
|
||||||
|
// Must not have ephemeral containers
|
||||||
|
invalidEphemeralContainersDeployment := validDeployment()
|
||||||
|
invalidEphemeralContainersDeployment.Spec.Template.Spec.EphemeralContainers = []api.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: api.EphemeralContainerCommon{
|
||||||
|
Name: "ec",
|
||||||
|
Image: "image",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File"},
|
||||||
|
}}
|
||||||
|
errorCases["ephemeral containers not allowed"] = invalidEphemeralContainersDeployment
|
||||||
|
|
||||||
for k, v := range errorCases {
|
for k, v := range errorCases {
|
||||||
errs := ValidateDeployment(v)
|
errs := ValidateDeployment(v)
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
|
@ -20,7 +20,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/fieldpath"
|
"k8s.io/kubernetes/pkg/fieldpath"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,6 +47,14 @@ func VisitContainersWithPath(podSpec *api.PodSpec, visitor ContainerVisitorWithP
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
|
||||||
|
path = field.NewPath("spec", "ephemeralContainers")
|
||||||
|
for i := range podSpec.EphemeralContainers {
|
||||||
|
if !visitor((*api.Container)(&podSpec.EphemeralContainers[i].EphemeralContainerCommon), path.Index(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,10 +21,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVisitContainersWithPath(t *testing.T) {
|
func TestVisitContainersWithPath(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
description string
|
||||||
haveSpec *api.PodSpec
|
haveSpec *api.PodSpec
|
||||||
@ -69,6 +74,37 @@ func TestVisitContainersWithPath(t *testing.T) {
|
|||||||
},
|
},
|
||||||
[]string{"spec.initContainers[0]", "spec.initContainers[1]", "spec.containers[0]", "spec.containers[1]"},
|
[]string{"spec.initContainers[0]", "spec.initContainers[1]", "spec.containers[0]", "spec.containers[1]"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ephemeral containers",
|
||||||
|
&api.PodSpec{
|
||||||
|
Containers: []api.Container{
|
||||||
|
{Name: "c1"},
|
||||||
|
{Name: "c2"},
|
||||||
|
},
|
||||||
|
EphemeralContainers: []api.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e1"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]string{"spec.containers[0]", "spec.containers[1]", "spec.ephemeralContainers[0]"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"all container types",
|
||||||
|
&api.PodSpec{
|
||||||
|
Containers: []api.Container{
|
||||||
|
{Name: "c1"},
|
||||||
|
{Name: "c2"},
|
||||||
|
},
|
||||||
|
InitContainers: []api.Container{
|
||||||
|
{Name: "i1"},
|
||||||
|
{Name: "i2"},
|
||||||
|
},
|
||||||
|
EphemeralContainers: []api.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e1"}},
|
||||||
|
{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "e2"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]string{"spec.initContainers[0]", "spec.initContainers[1]", "spec.containers[0]", "spec.containers[1]", "spec.ephemeralContainers[0]", "spec.ephemeralContainers[1]"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
@ -92,6 +92,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||||||
&RangeAllocation{},
|
&RangeAllocation{},
|
||||||
&ConfigMap{},
|
&ConfigMap{},
|
||||||
&ConfigMapList{},
|
&ConfigMapList{},
|
||||||
|
&EphemeralContainers{},
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -2591,6 +2591,15 @@ type PodSpec struct {
|
|||||||
InitContainers []Container
|
InitContainers []Container
|
||||||
// List of containers belonging to the pod.
|
// List of containers belonging to the pod.
|
||||||
Containers []Container
|
Containers []Container
|
||||||
|
// EphemeralContainers is the list of ephemeral containers that run in this pod. Ephemeral containers
|
||||||
|
// are added to an existing pod as a result of a user-initiated action such as troubleshooting.
|
||||||
|
// This list is read-only in the pod spec. It may not be specified in a create or modified in an
|
||||||
|
// update of a pod or pod template.
|
||||||
|
// To add an ephemeral container use the pod's ephemeralcontainers subresource, which allows update
|
||||||
|
// using the EphemeralContainers kind.
|
||||||
|
// This field is alpha-level and is only honored by servers that enable the EphemeralContainers feature.
|
||||||
|
// +optional
|
||||||
|
EphemeralContainers []EphemeralContainer
|
||||||
// +optional
|
// +optional
|
||||||
RestartPolicy RestartPolicy
|
RestartPolicy RestartPolicy
|
||||||
// Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request.
|
// Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request.
|
||||||
@ -2873,6 +2882,106 @@ type PodIP struct {
|
|||||||
IP string
|
IP string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EphemeralContainerCommon struct {
|
||||||
|
// Required: This must be a DNS_LABEL. Each container in a pod must
|
||||||
|
// have a unique name.
|
||||||
|
Name string
|
||||||
|
// Required.
|
||||||
|
Image string
|
||||||
|
// Optional: The docker image's entrypoint is used if this is not provided; cannot be updated.
|
||||||
|
// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
|
||||||
|
// cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax
|
||||||
|
// can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded,
|
||||||
|
// regardless of whether the variable exists or not.
|
||||||
|
// +optional
|
||||||
|
Command []string
|
||||||
|
// Optional: The docker image's cmd is used if this is not provided; cannot be updated.
|
||||||
|
// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
|
||||||
|
// cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax
|
||||||
|
// can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded,
|
||||||
|
// regardless of whether the variable exists or not.
|
||||||
|
// +optional
|
||||||
|
Args []string
|
||||||
|
// Optional: Defaults to Docker's default.
|
||||||
|
// +optional
|
||||||
|
WorkingDir string
|
||||||
|
// Ports are not allowed for ephemeral containers.
|
||||||
|
// +optional
|
||||||
|
Ports []ContainerPort
|
||||||
|
// List of sources to populate environment variables in the container.
|
||||||
|
// The keys defined within a source must be a C_IDENTIFIER. All invalid keys
|
||||||
|
// will be reported as an event when the container is starting. When a key exists in multiple
|
||||||
|
// sources, the value associated with the last source will take precedence.
|
||||||
|
// Values defined by an Env with a duplicate key will take precedence.
|
||||||
|
// Cannot be updated.
|
||||||
|
// +optional
|
||||||
|
EnvFrom []EnvFromSource
|
||||||
|
// +optional
|
||||||
|
Env []EnvVar
|
||||||
|
// Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources
|
||||||
|
// already allocated to the pod.
|
||||||
|
// +optional
|
||||||
|
Resources ResourceRequirements
|
||||||
|
// +optional
|
||||||
|
VolumeMounts []VolumeMount
|
||||||
|
// volumeDevices is the list of block devices to be used by the container.
|
||||||
|
// This is a beta feature.
|
||||||
|
// +optional
|
||||||
|
VolumeDevices []VolumeDevice
|
||||||
|
// Probes are not allowed for ephemeral containers.
|
||||||
|
// +optional
|
||||||
|
LivenessProbe *Probe
|
||||||
|
// Probes are not allowed for ephemeral containers.
|
||||||
|
// +optional
|
||||||
|
ReadinessProbe *Probe
|
||||||
|
// Lifecycle is not allowed for ephemeral containers.
|
||||||
|
// +optional
|
||||||
|
Lifecycle *Lifecycle
|
||||||
|
// Required.
|
||||||
|
// +optional
|
||||||
|
TerminationMessagePath string
|
||||||
|
// +optional
|
||||||
|
TerminationMessagePolicy TerminationMessagePolicy
|
||||||
|
// Required: Policy for pulling images for this container
|
||||||
|
ImagePullPolicy PullPolicy
|
||||||
|
// SecurityContext is not allowed for ephemeral containers.
|
||||||
|
// +optional
|
||||||
|
SecurityContext *SecurityContext
|
||||||
|
|
||||||
|
// Variables for interactive containers, these have very specialized use-cases (e.g. debugging)
|
||||||
|
// and shouldn't be used for general purpose containers.
|
||||||
|
// +optional
|
||||||
|
Stdin bool
|
||||||
|
// +optional
|
||||||
|
StdinOnce bool
|
||||||
|
// +optional
|
||||||
|
TTY bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// EphemeralContainerCommon converts to Container. All fields must be kept in sync between
|
||||||
|
// these two types.
|
||||||
|
var _ = Container(EphemeralContainerCommon{})
|
||||||
|
|
||||||
|
// An EphemeralContainer is a special type of container which doesn't come with any resource
|
||||||
|
// or scheduling guarantees but can be added to a pod that has already been created. They are
|
||||||
|
// intended for user-initiated activities such as troubleshooting a running pod.
|
||||||
|
// Ephemeral containers will not be restarted when they exit, and they will be killed if the
|
||||||
|
// pod is removed or restarted. If an ephemeral container causes a pod to exceed its resource
|
||||||
|
// allocation, the pod may be evicted.
|
||||||
|
// Ephemeral containers are added via a pod's ephemeralcontainers subresource and will appear
|
||||||
|
// in the pod spec once added.
|
||||||
|
// This is an alpha feature enabled by the EphemeralContainers feature flag.
|
||||||
|
type EphemeralContainer struct {
|
||||||
|
EphemeralContainerCommon
|
||||||
|
|
||||||
|
// If set, the name of the container from PodSpec that this ephemeral container targets.
|
||||||
|
// The ephemeral container will be run in the namespaces (IPC, PID, etc) of this container.
|
||||||
|
// If not set then the ephemeral container is run in whatever namespaces are shared
|
||||||
|
// for the pod. Note that the container runtime must support this feature.
|
||||||
|
// +optional
|
||||||
|
TargetContainerName string
|
||||||
|
}
|
||||||
|
|
||||||
// PodStatus represents information about the status of a pod. Status may trail the actual
|
// PodStatus represents information about the status of a pod. Status may trail the actual
|
||||||
// state of a system.
|
// state of a system.
|
||||||
type PodStatus struct {
|
type PodStatus struct {
|
||||||
@ -2920,6 +3029,11 @@ type PodStatus struct {
|
|||||||
// when we have done this.
|
// when we have done this.
|
||||||
// +optional
|
// +optional
|
||||||
ContainerStatuses []ContainerStatus
|
ContainerStatuses []ContainerStatus
|
||||||
|
|
||||||
|
// Status for any ephemeral containers that running in this pod.
|
||||||
|
// This field is alpha-level and is only honored by servers that enable the EphemeralContainers feature.
|
||||||
|
// +optional
|
||||||
|
EphemeralContainerStatuses []ContainerStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
@ -3926,6 +4040,18 @@ type Binding struct {
|
|||||||
Target ObjectReference
|
Target ObjectReference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// A list of ephemeral containers used in API operations
|
||||||
|
type EphemeralContainers struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
// +optional
|
||||||
|
metav1.ObjectMeta
|
||||||
|
|
||||||
|
// The new set of ephemeral containers to use for a pod.
|
||||||
|
EphemeralContainers []EphemeralContainer
|
||||||
|
}
|
||||||
|
|
||||||
// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
|
// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
|
||||||
type Preconditions struct {
|
type Preconditions struct {
|
||||||
// Specifies the target UID.
|
// Specifies the target UID.
|
||||||
|
@ -70,27 +70,45 @@ func TestWorkloadDefaults(t *testing.T) {
|
|||||||
".Spec.Containers[0].TerminationMessagePath": `"/dev/termination-log"`,
|
".Spec.Containers[0].TerminationMessagePath": `"/dev/termination-log"`,
|
||||||
".Spec.Containers[0].TerminationMessagePolicy": `"File"`,
|
".Spec.Containers[0].TerminationMessagePolicy": `"File"`,
|
||||||
".Spec.DNSPolicy": `"ClusterFirst"`,
|
".Spec.DNSPolicy": `"ClusterFirst"`,
|
||||||
".Spec.InitContainers[0].Env[0].ValueFrom.FieldRef.APIVersion": `"v1"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Env[0].ValueFrom.FieldRef.APIVersion": `"v1"`,
|
||||||
".Spec.InitContainers[0].ImagePullPolicy": `"IfNotPresent"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Lifecycle.PostStart.HTTPGet.Path": `"/"`,
|
||||||
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Path": `"/"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Lifecycle.PostStart.HTTPGet.Scheme": `"HTTP"`,
|
||||||
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Scheme": `"HTTP"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Lifecycle.PreStop.HTTPGet.Path": `"/"`,
|
||||||
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Path": `"/"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Lifecycle.PreStop.HTTPGet.Scheme": `"HTTP"`,
|
||||||
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Scheme": `"HTTP"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.FailureThreshold": "3",
|
||||||
".Spec.InitContainers[0].LivenessProbe.FailureThreshold": `3`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Path": `"/"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.PeriodSeconds": "10",
|
||||||
".Spec.InitContainers[0].LivenessProbe.PeriodSeconds": `10`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.SuccessThreshold": "1",
|
||||||
".Spec.InitContainers[0].LivenessProbe.SuccessThreshold": `1`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.TimeoutSeconds": "1",
|
||||||
".Spec.InitContainers[0].LivenessProbe.TimeoutSeconds": `1`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Ports[0].Protocol": `"TCP"`,
|
||||||
".Spec.InitContainers[0].Ports[0].Protocol": `"TCP"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.FailureThreshold": "3",
|
||||||
".Spec.InitContainers[0].ReadinessProbe.FailureThreshold": `3`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Path": `"/"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.PeriodSeconds": "10",
|
||||||
".Spec.InitContainers[0].ReadinessProbe.PeriodSeconds": `10`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.SuccessThreshold": "1",
|
||||||
".Spec.InitContainers[0].ReadinessProbe.SuccessThreshold": `1`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.TimeoutSeconds": "1",
|
||||||
".Spec.InitContainers[0].ReadinessProbe.TimeoutSeconds": `1`,
|
".Spec.InitContainers[0].Env[0].ValueFrom.FieldRef.APIVersion": `"v1"`,
|
||||||
".Spec.InitContainers[0].TerminationMessagePath": `"/dev/termination-log"`,
|
".Spec.InitContainers[0].ImagePullPolicy": `"IfNotPresent"`,
|
||||||
".Spec.InitContainers[0].TerminationMessagePolicy": `"File"`,
|
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.InitContainers[0].Ports[0].Protocol": `"TCP"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.InitContainers[0].TerminationMessagePath": `"/dev/termination-log"`,
|
||||||
|
".Spec.InitContainers[0].TerminationMessagePolicy": `"File"`,
|
||||||
".Spec.RestartPolicy": `"Always"`,
|
".Spec.RestartPolicy": `"Always"`,
|
||||||
".Spec.SchedulerName": `"default-scheduler"`,
|
".Spec.SchedulerName": `"default-scheduler"`,
|
||||||
".Spec.SecurityContext": `{}`,
|
".Spec.SecurityContext": `{}`,
|
||||||
@ -156,28 +174,46 @@ func TestPodDefaults(t *testing.T) {
|
|||||||
".Spec.Containers[0].TerminationMessagePolicy": `"File"`,
|
".Spec.Containers[0].TerminationMessagePolicy": `"File"`,
|
||||||
".Spec.DNSPolicy": `"ClusterFirst"`,
|
".Spec.DNSPolicy": `"ClusterFirst"`,
|
||||||
".Spec.EnableServiceLinks": `true`,
|
".Spec.EnableServiceLinks": `true`,
|
||||||
".Spec.InitContainers[0].Env[0].ValueFrom.FieldRef.APIVersion": `"v1"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Env[0].ValueFrom.FieldRef.APIVersion": `"v1"`,
|
||||||
".Spec.InitContainers[0].ImagePullPolicy": `"IfNotPresent"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Lifecycle.PostStart.HTTPGet.Path": `"/"`,
|
||||||
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Path": `"/"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Lifecycle.PostStart.HTTPGet.Scheme": `"HTTP"`,
|
||||||
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Scheme": `"HTTP"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Lifecycle.PreStop.HTTPGet.Path": `"/"`,
|
||||||
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Path": `"/"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Lifecycle.PreStop.HTTPGet.Scheme": `"HTTP"`,
|
||||||
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Scheme": `"HTTP"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.FailureThreshold": "3",
|
||||||
".Spec.InitContainers[0].LivenessProbe.FailureThreshold": `3`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Path": `"/"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.PeriodSeconds": "10",
|
||||||
".Spec.InitContainers[0].LivenessProbe.PeriodSeconds": `10`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.SuccessThreshold": "1",
|
||||||
".Spec.InitContainers[0].LivenessProbe.SuccessThreshold": `1`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.LivenessProbe.TimeoutSeconds": "1",
|
||||||
".Spec.InitContainers[0].LivenessProbe.TimeoutSeconds": `1`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.Ports[0].Protocol": `"TCP"`,
|
||||||
".Spec.InitContainers[0].Ports[0].Protocol": `"TCP"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.FailureThreshold": "3",
|
||||||
".Spec.InitContainers[0].ReadinessProbe.FailureThreshold": `3`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Path": `"/"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.PeriodSeconds": "10",
|
||||||
".Spec.InitContainers[0].ReadinessProbe.PeriodSeconds": `10`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.SuccessThreshold": "1",
|
||||||
".Spec.InitContainers[0].ReadinessProbe.SuccessThreshold": `1`,
|
".Spec.EphemeralContainers[0].EphemeralContainerCommon.ReadinessProbe.TimeoutSeconds": "1",
|
||||||
".Spec.InitContainers[0].ReadinessProbe.TimeoutSeconds": `1`,
|
".Spec.InitContainers[0].Env[0].ValueFrom.FieldRef.APIVersion": `"v1"`,
|
||||||
".Spec.InitContainers[0].Resources.Requests": `{"":"0"}`, // this gets defaulted from the limits field
|
".Spec.InitContainers[0].ImagePullPolicy": `"IfNotPresent"`,
|
||||||
".Spec.InitContainers[0].TerminationMessagePath": `"/dev/termination-log"`,
|
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Path": `"/"`,
|
||||||
".Spec.InitContainers[0].TerminationMessagePolicy": `"File"`,
|
".Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.InitContainers[0].LivenessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.InitContainers[0].Ports[0].Protocol": `"TCP"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.FailureThreshold": `3`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Path": `"/"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.Handler.HTTPGet.Scheme": `"HTTP"`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.PeriodSeconds": `10`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.SuccessThreshold": `1`,
|
||||||
|
".Spec.InitContainers[0].ReadinessProbe.TimeoutSeconds": `1`,
|
||||||
|
".Spec.InitContainers[0].Resources.Requests": `{"":"0"}`, // this gets defaulted from the limits field
|
||||||
|
".Spec.InitContainers[0].TerminationMessagePath": `"/dev/termination-log"`,
|
||||||
|
".Spec.InitContainers[0].TerminationMessagePolicy": `"File"`,
|
||||||
".Spec.RestartPolicy": `"Always"`,
|
".Spec.RestartPolicy": `"Always"`,
|
||||||
".Spec.SchedulerName": `"default-scheduler"`,
|
".Spec.SchedulerName": `"default-scheduler"`,
|
||||||
".Spec.SecurityContext": `{}`,
|
".Spec.SecurityContext": `{}`,
|
||||||
|
@ -26,6 +26,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
|
|
||||||
@ -73,6 +75,23 @@ var iscsiInitiatorIqnRegex = regexp.MustCompile(`iqn\.\d{4}-\d{2}\.([[:alnum:]-.
|
|||||||
var iscsiInitiatorEuiRegex = regexp.MustCompile(`^eui.[[:alnum:]]{16}$`)
|
var iscsiInitiatorEuiRegex = regexp.MustCompile(`^eui.[[:alnum:]]{16}$`)
|
||||||
var iscsiInitiatorNaaRegex = regexp.MustCompile(`^naa.[[:alnum:]]{32}$`)
|
var iscsiInitiatorNaaRegex = regexp.MustCompile(`^naa.[[:alnum:]]{32}$`)
|
||||||
|
|
||||||
|
var allowedEphemeralContainerFields = map[string]bool{
|
||||||
|
"Name": true,
|
||||||
|
"Image": true,
|
||||||
|
"Command": true,
|
||||||
|
"Args": true,
|
||||||
|
"WorkingDir": true,
|
||||||
|
"EnvFrom": true,
|
||||||
|
"Env": true,
|
||||||
|
"VolumeMounts": true,
|
||||||
|
"TerminationMessagePath": true,
|
||||||
|
"TerminationMessagePolicy": true,
|
||||||
|
"ImagePullPolicy": true,
|
||||||
|
"Stdin": true,
|
||||||
|
"StdinOnce": true,
|
||||||
|
"TTY": true,
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateHasLabel requires that metav1.ObjectMeta has a Label with key and expectedValue
|
// ValidateHasLabel requires that metav1.ObjectMeta has a Label with key and expectedValue
|
||||||
func ValidateHasLabel(meta metav1.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList {
|
func ValidateHasLabel(meta metav1.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
@ -2588,6 +2607,75 @@ func validatePullPolicy(policy core.PullPolicy, fldPath *field.Path) field.Error
|
|||||||
return allErrors
|
return allErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateEphemeralContainers(ephemeralContainers []core.EphemeralContainer, containers, initContainers []core.Container, volumes map[string]core.VolumeSource, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
|
if len(ephemeralContainers) == 0 {
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return early if EphemeralContainers disabled
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
|
||||||
|
return append(allErrs, field.Forbidden(fldPath, "disabled by EphemeralContainers feature-gate"))
|
||||||
|
}
|
||||||
|
|
||||||
|
allNames := sets.String{}
|
||||||
|
for _, c := range containers {
|
||||||
|
allNames.Insert(c.Name)
|
||||||
|
}
|
||||||
|
for _, c := range initContainers {
|
||||||
|
allNames.Insert(c.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, ec := range ephemeralContainers {
|
||||||
|
idxPath := fldPath.Index(i)
|
||||||
|
|
||||||
|
if ec.TargetContainerName != "" && !allNames.Has(ec.TargetContainerName) {
|
||||||
|
allErrs = append(allErrs, field.NotFound(idxPath.Child("targetContainerName"), ec.TargetContainerName))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ec.Name == "" {
|
||||||
|
allErrs = append(allErrs, field.Required(idxPath, "ephemeralContainer requires a name"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using validateContainers() here isn't ideal because it adds an index to the error message that
|
||||||
|
// doesn't really exist for EphemeralContainers (i.e. ephemeralContainers[0].spec[0].name instead
|
||||||
|
// of ephemeralContainers[0].spec.name)
|
||||||
|
// TODO(verb): factor a validateContainer() out of validateContainers() to be used here
|
||||||
|
c := core.Container(ec.EphemeralContainerCommon)
|
||||||
|
allErrs = append(allErrs, validateContainers([]core.Container{c}, false, volumes, idxPath)...)
|
||||||
|
// EphemeralContainers don't require the backwards-compatibility distinction between pod/podTemplate validation
|
||||||
|
allErrs = append(allErrs, validateContainersOnlyForPod([]core.Container{c}, idxPath)...)
|
||||||
|
|
||||||
|
if allNames.Has(ec.Name) {
|
||||||
|
allErrs = append(allErrs, field.Duplicate(idxPath.Child("name"), ec.Name))
|
||||||
|
} else {
|
||||||
|
allNames.Insert(ec.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ephemeral Containers should not be relied upon for fundamental pod services, so fields such as
|
||||||
|
// Lifecycle, probes, resources and ports should be disallowed. This is implemented as a whitelist
|
||||||
|
// so that new fields will be given consideration prior to inclusion in Ephemeral Containers.
|
||||||
|
specType, specValue := reflect.TypeOf(ec.EphemeralContainerCommon), reflect.ValueOf(ec.EphemeralContainerCommon)
|
||||||
|
for i := 0; i < specType.NumField(); i++ {
|
||||||
|
f := specType.Field(i)
|
||||||
|
if allowedEphemeralContainerFields[f.Name] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare the value of this field to its zero value to determine if it has been set
|
||||||
|
if !reflect.DeepEqual(specValue.Field(i).Interface(), reflect.Zero(f.Type).Interface()) {
|
||||||
|
r, n := utf8.DecodeRuneInString(f.Name)
|
||||||
|
lcName := string(unicode.ToLower(r)) + f.Name[n:]
|
||||||
|
allErrs = append(allErrs, field.Forbidden(idxPath.Child(lcName), "cannot be set for an Ephemeral Container"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
func validateInitContainers(containers, otherContainers []core.Container, deviceVolumes map[string]core.VolumeSource, fldPath *field.Path) field.ErrorList {
|
func validateInitContainers(containers, otherContainers []core.Container, deviceVolumes map[string]core.VolumeSource, fldPath *field.Path) field.ErrorList {
|
||||||
var allErrs field.ErrorList
|
var allErrs field.ErrorList
|
||||||
if len(containers) > 0 {
|
if len(containers) > 0 {
|
||||||
@ -3083,6 +3171,7 @@ func ValidatePodSpec(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
|
|||||||
allErrs = append(allErrs, vErrs...)
|
allErrs = append(allErrs, vErrs...)
|
||||||
allErrs = append(allErrs, validateContainers(spec.Containers, false, vols, fldPath.Child("containers"))...)
|
allErrs = append(allErrs, validateContainers(spec.Containers, false, vols, fldPath.Child("containers"))...)
|
||||||
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, fldPath.Child("initContainers"))...)
|
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, fldPath.Child("initContainers"))...)
|
||||||
|
allErrs = append(allErrs, validateEphemeralContainers(spec.EphemeralContainers, spec.Containers, spec.InitContainers, vols, fldPath.Child("ephemeralContainers"))...)
|
||||||
allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...)
|
allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...)
|
||||||
allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...)
|
allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...)
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
|
||||||
@ -3584,6 +3673,19 @@ func ValidateContainerUpdates(newContainers, oldContainers []core.Container, fld
|
|||||||
return allErrs, false
|
return allErrs, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidatePodCreate validates a pod in the context of its initial create
|
||||||
|
func ValidatePodCreate(pod *core.Pod) field.ErrorList {
|
||||||
|
allErrs := ValidatePod(pod)
|
||||||
|
|
||||||
|
fldPath := field.NewPath("spec")
|
||||||
|
// EphemeralContainers can only be set on update using the ephemeralcontainers subresource
|
||||||
|
if len(pod.Spec.EphemeralContainers) > 0 {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeralContainers"), "cannot be set on create"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
// ValidatePodUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields
|
// ValidatePodUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields
|
||||||
// that cannot be changed.
|
// that cannot be changed.
|
||||||
func ValidatePodUpdate(newPod, oldPod *core.Pod) field.ErrorList {
|
func ValidatePodUpdate(newPod, oldPod *core.Pod) field.ErrorList {
|
||||||
@ -3735,6 +3837,35 @@ func validatePodConditions(conditions []core.PodCondition, fldPath *field.Path)
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidatePodEphemeralContainersUpdate tests that a user update to EphemeralContainers is valid.
|
||||||
|
// newPod and oldPod must only differ in their EphemeralContainers.
|
||||||
|
func ValidatePodEphemeralContainersUpdate(newPod, oldPod *core.Pod) field.ErrorList {
|
||||||
|
spec := newPod.Spec
|
||||||
|
specPath := field.NewPath("spec").Child("ephemeralContainers")
|
||||||
|
|
||||||
|
vols := make(map[string]core.VolumeSource)
|
||||||
|
for _, vol := range spec.Volumes {
|
||||||
|
vols[vol.Name] = vol.VolumeSource
|
||||||
|
}
|
||||||
|
allErrs := validateEphemeralContainers(spec.EphemeralContainers, spec.Containers, spec.InitContainers, vols, specPath)
|
||||||
|
|
||||||
|
// Existing EphemeralContainers may not be changed. Order isn't preserved by patch, so check each individually.
|
||||||
|
newContainerIndex := make(map[string]*core.EphemeralContainer)
|
||||||
|
for i := range newPod.Spec.EphemeralContainers {
|
||||||
|
newContainerIndex[newPod.Spec.EphemeralContainers[i].Name] = &newPod.Spec.EphemeralContainers[i]
|
||||||
|
}
|
||||||
|
for _, old := range oldPod.Spec.EphemeralContainers {
|
||||||
|
if new, ok := newContainerIndex[old.Name]; !ok {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("existing ephemeral containers %q may not be removed\n", old.Name)))
|
||||||
|
} else if !apiequality.Semantic.DeepEqual(old, *new) {
|
||||||
|
specDiff := diff.ObjectDiff(old, *new)
|
||||||
|
allErrs = append(allErrs, field.Forbidden(specPath, fmt.Sprintf("existing ephemeral containers %q may not be changed\n%v", old.Name, specDiff)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
// ValidatePodBinding tests if required fields in the pod binding are legal.
|
// ValidatePodBinding tests if required fields in the pod binding are legal.
|
||||||
func ValidatePodBinding(binding *core.Binding) field.ErrorList {
|
func ValidatePodBinding(binding *core.Binding) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
@ -4149,6 +4280,11 @@ func ValidatePodTemplateSpec(spec *core.PodTemplateSpec, fldPath *field.Path) fi
|
|||||||
allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, fldPath.Child("annotations"))...)
|
allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, fldPath.Child("annotations"))...)
|
||||||
allErrs = append(allErrs, ValidatePodSpecificAnnotations(spec.Annotations, &spec.Spec, fldPath.Child("annotations"))...)
|
allErrs = append(allErrs, ValidatePodSpecificAnnotations(spec.Annotations, &spec.Spec, fldPath.Child("annotations"))...)
|
||||||
allErrs = append(allErrs, ValidatePodSpec(&spec.Spec, fldPath.Child("spec"))...)
|
allErrs = append(allErrs, ValidatePodSpec(&spec.Spec, fldPath.Child("spec"))...)
|
||||||
|
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) && len(spec.Spec.EphemeralContainers) > 0 {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(fldPath.Child("spec", "ephemeralContainers"), "ephemeral containers not allowed in pod template"))
|
||||||
|
}
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5527,6 +5527,243 @@ func getResourceLimits(cpu, memory string) core.ResourceList {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateEphemeralContainers(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
|
containers := []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
|
||||||
|
initContainers := []core.Container{{Name: "ictr", Image: "iimage", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}
|
||||||
|
vols := map[string]core.VolumeSource{"vol": {EmptyDir: &core.EmptyDirVolumeSource{}}}
|
||||||
|
|
||||||
|
// Success Cases
|
||||||
|
for title, ephemeralContainers := range map[string][]core.EphemeralContainer{
|
||||||
|
"Empty Ephemeral Containers": {},
|
||||||
|
"Single Container": {
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
},
|
||||||
|
"Multiple Containers": {
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug2", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
},
|
||||||
|
"Single Container with Target": {
|
||||||
|
{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
|
||||||
|
TargetContainerName: "ctr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"All Whitelisted Fields": {
|
||||||
|
{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
|
||||||
|
Name: "debug",
|
||||||
|
Image: "image",
|
||||||
|
Command: []string{"bash"},
|
||||||
|
Args: []string{"bash"},
|
||||||
|
WorkingDir: "/",
|
||||||
|
EnvFrom: []core.EnvFromSource{
|
||||||
|
{
|
||||||
|
ConfigMapRef: &core.ConfigMapEnvSource{
|
||||||
|
LocalObjectReference: core.LocalObjectReference{Name: "dummy"},
|
||||||
|
Optional: &[]bool{true}[0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Env: []core.EnvVar{
|
||||||
|
{Name: "TEST", Value: "TRUE"},
|
||||||
|
},
|
||||||
|
VolumeMounts: []core.VolumeMount{
|
||||||
|
{Name: "vol", MountPath: "/vol"},
|
||||||
|
},
|
||||||
|
TerminationMessagePath: "/dev/termination-log",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
Stdin: true,
|
||||||
|
StdinOnce: true,
|
||||||
|
TTY: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if errs := validateEphemeralContainers(ephemeralContainers, containers, initContainers, vols, field.NewPath("ephemeralContainers")); len(errs) != 0 {
|
||||||
|
t.Errorf("expected success for '%s' but got errors: %v", title, errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failure Cases
|
||||||
|
tcs := []struct {
|
||||||
|
title string
|
||||||
|
ephemeralContainers []core.EphemeralContainer
|
||||||
|
expectedError field.Error
|
||||||
|
}{
|
||||||
|
|
||||||
|
{
|
||||||
|
"Name Collision with Container.Containers",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name Collision with Container.InitContainers",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "ictr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[0].name"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name Collision with EphemeralContainers",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug1", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeDuplicate, Field: "ephemeralContainers[1].name"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"empty Container Container",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{}},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0]"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"empty Container Name",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeRequired, Field: "ephemeralContainers[0]"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"whitespace padded image name",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: " image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeInvalid, Field: "ephemeralContainers[0][0].image"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TargetContainerName doesn't exist",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"},
|
||||||
|
TargetContainerName: "bogus",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeNotFound, Field: "ephemeralContainers[0].targetContainerName"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container uses non-whitelisted field: Lifecycle",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debug",
|
||||||
|
Image: "image",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
Lifecycle: &core.Lifecycle{
|
||||||
|
PreStop: &core.Handler{
|
||||||
|
Exec: &core.ExecAction{Command: []string{"ls", "-l"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].lifecycle"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container uses non-whitelisted field: LivenessProbe",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debug",
|
||||||
|
Image: "image",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
LivenessProbe: &core.Probe{
|
||||||
|
Handler: core.Handler{
|
||||||
|
TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt(80)},
|
||||||
|
},
|
||||||
|
SuccessThreshold: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].livenessProbe"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container uses non-whitelisted field: Ports",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debug",
|
||||||
|
Image: "image",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
Ports: []core.ContainerPort{
|
||||||
|
{Protocol: "TCP", ContainerPort: 80},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].ports"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container uses non-whitelisted field: ReadinessProbe",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debug",
|
||||||
|
Image: "image",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
ReadinessProbe: &core.Probe{
|
||||||
|
Handler: core.Handler{
|
||||||
|
TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt(80)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].readinessProbe"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container uses non-whitelisted field: Resources",
|
||||||
|
[]core.EphemeralContainer{
|
||||||
|
{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debug",
|
||||||
|
Image: "image",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
Resources: core.ResourceRequirements{
|
||||||
|
Limits: core.ResourceList{
|
||||||
|
core.ResourceName(core.ResourceCPU): resource.MustParse("10"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field.Error{Type: field.ErrorTypeForbidden, Field: "ephemeralContainers[0].resources"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
errs := validateEphemeralContainers(tc.ephemeralContainers, containers, initContainers, vols, field.NewPath("ephemeralContainers"))
|
||||||
|
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Errorf("for test %q, expected error but received none", tc.title)
|
||||||
|
} else if len(errs) > 1 {
|
||||||
|
t.Errorf("for test %q, expected 1 error but received %d: %q", tc.title, len(errs), errs)
|
||||||
|
} else {
|
||||||
|
if errs[0].Type != tc.expectedError.Type {
|
||||||
|
t.Errorf("for test %q, expected error type %q but received %q: %q", tc.title, string(tc.expectedError.Type), string(errs[0].Type), errs)
|
||||||
|
}
|
||||||
|
if errs[0].Field != tc.expectedError.Field {
|
||||||
|
t.Errorf("for test %q, expected error for field %q but received error for field %q: %q", tc.title, tc.expectedError.Field, errs[0].Field, errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateContainers(t *testing.T) {
|
func TestValidateContainers(t *testing.T) {
|
||||||
volumeDevices := make(map[string]core.VolumeSource)
|
volumeDevices := make(map[string]core.VolumeSource)
|
||||||
capabilities.SetForTests(capabilities.Capabilities{
|
capabilities.SetForTests(capabilities.Capabilities{
|
||||||
@ -6330,6 +6567,7 @@ func TestValidatePodSpec(t *testing.T) {
|
|||||||
minGroupID := int64(0)
|
minGroupID := int64(0)
|
||||||
maxGroupID := int64(2147483647)
|
maxGroupID := int64(2147483647)
|
||||||
|
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RuntimeClass, true)()
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RuntimeClass, true)()
|
||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodOverhead, true)()
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodOverhead, true)()
|
||||||
|
|
||||||
@ -6672,6 +6910,34 @@ func TestValidatePodSpec(t *testing.T) {
|
|||||||
t.Errorf("expected failure for %q", k)
|
t.Errorf("expected failure for %q", k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, false)()
|
||||||
|
|
||||||
|
featuregatedCases := map[string]core.PodSpec{
|
||||||
|
"disabled by EphemeralContainers feature-gate": {
|
||||||
|
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
EphemeralContainers: []core.EphemeralContainer{
|
||||||
|
{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debug",
|
||||||
|
Image: "image",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RestartPolicy: core.RestartPolicyAlways,
|
||||||
|
DNSPolicy: core.DNSClusterFirst,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for expectedErr, spec := range featuregatedCases {
|
||||||
|
errs := ValidatePodSpec(&spec, field.NewPath("field"))
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Errorf("expected failure due to gated feature: %s\n%+v", expectedErr, spec)
|
||||||
|
} else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, expectedErr) {
|
||||||
|
t.Errorf("unexpected error message for gated feature. Expected error: %s\nActual error: %s", expectedErr, actualErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func extendPodSpecwithTolerations(in core.PodSpec, tolerations []core.Toleration) core.PodSpec {
|
func extendPodSpecwithTolerations(in core.PodSpec, tolerations []core.Toleration) core.PodSpec {
|
||||||
@ -8321,6 +8587,27 @@ func TestValidatePodUpdate(t *testing.T) {
|
|||||||
"spec.initContainers[0].image",
|
"spec.initContainers[0].image",
|
||||||
"init container image change to empty",
|
"init container image change to empty",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
EphemeralContainers: []core.EphemeralContainer{
|
||||||
|
{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "ephemeral",
|
||||||
|
Image: "busybox",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
core.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
|
||||||
|
Spec: core.PodSpec{},
|
||||||
|
},
|
||||||
|
"Forbidden: pod updates may not change fields other than",
|
||||||
|
"ephemeralContainer changes are not allowed via normal pod update",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
core.Pod{
|
core.Pod{
|
||||||
Spec: core.PodSpec{},
|
Spec: core.PodSpec{},
|
||||||
@ -8902,6 +9189,272 @@ func makeValidService() core.Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidatePodEphemeralContainersUpdate(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
new []core.EphemeralContainer
|
||||||
|
old []core.EphemeralContainer
|
||||||
|
err string
|
||||||
|
test string
|
||||||
|
}{
|
||||||
|
{[]core.EphemeralContainer{}, []core.EphemeralContainer{}, "", "nothing"},
|
||||||
|
{
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger2",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger2",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
"",
|
||||||
|
"No change in Ephemeral Containers",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger2",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger2",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
"",
|
||||||
|
"Ephemeral Container list order changes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
[]core.EphemeralContainer{},
|
||||||
|
"",
|
||||||
|
"Add an Ephemeral Container",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger1",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger2",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
[]core.EphemeralContainer{},
|
||||||
|
"",
|
||||||
|
"Add two Ephemeral Containers",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger2",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
"",
|
||||||
|
"Add to an existing Ephemeral Containers",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger3",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger2",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger2",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
"",
|
||||||
|
"Add to an existing Ephemeral Containers, list order changes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]core.EphemeralContainer{},
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
"may not be removed",
|
||||||
|
"Remove an Ephemeral Container",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "firstone",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "thentheother",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
"may not be removed",
|
||||||
|
"Replace an Ephemeral Container",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger1",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger2",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
[]core.EphemeralContainer{{
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger1",
|
||||||
|
Image: "debian",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
EphemeralContainerCommon: core.EphemeralContainerCommon{
|
||||||
|
Name: "debugger2",
|
||||||
|
Image: "busybox",
|
||||||
|
ImagePullPolicy: "IfNotPresent",
|
||||||
|
TerminationMessagePolicy: "File",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
"may not be changed",
|
||||||
|
"Change an Ephemeral Containers",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
new := core.Pod{Spec: core.PodSpec{EphemeralContainers: test.new}}
|
||||||
|
old := core.Pod{Spec: core.PodSpec{EphemeralContainers: test.old}}
|
||||||
|
errs := ValidatePodEphemeralContainersUpdate(&new, &old)
|
||||||
|
if test.err == "" {
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Errorf("unexpected invalid: %s (%+v)\nA: %+v\nB: %+v", test.test, errs, test.new, test.old)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(errs) == 0 {
|
||||||
|
t.Errorf("unexpected valid: %s\nA: %+v\nB: %+v", test.test, test.new, test.old)
|
||||||
|
} else if actualErr := errs.ToAggregate().Error(); !strings.Contains(actualErr, test.err) {
|
||||||
|
t.Errorf("unexpected error message: %s\nExpected error: %s\nActual error: %s", test.test, test.err, actualErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateService(t *testing.T) {
|
func TestValidateService(t *testing.T) {
|
||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SCTPSupport, true)()
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SCTPSupport, true)()
|
||||||
|
|
||||||
@ -10006,6 +10559,8 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateReplicationController(t *testing.T) {
|
func TestValidateReplicationController(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EphemeralContainers, true)()
|
||||||
|
|
||||||
validSelector := map[string]string{"a": "b"}
|
validSelector := map[string]string{"a": "b"}
|
||||||
validPodTemplate := core.PodTemplate{
|
validPodTemplate := core.PodTemplate{
|
||||||
Template: core.PodTemplateSpec{
|
Template: core.PodTemplateSpec{
|
||||||
@ -10199,6 +10754,24 @@ func TestValidateReplicationController(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"template may not contain ephemeral containers": {
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
|
||||||
|
Spec: core.ReplicationControllerSpec{
|
||||||
|
Replicas: 1,
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &core.PodTemplateSpec{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Labels: validSelector,
|
||||||
|
},
|
||||||
|
Spec: core.PodSpec{
|
||||||
|
RestartPolicy: core.RestartPolicyAlways,
|
||||||
|
DNSPolicy: core.DNSClusterFirst,
|
||||||
|
Containers: []core.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||||
|
EphemeralContainers: []core.EphemeralContainer{{EphemeralContainerCommon: core.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for k, v := range errorCases {
|
for k, v := range errorCases {
|
||||||
errs := ValidateReplicationController(&v)
|
errs := ValidateReplicationController(&v)
|
||||||
|
@ -31,10 +31,12 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/storage"
|
"k8s.io/apiserver/pkg/storage"
|
||||||
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
storeerr "k8s.io/apiserver/pkg/storage/errors"
|
||||||
"k8s.io/apiserver/pkg/util/dryrun"
|
"k8s.io/apiserver/pkg/util/dryrun"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1beta1"
|
policyclient "k8s.io/client-go/kubernetes/typed/policy/v1beta1"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
"k8s.io/kubernetes/pkg/printers"
|
"k8s.io/kubernetes/pkg/printers"
|
||||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||||
@ -45,15 +47,16 @@ import (
|
|||||||
|
|
||||||
// PodStorage includes storage for pods and all sub resources
|
// PodStorage includes storage for pods and all sub resources
|
||||||
type PodStorage struct {
|
type PodStorage struct {
|
||||||
Pod *REST
|
Pod *REST
|
||||||
Binding *BindingREST
|
Binding *BindingREST
|
||||||
Eviction *EvictionREST
|
Eviction *EvictionREST
|
||||||
Status *StatusREST
|
Status *StatusREST
|
||||||
Log *podrest.LogREST
|
EphemeralContainers *EphemeralContainersREST
|
||||||
Proxy *podrest.ProxyREST
|
Log *podrest.LogREST
|
||||||
Exec *podrest.ExecREST
|
Proxy *podrest.ProxyREST
|
||||||
Attach *podrest.AttachREST
|
Exec *podrest.ExecREST
|
||||||
PortForward *podrest.PortForwardREST
|
Attach *podrest.AttachREST
|
||||||
|
PortForward *podrest.PortForwardREST
|
||||||
}
|
}
|
||||||
|
|
||||||
// REST implements a RESTStorage for pods
|
// REST implements a RESTStorage for pods
|
||||||
@ -89,17 +92,20 @@ func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGet
|
|||||||
|
|
||||||
statusStore := *store
|
statusStore := *store
|
||||||
statusStore.UpdateStrategy = pod.StatusStrategy
|
statusStore.UpdateStrategy = pod.StatusStrategy
|
||||||
|
ephemeralContainersStore := *store
|
||||||
|
ephemeralContainersStore.UpdateStrategy = pod.EphemeralContainersStrategy
|
||||||
|
|
||||||
return PodStorage{
|
return PodStorage{
|
||||||
Pod: &REST{store, proxyTransport},
|
Pod: &REST{store, proxyTransport},
|
||||||
Binding: &BindingREST{store: store},
|
Binding: &BindingREST{store: store},
|
||||||
Eviction: newEvictionStorage(store, podDisruptionBudgetClient),
|
Eviction: newEvictionStorage(store, podDisruptionBudgetClient),
|
||||||
Status: &StatusREST{store: &statusStore},
|
Status: &StatusREST{store: &statusStore},
|
||||||
Log: &podrest.LogREST{Store: store, KubeletConn: k},
|
EphemeralContainers: &EphemeralContainersREST{store: &ephemeralContainersStore},
|
||||||
Proxy: &podrest.ProxyREST{Store: store, ProxyTransport: proxyTransport},
|
Log: &podrest.LogREST{Store: store, KubeletConn: k},
|
||||||
Exec: &podrest.ExecREST{Store: store, KubeletConn: k},
|
Proxy: &podrest.ProxyREST{Store: store, ProxyTransport: proxyTransport},
|
||||||
Attach: &podrest.AttachREST{Store: store, KubeletConn: k},
|
Exec: &podrest.ExecREST{Store: store, KubeletConn: k},
|
||||||
PortForward: &podrest.PortForwardREST{Store: store, KubeletConn: k},
|
Attach: &podrest.AttachREST{Store: store, KubeletConn: k},
|
||||||
|
PortForward: &podrest.PortForwardREST{Store: store, KubeletConn: k},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,3 +239,96 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
|
|||||||
// subresources should never allow create on update.
|
// subresources should never allow create on update.
|
||||||
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
|
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EphemeralContainersREST implements the REST endpoint for adding EphemeralContainers
|
||||||
|
type EphemeralContainersREST struct {
|
||||||
|
store *genericregistry.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = rest.Patcher(&EphemeralContainersREST{})
|
||||||
|
|
||||||
|
// Get of this endpoint will return the list of ephemeral containers in this pod
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new EphemeralContainers resource
|
||||||
|
func (r *EphemeralContainersREST) New() runtime.Object {
|
||||||
|
return &api.EphemeralContainers{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update alters the EphemeralContainers field in PodSpec
|
||||||
|
func (r *EphemeralContainersREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
obj, _, err = r.store.Update(ctx, name, updatedPodInfo, createValidation, updateValidation, false, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return ephemeralContainersInPod(obj.(*api.Pod)), false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -84,7 +84,7 @@ func (podStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object
|
|||||||
// Validate validates a new pod.
|
// Validate validates a new pod.
|
||||||
func (podStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (podStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
pod := obj.(*api.Pod)
|
pod := obj.(*api.Pod)
|
||||||
allErrs := validation.ValidatePod(pod)
|
allErrs := validation.ValidatePodCreate(pod)
|
||||||
allErrs = append(allErrs, validation.ValidateConditionalPod(pod, nil, field.NewPath(""))...)
|
allErrs = append(allErrs, validation.ValidateConditionalPod(pod, nil, field.NewPath(""))...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
@ -174,6 +174,16 @@ func (podStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob
|
|||||||
return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod))
|
return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type podEphemeralContainersStrategy struct {
|
||||||
|
podStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
var EphemeralContainersStrategy = podEphemeralContainersStrategy{Strategy}
|
||||||
|
|
||||||
|
func (podEphemeralContainersStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
|
return validation.ValidatePodEphemeralContainersUpdate(obj.(*api.Pod), old.(*api.Pod))
|
||||||
|
}
|
||||||
|
|
||||||
// GetAttrs returns labels and fields of a given object for filtering purposes.
|
// GetAttrs returns labels and fields of a given object for filtering purposes.
|
||||||
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
|
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
|
||||||
pod, ok := obj.(*api.Pod)
|
pod, ok := obj.(*api.Pod)
|
||||||
|
@ -238,6 +238,9 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi
|
|||||||
if serviceAccountStorage.Token != nil {
|
if serviceAccountStorage.Token != nil {
|
||||||
restStorageMap["serviceaccounts/token"] = serviceAccountStorage.Token
|
restStorageMap["serviceaccounts/token"] = serviceAccountStorage.Token
|
||||||
}
|
}
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.EphemeralContainers) {
|
||||||
|
restStorageMap["pods/ephemeralcontainers"] = podStorage.EphemeralContainers
|
||||||
|
}
|
||||||
apiGroupInfo.VersionedResourcesStorageMap["v1"] = restStorageMap
|
apiGroupInfo.VersionedResourcesStorageMap["v1"] = restStorageMap
|
||||||
|
|
||||||
return restStorage, apiGroupInfo, nil
|
return restStorage, apiGroupInfo, nil
|
||||||
|
@ -88,6 +88,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||||||
&RangeAllocation{},
|
&RangeAllocation{},
|
||||||
&ConfigMap{},
|
&ConfigMap{},
|
||||||
&ConfigMapList{},
|
&ConfigMapList{},
|
||||||
|
&EphemeralContainers{},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add common types
|
// Add common types
|
||||||
|
@ -2843,6 +2843,17 @@ type PodSpec struct {
|
|||||||
// +patchMergeKey=name
|
// +patchMergeKey=name
|
||||||
// +patchStrategy=merge
|
// +patchStrategy=merge
|
||||||
Containers []Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=containers"`
|
Containers []Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=containers"`
|
||||||
|
// EphemeralContainers is the list of ephemeral containers that run in this pod. Ephemeral containers
|
||||||
|
// are added to an existing pod as a result of a user-initiated action such as troubleshooting.
|
||||||
|
// This list is read-only in the pod spec. It may not be specified in a create or modified in an
|
||||||
|
// update of a pod or pod template.
|
||||||
|
// To add an ephemeral container use the pod's ephemeralcontainers subresource, which allows update
|
||||||
|
// using the EphemeralContainers kind.
|
||||||
|
// This field is alpha-level and is only honored by servers that enable the EphemeralContainers feature.
|
||||||
|
// +optional
|
||||||
|
// +patchMergeKey=name
|
||||||
|
// +patchStrategy=merge
|
||||||
|
EphemeralContainers []EphemeralContainer `json:"ephemeralContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,34,rep,name=ephemeralContainers"`
|
||||||
// Restart policy for all containers within the pod.
|
// Restart policy for all containers within the pod.
|
||||||
// One of Always, OnFailure, Never.
|
// One of Always, OnFailure, Never.
|
||||||
// Default to Always.
|
// Default to Always.
|
||||||
@ -3209,6 +3220,156 @@ type PodIP struct {
|
|||||||
IP string `json:"ip,omitempty" protobuf:"bytes,1,opt,name=ip"`
|
IP string `json:"ip,omitempty" protobuf:"bytes,1,opt,name=ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EphemeralContainerCommon struct {
|
||||||
|
// Name of the ephemeral container specified as a DNS_LABEL.
|
||||||
|
// This name must be unique among all containers, init containers and ephemeral containers.
|
||||||
|
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||||
|
// Docker image name.
|
||||||
|
// More info: https://kubernetes.io/docs/concepts/containers/images
|
||||||
|
Image string `json:"image,omitempty" protobuf:"bytes,2,opt,name=image"`
|
||||||
|
// Entrypoint array. Not executed within a shell.
|
||||||
|
// The docker image's ENTRYPOINT is used if this is not provided.
|
||||||
|
// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
|
||||||
|
// cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax
|
||||||
|
// can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded,
|
||||||
|
// regardless of whether the variable exists or not.
|
||||||
|
// Cannot be updated.
|
||||||
|
// More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
|
||||||
|
// +optional
|
||||||
|
Command []string `json:"command,omitempty" protobuf:"bytes,3,rep,name=command"`
|
||||||
|
// Arguments to the entrypoint.
|
||||||
|
// The docker image's CMD is used if this is not provided.
|
||||||
|
// Variable references $(VAR_NAME) are expanded using the container's environment. If a variable
|
||||||
|
// cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax
|
||||||
|
// can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded,
|
||||||
|
// regardless of whether the variable exists or not.
|
||||||
|
// Cannot be updated.
|
||||||
|
// More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
|
||||||
|
// +optional
|
||||||
|
Args []string `json:"args,omitempty" protobuf:"bytes,4,rep,name=args"`
|
||||||
|
// Container's working directory.
|
||||||
|
// If not specified, the container runtime's default will be used, which
|
||||||
|
// might be configured in the container image.
|
||||||
|
// Cannot be updated.
|
||||||
|
// +optional
|
||||||
|
WorkingDir string `json:"workingDir,omitempty" protobuf:"bytes,5,opt,name=workingDir"`
|
||||||
|
// Ports are not allowed for ephemeral containers.
|
||||||
|
Ports []ContainerPort `json:"ports,omitempty" protobuf:"bytes,6,rep,name=ports"`
|
||||||
|
// List of sources to populate environment variables in the container.
|
||||||
|
// The keys defined within a source must be a C_IDENTIFIER. All invalid keys
|
||||||
|
// will be reported as an event when the container is starting. When a key exists in multiple
|
||||||
|
// sources, the value associated with the last source will take precedence.
|
||||||
|
// Values defined by an Env with a duplicate key will take precedence.
|
||||||
|
// Cannot be updated.
|
||||||
|
// +optional
|
||||||
|
EnvFrom []EnvFromSource `json:"envFrom,omitempty" protobuf:"bytes,19,rep,name=envFrom"`
|
||||||
|
// List of environment variables to set in the container.
|
||||||
|
// Cannot be updated.
|
||||||
|
// +optional
|
||||||
|
// +patchMergeKey=name
|
||||||
|
// +patchStrategy=merge
|
||||||
|
Env []EnvVar `json:"env,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,7,rep,name=env"`
|
||||||
|
// Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources
|
||||||
|
// already allocated to the pod.
|
||||||
|
// +optional
|
||||||
|
Resources ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,8,opt,name=resources"`
|
||||||
|
// Pod volumes to mount into the container's filesystem.
|
||||||
|
// Cannot be updated.
|
||||||
|
// +optional
|
||||||
|
// +patchMergeKey=mountPath
|
||||||
|
// +patchStrategy=merge
|
||||||
|
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" patchStrategy:"merge" patchMergeKey:"mountPath" protobuf:"bytes,9,rep,name=volumeMounts"`
|
||||||
|
// volumeDevices is the list of block devices to be used by the container.
|
||||||
|
// This is a beta feature.
|
||||||
|
// +patchMergeKey=devicePath
|
||||||
|
// +patchStrategy=merge
|
||||||
|
// +optional
|
||||||
|
VolumeDevices []VolumeDevice `json:"volumeDevices,omitempty" patchStrategy:"merge" patchMergeKey:"devicePath" protobuf:"bytes,21,rep,name=volumeDevices"`
|
||||||
|
// Probes are not allowed for ephemeral containers.
|
||||||
|
// +optional
|
||||||
|
LivenessProbe *Probe `json:"livenessProbe,omitempty" protobuf:"bytes,10,opt,name=livenessProbe"`
|
||||||
|
// Probes are not allowed for ephemeral containers.
|
||||||
|
// +optional
|
||||||
|
ReadinessProbe *Probe `json:"readinessProbe,omitempty" protobuf:"bytes,11,opt,name=readinessProbe"`
|
||||||
|
// Lifecycle is not allowed for ephemeral containers.
|
||||||
|
// +optional
|
||||||
|
Lifecycle *Lifecycle `json:"lifecycle,omitempty" protobuf:"bytes,12,opt,name=lifecycle"`
|
||||||
|
// Optional: Path at which the file to which the container's termination message
|
||||||
|
// will be written is mounted into the container's filesystem.
|
||||||
|
// Message written is intended to be brief final status, such as an assertion failure message.
|
||||||
|
// Will be truncated by the node if greater than 4096 bytes. The total message length across
|
||||||
|
// all containers will be limited to 12kb.
|
||||||
|
// Defaults to /dev/termination-log.
|
||||||
|
// Cannot be updated.
|
||||||
|
// +optional
|
||||||
|
TerminationMessagePath string `json:"terminationMessagePath,omitempty" protobuf:"bytes,13,opt,name=terminationMessagePath"`
|
||||||
|
// Indicate how the termination message should be populated. File will use the contents of
|
||||||
|
// terminationMessagePath to populate the container status message on both success and failure.
|
||||||
|
// FallbackToLogsOnError will use the last chunk of container log output if the termination
|
||||||
|
// message file is empty and the container exited with an error.
|
||||||
|
// The log output is limited to 2048 bytes or 80 lines, whichever is smaller.
|
||||||
|
// Defaults to File.
|
||||||
|
// Cannot be updated.
|
||||||
|
// +optional
|
||||||
|
TerminationMessagePolicy TerminationMessagePolicy `json:"terminationMessagePolicy,omitempty" protobuf:"bytes,20,opt,name=terminationMessagePolicy,casttype=TerminationMessagePolicy"`
|
||||||
|
// Image pull policy.
|
||||||
|
// One of Always, Never, IfNotPresent.
|
||||||
|
// Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.
|
||||||
|
// Cannot be updated.
|
||||||
|
// More info: https://kubernetes.io/docs/concepts/containers/images#updating-images
|
||||||
|
// +optional
|
||||||
|
ImagePullPolicy PullPolicy `json:"imagePullPolicy,omitempty" protobuf:"bytes,14,opt,name=imagePullPolicy,casttype=PullPolicy"`
|
||||||
|
// SecurityContext is not allowed for ephemeral containers.
|
||||||
|
// +optional
|
||||||
|
SecurityContext *SecurityContext `json:"securityContext,omitempty" protobuf:"bytes,15,opt,name=securityContext"`
|
||||||
|
|
||||||
|
// Variables for interactive containers, these have very specialized use-cases (e.g. debugging)
|
||||||
|
// and shouldn't be used for general purpose containers.
|
||||||
|
|
||||||
|
// Whether this container should allocate a buffer for stdin in the container runtime. If this
|
||||||
|
// is not set, reads from stdin in the container will always result in EOF.
|
||||||
|
// Default is false.
|
||||||
|
// +optional
|
||||||
|
Stdin bool `json:"stdin,omitempty" protobuf:"varint,16,opt,name=stdin"`
|
||||||
|
// Whether the container runtime should close the stdin channel after it has been opened by
|
||||||
|
// a single attach. When stdin is true the stdin stream will remain open across multiple attach
|
||||||
|
// sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the
|
||||||
|
// first client attaches to stdin, and then remains open and accepts data until the client disconnects,
|
||||||
|
// at which time stdin is closed and remains closed until the container is restarted. If this
|
||||||
|
// flag is false, a container processes that reads from stdin will never receive an EOF.
|
||||||
|
// Default is false
|
||||||
|
// +optional
|
||||||
|
StdinOnce bool `json:"stdinOnce,omitempty" protobuf:"varint,17,opt,name=stdinOnce"`
|
||||||
|
// Whether this container should allocate a TTY for itself, also requires 'stdin' to be true.
|
||||||
|
// Default is false.
|
||||||
|
// +optional
|
||||||
|
TTY bool `json:"tty,omitempty" protobuf:"varint,18,opt,name=tty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EphemeralContainerCommon converts to Container. All fields must be kept in sync between
|
||||||
|
// these two types.
|
||||||
|
var _ = Container(EphemeralContainerCommon{})
|
||||||
|
|
||||||
|
// An EphemeralContainer is a special type of container which doesn't come with any resource
|
||||||
|
// or scheduling guarantees but can be added to a pod that has already been created. They are
|
||||||
|
// intended for user-initiated activities such as troubleshooting a running pod.
|
||||||
|
// Ephemeral containers will not be restarted when they exit, and they will be killed if the
|
||||||
|
// pod is removed or restarted. If an ephemeral container causes a pod to exceed its resource
|
||||||
|
// allocation, the pod may be evicted.
|
||||||
|
// Ephemeral containers are added via a pod's ephemeralcontainers subresource and will appear
|
||||||
|
// in the pod spec once added. No fields in EphemeralContainer may be changed once added.
|
||||||
|
// This is an alpha feature enabled by the EphemeralContainers feature flag.
|
||||||
|
type EphemeralContainer struct {
|
||||||
|
EphemeralContainerCommon `json:",inline" protobuf:"bytes,1,req"`
|
||||||
|
|
||||||
|
// If set, the name of the container from PodSpec that this ephemeral container targets.
|
||||||
|
// The ephemeral container will be run in the namespaces (IPC, PID, etc) of this container.
|
||||||
|
// If not set then the ephemeral container is run in whatever namespaces are shared
|
||||||
|
// for the pod. Note that the container runtime must support this feature.
|
||||||
|
// +optional
|
||||||
|
TargetContainerName string `json:"targetContainerName,omitempty" protobuf:"bytes,2,opt,name=targetContainerName"`
|
||||||
|
}
|
||||||
|
|
||||||
// PodStatus represents information about the status of a pod. Status may trail the actual
|
// PodStatus represents information about the status of a pod. Status may trail the actual
|
||||||
// state of a system, especially if the node that hosts the pod cannot contact the control
|
// state of a system, especially if the node that hosts the pod cannot contact the control
|
||||||
// plane.
|
// plane.
|
||||||
@ -3293,6 +3454,10 @@ type PodStatus struct {
|
|||||||
// More info: https://git.k8s.io/community/contributors/design-proposals/node/resource-qos.md
|
// More info: https://git.k8s.io/community/contributors/design-proposals/node/resource-qos.md
|
||||||
// +optional
|
// +optional
|
||||||
QOSClass PodQOSClass `json:"qosClass,omitempty" protobuf:"bytes,9,rep,name=qosClass"`
|
QOSClass PodQOSClass `json:"qosClass,omitempty" protobuf:"bytes,9,rep,name=qosClass"`
|
||||||
|
// Status for any ephemeral containers that running in this pod.
|
||||||
|
// This field is alpha-level and is only honored by servers that enable the EphemeralContainers feature.
|
||||||
|
// +optional
|
||||||
|
EphemeralContainerStatuses []ContainerStatus `json:"ephemeralContainerStatuses,omitempty" protobuf:"bytes,13,rep,name=ephemeralContainerStatuses"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
@ -3314,6 +3479,8 @@ type PodStatusResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// +genclient
|
// +genclient
|
||||||
|
// +genclient:method=GetEphemeralContainers,verb=get,subresource=ephemeralcontainers,result=EphemeralContainers
|
||||||
|
// +genclient:method=UpdateEphemeralContainers,verb=update,subresource=ephemeralcontainers,input=EphemeralContainers,result=EphemeralContainers
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +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
|
// Pod is a collection of containers that can run on a host. This resource is created
|
||||||
@ -4494,6 +4661,20 @@ type Binding struct {
|
|||||||
Target ObjectReference `json:"target" protobuf:"bytes,2,opt,name=target"`
|
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 in API operations
|
||||||
|
type EphemeralContainers struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
// +optional
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
|
||||||
|
// The new set of ephemeral containers to use for a pod.
|
||||||
|
// +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.
|
// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
|
||||||
// +k8s:openapi-gen=false
|
// +k8s:openapi-gen=false
|
||||||
type Preconditions struct {
|
type Preconditions struct {
|
||||||
|
@ -58,6 +58,7 @@ var kindWhiteList = sets.NewString(
|
|||||||
"APIVersions",
|
"APIVersions",
|
||||||
"Binding",
|
"Binding",
|
||||||
"DeleteOptions",
|
"DeleteOptions",
|
||||||
|
"EphemeralContainers",
|
||||||
"ExportOptions",
|
"ExportOptions",
|
||||||
"GetOptions",
|
"GetOptions",
|
||||||
"ListOptions",
|
"ListOptions",
|
||||||
|
Loading…
Reference in New Issue
Block a user