k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/downwardapi/downwardapi.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package downwardapi
    18  
    19  import (
    20  	"fmt"
    21  	"path/filepath"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/types"
    25  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    26  	"k8s.io/klog/v2"
    27  	"k8s.io/kubernetes/pkg/api/v1/resource"
    28  	"k8s.io/kubernetes/pkg/fieldpath"
    29  	"k8s.io/kubernetes/pkg/volume"
    30  	volumeutil "k8s.io/kubernetes/pkg/volume/util"
    31  	utilstrings "k8s.io/utils/strings"
    32  )
    33  
    34  // ProbeVolumePlugins is the entry point for plugin detection in a package.
    35  func ProbeVolumePlugins() []volume.VolumePlugin {
    36  	return []volume.VolumePlugin{&downwardAPIPlugin{}}
    37  }
    38  
    39  const (
    40  	downwardAPIPluginName = "kubernetes.io/downward-api"
    41  )
    42  
    43  // downwardAPIPlugin implements the VolumePlugin interface.
    44  type downwardAPIPlugin struct {
    45  	host volume.VolumeHost
    46  }
    47  
    48  var _ volume.VolumePlugin = &downwardAPIPlugin{}
    49  
    50  func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
    51  	return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(downwardAPIPluginName), volName)
    52  }
    53  
    54  func wrappedVolumeSpec() volume.Spec {
    55  	return volume.Spec{
    56  		Volume: &v1.Volume{VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{Medium: v1.StorageMediumMemory}}},
    57  	}
    58  }
    59  
    60  func (plugin *downwardAPIPlugin) Init(host volume.VolumeHost) error {
    61  	plugin.host = host
    62  	return nil
    63  }
    64  
    65  func (plugin *downwardAPIPlugin) GetPluginName() string {
    66  	return downwardAPIPluginName
    67  }
    68  
    69  func (plugin *downwardAPIPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
    70  	volumeSource, _ := getVolumeSource(spec)
    71  	if volumeSource == nil {
    72  		return "", fmt.Errorf("Spec does not reference a DownwardAPI volume type")
    73  	}
    74  
    75  	// Return user defined volume name, since this is an ephemeral volume type
    76  	return spec.Name(), nil
    77  }
    78  
    79  func (plugin *downwardAPIPlugin) CanSupport(spec *volume.Spec) bool {
    80  	return spec.Volume != nil && spec.Volume.DownwardAPI != nil
    81  }
    82  
    83  func (plugin *downwardAPIPlugin) RequiresRemount(spec *volume.Spec) bool {
    84  	return true
    85  }
    86  
    87  func (plugin *downwardAPIPlugin) SupportsMountOption() bool {
    88  	return false
    89  }
    90  
    91  func (plugin *downwardAPIPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) {
    92  	return false, nil
    93  }
    94  
    95  func (plugin *downwardAPIPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
    96  	v := &downwardAPIVolume{
    97  		volName:         spec.Name(),
    98  		items:           spec.Volume.DownwardAPI.Items,
    99  		pod:             pod,
   100  		podUID:          pod.UID,
   101  		plugin:          plugin,
   102  		MetricsProvider: volume.NewCachedMetrics(volume.NewMetricsDu(getPath(pod.UID, spec.Name(), plugin.host))),
   103  	}
   104  	return &downwardAPIVolumeMounter{
   105  		downwardAPIVolume: v,
   106  		source:            *spec.Volume.DownwardAPI,
   107  		opts:              &opts,
   108  	}, nil
   109  }
   110  
   111  func (plugin *downwardAPIPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
   112  	return &downwardAPIVolumeUnmounter{
   113  		&downwardAPIVolume{
   114  			volName:         volName,
   115  			podUID:          podUID,
   116  			plugin:          plugin,
   117  			MetricsProvider: volume.NewCachedMetrics(volume.NewMetricsDu(getPath(podUID, volName, plugin.host))),
   118  		},
   119  	}, nil
   120  }
   121  
   122  func (plugin *downwardAPIPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) {
   123  	downwardAPIVolume := &v1.Volume{
   124  		Name: volumeName,
   125  		VolumeSource: v1.VolumeSource{
   126  			DownwardAPI: &v1.DownwardAPIVolumeSource{},
   127  		},
   128  	}
   129  	return volume.ReconstructedVolume{
   130  		Spec: volume.NewSpecFromVolume(downwardAPIVolume),
   131  	}, nil
   132  }
   133  
   134  // downwardAPIVolume retrieves downward API data and placing them into the volume on the host.
   135  type downwardAPIVolume struct {
   136  	volName string
   137  	items   []v1.DownwardAPIVolumeFile
   138  	pod     *v1.Pod
   139  	podUID  types.UID // TODO: remove this redundancy as soon NewUnmounter func will have *v1.POD and not only types.UID
   140  	plugin  *downwardAPIPlugin
   141  	volume.MetricsProvider
   142  }
   143  
   144  // downwardAPIVolumeMounter fetches info from downward API from the pod
   145  // and dumps it in files
   146  type downwardAPIVolumeMounter struct {
   147  	*downwardAPIVolume
   148  	source v1.DownwardAPIVolumeSource
   149  	opts   *volume.VolumeOptions
   150  }
   151  
   152  // downwardAPIVolumeMounter implements volume.Mounter interface
   153  var _ volume.Mounter = &downwardAPIVolumeMounter{}
   154  
   155  // downward API volumes are always ReadOnlyManaged
   156  func (d *downwardAPIVolume) GetAttributes() volume.Attributes {
   157  	return volume.Attributes{
   158  		ReadOnly:       true,
   159  		Managed:        true,
   160  		SELinuxRelabel: true,
   161  	}
   162  }
   163  
   164  // SetUp puts in place the volume plugin.
   165  // This function is not idempotent by design. We want the data to be refreshed periodically.
   166  // The internal sync interval of kubelet will drive the refresh of data.
   167  // TODO: Add volume specific ticker and refresh loop
   168  func (b *downwardAPIVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error {
   169  	return b.SetUpAt(b.GetPath(), mounterArgs)
   170  }
   171  
   172  func (b *downwardAPIVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
   173  	klog.V(3).Infof("Setting up a downwardAPI volume %v for pod %v/%v at %v", b.volName, b.pod.Namespace, b.pod.Name, dir)
   174  	// Wrap EmptyDir. Here we rely on the idempotency of the wrapped plugin to avoid repeatedly mounting
   175  	wrapped, err := b.plugin.host.NewWrapperMounter(b.volName, wrappedVolumeSpec(), b.pod, *b.opts)
   176  	if err != nil {
   177  		klog.Errorf("Couldn't setup downwardAPI volume %v for pod %v/%v: %s", b.volName, b.pod.Namespace, b.pod.Name, err.Error())
   178  		return err
   179  	}
   180  
   181  	data, err := CollectData(b.source.Items, b.pod, b.plugin.host, b.source.DefaultMode)
   182  	if err != nil {
   183  		klog.Errorf("Error preparing data for downwardAPI volume %v for pod %v/%v: %s", b.volName, b.pod.Namespace, b.pod.Name, err.Error())
   184  		return err
   185  	}
   186  
   187  	setupSuccess := false
   188  	if err := wrapped.SetUpAt(dir, mounterArgs); err != nil {
   189  		klog.Errorf("Unable to setup downwardAPI volume %v for pod %v/%v: %s", b.volName, b.pod.Namespace, b.pod.Name, err.Error())
   190  		return err
   191  	}
   192  
   193  	if err := volumeutil.MakeNestedMountpoints(b.volName, dir, *b.pod); err != nil {
   194  		return err
   195  	}
   196  
   197  	defer func() {
   198  		// Clean up directories if setup fails
   199  		if !setupSuccess {
   200  			unmounter, unmountCreateErr := b.plugin.NewUnmounter(b.volName, b.podUID)
   201  			if unmountCreateErr != nil {
   202  				klog.Errorf("error cleaning up mount %s after failure. Create unmounter failed with %v", b.volName, unmountCreateErr)
   203  				return
   204  			}
   205  			tearDownErr := unmounter.TearDown()
   206  			if tearDownErr != nil {
   207  				klog.Errorf("error tearing down volume %s with : %v", b.volName, tearDownErr)
   208  			}
   209  		}
   210  	}()
   211  
   212  	writerContext := fmt.Sprintf("pod %v/%v volume %v", b.pod.Namespace, b.pod.Name, b.volName)
   213  	writer, err := volumeutil.NewAtomicWriter(dir, writerContext)
   214  	if err != nil {
   215  		klog.Errorf("Error creating atomic writer: %v", err)
   216  		return err
   217  	}
   218  
   219  	setPerms := func(_ string) error {
   220  		// This may be the first time writing and new files get created outside the timestamp subdirectory:
   221  		// change the permissions on the whole volume and not only in the timestamp directory.
   222  		return volume.SetVolumeOwnership(b, dir, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil))
   223  	}
   224  	err = writer.Write(data, setPerms)
   225  	if err != nil {
   226  		klog.Errorf("Error writing payload to dir: %v", err)
   227  		return err
   228  	}
   229  
   230  	setupSuccess = true
   231  	return nil
   232  }
   233  
   234  // CollectData collects requested downwardAPI in data map.
   235  // Map's key is the requested name of file to dump
   236  // Map's value is the (sorted) content of the field to be dumped in the file.
   237  //
   238  // Note: this function is exported so that it can be called from the projection volume driver
   239  func CollectData(items []v1.DownwardAPIVolumeFile, pod *v1.Pod, host volume.VolumeHost, defaultMode *int32) (map[string]volumeutil.FileProjection, error) {
   240  	if defaultMode == nil {
   241  		return nil, fmt.Errorf("no defaultMode used, not even the default value for it")
   242  	}
   243  
   244  	errlist := []error{}
   245  	data := make(map[string]volumeutil.FileProjection)
   246  	for _, fileInfo := range items {
   247  		var fileProjection volumeutil.FileProjection
   248  		fPath := filepath.Clean(fileInfo.Path)
   249  		if fileInfo.Mode != nil {
   250  			fileProjection.Mode = *fileInfo.Mode
   251  		} else {
   252  			fileProjection.Mode = *defaultMode
   253  		}
   254  		if fileInfo.FieldRef != nil {
   255  			// TODO: unify with Kubelet.podFieldSelectorRuntimeValue
   256  			if values, err := fieldpath.ExtractFieldPathAsString(pod, fileInfo.FieldRef.FieldPath); err != nil {
   257  				klog.Errorf("Unable to extract field %s: %s", fileInfo.FieldRef.FieldPath, err.Error())
   258  				errlist = append(errlist, err)
   259  			} else {
   260  				fileProjection.Data = []byte(values)
   261  			}
   262  		} else if fileInfo.ResourceFieldRef != nil {
   263  			containerName := fileInfo.ResourceFieldRef.ContainerName
   264  			nodeAllocatable, err := host.GetNodeAllocatable()
   265  			if err != nil {
   266  				errlist = append(errlist, err)
   267  			} else if values, err := resource.ExtractResourceValueByContainerNameAndNodeAllocatable(fileInfo.ResourceFieldRef, pod, containerName, nodeAllocatable); err != nil {
   268  				klog.Errorf("Unable to extract field %s: %s", fileInfo.ResourceFieldRef.Resource, err.Error())
   269  				errlist = append(errlist, err)
   270  			} else {
   271  				fileProjection.Data = []byte(values)
   272  			}
   273  		}
   274  
   275  		data[fPath] = fileProjection
   276  	}
   277  	return data, utilerrors.NewAggregate(errlist)
   278  }
   279  
   280  func (d *downwardAPIVolume) GetPath() string {
   281  	return d.plugin.host.GetPodVolumeDir(d.podUID, utilstrings.EscapeQualifiedName(downwardAPIPluginName), d.volName)
   282  }
   283  
   284  // downwardAPIVolumeCleaner handles cleaning up downwardAPI volumes
   285  type downwardAPIVolumeUnmounter struct {
   286  	*downwardAPIVolume
   287  }
   288  
   289  // downwardAPIVolumeUnmounter implements volume.Unmounter interface
   290  var _ volume.Unmounter = &downwardAPIVolumeUnmounter{}
   291  
   292  func (c *downwardAPIVolumeUnmounter) TearDown() error {
   293  	return c.TearDownAt(c.GetPath())
   294  }
   295  
   296  func (c *downwardAPIVolumeUnmounter) TearDownAt(dir string) error {
   297  	return volumeutil.UnmountViaEmptyDir(dir, c.plugin.host, c.volName, wrappedVolumeSpec(), c.podUID)
   298  }
   299  
   300  func getVolumeSource(spec *volume.Spec) (*v1.DownwardAPIVolumeSource, bool) {
   301  	var readOnly bool
   302  	var volumeSource *v1.DownwardAPIVolumeSource
   303  
   304  	if spec.Volume != nil && spec.Volume.DownwardAPI != nil {
   305  		volumeSource = spec.Volume.DownwardAPI
   306  		readOnly = spec.ReadOnly
   307  	}
   308  
   309  	return volumeSource, readOnly
   310  }