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