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