mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 04:33:26 +00:00
Add ImageVolumeSource API
Adding the required Kubernetes API so that the kubelet can start using it. This patch also adds the corresponding alpha feature gate as outlined in KEP 4639. Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
This commit is contained in:
parent
ad72be434d
commit
f7ca3131e0
18
api/openapi-spec/swagger.json
generated
18
api/openapi-spec/swagger.json
generated
@ -7642,6 +7642,20 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.ImageVolumeSource": {
|
||||
"description": "ImageVolumeSource represents a image volume resource.",
|
||||
"properties": {
|
||||
"pullPolicy": {
|
||||
"description": "Policy for pulling OCI objects. Possible values are: Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.",
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"description": "Required: Image or artifact reference to be used. Behaves in the same way as pod.spec.containers[*].image. Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.KeyToPath": {
|
||||
"description": "Maps a string key to a path within a volume.",
|
||||
"properties": {
|
||||
@ -11732,6 +11746,10 @@
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.HostPathVolumeSource",
|
||||
"description": "hostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath"
|
||||
},
|
||||
"image": {
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.ImageVolumeSource",
|
||||
"description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath)."
|
||||
},
|
||||
"iscsi": {
|
||||
"$ref": "#/definitions/io.k8s.api.core.v1.ISCSIVolumeSource",
|
||||
"description": "iscsi represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md"
|
||||
|
@ -2980,6 +2980,20 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.ImageVolumeSource": {
|
||||
"description": "ImageVolumeSource represents a image volume resource.",
|
||||
"properties": {
|
||||
"pullPolicy": {
|
||||
"description": "Policy for pulling OCI objects. Possible values are: Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.",
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"description": "Required: Image or artifact reference to be used. Behaves in the same way as pod.spec.containers[*].image. Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.KeyToPath": {
|
||||
"description": "Maps a string key to a path within a volume.",
|
||||
"properties": {
|
||||
@ -8131,6 +8145,14 @@
|
||||
],
|
||||
"description": "hostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath"
|
||||
},
|
||||
"image": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.core.v1.ImageVolumeSource"
|
||||
}
|
||||
],
|
||||
"description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath)."
|
||||
},
|
||||
"iscsi": {
|
||||
"allOf": [
|
||||
{
|
||||
|
@ -2806,6 +2806,20 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.ImageVolumeSource": {
|
||||
"description": "ImageVolumeSource represents a image volume resource.",
|
||||
"properties": {
|
||||
"pullPolicy": {
|
||||
"description": "Policy for pulling OCI objects. Possible values are: Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.",
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"description": "Required: Image or artifact reference to be used. Behaves in the same way as pod.spec.containers[*].image. Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.KeyToPath": {
|
||||
"description": "Maps a string key to a path within a volume.",
|
||||
"properties": {
|
||||
@ -5022,6 +5036,14 @@
|
||||
],
|
||||
"description": "hostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath"
|
||||
},
|
||||
"image": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.core.v1.ImageVolumeSource"
|
||||
}
|
||||
],
|
||||
"description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath)."
|
||||
},
|
||||
"iscsi": {
|
||||
"allOf": [
|
||||
{
|
||||
|
@ -2155,6 +2155,20 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.ImageVolumeSource": {
|
||||
"description": "ImageVolumeSource represents a image volume resource.",
|
||||
"properties": {
|
||||
"pullPolicy": {
|
||||
"description": "Policy for pulling OCI objects. Possible values are: Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.",
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"description": "Required: Image or artifact reference to be used. Behaves in the same way as pod.spec.containers[*].image. Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.core.v1.KeyToPath": {
|
||||
"description": "Maps a string key to a path within a volume.",
|
||||
"properties": {
|
||||
@ -4226,6 +4240,14 @@
|
||||
],
|
||||
"description": "hostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath"
|
||||
},
|
||||
"image": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.core.v1.ImageVolumeSource"
|
||||
}
|
||||
],
|
||||
"description": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath)."
|
||||
},
|
||||
"iscsi": {
|
||||
"allOf": [
|
||||
{
|
||||
|
@ -385,6 +385,7 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
||||
AllowInvalidTopologySpreadConstraintLabelSelector: false,
|
||||
AllowNamespacedSysctlsForHostNetAndHostIPC: false,
|
||||
AllowNonLocalProjectedTokenPath: false,
|
||||
AllowImageVolumeSource: utilfeature.DefaultFeatureGate.Enabled(features.ImageVolume),
|
||||
}
|
||||
|
||||
// If old spec uses relaxed validation or enabled the RelaxedEnvironmentVariableValidation feature gate,
|
||||
@ -713,6 +714,7 @@ func dropDisabledFields(
|
||||
}
|
||||
|
||||
dropPodLifecycleSleepAction(podSpec, oldPodSpec)
|
||||
dropImageVolumes(podSpec, oldPodSpec)
|
||||
}
|
||||
|
||||
func dropPodLifecycleSleepAction(podSpec, oldPodSpec *api.PodSpec) {
|
||||
@ -1260,3 +1262,56 @@ func MarkPodProposedForResize(oldPod, newPod *api.Pod) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// KEP: https://kep.k8s.io/4639
|
||||
func dropImageVolumes(podSpec, oldPodSpec *api.PodSpec) {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.ImageVolume) || imageVolumesInUse(oldPodSpec) {
|
||||
return
|
||||
}
|
||||
|
||||
imageVolumeNames := sets.New[string]()
|
||||
var newVolumes []api.Volume
|
||||
for _, v := range podSpec.Volumes {
|
||||
if v.Image != nil {
|
||||
imageVolumeNames.Insert(v.Name)
|
||||
continue
|
||||
}
|
||||
newVolumes = append(newVolumes, v)
|
||||
}
|
||||
podSpec.Volumes = newVolumes
|
||||
|
||||
dropVolumeMounts := func(givenMounts []api.VolumeMount) (newVolumeMounts []api.VolumeMount) {
|
||||
for _, m := range givenMounts {
|
||||
if !imageVolumeNames.Has(m.Name) {
|
||||
newVolumeMounts = append(newVolumeMounts, m)
|
||||
}
|
||||
}
|
||||
return newVolumeMounts
|
||||
}
|
||||
|
||||
for i, c := range podSpec.Containers {
|
||||
podSpec.Containers[i].VolumeMounts = dropVolumeMounts(c.VolumeMounts)
|
||||
}
|
||||
|
||||
for i, c := range podSpec.InitContainers {
|
||||
podSpec.InitContainers[i].VolumeMounts = dropVolumeMounts(c.VolumeMounts)
|
||||
}
|
||||
|
||||
for i, c := range podSpec.EphemeralContainers {
|
||||
podSpec.EphemeralContainers[i].VolumeMounts = dropVolumeMounts(c.VolumeMounts)
|
||||
}
|
||||
}
|
||||
|
||||
func imageVolumesInUse(podSpec *api.PodSpec) bool {
|
||||
if podSpec == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range podSpec.Volumes {
|
||||
if v.Image != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -3574,3 +3574,156 @@ func TestDropSupplementalGroupsPolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropImageVolumes(t *testing.T) {
|
||||
const (
|
||||
volumeNameImage = "volume"
|
||||
volumeNameOther = "volume-other"
|
||||
)
|
||||
anotherVolume := api.Volume{Name: volumeNameOther, VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{}}}
|
||||
podWithVolume := &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{
|
||||
{Name: volumeNameImage, VolumeSource: api.VolumeSource{Image: &api.ImageVolumeSource{}}},
|
||||
anotherVolume,
|
||||
},
|
||||
Containers: []api.Container{{
|
||||
VolumeMounts: []api.VolumeMount{{Name: volumeNameImage}, {Name: volumeNameOther}},
|
||||
}},
|
||||
InitContainers: []api.Container{{
|
||||
VolumeMounts: []api.VolumeMount{{Name: volumeNameImage}},
|
||||
}},
|
||||
EphemeralContainers: []api.EphemeralContainer{
|
||||
{EphemeralContainerCommon: api.EphemeralContainerCommon{
|
||||
VolumeMounts: []api.VolumeMount{{Name: volumeNameImage}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
podWithoutVolume := &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{anotherVolume},
|
||||
Containers: []api.Container{{VolumeMounts: []api.VolumeMount{{Name: volumeNameOther}}}},
|
||||
InitContainers: []api.Container{{}},
|
||||
EphemeralContainers: []api.EphemeralContainer{{}},
|
||||
},
|
||||
}
|
||||
|
||||
noPod := &api.Pod{}
|
||||
|
||||
testcases := []struct {
|
||||
description string
|
||||
enabled bool
|
||||
oldPod *api.Pod
|
||||
newPod *api.Pod
|
||||
wantPod *api.Pod
|
||||
}{
|
||||
{
|
||||
description: "old with volume / new with volume / disabled",
|
||||
oldPod: podWithVolume,
|
||||
newPod: podWithVolume,
|
||||
wantPod: podWithVolume,
|
||||
},
|
||||
{
|
||||
description: "old without volume / new with volume / disabled",
|
||||
oldPod: podWithoutVolume,
|
||||
newPod: podWithVolume,
|
||||
wantPod: podWithoutVolume,
|
||||
},
|
||||
{
|
||||
description: "no old pod/ new with volume / disabled",
|
||||
oldPod: noPod,
|
||||
newPod: podWithVolume,
|
||||
wantPod: podWithoutVolume,
|
||||
},
|
||||
{
|
||||
description: "nil old pod/ new with volume / disabled",
|
||||
oldPod: nil,
|
||||
newPod: podWithVolume,
|
||||
wantPod: podWithoutVolume,
|
||||
},
|
||||
{
|
||||
description: "old with volume / new without volume / disabled",
|
||||
oldPod: podWithVolume,
|
||||
newPod: podWithoutVolume,
|
||||
wantPod: podWithoutVolume,
|
||||
},
|
||||
{
|
||||
description: "old without volume / new without volume / disabled",
|
||||
oldPod: podWithoutVolume,
|
||||
newPod: podWithoutVolume,
|
||||
wantPod: podWithoutVolume,
|
||||
},
|
||||
{
|
||||
description: "no old pod/ new without volume / disabled",
|
||||
oldPod: noPod,
|
||||
newPod: podWithoutVolume,
|
||||
wantPod: podWithoutVolume,
|
||||
},
|
||||
|
||||
{
|
||||
description: "old with volume / new with volume / enabled",
|
||||
enabled: true,
|
||||
oldPod: podWithVolume,
|
||||
newPod: podWithVolume,
|
||||
wantPod: podWithVolume,
|
||||
},
|
||||
{
|
||||
description: "old without volume / new with volume / enabled",
|
||||
enabled: true,
|
||||
oldPod: podWithoutVolume,
|
||||
newPod: podWithVolume,
|
||||
wantPod: podWithVolume,
|
||||
},
|
||||
{
|
||||
description: "no old pod/ new with volume / enabled",
|
||||
enabled: true,
|
||||
oldPod: noPod,
|
||||
newPod: podWithVolume,
|
||||
wantPod: podWithVolume,
|
||||
},
|
||||
|
||||
{
|
||||
description: "old with volume / new without volume / enabled",
|
||||
enabled: true,
|
||||
oldPod: podWithVolume,
|
||||
newPod: podWithoutVolume,
|
||||
wantPod: podWithoutVolume,
|
||||
},
|
||||
{
|
||||
description: "old without volume / new without volume / enabled",
|
||||
enabled: true,
|
||||
oldPod: podWithoutVolume,
|
||||
newPod: podWithoutVolume,
|
||||
wantPod: podWithoutVolume,
|
||||
},
|
||||
{
|
||||
description: "no old pod/ new without volume / enabled",
|
||||
enabled: true,
|
||||
oldPod: noPod,
|
||||
newPod: podWithoutVolume,
|
||||
wantPod: podWithoutVolume,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ImageVolume, tc.enabled)
|
||||
|
||||
oldPod := tc.oldPod.DeepCopy()
|
||||
newPod := tc.newPod.DeepCopy()
|
||||
wantPod := tc.wantPod
|
||||
DropDisabledPodFields(newPod, oldPod)
|
||||
|
||||
// old pod should never be changed
|
||||
if diff := cmp.Diff(oldPod, tc.oldPod); diff != "" {
|
||||
t.Errorf("old pod changed: %s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(wantPod, newPod); diff != "" {
|
||||
t.Errorf("new pod changed (- want, + got): %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -184,6 +184,22 @@ type VolumeSource struct {
|
||||
//
|
||||
// +optional
|
||||
Ephemeral *EphemeralVolumeSource
|
||||
// Image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine.
|
||||
// The volume is resolved at pod startup depending on which PullPolicy value is provided:
|
||||
//
|
||||
// - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails.
|
||||
// - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present.
|
||||
// - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.
|
||||
//
|
||||
// The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation.
|
||||
// A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message.
|
||||
// The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field.
|
||||
// The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images.
|
||||
// The volume will be mounted read-only (ro) and non-executable files (noexec).
|
||||
// Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath).
|
||||
// +featureGate=ImageVolume
|
||||
// +optional
|
||||
Image *ImageVolumeSource
|
||||
}
|
||||
|
||||
// PersistentVolumeSource is similar to VolumeSource but meant for the administrator who creates PVs.
|
||||
@ -6508,3 +6524,23 @@ const (
|
||||
// the destination set to the node's IP and port or the pod's IP and port.
|
||||
LoadBalancerIPModeProxy LoadBalancerIPMode = "Proxy"
|
||||
)
|
||||
|
||||
// ImageVolumeSource represents a image volume resource.
|
||||
type ImageVolumeSource struct {
|
||||
// Required: Image or artifact reference to be used.
|
||||
// Behaves in the same way as pod.spec.containers[*].image.
|
||||
// Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets.
|
||||
// More info: https://kubernetes.io/docs/concepts/containers/images
|
||||
// This field is optional to allow higher level config management to default or override
|
||||
// container images in workload controllers like Deployments and StatefulSets.
|
||||
// +optional
|
||||
Reference string
|
||||
|
||||
// Policy for pulling OCI objects. Possible values are:
|
||||
// Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails.
|
||||
// Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present.
|
||||
// IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.
|
||||
// Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.
|
||||
// +optional
|
||||
PullPolicy PullPolicy
|
||||
}
|
||||
|
@ -69,6 +69,15 @@ func SetDefaults_Volume(obj *v1.Volume) {
|
||||
EmptyDir: &v1.EmptyDirVolumeSource{},
|
||||
}
|
||||
}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.ImageVolume) && obj.Image != nil && obj.Image.PullPolicy == "" {
|
||||
// PullPolicy defaults to Always if :latest tag is specified, or IfNotPresent otherwise.
|
||||
_, tag, _, _ := parsers.ParseImageName(obj.Image.Reference)
|
||||
if tag == "latest" {
|
||||
obj.Image.PullPolicy = v1.PullAlways
|
||||
} else {
|
||||
obj.Image.PullPolicy = v1.PullIfNotPresent
|
||||
}
|
||||
}
|
||||
}
|
||||
func SetDefaults_Container(obj *v1.Container) {
|
||||
if obj.ImagePullPolicy == "" {
|
||||
|
@ -165,6 +165,7 @@ func testWorkloadDefaults(t *testing.T, featuresEnabled bool) {
|
||||
".Spec.Volumes[0].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.VolumeMode": `"Filesystem"`,
|
||||
".Spec.Volumes[0].VolumeSource.HostPath.Type": `""`,
|
||||
".Spec.Volumes[0].VolumeSource.ISCSI.ISCSIInterface": `"default"`,
|
||||
".Spec.Volumes[0].VolumeSource.Image.PullPolicy": `"IfNotPresent"`,
|
||||
".Spec.Volumes[0].VolumeSource.Projected.DefaultMode": `420`,
|
||||
".Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`,
|
||||
".Spec.Volumes[0].VolumeSource.Projected.Sources[0].ServiceAccountToken.ExpirationSeconds": `3600`,
|
||||
@ -175,6 +176,9 @@ func testWorkloadDefaults(t *testing.T, featuresEnabled bool) {
|
||||
".Spec.Volumes[0].VolumeSource.ScaleIO.StorageMode": `"ThinProvisioned"`,
|
||||
".Spec.Volumes[0].VolumeSource.Secret.DefaultMode": `420`,
|
||||
}
|
||||
if !featuresEnabled {
|
||||
delete(expectedDefaults, ".Spec.Volumes[0].VolumeSource.Image.PullPolicy")
|
||||
}
|
||||
t.Run("empty PodTemplateSpec", func(t *testing.T) {
|
||||
rc := &v1.ReplicationController{Spec: v1.ReplicationControllerSpec{Template: &v1.PodTemplateSpec{}}}
|
||||
template := rc.Spec.Template
|
||||
@ -353,6 +357,7 @@ func testPodDefaults(t *testing.T, featuresEnabled bool) {
|
||||
".Spec.Volumes[0].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.VolumeMode": `"Filesystem"`,
|
||||
".Spec.Volumes[0].VolumeSource.HostPath.Type": `""`,
|
||||
".Spec.Volumes[0].VolumeSource.ISCSI.ISCSIInterface": `"default"`,
|
||||
".Spec.Volumes[0].VolumeSource.Image.PullPolicy": `"IfNotPresent"`,
|
||||
".Spec.Volumes[0].VolumeSource.Projected.DefaultMode": `420`,
|
||||
".Spec.Volumes[0].VolumeSource.Projected.Sources[0].DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`,
|
||||
".Spec.Volumes[0].VolumeSource.Projected.Sources[0].ServiceAccountToken.ExpirationSeconds": `3600`,
|
||||
@ -363,6 +368,9 @@ func testPodDefaults(t *testing.T, featuresEnabled bool) {
|
||||
".Spec.Volumes[0].VolumeSource.ScaleIO.StorageMode": `"ThinProvisioned"`,
|
||||
".Spec.Volumes[0].VolumeSource.Secret.DefaultMode": `420`,
|
||||
}
|
||||
if !featuresEnabled {
|
||||
delete(expectedDefaults, ".Spec.Volumes[0].VolumeSource.Image.PullPolicy")
|
||||
}
|
||||
defaults := detectDefaults(t, pod, reflect.ValueOf(pod))
|
||||
if !reflect.DeepEqual(expectedDefaults, defaults) {
|
||||
t.Errorf("Defaults for PodSpec changed. This can cause spurious restarts of containers on API server upgrade.")
|
||||
@ -2306,3 +2314,34 @@ func TestSetDefaultResizePolicy(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaults_Volume(t *testing.T) {
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ImageVolume, true)
|
||||
for desc, tc := range map[string]struct {
|
||||
given, expected *v1.Volume
|
||||
}{
|
||||
"defaults to emptyDir": {
|
||||
given: &v1.Volume{},
|
||||
expected: &v1.Volume{VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
"default image volume source pull policy is IfNotPresent": {
|
||||
given: &v1.Volume{VolumeSource: v1.VolumeSource{Image: &v1.ImageVolumeSource{Reference: "image:v1"}}},
|
||||
expected: &v1.Volume{VolumeSource: v1.VolumeSource{Image: &v1.ImageVolumeSource{Reference: "image:v1", PullPolicy: v1.PullIfNotPresent}}},
|
||||
},
|
||||
"default image volume source pull policy Always if 'latest' tag is used": {
|
||||
given: &v1.Volume{VolumeSource: v1.VolumeSource{Image: &v1.ImageVolumeSource{Reference: "image:latest"}}},
|
||||
expected: &v1.Volume{VolumeSource: v1.VolumeSource{Image: &v1.ImageVolumeSource{Reference: "image:latest", PullPolicy: v1.PullAlways}}},
|
||||
},
|
||||
"default image volume source pull policy Always if no tag is used": {
|
||||
given: &v1.Volume{VolumeSource: v1.VolumeSource{Image: &v1.ImageVolumeSource{Reference: "image"}}},
|
||||
expected: &v1.Volume{VolumeSource: v1.VolumeSource{Image: &v1.ImageVolumeSource{Reference: "image", PullPolicy: v1.PullAlways}}},
|
||||
},
|
||||
} {
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
corev1.SetDefaults_Volume(tc.given)
|
||||
if !cmp.Equal(tc.given, tc.expected) {
|
||||
t.Errorf("expected volume %+v, but got %+v", tc.expected, tc.given)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
34
pkg/apis/core/v1/zz_generated.conversion.go
generated
34
pkg/apis/core/v1/zz_generated.conversion.go
generated
@ -792,6 +792,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1.ImageVolumeSource)(nil), (*core.ImageVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_ImageVolumeSource_To_core_ImageVolumeSource(a.(*v1.ImageVolumeSource), b.(*core.ImageVolumeSource), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*core.ImageVolumeSource)(nil), (*v1.ImageVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_core_ImageVolumeSource_To_v1_ImageVolumeSource(a.(*core.ImageVolumeSource), b.(*v1.ImageVolumeSource), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1.KeyToPath)(nil), (*core.KeyToPath)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_KeyToPath_To_core_KeyToPath(a.(*v1.KeyToPath), b.(*core.KeyToPath), scope)
|
||||
}); err != nil {
|
||||
@ -4407,6 +4417,28 @@ func Convert_core_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in *core.ISCSIVolume
|
||||
return autoConvert_core_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ImageVolumeSource_To_core_ImageVolumeSource(in *v1.ImageVolumeSource, out *core.ImageVolumeSource, s conversion.Scope) error {
|
||||
out.Reference = in.Reference
|
||||
out.PullPolicy = core.PullPolicy(in.PullPolicy)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ImageVolumeSource_To_core_ImageVolumeSource is an autogenerated conversion function.
|
||||
func Convert_v1_ImageVolumeSource_To_core_ImageVolumeSource(in *v1.ImageVolumeSource, out *core.ImageVolumeSource, s conversion.Scope) error {
|
||||
return autoConvert_v1_ImageVolumeSource_To_core_ImageVolumeSource(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_core_ImageVolumeSource_To_v1_ImageVolumeSource(in *core.ImageVolumeSource, out *v1.ImageVolumeSource, s conversion.Scope) error {
|
||||
out.Reference = in.Reference
|
||||
out.PullPolicy = v1.PullPolicy(in.PullPolicy)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_core_ImageVolumeSource_To_v1_ImageVolumeSource is an autogenerated conversion function.
|
||||
func Convert_core_ImageVolumeSource_To_v1_ImageVolumeSource(in *core.ImageVolumeSource, out *v1.ImageVolumeSource, s conversion.Scope) error {
|
||||
return autoConvert_core_ImageVolumeSource_To_v1_ImageVolumeSource(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_KeyToPath_To_core_KeyToPath(in *v1.KeyToPath, out *core.KeyToPath, s conversion.Scope) error {
|
||||
out.Key = in.Key
|
||||
out.Path = in.Path
|
||||
@ -8853,6 +8885,7 @@ func autoConvert_v1_VolumeSource_To_core_VolumeSource(in *v1.VolumeSource, out *
|
||||
out.StorageOS = (*core.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS))
|
||||
out.CSI = (*core.CSIVolumeSource)(unsafe.Pointer(in.CSI))
|
||||
out.Ephemeral = (*core.EphemeralVolumeSource)(unsafe.Pointer(in.Ephemeral))
|
||||
out.Image = (*core.ImageVolumeSource)(unsafe.Pointer(in.Image))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -8899,6 +8932,7 @@ func autoConvert_core_VolumeSource_To_v1_VolumeSource(in *core.VolumeSource, out
|
||||
out.StorageOS = (*v1.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS))
|
||||
out.CSI = (*v1.CSIVolumeSource)(unsafe.Pointer(in.CSI))
|
||||
out.Ephemeral = (*v1.EphemeralVolumeSource)(unsafe.Pointer(in.Ephemeral))
|
||||
out.Image = (*v1.ImageVolumeSource)(unsafe.Pointer(in.Image))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -732,6 +732,14 @@ func validateVolumeSource(source *core.VolumeSource, fldPath *field.Path, volNam
|
||||
}
|
||||
}
|
||||
}
|
||||
if opts.AllowImageVolumeSource && source.Image != nil {
|
||||
if numVolumes > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("image"), "may not specify more than 1 volume type"))
|
||||
} else {
|
||||
numVolumes++
|
||||
allErrs = append(allErrs, validateImageVolumeSource(source.Image, fldPath.Child("image"), opts)...)
|
||||
}
|
||||
}
|
||||
|
||||
if numVolumes == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type"))
|
||||
@ -2892,7 +2900,7 @@ func GetVolumeDeviceMap(devices []core.VolumeDevice) map[string]string {
|
||||
return volDevices
|
||||
}
|
||||
|
||||
func ValidateVolumeMounts(mounts []core.VolumeMount, voldevices map[string]string, volumes map[string]core.VolumeSource, container *core.Container, fldPath *field.Path) field.ErrorList {
|
||||
func ValidateVolumeMounts(mounts []core.VolumeMount, voldevices map[string]string, volumes map[string]core.VolumeSource, container *core.Container, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
mountpoints := sets.New[string]()
|
||||
|
||||
@ -2920,6 +2928,18 @@ func ValidateVolumeMounts(mounts []core.VolumeMount, voldevices map[string]strin
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must not already exist as a path in volumeDevices"))
|
||||
}
|
||||
|
||||
// Disallow subPath/subPathExpr for image volumes
|
||||
if opts.AllowImageVolumeSource {
|
||||
if v, ok := volumes[mnt.Name]; ok && v.Image != nil {
|
||||
if len(mnt.SubPath) != 0 {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("subPath"), mnt.SubPath, "not allowed in image volume sources"))
|
||||
}
|
||||
if len(mnt.SubPathExpr) != 0 {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("subPathExpr"), mnt.SubPathExpr, "not allowed in image volume sources"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(mnt.SubPath) > 0 {
|
||||
allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...)
|
||||
}
|
||||
@ -3560,7 +3580,7 @@ func validateContainerCommon(ctr *core.Container, volumes map[string]core.Volume
|
||||
allErrs = append(allErrs, validateContainerPorts(ctr.Ports, path.Child("ports"))...)
|
||||
allErrs = append(allErrs, ValidateEnv(ctr.Env, path.Child("env"), opts)...)
|
||||
allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, path.Child("envFrom"), opts)...)
|
||||
allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volDevices, volumes, ctr, path.Child("volumeMounts"))...)
|
||||
allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volDevices, volumes, ctr, path.Child("volumeMounts"), opts)...)
|
||||
allErrs = append(allErrs, ValidateVolumeDevices(ctr.VolumeDevices, volMounts, volumes, path.Child("volumeDevices"))...)
|
||||
allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, path.Child("imagePullPolicy"))...)
|
||||
allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, podClaimNames, path.Child("resources"), opts)...)
|
||||
@ -4010,6 +4030,8 @@ type PodValidationOptions struct {
|
||||
ResourceIsPod bool
|
||||
// Allow relaxed validation of environment variable names
|
||||
AllowRelaxedEnvironmentVariableValidation bool
|
||||
// Allow the use of the ImageVolumeSource API.
|
||||
AllowImageVolumeSource bool
|
||||
}
|
||||
|
||||
// validatePodMetadataAndSpec tests if required fields in the pod.metadata and pod.spec are set,
|
||||
@ -8172,3 +8194,12 @@ func validateLinuxContainerUser(linuxContainerUser *core.LinuxContainerUser, fld
|
||||
}
|
||||
return allErrors
|
||||
}
|
||||
|
||||
func validateImageVolumeSource(imageVolume *core.ImageVolumeSource, fldPath *field.Path, opts PodValidationOptions) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if opts.ResourceIsPod && len(imageVolume.Reference) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("reference"), ""))
|
||||
}
|
||||
allErrs = append(allErrs, validatePullPolicy(imageVolume.PullPolicy, fldPath.Child("pullPolicy"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
@ -5352,6 +5352,97 @@ func TestValidateVolumes(t *testing.T) {
|
||||
field: "projected.sources[1]",
|
||||
}},
|
||||
},
|
||||
// ImageVolumeSource
|
||||
{
|
||||
name: "valid image volume on pod",
|
||||
vol: core.Volume{
|
||||
Name: "image-volume",
|
||||
VolumeSource: core.VolumeSource{
|
||||
Image: &core.ImageVolumeSource{
|
||||
Reference: "quay.io/my/artifact:v1",
|
||||
PullPolicy: "IfNotPresent",
|
||||
},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{AllowImageVolumeSource: true},
|
||||
}, {
|
||||
name: "feature disabled",
|
||||
vol: core.Volume{
|
||||
Name: "image-volume",
|
||||
VolumeSource: core.VolumeSource{
|
||||
Image: &core.ImageVolumeSource{
|
||||
Reference: "quay.io/my/artifact:v1",
|
||||
PullPolicy: "IfNotPresent",
|
||||
},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{AllowImageVolumeSource: false},
|
||||
errs: []verr{{
|
||||
etype: field.ErrorTypeRequired,
|
||||
field: "field[0]",
|
||||
detail: "must specify a volume type",
|
||||
}},
|
||||
}, {
|
||||
name: "image volume with empty name",
|
||||
vol: core.Volume{
|
||||
Name: "",
|
||||
VolumeSource: core.VolumeSource{
|
||||
Image: &core.ImageVolumeSource{
|
||||
Reference: "quay.io/my/artifact:v1",
|
||||
PullPolicy: "IfNotPresent",
|
||||
},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{AllowImageVolumeSource: true},
|
||||
errs: []verr{{
|
||||
etype: field.ErrorTypeRequired,
|
||||
field: "name",
|
||||
}},
|
||||
}, {
|
||||
name: "image volume with empty reference on pod",
|
||||
vol: core.Volume{
|
||||
Name: "image-volume",
|
||||
VolumeSource: core.VolumeSource{
|
||||
Image: &core.ImageVolumeSource{
|
||||
Reference: "",
|
||||
PullPolicy: "IfNotPresent",
|
||||
},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{AllowImageVolumeSource: true, ResourceIsPod: true},
|
||||
errs: []verr{{
|
||||
etype: field.ErrorTypeRequired,
|
||||
field: "image.reference",
|
||||
}},
|
||||
}, {
|
||||
name: "image volume with empty reference on other object",
|
||||
vol: core.Volume{
|
||||
Name: "image-volume",
|
||||
VolumeSource: core.VolumeSource{
|
||||
Image: &core.ImageVolumeSource{
|
||||
Reference: "",
|
||||
PullPolicy: "IfNotPresent",
|
||||
},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{AllowImageVolumeSource: true, ResourceIsPod: false},
|
||||
}, {
|
||||
name: "image volume with wrong pullPolicy",
|
||||
vol: core.Volume{
|
||||
Name: "image-volume",
|
||||
VolumeSource: core.VolumeSource{
|
||||
Image: &core.ImageVolumeSource{
|
||||
Reference: "quay.io/my/artifact:v1",
|
||||
PullPolicy: "wrong",
|
||||
},
|
||||
},
|
||||
},
|
||||
opts: PodValidationOptions{AllowImageVolumeSource: true},
|
||||
errs: []verr{{
|
||||
etype: field.ErrorTypeNotSupported,
|
||||
field: "image.pullPolicy",
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -5368,7 +5459,7 @@ func TestValidateVolumes(t *testing.T) {
|
||||
if err.Type != expErr.etype {
|
||||
t.Errorf("unexpected error type:\n\twant: %q\n\t got: %q", expErr.etype, err.Type)
|
||||
}
|
||||
if !strings.HasSuffix(err.Field, "."+expErr.field) {
|
||||
if err.Field != expErr.field && !strings.HasSuffix(err.Field, "."+expErr.field) {
|
||||
t.Errorf("unexpected error field:\n\twant: %q\n\t got: %q", expErr.field, err.Field)
|
||||
}
|
||||
if !strings.Contains(err.Detail, expErr.detail) {
|
||||
@ -6959,8 +7050,10 @@ func TestValidateVolumeMounts(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}}}},
|
||||
{Name: "image-volume", VolumeSource: core.VolumeSource{Image: &core.ImageVolumeSource{Reference: "quay.io/my/artifact:v1", PullPolicy: "IfNotPresent"}}},
|
||||
}
|
||||
vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), PodValidationOptions{})
|
||||
opts := PodValidationOptions{AllowImageVolumeSource: true}
|
||||
vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"), opts)
|
||||
if len(v1err) > 0 {
|
||||
t.Errorf("Invalid test volume - expected success %v", v1err)
|
||||
return
|
||||
@ -6988,38 +7081,41 @@ func TestValidateVolumeMounts(t *testing.T) {
|
||||
{Name: "123", MountPath: "/rro-ifpossible", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyIfPossible)},
|
||||
{Name: "123", MountPath: "/rro-enabled", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled)},
|
||||
{Name: "123", MountPath: "/rro-enabled-2", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled), MountPropagation: ptr.To(core.MountPropagationNone)},
|
||||
{Name: "image-volume", MountPath: "/image-volume"},
|
||||
}
|
||||
goodVolumeDevices := []core.VolumeDevice{
|
||||
{Name: "xyz", DevicePath: "/foofoo"},
|
||||
{Name: "uvw", DevicePath: "/foofoo/share/test"},
|
||||
}
|
||||
if errs := ValidateVolumeMounts(successCase, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field")); len(errs) != 0 {
|
||||
if errs := ValidateVolumeMounts(successCase, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"), opts); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
|
||||
errorCases := map[string][]core.VolumeMount{
|
||||
"empty name": {{Name: "", MountPath: "/foo"}},
|
||||
"name not found": {{Name: "", MountPath: "/foo"}},
|
||||
"empty mountpath": {{Name: "abc", MountPath: ""}},
|
||||
"mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}},
|
||||
"absolute subpath": {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}},
|
||||
"subpath in ..": {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}},
|
||||
"subpath contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
|
||||
"subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
|
||||
"disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}},
|
||||
"name exists in volumeDevice": {{Name: "xyz", MountPath: "/bar"}},
|
||||
"mountpath exists in volumeDevice": {{Name: "uvw", MountPath: "/mnt/exists"}},
|
||||
"both exist in volumeDevice": {{Name: "xyz", MountPath: "/mnt/exists"}},
|
||||
"rro but not ro": {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled)}},
|
||||
"rro with incompatible propagation": {{Name: "123", MountPath: "/rro-bad2", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled), MountPropagation: ptr.To(core.MountPropagationHostToContainer)}},
|
||||
"rro-if-possible but not ro": {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyIfPossible)}},
|
||||
"empty name": {{Name: "", MountPath: "/foo"}},
|
||||
"name not found": {{Name: "", MountPath: "/foo"}},
|
||||
"empty mountpath": {{Name: "abc", MountPath: ""}},
|
||||
"mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}},
|
||||
"absolute subpath": {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}},
|
||||
"subpath in ..": {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}},
|
||||
"subpath contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
|
||||
"subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
|
||||
"disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}},
|
||||
"name exists in volumeDevice": {{Name: "xyz", MountPath: "/bar"}},
|
||||
"mountpath exists in volumeDevice": {{Name: "uvw", MountPath: "/mnt/exists"}},
|
||||
"both exist in volumeDevice": {{Name: "xyz", MountPath: "/mnt/exists"}},
|
||||
"rro but not ro": {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled)}},
|
||||
"rro with incompatible propagation": {{Name: "123", MountPath: "/rro-bad2", ReadOnly: true, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyEnabled), MountPropagation: ptr.To(core.MountPropagationHostToContainer)}},
|
||||
"rro-if-possible but not ro": {{Name: "123", MountPath: "/rro-bad1", ReadOnly: false, RecursiveReadOnly: ptr.To(core.RecursiveReadOnlyIfPossible)}},
|
||||
"subPath not allowed for image volume sources": {{Name: "image-volume", MountPath: "/image-volume-err-1", SubPath: "/foo"}},
|
||||
"subPathExpr not allowed for image volume sources": {{Name: "image-volume", MountPath: "/image-volume-err-2", SubPathExpr: "$(POD_NAME)"}},
|
||||
}
|
||||
badVolumeDevice := []core.VolumeDevice{
|
||||
{Name: "xyz", DevicePath: "/mnt/exists"},
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
if errs := ValidateVolumeMounts(v, GetVolumeDeviceMap(badVolumeDevice), vols, &container, field.NewPath("field")); len(errs) == 0 {
|
||||
if errs := ValidateVolumeMounts(v, GetVolumeDeviceMap(badVolumeDevice), vols, &container, field.NewPath("field"), opts); len(errs) == 0 {
|
||||
t.Errorf("expected failure for %s", k)
|
||||
}
|
||||
}
|
||||
@ -7085,7 +7181,7 @@ func TestValidateSubpathMutuallyExclusive(t *testing.T) {
|
||||
}
|
||||
|
||||
for name, test := range cases {
|
||||
errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
|
||||
errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"), PodValidationOptions{})
|
||||
|
||||
if len(errs) != 0 && !test.expectError {
|
||||
t.Errorf("test %v failed: %+v", name, errs)
|
||||
@ -7141,7 +7237,7 @@ func TestValidateDisabledSubpathExpr(t *testing.T) {
|
||||
}
|
||||
|
||||
for name, test := range cases {
|
||||
errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
|
||||
errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"), PodValidationOptions{})
|
||||
|
||||
if len(errs) != 0 && !test.expectError {
|
||||
t.Errorf("test %v failed: %+v", name, errs)
|
||||
@ -7249,7 +7345,7 @@ func TestValidateMountPropagation(t *testing.T) {
|
||||
return
|
||||
}
|
||||
for i, test := range tests {
|
||||
errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, nil, vols2, test.container, field.NewPath("field"))
|
||||
errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, nil, vols2, test.container, field.NewPath("field"), PodValidationOptions{})
|
||||
if test.expectError && len(errs) == 0 {
|
||||
t.Errorf("test %d expected error, got none", i)
|
||||
}
|
||||
|
21
pkg/apis/core/zz_generated.deepcopy.go
generated
21
pkg/apis/core/zz_generated.deepcopy.go
generated
@ -2044,6 +2044,22 @@ func (in *ISCSIVolumeSource) DeepCopy() *ISCSIVolumeSource {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageVolumeSource) DeepCopyInto(out *ImageVolumeSource) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageVolumeSource.
|
||||
func (in *ImageVolumeSource) DeepCopy() *ImageVolumeSource {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageVolumeSource)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KeyToPath) DeepCopyInto(out *KeyToPath) {
|
||||
*out = *in
|
||||
@ -6477,6 +6493,11 @@ func (in *VolumeSource) DeepCopyInto(out *VolumeSource) {
|
||||
*out = new(EphemeralVolumeSource)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Image != nil {
|
||||
in, out := &in.Image, &out.Image
|
||||
*out = new(ImageVolumeSource)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -958,6 +958,13 @@ const (
|
||||
//
|
||||
// Enable SupplementalGroupsPolicy feature in PodSecurityContext
|
||||
SupplementalGroupsPolicy featuregate.Feature = "SupplementalGroupsPolicy"
|
||||
|
||||
// owner: @saschagrunert
|
||||
// kep: https://kep.k8s.io/4639
|
||||
// alpha: v1.31
|
||||
//
|
||||
// Enables the image volume source.
|
||||
ImageVolume featuregate.Feature = "ImageVolume"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -1215,6 +1222,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
|
||||
SupplementalGroupsPolicy: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
ImageVolume: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||
// unintentionally on either side:
|
||||
|
||||
|
45
pkg/generated/openapi/zz_generated.openapi.go
generated
45
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -452,6 +452,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"k8s.io/api/core/v1.HostPathVolumeSource": schema_k8sio_api_core_v1_HostPathVolumeSource(ref),
|
||||
"k8s.io/api/core/v1.ISCSIPersistentVolumeSource": schema_k8sio_api_core_v1_ISCSIPersistentVolumeSource(ref),
|
||||
"k8s.io/api/core/v1.ISCSIVolumeSource": schema_k8sio_api_core_v1_ISCSIVolumeSource(ref),
|
||||
"k8s.io/api/core/v1.ImageVolumeSource": schema_k8sio_api_core_v1_ImageVolumeSource(ref),
|
||||
"k8s.io/api/core/v1.KeyToPath": schema_k8sio_api_core_v1_KeyToPath(ref),
|
||||
"k8s.io/api/core/v1.Lifecycle": schema_k8sio_api_core_v1_Lifecycle(ref),
|
||||
"k8s.io/api/core/v1.LifecycleHandler": schema_k8sio_api_core_v1_LifecycleHandler(ref),
|
||||
@ -23291,6 +23292,34 @@ func schema_k8sio_api_core_v1_ISCSIVolumeSource(ref common.ReferenceCallback) co
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_core_v1_ImageVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "ImageVolumeSource represents a image volume resource.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"reference": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Required: Image or artifact reference to be used. Behaves in the same way as pod.spec.containers[*].image. Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"pullPolicy": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Policy for pulling OCI objects. Possible values are: Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.\n\nPossible enum values:\n - `\"Always\"` means that kubelet always attempts to pull the latest image. Container will fail If the pull fails.\n - `\"IfNotPresent\"` means that kubelet pulls if the image isn't present on disk. Container will fail if the image isn't present and the pull fails.\n - `\"Never\"` means that kubelet never pulls an image, but only uses a local image. Container will fail if the image isn't present",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
Enum: []interface{}{"Always", "IfNotPresent", "Never"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_core_v1_KeyToPath(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
@ -31605,12 +31634,18 @@ func schema_k8sio_api_core_v1_Volume(ref common.ReferenceCallback) common.OpenAP
|
||||
Ref: ref("k8s.io/api/core/v1.EphemeralVolumeSource"),
|
||||
},
|
||||
},
|
||||
"image": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath).",
|
||||
Ref: ref("k8s.io/api/core/v1.ImageVolumeSource"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource", "k8s.io/api/core/v1.AzureDiskVolumeSource", "k8s.io/api/core/v1.AzureFileVolumeSource", "k8s.io/api/core/v1.CSIVolumeSource", "k8s.io/api/core/v1.CephFSVolumeSource", "k8s.io/api/core/v1.CinderVolumeSource", "k8s.io/api/core/v1.ConfigMapVolumeSource", "k8s.io/api/core/v1.DownwardAPIVolumeSource", "k8s.io/api/core/v1.EmptyDirVolumeSource", "k8s.io/api/core/v1.EphemeralVolumeSource", "k8s.io/api/core/v1.FCVolumeSource", "k8s.io/api/core/v1.FlexVolumeSource", "k8s.io/api/core/v1.FlockerVolumeSource", "k8s.io/api/core/v1.GCEPersistentDiskVolumeSource", "k8s.io/api/core/v1.GitRepoVolumeSource", "k8s.io/api/core/v1.GlusterfsVolumeSource", "k8s.io/api/core/v1.HostPathVolumeSource", "k8s.io/api/core/v1.ISCSIVolumeSource", "k8s.io/api/core/v1.NFSVolumeSource", "k8s.io/api/core/v1.PersistentVolumeClaimVolumeSource", "k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource", "k8s.io/api/core/v1.PortworxVolumeSource", "k8s.io/api/core/v1.ProjectedVolumeSource", "k8s.io/api/core/v1.QuobyteVolumeSource", "k8s.io/api/core/v1.RBDVolumeSource", "k8s.io/api/core/v1.ScaleIOVolumeSource", "k8s.io/api/core/v1.SecretVolumeSource", "k8s.io/api/core/v1.StorageOSVolumeSource", "k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource"},
|
||||
"k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource", "k8s.io/api/core/v1.AzureDiskVolumeSource", "k8s.io/api/core/v1.AzureFileVolumeSource", "k8s.io/api/core/v1.CSIVolumeSource", "k8s.io/api/core/v1.CephFSVolumeSource", "k8s.io/api/core/v1.CinderVolumeSource", "k8s.io/api/core/v1.ConfigMapVolumeSource", "k8s.io/api/core/v1.DownwardAPIVolumeSource", "k8s.io/api/core/v1.EmptyDirVolumeSource", "k8s.io/api/core/v1.EphemeralVolumeSource", "k8s.io/api/core/v1.FCVolumeSource", "k8s.io/api/core/v1.FlexVolumeSource", "k8s.io/api/core/v1.FlockerVolumeSource", "k8s.io/api/core/v1.GCEPersistentDiskVolumeSource", "k8s.io/api/core/v1.GitRepoVolumeSource", "k8s.io/api/core/v1.GlusterfsVolumeSource", "k8s.io/api/core/v1.HostPathVolumeSource", "k8s.io/api/core/v1.ISCSIVolumeSource", "k8s.io/api/core/v1.ImageVolumeSource", "k8s.io/api/core/v1.NFSVolumeSource", "k8s.io/api/core/v1.PersistentVolumeClaimVolumeSource", "k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource", "k8s.io/api/core/v1.PortworxVolumeSource", "k8s.io/api/core/v1.ProjectedVolumeSource", "k8s.io/api/core/v1.QuobyteVolumeSource", "k8s.io/api/core/v1.RBDVolumeSource", "k8s.io/api/core/v1.ScaleIOVolumeSource", "k8s.io/api/core/v1.SecretVolumeSource", "k8s.io/api/core/v1.StorageOSVolumeSource", "k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource"},
|
||||
}
|
||||
}
|
||||
|
||||
@ -32044,11 +32079,17 @@ func schema_k8sio_api_core_v1_VolumeSource(ref common.ReferenceCallback) common.
|
||||
Ref: ref("k8s.io/api/core/v1.EphemeralVolumeSource"),
|
||||
},
|
||||
},
|
||||
"image": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath).",
|
||||
Ref: ref("k8s.io/api/core/v1.ImageVolumeSource"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource", "k8s.io/api/core/v1.AzureDiskVolumeSource", "k8s.io/api/core/v1.AzureFileVolumeSource", "k8s.io/api/core/v1.CSIVolumeSource", "k8s.io/api/core/v1.CephFSVolumeSource", "k8s.io/api/core/v1.CinderVolumeSource", "k8s.io/api/core/v1.ConfigMapVolumeSource", "k8s.io/api/core/v1.DownwardAPIVolumeSource", "k8s.io/api/core/v1.EmptyDirVolumeSource", "k8s.io/api/core/v1.EphemeralVolumeSource", "k8s.io/api/core/v1.FCVolumeSource", "k8s.io/api/core/v1.FlexVolumeSource", "k8s.io/api/core/v1.FlockerVolumeSource", "k8s.io/api/core/v1.GCEPersistentDiskVolumeSource", "k8s.io/api/core/v1.GitRepoVolumeSource", "k8s.io/api/core/v1.GlusterfsVolumeSource", "k8s.io/api/core/v1.HostPathVolumeSource", "k8s.io/api/core/v1.ISCSIVolumeSource", "k8s.io/api/core/v1.NFSVolumeSource", "k8s.io/api/core/v1.PersistentVolumeClaimVolumeSource", "k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource", "k8s.io/api/core/v1.PortworxVolumeSource", "k8s.io/api/core/v1.ProjectedVolumeSource", "k8s.io/api/core/v1.QuobyteVolumeSource", "k8s.io/api/core/v1.RBDVolumeSource", "k8s.io/api/core/v1.ScaleIOVolumeSource", "k8s.io/api/core/v1.SecretVolumeSource", "k8s.io/api/core/v1.StorageOSVolumeSource", "k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource"},
|
||||
"k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource", "k8s.io/api/core/v1.AzureDiskVolumeSource", "k8s.io/api/core/v1.AzureFileVolumeSource", "k8s.io/api/core/v1.CSIVolumeSource", "k8s.io/api/core/v1.CephFSVolumeSource", "k8s.io/api/core/v1.CinderVolumeSource", "k8s.io/api/core/v1.ConfigMapVolumeSource", "k8s.io/api/core/v1.DownwardAPIVolumeSource", "k8s.io/api/core/v1.EmptyDirVolumeSource", "k8s.io/api/core/v1.EphemeralVolumeSource", "k8s.io/api/core/v1.FCVolumeSource", "k8s.io/api/core/v1.FlexVolumeSource", "k8s.io/api/core/v1.FlockerVolumeSource", "k8s.io/api/core/v1.GCEPersistentDiskVolumeSource", "k8s.io/api/core/v1.GitRepoVolumeSource", "k8s.io/api/core/v1.GlusterfsVolumeSource", "k8s.io/api/core/v1.HostPathVolumeSource", "k8s.io/api/core/v1.ISCSIVolumeSource", "k8s.io/api/core/v1.ImageVolumeSource", "k8s.io/api/core/v1.NFSVolumeSource", "k8s.io/api/core/v1.PersistentVolumeClaimVolumeSource", "k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource", "k8s.io/api/core/v1.PortworxVolumeSource", "k8s.io/api/core/v1.ProjectedVolumeSource", "k8s.io/api/core/v1.QuobyteVolumeSource", "k8s.io/api/core/v1.RBDVolumeSource", "k8s.io/api/core/v1.ScaleIOVolumeSource", "k8s.io/api/core/v1.SecretVolumeSource", "k8s.io/api/core/v1.StorageOSVolumeSource", "k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource"},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,6 +73,13 @@ func (a *AlwaysPullImages) Admit(ctx context.Context, attributes admission.Attri
|
||||
return true
|
||||
})
|
||||
|
||||
// See: https://kep.k8s.io/4639
|
||||
for _, v := range pod.Spec.Volumes {
|
||||
if v.Image != nil {
|
||||
v.Image.PullPolicy = api.PullAlways
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -96,6 +103,20 @@ func (*AlwaysPullImages) Validate(ctx context.Context, attributes admission.Attr
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// See: https://kep.k8s.io/4639
|
||||
for i, v := range pod.Spec.Volumes {
|
||||
if v.Image != nil && v.Image.PullPolicy != api.PullAlways {
|
||||
allErrs = append(allErrs, admission.NewForbidden(attributes,
|
||||
field.NotSupported(
|
||||
field.NewPath("spec").Child("volumes").Index(i).Child("image").Child("pullPolicy"),
|
||||
v.Image.PullPolicy,
|
||||
[]string{string(api.PullAlways)},
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if len(allErrs) > 0 {
|
||||
return utilerrors.NewAggregate(allErrs)
|
||||
}
|
||||
|
@ -47,6 +47,11 @@ func TestAdmission(t *testing.T) {
|
||||
{Name: "ctr3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "ctr4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
Volumes: []api.Volume{
|
||||
{Name: "volume1", VolumeSource: api.VolumeSource{Image: &api.ImageVolumeSource{PullPolicy: api.PullNever}}},
|
||||
{Name: "volume2", VolumeSource: api.VolumeSource{Image: &api.ImageVolumeSource{PullPolicy: api.PullIfNotPresent}}},
|
||||
{Name: "volume3", VolumeSource: api.VolumeSource{Image: &api.ImageVolumeSource{PullPolicy: api.PullAlways}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := handler.Admit(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
|
||||
@ -63,6 +68,11 @@ func TestAdmission(t *testing.T) {
|
||||
t.Errorf("Container %v: expected pull always, got %v", c, c.ImagePullPolicy)
|
||||
}
|
||||
}
|
||||
for _, v := range pod.Spec.Volumes {
|
||||
if v.Image.PullPolicy != api.PullAlways {
|
||||
t.Errorf("Image volume %v: expected pull always, got %v", v, v.Image.PullPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
@ -83,6 +93,12 @@ func TestValidate(t *testing.T) {
|
||||
{Name: "ctr3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "ctr4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
Volumes: []api.Volume{
|
||||
{Name: "volume1", VolumeSource: api.VolumeSource{Image: &api.ImageVolumeSource{PullPolicy: ""}}},
|
||||
{Name: "volume2", VolumeSource: api.VolumeSource{Image: &api.ImageVolumeSource{PullPolicy: api.PullNever}}},
|
||||
{Name: "volume3", VolumeSource: api.VolumeSource{Image: &api.ImageVolumeSource{PullPolicy: api.PullIfNotPresent}}},
|
||||
{Name: "volume4", VolumeSource: api.VolumeSource{Image: &api.ImageVolumeSource{PullPolicy: api.PullAlways}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedError := `[` +
|
||||
@ -91,7 +107,11 @@ func TestValidate(t *testing.T) {
|
||||
`pods "123" is forbidden: spec.initContainers[2].imagePullPolicy: Unsupported value: "IfNotPresent": supported values: "Always", ` +
|
||||
`pods "123" is forbidden: spec.containers[0].imagePullPolicy: Unsupported value: "": supported values: "Always", ` +
|
||||
`pods "123" is forbidden: spec.containers[1].imagePullPolicy: Unsupported value: "Never": supported values: "Always", ` +
|
||||
`pods "123" is forbidden: spec.containers[2].imagePullPolicy: Unsupported value: "IfNotPresent": supported values: "Always"]`
|
||||
`pods "123" is forbidden: spec.containers[2].imagePullPolicy: Unsupported value: "IfNotPresent": supported values: "Always", ` +
|
||||
`pods "123" is forbidden: spec.volumes[0].image.pullPolicy: Unsupported value: "": supported values: "Always", ` +
|
||||
`pods "123" is forbidden: spec.volumes[1].image.pullPolicy: Unsupported value: "Never": supported values: "Always", ` +
|
||||
`pods "123" is forbidden: spec.volumes[2].image.pullPolicy: Unsupported value: "IfNotPresent": supported values: "Always"` +
|
||||
`]`
|
||||
err := handler.Validate(context.TODO(), admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
|
||||
if err == nil {
|
||||
t.Fatal("missing expected error")
|
||||
|
2534
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
2534
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@ -2125,6 +2125,26 @@ message ISCSIVolumeSource {
|
||||
optional string initiatorName = 12;
|
||||
}
|
||||
|
||||
// ImageVolumeSource represents a image volume resource.
|
||||
message ImageVolumeSource {
|
||||
// Required: Image or artifact reference to be used.
|
||||
// Behaves in the same way as pod.spec.containers[*].image.
|
||||
// Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets.
|
||||
// More info: https://kubernetes.io/docs/concepts/containers/images
|
||||
// This field is optional to allow higher level config management to default or override
|
||||
// container images in workload controllers like Deployments and StatefulSets.
|
||||
// +optional
|
||||
optional string reference = 1;
|
||||
|
||||
// Policy for pulling OCI objects. Possible values are:
|
||||
// Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails.
|
||||
// Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present.
|
||||
// IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.
|
||||
// Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.
|
||||
// +optional
|
||||
optional string pullPolicy = 2;
|
||||
}
|
||||
|
||||
// Maps a string key to a path within a volume.
|
||||
message KeyToPath {
|
||||
// key is the key to project.
|
||||
@ -6610,6 +6630,23 @@ message VolumeSource {
|
||||
//
|
||||
// +optional
|
||||
optional EphemeralVolumeSource ephemeral = 29;
|
||||
|
||||
// image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine.
|
||||
// The volume is resolved at pod startup depending on which PullPolicy value is provided:
|
||||
//
|
||||
// - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails.
|
||||
// - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present.
|
||||
// - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.
|
||||
//
|
||||
// The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation.
|
||||
// A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message.
|
||||
// The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field.
|
||||
// The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images.
|
||||
// The volume will be mounted read-only (ro) and non-executable files (noexec).
|
||||
// Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath).
|
||||
// +featureGate=ImageVolume
|
||||
// +optional
|
||||
optional ImageVolumeSource image = 30;
|
||||
}
|
||||
|
||||
// Represents a vSphere volume resource.
|
||||
|
@ -181,6 +181,22 @@ type VolumeSource struct {
|
||||
//
|
||||
// +optional
|
||||
Ephemeral *EphemeralVolumeSource `json:"ephemeral,omitempty" protobuf:"bytes,29,opt,name=ephemeral"`
|
||||
// image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine.
|
||||
// The volume is resolved at pod startup depending on which PullPolicy value is provided:
|
||||
//
|
||||
// - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails.
|
||||
// - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present.
|
||||
// - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.
|
||||
//
|
||||
// The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation.
|
||||
// A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message.
|
||||
// The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field.
|
||||
// The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images.
|
||||
// The volume will be mounted read-only (ro) and non-executable files (noexec).
|
||||
// Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath).
|
||||
// +featureGate=ImageVolume
|
||||
// +optional
|
||||
Image *ImageVolumeSource `json:"image,omitempty" protobuf:"bytes,30,opt,name=image"`
|
||||
}
|
||||
|
||||
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
|
||||
@ -7662,3 +7678,23 @@ const (
|
||||
// the destination set to the node's IP and port or the pod's IP and port.
|
||||
LoadBalancerIPModeProxy LoadBalancerIPMode = "Proxy"
|
||||
)
|
||||
|
||||
// ImageVolumeSource represents a image volume resource.
|
||||
type ImageVolumeSource struct {
|
||||
// Required: Image or artifact reference to be used.
|
||||
// Behaves in the same way as pod.spec.containers[*].image.
|
||||
// Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets.
|
||||
// More info: https://kubernetes.io/docs/concepts/containers/images
|
||||
// This field is optional to allow higher level config management to default or override
|
||||
// container images in workload controllers like Deployments and StatefulSets.
|
||||
// +optional
|
||||
Reference string `json:"reference,omitempty" protobuf:"bytes,1,opt,name=reference"`
|
||||
|
||||
// Policy for pulling OCI objects. Possible values are:
|
||||
// Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails.
|
||||
// Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present.
|
||||
// IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.
|
||||
// Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.
|
||||
// +optional
|
||||
PullPolicy PullPolicy `json:"pullPolicy,omitempty" protobuf:"bytes,2,opt,name=pullPolicy,casttype=PullPolicy"`
|
||||
}
|
||||
|
@ -933,6 +933,16 @@ func (ISCSIVolumeSource) SwaggerDoc() map[string]string {
|
||||
return map_ISCSIVolumeSource
|
||||
}
|
||||
|
||||
var map_ImageVolumeSource = map[string]string{
|
||||
"": "ImageVolumeSource represents a image volume resource.",
|
||||
"reference": "Required: Image or artifact reference to be used. Behaves in the same way as pod.spec.containers[*].image. Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.",
|
||||
"pullPolicy": "Policy for pulling OCI objects. Possible values are: Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.",
|
||||
}
|
||||
|
||||
func (ImageVolumeSource) SwaggerDoc() map[string]string {
|
||||
return map_ImageVolumeSource
|
||||
}
|
||||
|
||||
var map_KeyToPath = map[string]string{
|
||||
"": "Maps a string key to a path within a volume.",
|
||||
"key": "key is the key to project.",
|
||||
@ -2716,6 +2726,7 @@ var map_VolumeSource = map[string]string{
|
||||
"storageos": "storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.",
|
||||
"csi": "csi (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature).",
|
||||
"ephemeral": "ephemeral represents a volume that is handled by a cluster storage driver. The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, and deleted when the pod is removed.\n\nUse this if: a) the volume is only needed while the pod runs, b) features of normal volumes like restoring from snapshot or capacity\n tracking are needed,\nc) the storage driver is specified through a storage class, and d) the storage driver supports dynamic volume provisioning through\n a PersistentVolumeClaim (see EphemeralVolumeSource for more\n information on the connection between this volume type\n and PersistentVolumeClaim).\n\nUse PersistentVolumeClaim or one of the vendor-specific APIs for volumes that persist for longer than the lifecycle of an individual pod.\n\nUse CSI for light-weight local ephemeral volumes if the CSI driver is meant to be used that way - see the documentation of the driver for more information.\n\nA pod can use both types of ephemeral volumes and persistent volumes at the same time.",
|
||||
"image": "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath).",
|
||||
}
|
||||
|
||||
func (VolumeSource) SwaggerDoc() map[string]string {
|
||||
|
@ -2044,6 +2044,22 @@ func (in *ISCSIVolumeSource) DeepCopy() *ISCSIVolumeSource {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageVolumeSource) DeepCopyInto(out *ImageVolumeSource) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageVolumeSource.
|
||||
func (in *ImageVolumeSource) DeepCopy() *ImageVolumeSource {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageVolumeSource)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *KeyToPath) DeepCopyInto(out *KeyToPath) {
|
||||
*out = *in
|
||||
@ -6492,6 +6508,11 @@ func (in *VolumeSource) DeepCopyInto(out *VolumeSource) {
|
||||
*out = new(EphemeralVolumeSource)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Image != nil {
|
||||
in, out := &in.Image, &out.Image
|
||||
*out = new(ImageVolumeSource)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -491,6 +491,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1084,6 +1084,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -492,6 +492,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1092,6 +1092,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -493,6 +493,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1084,6 +1084,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -492,6 +492,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1092,6 +1092,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -492,6 +492,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1094,6 +1094,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -492,6 +492,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1092,6 +1092,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -491,6 +491,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1084,6 +1084,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -492,6 +492,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1092,6 +1092,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -493,6 +493,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1084,6 +1084,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -492,6 +492,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1092,6 +1092,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -575,6 +575,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1144,6 +1144,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -526,6 +526,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
BIN
staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb
vendored
BIN
staging/src/k8s.io/api/testdata/HEAD/batch.v1.Job.pb
vendored
Binary file not shown.
@ -1108,6 +1108,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -575,6 +575,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1144,6 +1144,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -433,6 +433,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
BIN
staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.pb
vendored
BIN
staging/src/k8s.io/api/testdata/HEAD/core.v1.Pod.pb
vendored
Binary file not shown.
@ -1040,6 +1040,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -476,6 +476,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1073,6 +1073,9 @@ template:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -482,6 +482,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1078,6 +1078,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -491,6 +491,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1084,6 +1084,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -492,6 +492,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1094,6 +1094,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -493,6 +493,10 @@
|
||||
"volumeAttributesClassName": "volumeAttributesClassNameValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"reference": "referenceValue",
|
||||
"pullPolicy": "pullPolicyValue"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
Binary file not shown.
@ -1084,6 +1084,9 @@ spec:
|
||||
hostPath:
|
||||
path: pathValue
|
||||
type: typeValue
|
||||
image:
|
||||
pullPolicy: pullPolicyValue
|
||||
reference: referenceValue
|
||||
iscsi:
|
||||
chapAuthDiscovery: true
|
||||
chapAuthSession: true
|
||||
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ImageVolumeSourceApplyConfiguration represents a declarative configuration of the ImageVolumeSource type for use
|
||||
// with apply.
|
||||
type ImageVolumeSourceApplyConfiguration struct {
|
||||
Reference *string `json:"reference,omitempty"`
|
||||
PullPolicy *v1.PullPolicy `json:"pullPolicy,omitempty"`
|
||||
}
|
||||
|
||||
// ImageVolumeSourceApplyConfiguration constructs a declarative configuration of the ImageVolumeSource type for use with
|
||||
// apply.
|
||||
func ImageVolumeSource() *ImageVolumeSourceApplyConfiguration {
|
||||
return &ImageVolumeSourceApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithReference sets the Reference field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Reference field is set to the value of the last call.
|
||||
func (b *ImageVolumeSourceApplyConfiguration) WithReference(value string) *ImageVolumeSourceApplyConfiguration {
|
||||
b.Reference = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithPullPolicy sets the PullPolicy field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the PullPolicy field is set to the value of the last call.
|
||||
func (b *ImageVolumeSourceApplyConfiguration) WithPullPolicy(value v1.PullPolicy) *ImageVolumeSourceApplyConfiguration {
|
||||
b.PullPolicy = &value
|
||||
return b
|
||||
}
|
@ -270,3 +270,11 @@ func (b *VolumeApplyConfiguration) WithEphemeral(value *EphemeralVolumeSourceApp
|
||||
b.Ephemeral = value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithImage sets the Image field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Image field is set to the value of the last call.
|
||||
func (b *VolumeApplyConfiguration) WithImage(value *ImageVolumeSourceApplyConfiguration) *VolumeApplyConfiguration {
|
||||
b.Image = value
|
||||
return b
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ type VolumeSourceApplyConfiguration struct {
|
||||
StorageOS *StorageOSVolumeSourceApplyConfiguration `json:"storageos,omitempty"`
|
||||
CSI *CSIVolumeSourceApplyConfiguration `json:"csi,omitempty"`
|
||||
Ephemeral *EphemeralVolumeSourceApplyConfiguration `json:"ephemeral,omitempty"`
|
||||
Image *ImageVolumeSourceApplyConfiguration `json:"image,omitempty"`
|
||||
}
|
||||
|
||||
// VolumeSourceApplyConfiguration constructs a declarative configuration of the VolumeSource type for use with
|
||||
@ -289,3 +290,11 @@ func (b *VolumeSourceApplyConfiguration) WithEphemeral(value *EphemeralVolumeSou
|
||||
b.Ephemeral = value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithImage sets the Image field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Image field is set to the value of the last call.
|
||||
func (b *VolumeSourceApplyConfiguration) WithImage(value *ImageVolumeSourceApplyConfiguration) *VolumeSourceApplyConfiguration {
|
||||
b.Image = value
|
||||
return b
|
||||
}
|
||||
|
@ -5762,6 +5762,15 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: io.k8s.api.core.v1.ImageVolumeSource
|
||||
map:
|
||||
fields:
|
||||
- name: pullPolicy
|
||||
type:
|
||||
scalar: string
|
||||
- name: reference
|
||||
type:
|
||||
scalar: string
|
||||
- name: io.k8s.api.core.v1.KeyToPath
|
||||
map:
|
||||
fields:
|
||||
@ -8209,6 +8218,9 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
- name: hostPath
|
||||
type:
|
||||
namedType: io.k8s.api.core.v1.HostPathVolumeSource
|
||||
- name: image
|
||||
type:
|
||||
namedType: io.k8s.api.core.v1.ImageVolumeSource
|
||||
- name: iscsi
|
||||
type:
|
||||
namedType: io.k8s.api.core.v1.ISCSIVolumeSource
|
||||
|
@ -754,6 +754,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
return &applyconfigurationscorev1.HTTPGetActionApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("HTTPHeader"):
|
||||
return &applyconfigurationscorev1.HTTPHeaderApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("ImageVolumeSource"):
|
||||
return &applyconfigurationscorev1.ImageVolumeSourceApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("ISCSIPersistentVolumeSource"):
|
||||
return &applyconfigurationscorev1.ISCSIPersistentVolumeSourceApplyConfiguration{}
|
||||
case corev1.SchemeGroupVersion.WithKind("ISCSIVolumeSource"):
|
||||
|
@ -651,7 +651,7 @@ type Mount struct {
|
||||
// - (readonly == false && recursive_read_only == false) does not make the mount read-only.
|
||||
RecursiveReadOnly bool `protobuf:"varint,8,opt,name=recursive_read_only,json=recursiveReadOnly,proto3" json:"recursive_read_only,omitempty"`
|
||||
// Mount an image reference (image ID, with or without digest), which is a
|
||||
// special use case for OCI volume mounts. If this field is set, then
|
||||
// special use case for image volume mounts. If this field is set, then
|
||||
// host_path should be unset. All OCI mounts are per feature definition
|
||||
// readonly. The kubelet does an PullImage RPC and evaluates the returned
|
||||
// PullImageResponse.image_ref value, which is then set to the
|
||||
|
@ -245,7 +245,7 @@ message Mount {
|
||||
// - (readonly == false && recursive_read_only == false) does not make the mount read-only.
|
||||
bool recursive_read_only = 8;
|
||||
// Mount an image reference (image ID, with or without digest), which is a
|
||||
// special use case for OCI volume mounts. If this field is set, then
|
||||
// special use case for image volume mounts. If this field is set, then
|
||||
// host_path should be unset. All OCI mounts are per feature definition
|
||||
// readonly. The kubelet does an PullImage RPC and evaluates the returned
|
||||
// PullImageResponse.image_ref value, which is then set to the
|
||||
|
Loading…
Reference in New Issue
Block a user