Skip to content

Commit

Permalink
Merge pull request #23819 from l0rd/kube-play-image-type-volumes
Browse files Browse the repository at this point in the history
Add `kube play` support for volumes of type image
  • Loading branch information
openshift-merge-bot[bot] authored Sep 11, 2024
2 parents c66d46c + db12343 commit 7764bea
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 52 deletions.
7 changes: 5 additions & 2 deletions docs/source/markdown/podman-kube-play.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ Currently, the supported Kubernetes kinds are:

`Kubernetes Pods or Deployments`

Only three volume types are supported by kube play, the *hostPath*, *emptyDir*, and *persistentVolumeClaim* volume types.
Only four volume types are supported by kube play, the *hostPath*, *emptyDir*, *persistentVolumeClaim*, and *image* volume types.

- When using the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, *Socket*, *CharDevice* and *BlockDevice* subtypes are supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume.
- When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume.
- When using an *emptyDir* volume, Podman creates an anonymous volume that is attached the containers running inside the pod and is deleted once the pod is removed.
- When using an *image* volume, Podman creates a read-only image volume with an empty subpath (the whole image is mounted). The image must already exist locally. It is supported in rootful mode only.

Note: The default restart policy for containers is `always`. You can change the default by setting the `restartPolicy` field in the spec.

Expand Down Expand Up @@ -159,7 +160,9 @@ spec:

and as a result environment variable `FOO` is set to `bar` for container `container-1`.

`Automounting Volumes`
`Automounting Volumes (deprecated)`

Note: The automounting annotation is deprecated. Kubernetes has [native support for image volumes](https://kubernetes.io/docs/tasks/configure-pod-container/image-volumes/) and that should be used rather than this podman-specific annotation.

An image can be automatically mounted into a container if the annotation `io.podman.annotations.kube.image.automount/$ctrname` is given. The following rules apply:

Expand Down
143 changes: 96 additions & 47 deletions pkg/domain/infra/abi/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,21 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
return nil, nil, err
}
}
} else if v.Type == kube.KubeVolumeTypeImage {
var cwd string
if options.ContextDir != "" {
cwd = options.ContextDir
} else {
cwd, err = os.Getwd()
if err != nil {
return nil, nil, err
}
}

_, err := ic.buildOrPullImage(ctx, cwd, writer, v.Source, v.ImagePullPolicy, options)
if err != nil {
return nil, nil, err
}
}
}

Expand Down Expand Up @@ -1168,76 +1183,110 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
return &report, sdNotifyProxies, nil
}

