k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/configmap/configmap.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 configmap
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"k8s.io/klog/v2"
    23  	"k8s.io/mount-utils"
    24  	utilstrings "k8s.io/utils/strings"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	"k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"k8s.io/kubernetes/pkg/volume"
    31  	volumeutil "k8s.io/kubernetes/pkg/volume/util"
    32  )
    33  
    34  // ProbeVolumePlugins is the entry point for plugin detection in a package.
    35  func ProbeVolumePlugins() []volume.VolumePlugin {
    36  	return []volume.VolumePlugin{&configMapPlugin{}}
    37  }
    38  
    39  const (
    40  	configMapPluginName = "kubernetes.io/configmap"
    41  )
    42  
    43  // configMapPlugin implements the VolumePlugin interface.
    44  type configMapPlugin struct {
    45  	host         volume.VolumeHost
    46  	getConfigMap func(namespace, name string) (*v1.ConfigMap, error)
    47  }
    48  
    49  var _ volume.VolumePlugin = &configMapPlugin{}
    50  
    51  func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
    52  	return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(configMapPluginName), volName)
    53  }
    54  
    55  func (plugin *configMapPlugin) Init(host volume.VolumeHost) error {
    56  	plugin.host = host
    57  	plugin.getConfigMap = host.GetConfigMapFunc()
    58  	return nil
    59  }
    60  
    61  func (plugin *configMapPlugin) GetPluginName() string {
    62  	return configMapPluginName
    63  }
    64  
    65  func (plugin *configMapPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
    66  	volumeSource, _ := getVolumeSource(spec)
    67  	if volumeSource == nil {
    68  		return "", fmt.Errorf("Spec does not reference a ConfigMap volume type")
    69  	}
    70  
    71  	return fmt.Sprintf(
    72  		"%v/%v",
    73  		spec.Name(),
    74  		volumeSource.Name), nil
    75  }
    76  
    77  func (plugin *configMapPlugin) CanSupport(spec *volume.Spec) bool {
    78  	return spec.Volume != nil && spec.Volume.ConfigMap != nil
    79  }
    80  
    81  func (plugin *configMapPlugin) RequiresRemount(spec *volume.Spec) bool {
    82  	return true
    83  }
    84  
    85  func (plugin *configMapPlugin) SupportsMountOption() bool {
    86  	return false
    87  }
    88  
    89  func (plugin *configMapPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) {
    90  	return false, nil
    91  }
    92  
    93  func (plugin *configMapPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
    94  	return &configMapVolumeMounter{
    95  		configMapVolume: &configMapVolume{
    96  			spec.Name(),
    97  			pod.UID,
    98  			plugin,
    99  			plugin.host.GetMounter(plugin.GetPluginName()),
   100  			volume.NewCachedMetrics(volume.NewMetricsDu(getPath(pod.UID, spec.Name(), plugin.host))),
   101  		},
   102  		source:       *spec.Volume.ConfigMap,
   103  		pod:          *pod,
   104  		opts:         &opts,
   105  		getConfigMap: plugin.getConfigMap,
   106  	}, nil
   107  }
   108  
   109  func (plugin *configMapPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
   110  	return &configMapVolumeUnmounter{
   111  		&configMapVolume{
   112  			volName,
   113  			podUID,
   114  			plugin,
   115  			plugin.host.GetMounter(plugin.GetPluginName()),
   116  			volume.NewCachedMetrics(volume.NewMetricsDu(getPath(podUID, volName, plugin.host))),
   117  		},
   118  	}, nil
   119  }
   120  
   121  func (plugin *configMapPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) {
   122  	configMapVolume := &v1.Volume{
   123  		Name: volumeName,
   124  		VolumeSource: v1.VolumeSource{
   125  			ConfigMap: &v1.ConfigMapVolumeSource{},
   126  		},
   127  	}
   128  	return volume.ReconstructedVolume{
   129  		Spec: volume.NewSpecFromVolume(configMapVolume),
   130  	}, nil
   131  }
   132  
   133  type configMapVolume struct {
   134  	volName string
   135  	podUID  types.UID
   136  	plugin  *configMapPlugin
   137  	mounter mount.Interface
   138  	volume.MetricsProvider
   139  }
   140  
   141  var _ volume.Volume = &configMapVolume{}
   142  
   143  func (sv *configMapVolume) GetPath() string {
   144  	return sv.plugin.host.GetPodVolumeDir(sv.podUID, utilstrings.EscapeQualifiedName(configMapPluginName), sv.volName)
   145  }
   146  
   147  // configMapVolumeMounter handles retrieving secrets from the API server
   148  // and placing them into the volume on the host.
   149  type configMapVolumeMounter struct {
   150  	*configMapVolume
   151  
   152  	source       v1.ConfigMapVolumeSource
   153  	pod          v1.Pod
   154  	opts         *volume.VolumeOptions
   155  	getConfigMap func(namespace, name string) (*v1.ConfigMap, error)
   156  }
   157  
   158  var _ volume.Mounter = &configMapVolumeMounter{}
   159  
   160  func (sv *configMapVolume) GetAttributes() volume.Attributes {
   161  	return volume.Attributes{
   162  		ReadOnly:       true,
   163  		Managed:        true,
   164  		SELinuxRelabel: true,
   165  	}
   166  }
   167  
   168  func wrappedVolumeSpec() volume.Spec {
   169  	// This is the spec for the volume that this plugin wraps.
   170  	return volume.Spec{
   171  		// This should be on a tmpfs instead of the local disk; the problem is
   172  		// charging the memory for the tmpfs to the right cgroup.  We should make
   173  		// this a tmpfs when we can do the accounting correctly.
   174  		Volume: &v1.Volume{VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}},
   175  	}
   176  }
   177  
   178  func (b *configMapVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error {
   179  	return b.SetUpAt(b.GetPath(), mounterArgs)
   180  }
   181  
   182  func (b *configMapVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
   183  	klog.V(3).Infof("Setting up volume %v for pod %v at %v", b.volName, b.pod.UID, dir)
   184  
   185  	// Wrap EmptyDir, let it do the setup.
   186  	wrapped, err := b.plugin.host.NewWrapperMounter(b.volName, wrappedVolumeSpec(), &b.pod, *b.opts)
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	optional := b.source.Optional != nil && *b.source.Optional
   192  	configMap, err := b.getConfigMap(b.pod.Namespace, b.source.Name)
   193  	if err != nil {
   194  		if !(errors.IsNotFound(err) && optional) {
   195  			klog.Errorf("Couldn't get configMap %v/%v: %v", b.pod.Namespace, b.source.Name, err)
   196  			return err
   197  		}
   198  		configMap = &v1.ConfigMap{
   199  			ObjectMeta: metav1.ObjectMeta{
   200  				Namespace: b.pod.Namespace,
   201  				Name:      b.source.Name,
   202  			},
   203  		}
   204  	}
   205  
   206  	totalBytes := totalBytes(configMap)
   207  	klog.V(3).Infof("Received configMap %v/%v containing (%v) pieces of data, %v total bytes",
   208  		b.pod.Namespace,
   209  		b.source.Name,
   210  		len(configMap.Data)+len(configMap.BinaryData),
   211  		totalBytes)
   212  
   213  	payload, err := MakePayload(b.source.Items, configMap, b.source.DefaultMode, optional)
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	setupSuccess := false
   219  	if err := wrapped.SetUpAt(dir, mounterArgs); err != nil {
   220  		return err
   221  	}
   222  	if err := volumeutil.MakeNestedMountpoints(b.volName, dir, b.pod); err != nil {
   223  		return err
   224  	}
   225  
   226  	defer func() {
   227  		// Clean up directories if setup fails
   228  		if !setupSuccess {
   229  			unmounter, unmountCreateErr := b.plugin.NewUnmounter(b.volName, b.podUID)
   230  			if unmountCreateErr != nil {
   231  				klog.Errorf("error cleaning up mount %s after failure. Create unmounter failed with %v", b.volName, unmountCreateErr)
   232  				return
   233  			}
   234  			tearDownErr := unmounter.TearDown()
   235  			if tearDownErr != nil {
   236  				klog.Errorf("Error tearing down volume %s with : %v", b.volName, tearDownErr)
   237  			}
   238  		}
   239  	}()
   240  
   241  	writerContext := fmt.Sprintf("pod %v/%v volume %v", b.pod.Namespace, b.pod.Name, b.volName)
   242  	writer, err := volumeutil.NewAtomicWriter(dir, writerContext)
   243  	if err != nil {
   244  		klog.Errorf("Error creating atomic writer: %v", err)
   245  		return err
   246  	}
   247  
   248  	setPerms := func(_ string) error {
   249  		// This may be the first time writing and new files get created outside the timestamp subdirectory:
   250  		// change the permissions on the whole volume and not only in the timestamp directory.
   251  		return volume.SetVolumeOwnership(b, dir, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil))
   252  	}
   253  	err = writer.Write(payload, setPerms)
   254  	if err != nil {
   255  		klog.Errorf("Error writing payload to dir: %v", err)
   256  		return err
   257  	}
   258  
   259  	setupSuccess = true
   260  	return nil
   261  }
   262  
   263  // MakePayload function is exported so that it can be called from the projection volume driver
   264  func MakePayload(mappings []v1.KeyToPath, configMap *v1.ConfigMap, defaultMode *int32, optional bool) (map[string]volumeutil.FileProjection, error) {
   265  	if defaultMode == nil {
   266  		return nil, fmt.Errorf("no defaultMode used, not even the default value for it")
   267  	}
   268  
   269  	payload := make(map[string]volumeutil.FileProjection, (len(configMap.Data) + len(configMap.BinaryData)))
   270  	var fileProjection volumeutil.FileProjection
   271  
   272  	if len(mappings) == 0 {
   273  		for name, data := range configMap.Data {
   274  			fileProjection.Data = []byte(data)
   275  			fileProjection.Mode = *defaultMode
   276  			payload[name] = fileProjection
   277  		}
   278  		for name, data := range configMap.BinaryData {
   279  			fileProjection.Data = data
   280  			fileProjection.Mode = *defaultMode
   281  			payload[name] = fileProjection
   282  		}
   283  	} else {
   284  		for _, ktp := range mappings {
   285  			if stringData, ok := configMap.Data[ktp.Key]; ok {
   286  				fileProjection.Data = []byte(stringData)
   287  			} else if binaryData, ok := configMap.BinaryData[ktp.Key]; ok {
   288  				fileProjection.Data = binaryData
   289  			} else {
   290  				if optional {
   291  					continue
   292  				}
   293  				return nil, fmt.Errorf("configmap references non-existent config key: %s", ktp.Key)
   294  			}
   295  
   296  			if ktp.Mode != nil {
   297  				fileProjection.Mode = *ktp.Mode
   298  			} else {
   299  				fileProjection.Mode = *defaultMode
   300  			}
   301  			payload[ktp.Path] = fileProjection
   302  		}
   303  	}
   304  
   305  	return payload, nil
   306  }
   307  
   308  func totalBytes(configMap *v1.ConfigMap) int {
   309  	totalSize := 0
   310  	for _, value := range configMap.Data {
   311  		totalSize += len(value)
   312  	}
   313  	for _, value := range configMap.BinaryData {
   314  		totalSize += len(value)
   315  	}
   316  
   317  	return totalSize
   318  }
   319  
   320  // configMapVolumeUnmounter handles cleaning up configMap volumes.
   321  type configMapVolumeUnmounter struct {
   322  	*configMapVolume
   323  }
   324  
   325  var _ volume.Unmounter = &configMapVolumeUnmounter{}
   326  
   327  func (c *configMapVolumeUnmounter) TearDown() error {
   328  	return c.TearDownAt(c.GetPath())
   329  }
   330  
   331  func (c *configMapVolumeUnmounter) TearDownAt(dir string) error {
   332  	return volumeutil.UnmountViaEmptyDir(dir, c.plugin.host, c.volName, wrappedVolumeSpec(), c.podUID)
   333  }
   334  
   335  func getVolumeSource(spec *volume.Spec) (*v1.ConfigMapVolumeSource, bool) {
   336  	var readOnly bool
   337  	var volumeSource *v1.ConfigMapVolumeSource
   338  
   339  	if spec.Volume != nil && spec.Volume.ConfigMap != nil {
   340  		volumeSource = spec.Volume.ConfigMap
   341  		readOnly = spec.ReadOnly
   342  	}
   343  
   344  	return volumeSource, readOnly
   345  }