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 }