// getImageAndLabelInfo returns the image information and how the image should be pulled plus as well as labels to be used for the container in the pod.
// Moved this to a separate function so that it can be used for both init and regular containers when playing a kube yaml.
func (ic *ContainerEngine) getImageAndLabelInfo(ctx context.Context, cwd string, annotations map[string]string, writer io.Writer, container v1.Container, options entities.PlayKubeOptions) (*libimage.Image, map[string]string, error) {
// Contains all labels obtained from kube
labels := make(map[string]string)
var pulledImage *libimage.Image
buildFile, err := getBuildFile(container.Image, cwd)
// buildImageFromContainerfile builds the container image and returns its details if these conditions are met:
// - A folder with the name of the image exists in current directory
// - A Dockerfile or Containerfile exists in that folder
// - The image doesn't exist locally OR the user explicitly provided the option `--build`
func (ic *ContainerEngine) buildImageFromContainerfile(ctx context.Context, cwd string, writer io.Writer, image string, options entities.PlayKubeOptions) (*libimage.Image, error) {
buildFile, err := getBuildFile(image, cwd)
if err != nil {
return nil, nil, err
return nil, err
}
existsLocally, err := ic.Libpod.LibimageRuntime().Exists(container.Image)
existsLocally, err := ic.Libpod.LibimageRuntime().Exists(image)
if err != nil {
return nil, nil, err
return nil, err
}
if (len(buildFile) > 0) && ((!existsLocally && options.Build != types.OptionalBoolFalse) || (options.Build == types.OptionalBoolTrue)) {
buildOpts := new(buildahDefine.BuildOptions)
commonOpts := new(buildahDefine.CommonBuildOptions)
buildOpts.ConfigureNetwork = buildahDefine.NetworkDefault
isolation, err := bparse.IsolationOption("")
if err != nil {
return nil, nil, err
return nil, err
}
buildOpts.Isolation = isolation
buildOpts.CommonBuildOpts = commonOpts
buildOpts.SystemContext = options.SystemContext
buildOpts.Output = container.Image
buildOpts.Output = image
buildOpts.ContextDirectory = filepath.Dir(buildFile)
buildOpts.ReportWriter = writer
if _, _, err := ic.Libpod.Build(ctx, *buildOpts, []string{buildFile}...); err != nil {
return nil, nil, err
return nil, err
}
i, _, err := ic.Libpod.LibimageRuntime().LookupImage(container.Image, new(libimage.LookupImageOptions))
builtImage, _, err := ic.Libpod.LibimageRuntime().LookupImage(image, new(libimage.LookupImageOptions))
if err != nil {
return nil, nil, err
return nil, err
}
return builtImage, nil
}
return nil, nil
}

// pullImageWithPolicy invokes libimage.Pull() to pull an image with the given PullPolicy.
// If the PullPolicy is not set:
// - use PullPolicyNewer if the image tag is set to "latest" or is not set
// - use PullPolicyMissing the policy is set to PullPolicyNewer.
func (ic *ContainerEngine) pullImageWithPolicy(ctx context.Context, writer io.Writer, image string, policy v1.PullPolicy, options entities.PlayKubeOptions) (*libimage.Image, error) {
pullPolicy := config.PullPolicyMissing
if len(policy) > 0 {
// Make sure to lower the strings since K8s pull policy
// may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905).
rawPolicy := string(policy)
parsedPolicy, err := config.ParsePullPolicy(strings.ToLower(rawPolicy))
if err != nil {
return nil, err
}
pulledImage = i
pullPolicy = parsedPolicy
} else {
pullPolicy := config.PullPolicyMissing
if len(container.ImagePullPolicy) > 0 {
// Make sure to lower the strings since K8s pull policy
// may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905).
rawPolicy := string(container.ImagePullPolicy)
pullPolicy, err = config.ParsePullPolicy(strings.ToLower(rawPolicy))
if err != nil {
return nil, nil, err
}
} else {
if named, err := reference.ParseNamed(container.Image); err == nil {
tagged, isTagged := named.(reference.NamedTagged)
if !isTagged || tagged.Tag() == "latest" {
// Make sure to always pull the latest image in case it got updated.
pullPolicy = config.PullPolicyNewer
}
if named, err := reference.ParseNamed(image); err == nil {
tagged, isTagged := named.(reference.NamedTagged)
if !isTagged || tagged.Tag() == "latest" {
// Make sure to always pull the latest image in case it got updated.
pullPolicy = config.PullPolicyNewer
}
}
// This ensures the image is the image store
pullOptions := &libimage.PullOptions{}
pullOptions.AuthFilePath = options.Authfile
pullOptions.CertDirPath = options.CertDir
pullOptions.SignaturePolicyPath = options.SignaturePolicy
pullOptions.Writer = writer
pullOptions.Username = options.Username
pullOptions.Password = options.Password
pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify

pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, container.Image, pullPolicy, pullOptions)
if err != nil {
return nil, nil, err
}
pulledImage = pulledImages[0]
}
// This ensures the image is the image store
pullOptions := &libimage.PullOptions{}
pullOptions.AuthFilePath = options.Authfile
pullOptions.CertDirPath = options.CertDir
pullOptions.SignaturePolicyPath = options.SignaturePolicy
pullOptions.Writer = writer
pullOptions.Username = options.Username
pullOptions.Password = options.Password
pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify

pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, image, pullPolicy, pullOptions)
if err != nil {
return nil, err
}
return pulledImages[0], err
}

// buildOrPullImage builds the image if a Containerfile is present in a directory
// with the name of the image. It pulls the image otherwise. It returns the image
// details.
func (ic *ContainerEngine) buildOrPullImage(ctx context.Context, cwd string, writer io.Writer, image string, policy v1.PullPolicy, options entities.PlayKubeOptions) (*libimage.Image, error) {
buildImage, err := ic.buildImageFromContainerfile(ctx, cwd, writer, image, options)
if err != nil {
return nil, err
}
if buildImage != nil {
return buildImage, nil
} else {
return ic.pullImageWithPolicy(ctx, writer, image, policy, options)
}
}

// getImageAndLabelInfo returns the image information and how the image should be pulled plus as well as labels to be used for the container in the pod.
// Moved this to a separate function so that it can be used for both init and regular containers when playing a kube yaml.
func (ic *ContainerEngine) getImageAndLabelInfo(ctx context.Context, cwd string, annotations map[string]string, writer io.Writer, container v1.Container, options entities.PlayKubeOptions) (*libimage.Image, map[string]string, error) {
// Contains all labels obtained from kube
labels := make(map[string]string)

pulledImage, err := ic.buildOrPullImage(ctx, cwd, writer, container.Image, container.ImagePullPolicy, options)
if err != nil {
return nil, labels, err
}

// Handle kube annotations
Expand Down
33 changes: 33 additions & 0 deletions pkg/k8s.io/api/core/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ type VolumeSource struct {
// More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir
// +optional
EmptyDir *EmptyDirVolumeSource `json:"emptyDir,omitempty"`
// image represents a container image pulled and mounted on the host machine.
// The volume is resolved at pod startup depending on which PullPolicy value is provided:
//
// - Always: podman always attempts to pull the reference. Container creation will fail If the pull fails.
// - Never: podman never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present.
// - IfNotPresent: podman 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 the pod won't be created.
// The container image 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).
// The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type.
// +optional
Image *ImageVolumeSource `json:"image,omitempty"`
}

// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
Expand Down Expand Up @@ -465,6 +480,24 @@ type EmptyDirVolumeSource struct {
SizeLimit *resource.Quantity `json:"sizeLimit,omitempty"`
}

// ImageVolumeSource represents a image volume resource.
type ImageVolumeSource struct {
// Required: Container image reference to be used.
// Behaves in the same way as pod.spec.containers[*].image.
// 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"`

// Policy for pulling OCI objects. Possible values are:
// Always: podman always attempts to pull the reference. Container creation will fail If the pull fails.
// Never: podman never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present.
// IfNotPresent: podman 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"`
}

// SecretReference represents a Secret Reference. It has enough information to retrieve secret
// in any namespace
// +structType=atomic
Expand Down
8 changes: 8 additions & 0 deletions pkg/specgen/generate/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,14 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
Source: define.TypeTmpfs,
}
s.Mounts = append(s.Mounts, memVolume)
case KubeVolumeTypeImage:
imageVolume := specgen.ImageVolume{
Destination: volume.MountPath,
ReadWrite: false,
Source: volumeSource.Source,
SubPath: "",
}
s.ImageVolumes = append(s.ImageVolumes, &imageVolume)
default:
return nil, errors.New("unsupported volume source type")
}
Expand Down
15 changes: 14 additions & 1 deletion pkg/specgen/generate/kube/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ const (
KubeVolumeTypeSecret
KubeVolumeTypeEmptyDir
KubeVolumeTypeEmptyDirTmpfs
KubeVolumeTypeImage
)

