1/* 2Copyright 2015 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package configmap 18 19import ( 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. 35func ProbeVolumePlugins() []volume.VolumePlugin { 36 return []volume.VolumePlugin{&configMapPlugin{}} 37} 38 39const ( 40 configMapPluginName = "kubernetes.io/configmap" 41) 42 43// configMapPlugin implements the VolumePlugin interface. 44type configMapPlugin struct { 45 host volume.VolumeHost 46 getConfigMap func(namespace, name string) (*v1.ConfigMap, error) 47} 48 49var _ volume.VolumePlugin = &configMapPlugin{} 50 51func getPath(uid types.UID, volName string, host volume.VolumeHost) string { 52 return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(configMapPluginName), volName) 53} 54 55func (plugin *configMapPlugin) Init(host volume.VolumeHost) error { 56 plugin.host = host 57 plugin.getConfigMap = host.GetConfigMapFunc() 58 return nil 59} 60 61func (plugin *configMapPlugin) GetPluginName() string { 62 return configMapPluginName 63} 64 65func (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 77func (plugin *configMapPlugin) CanSupport(spec *volume.Spec) bool { 78 return spec.Volume != nil && spec.Volume.ConfigMap != nil 79} 80 81func (plugin *configMapPlugin) RequiresRemount(spec *volume.Spec) bool { 82 return true 83} 84 85func (plugin *configMapPlugin) SupportsMountOption() bool { 86 return false 87} 88 89func (plugin *configMapPlugin) SupportsBulkVolumeVerification() bool { 90 return false 91} 92 93func (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 109func (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 121func (plugin *configMapPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { 122 configMapVolume := &v1.Volume{ 123 Name: volumeName, 124 VolumeSource: v1.VolumeSource{ 125 ConfigMap: &v1.ConfigMapVolumeSource{}, 126 }, 127 } 128 return volume.NewSpecFromVolume(configMapVolume), nil 129} 130 131type configMapVolume struct { 132 volName string 133 podUID types.UID 134 plugin *configMapPlugin 135 mounter mount.Interface 136 volume.MetricsProvider 137} 138 139var _ volume.Volume = &configMapVolume{} 140 141func (sv *configMapVolume) GetPath() string { 142 return sv.plugin.host.GetPodVolumeDir(sv.podUID, utilstrings.EscapeQualifiedName(configMapPluginName), sv.volName) 143} 144 145// configMapVolumeMounter handles retrieving secrets from the API server 146// and placing them into the volume on the host. 147type configMapVolumeMounter struct { 148 *configMapVolume 149 150 source v1.ConfigMapVolumeSource 151 pod v1.Pod 152 opts *volume.VolumeOptions 153 getConfigMap func(namespace, name string) (*v1.ConfigMap, error) 154} 155 156var _ volume.Mounter = &configMapVolumeMounter{} 157 158func (sv *configMapVolume) GetAttributes() volume.Attributes { 159 return volume.Attributes{ 160 ReadOnly: true, 161 Managed: true, 162 SupportsSELinux: true, 163 } 164} 165 166func wrappedVolumeSpec() volume.Spec { 167 // This is the spec for the volume that this plugin wraps. 168 return volume.Spec{ 169 // This should be on a tmpfs instead of the local disk; the problem is 170 // charging the memory for the tmpfs to the right cgroup. We should make 171 // this a tmpfs when we can do the accounting correctly. 172 Volume: &v1.Volume{VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}}, 173 } 174} 175 176// Checks prior to mount operations to verify that the required components (binaries, etc.) 177// to mount the volume are available on the underlying node. 178// If not, it returns an error 179func (b *configMapVolumeMounter) CanMount() error { 180 return nil 181} 182 183func (b *configMapVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error { 184 return b.SetUpAt(b.GetPath(), mounterArgs) 185} 186 187func (b *configMapVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { 188 klog.V(3).Infof("Setting up volume %v for pod %v at %v", b.volName, b.pod.UID, dir) 189 190 // Wrap EmptyDir, let it do the setup. 191 wrapped, err := b.plugin.host.NewWrapperMounter(b.volName, wrappedVolumeSpec(), &b.pod, *b.opts) 192 if err != nil { 193 return err 194 } 195 196 optional := b.source.Optional != nil && *b.source.Optional 197 configMap, err := b.getConfigMap(b.pod.Namespace, b.source.Name) 198 if err != nil { 199 if !(errors.IsNotFound(err) && optional) { 200 klog.Errorf("Couldn't get configMap %v/%v: %v", b.pod.Namespace, b.source.Name, err) 201 return err 202 } 203 configMap = &v1.ConfigMap{ 204 ObjectMeta: metav1.ObjectMeta{ 205 Namespace: b.pod.Namespace, 206 Name: b.source.Name, 207 }, 208 } 209 } 210 211 totalBytes := totalBytes(configMap) 212 klog.V(3).Infof("Received configMap %v/%v containing (%v) pieces of data, %v total bytes", 213 b.pod.Namespace, 214 b.source.Name, 215 len(configMap.Data)+len(configMap.BinaryData), 216 totalBytes) 217 218 payload, err := MakePayload(b.source.Items, configMap, b.source.DefaultMode, optional) 219 if err != nil { 220 return err 221 } 222 223 setupSuccess := false 224 if err := wrapped.SetUpAt(dir, mounterArgs); err != nil { 225 return err 226 } 227 if err := volumeutil.MakeNestedMountpoints(b.volName, dir, b.pod); err != nil { 228 return err 229 } 230 231 defer func() { 232 // Clean up directories if setup fails 233 if !setupSuccess { 234 unmounter, unmountCreateErr := b.plugin.NewUnmounter(b.volName, b.podUID) 235 if unmountCreateErr != nil { 236 klog.Errorf("error cleaning up mount %s after failure. Create unmounter failed with %v", b.volName, unmountCreateErr) 237 return 238 } 239 tearDownErr := unmounter.TearDown() 240 if tearDownErr != nil { 241 klog.Errorf("Error tearing down volume %s with : %v", b.volName, tearDownErr) 242 } 243 } 244 }() 245 246 writerContext := fmt.Sprintf("pod %v/%v volume %v", b.pod.Namespace, b.pod.Name, b.volName) 247 writer, err := volumeutil.NewAtomicWriter(dir, writerContext) 248 if err != nil { 249 klog.Errorf("Error creating atomic writer: %v", err) 250 return err 251 } 252 253 err = writer.Write(payload) 254 if err != nil { 255 klog.Errorf("Error writing payload to dir: %v", err) 256 return err 257 } 258 259 err = volume.SetVolumeOwnership(b, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil)) 260 if err != nil { 261 klog.Errorf("Error applying volume ownership settings for group: %v", mounterArgs.FsGroup) 262 return err 263 } 264 setupSuccess = true 265 return nil 266} 267 268// MakePayload function is exported so that it can be called from the projection volume driver 269func MakePayload(mappings []v1.KeyToPath, configMap *v1.ConfigMap, defaultMode *int32, optional bool) (map[string]volumeutil.FileProjection, error) { 270 if defaultMode == nil { 271 return nil, fmt.Errorf("no defaultMode used, not even the default value for it") 272 } 273 274 payload := make(map[string]volumeutil.FileProjection, (len(configMap.Data) + len(configMap.BinaryData))) 275 var fileProjection volumeutil.FileProjection 276 277 if len(mappings) == 0 { 278 for name, data := range configMap.Data { 279 fileProjection.Data = []byte(data) 280 fileProjection.Mode = *defaultMode 281 payload[name] = fileProjection 282 } 283 for name, data := range configMap.BinaryData { 284 fileProjection.Data = data 285 fileProjection.Mode = *defaultMode 286 payload[name] = fileProjection 287 } 288 } else { 289 for _, ktp := range mappings { 290 if stringData, ok := configMap.Data[ktp.Key]; ok { 291 fileProjection.Data = []byte(stringData) 292 } else if binaryData, ok := configMap.BinaryData[ktp.Key]; ok { 293 fileProjection.Data = binaryData 294 } else { 295 if optional { 296 continue 297 } 298 return nil, fmt.Errorf("configmap references non-existent config key: %s", ktp.Key) 299 } 300 301 if ktp.Mode != nil { 302 fileProjection.Mode = *ktp.Mode 303 } else { 304 fileProjection.Mode = *defaultMode 305 } 306 payload[ktp.Path] = fileProjection 307 } 308 } 309 310 return payload, nil 311} 312 313func totalBytes(configMap *v1.ConfigMap) int { 314 totalSize := 0 315 for _, value := range configMap.Data { 316 totalSize += len(value) 317 } 318 for _, value := range configMap.BinaryData { 319 totalSize += len(value) 320 } 321 322 return totalSize 323} 324 325// configMapVolumeUnmounter handles cleaning up configMap volumes. 326type configMapVolumeUnmounter struct { 327 *configMapVolume 328} 329 330var _ volume.Unmounter = &configMapVolumeUnmounter{} 331 332func (c *configMapVolumeUnmounter) TearDown() error { 333 return c.TearDownAt(c.GetPath()) 334} 335 336func (c *configMapVolumeUnmounter) TearDownAt(dir string) error { 337 return volumeutil.UnmountViaEmptyDir(dir, c.plugin.host, c.volName, wrappedVolumeSpec(), c.podUID) 338} 339 340func getVolumeSource(spec *volume.Spec) (*v1.ConfigMapVolumeSource, bool) { 341 var readOnly bool 342 var volumeSource *v1.ConfigMapVolumeSource 343 344 if spec.Volume != nil && spec.Volume.ConfigMap != nil { 345 volumeSource = spec.Volume.ConfigMap 346 readOnly = spec.ReadOnly 347 } 348 349 return volumeSource, readOnly 350} 351