//nolint:revive
type KubeVolume struct {
// Type of volume to create
Type KubeVolumeType
// Path for bind mount or volume name for named volume
// Path for bind mount, volume name for named volume or image name for image volume
Source string
// Items to add to a named volume created where the key is the file name and the value is the data
// This is only used when there are volumes in the yaml that refer to a configmap
Expand All @@ -56,6 +57,8 @@ type KubeVolume struct {
// DefaultMode sets the permissions on files created for the volume
// This is optional and defaults to 0644
DefaultMode int32
// Used for volumes of type Image. Ignored for other volumes types.
ImagePullPolicy v1.PullPolicy
}

// Create a KubeVolume from an HostPathVolumeSource
Expand Down Expand Up @@ -279,6 +282,14 @@ func VolumeFromEmptyDir(emptyDirVolumeSource *v1.EmptyDirVolumeSource, name stri
}
}

func VolumeFromImage(imageVolumeSource *v1.ImageVolumeSource, name string) (*KubeVolume, error) {
return &KubeVolume{
Type: KubeVolumeTypeImage,
Source: imageVolumeSource.Reference,
ImagePullPolicy: imageVolumeSource.PullPolicy,
}, nil
}

// Create a KubeVolume from one of the supported VolumeSource
func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager, volName, mountLabel string) (*KubeVolume, error) {
switch {
Expand All @@ -292,6 +303,8 @@ func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, s
return VolumeFromSecret(volumeSource.Secret, secretsManager)
case volumeSource.EmptyDir != nil:
return VolumeFromEmptyDir(volumeSource.EmptyDir, volName)
case volumeSource.Image != nil:
return VolumeFromImage(volumeSource.Image, volName)
default:
return nil, errors.New("HostPath, ConfigMap, EmptyDir, Secret, and PersistentVolumeClaim are currently the only supported VolumeSource")
}
Expand Down
Loading

1 comment on commit 7764bea

@packit-as-a-service
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

podman-next COPR build failed. @containers/packit-build please check.

Please sign in to comment.