k8s.io/kubernetes@v1.29.3/pkg/volume/downwardapi/downwardapi.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 downwardapi 18 19 import ( 20 "fmt" 21 "path/filepath" 22 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/types" 25 utilerrors "k8s.io/apimachinery/pkg/util/errors" 26 "k8s.io/klog/v2" 27 "k8s.io/kubernetes/pkg/api/v1/resource" 28 "k8s.io/kubernetes/pkg/fieldpath" 29 "k8s.io/kubernetes/pkg/volume" 30 volumeutil "k8s.io/kubernetes/pkg/volume/util" 31 utilstrings "k8s.io/utils/strings" 32 ) 33 34 // ProbeVolumePlugins is the entry point for plugin detection in a package. 35 func ProbeVolumePlugins() []volume.VolumePlugin { 36 return []volume.VolumePlugin{&downwardAPIPlugin{}} 37 } 38 39 const ( 40 downwardAPIPluginName = "kubernetes.io/downward-api" 41 ) 42 43 // downwardAPIPlugin implements the VolumePlugin interface. 44 type downwardAPIPlugin struct { 45 host volume.VolumeHost 46 } 47 48 var _ volume.VolumePlugin = &downwardAPIPlugin{} 49 50 func getPath(uid types.UID, volName string, host volume.VolumeHost) string { 51 return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(downwardAPIPluginName), volName) 52 } 53 54 func wrappedVolumeSpec() volume.Spec { 55 return volume.Spec{ 56 Volume: &v1.Volume{VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{Medium: v1.StorageMediumMemory}}}, 57 } 58 } 59 60 func (plugin *downwardAPIPlugin) Init(host volume.VolumeHost) error { 61 plugin.host = host 62 return nil 63 } 64 65 func (plugin *downwardAPIPlugin) GetPluginName() string { 66 return downwardAPIPluginName 67 } 68 69 func (plugin *downwardAPIPlugin) GetVolumeName(spec *volume.Spec) (string, error) { 70 volumeSource, _ := getVolumeSource(spec) 71 if volumeSource == nil { 72 return "", fmt.Errorf("Spec does not reference a DownwardAPI volume type") 73 } 74 75 // Return user defined volume name, since this is an ephemeral volume type 76 return spec.Name(), nil 77 } 78 79 func (plugin *downwardAPIPlugin) CanSupport(spec *volume.Spec) bool { 80 return spec.Volume != nil && spec.Volume.DownwardAPI != nil 81 } 82 83 func (plugin *downwardAPIPlugin) RequiresRemount(spec *volume.Spec) bool { 84 return true 85 } 86 87 func (plugin *downwardAPIPlugin) SupportsMountOption() bool { 88 return false 89 } 90 91 func (plugin *downwardAPIPlugin) SupportsBulkVolumeVerification() bool { 92 return false 93 } 94 95 func (plugin *downwardAPIPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { 96 return false, nil 97 } 98 99 func (plugin *downwardAPIPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { 100 v := &downwardAPIVolume{ 101 volName: spec.Name(), 102 items: spec.Volume.DownwardAPI.Items, 103 pod: pod, 104 podUID: pod.UID, 105 plugin: plugin, 106 MetricsProvider: volume.NewCachedMetrics(volume.NewMetricsDu(getPath(pod.UID, spec.Name(), plugin.host))), 107 } 108 return &downwardAPIVolumeMounter{ 109 downwardAPIVolume: v, 110 source: *spec.Volume.DownwardAPI, 111 opts: &opts, 112 }, nil 113 } 114 115 func (plugin *downwardAPIPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { 116 return &downwardAPIVolumeUnmounter{ 117 &downwardAPIVolume{ 118 volName: volName, 119 podUID: podUID, 120 plugin: plugin, 121 MetricsProvider: volume.NewCachedMetrics(volume.NewMetricsDu(getPath(podUID, volName, plugin.host))), 122 }, 123 }, nil 124 } 125 126 func (plugin *downwardAPIPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { 127 downwardAPIVolume := &v1.Volume{ 128 Name: volumeName, 129 VolumeSource: v1.VolumeSource{ 130 DownwardAPI: &v1.DownwardAPIVolumeSource{}, 131 }, 132 } 133 return volume.ReconstructedVolume{ 134 Spec: volume.NewSpecFromVolume(downwardAPIVolume), 135 }, nil 136 } 137 138 // downwardAPIVolume retrieves downward API data and placing them into the volume on the host. 139 type downwardAPIVolume struct { 140 volName string 141 items []v1.DownwardAPIVolumeFile 142 pod *v1.Pod 143 podUID types.UID // TODO: remove this redundancy as soon NewUnmounter func will have *v1.POD and not only types.UID 144 plugin *downwardAPIPlugin 145 volume.MetricsProvider 146 } 147 148 // downwardAPIVolumeMounter fetches info from downward API from the pod 149 // and dumps it in files 150 type downwardAPIVolumeMounter struct { 151 *downwardAPIVolume 152 source v1.DownwardAPIVolumeSource 153 opts *volume.VolumeOptions 154 } 155 156 // downwardAPIVolumeMounter implements volume.Mounter interface 157 var _ volume.Mounter = &downwardAPIVolumeMounter{} 158 159 // downward API volumes are always ReadOnlyManaged 160 func (d *downwardAPIVolume) GetAttributes() volume.Attributes { 161 return volume.Attributes{ 162 ReadOnly: true, 163 Managed: true, 164 SELinuxRelabel: true, 165 } 166 } 167 168 // SetUp puts in place the volume plugin. 169 // This function is not idempotent by design. We want the data to be refreshed periodically. 170 // The internal sync interval of kubelet will drive the refresh of data. 171 // TODO: Add volume specific ticker and refresh loop 172 func (b *downwardAPIVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error { 173 return b.SetUpAt(b.GetPath(), mounterArgs) 174 } 175 176 func (b *downwardAPIVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { 177 klog.V(3).Infof("Setting up a downwardAPI volume %v for pod %v/%v at %v", b.volName, b.pod.Namespace, b.pod.Name, dir) 178 // Wrap EmptyDir. Here we rely on the idempotency of the wrapped plugin to avoid repeatedly mounting 179 wrapped, err := b.plugin.host.NewWrapperMounter(b.volName, wrappedVolumeSpec(), b.pod, *b.opts) 180 if err != nil { 181 klog.Errorf("Couldn't setup downwardAPI volume %v for pod %v/%v: %s", b.volName, b.pod.Namespace, b.pod.Name, err.Error()) 182 return err 183 } 184 185 data, err := CollectData(b.source.Items, b.pod, b.plugin.host, b.source.DefaultMode) 186 if err != nil { 187 klog.Errorf("Error preparing data for downwardAPI volume %v for pod %v/%v: %s", b.volName, b.pod.Namespace, b.pod.Name, err.Error()) 188 return err 189 } 190 191 setupSuccess := false 192 if err := wrapped.SetUpAt(dir, mounterArgs); err != nil { 193 klog.Errorf("Unable to setup downwardAPI volume %v for pod %v/%v: %s", b.volName, b.pod.Namespace, b.pod.Name, err.Error()) 194 return err 195 } 196 197 if err := volumeutil.MakeNestedMountpoints(b.volName, dir, *b.pod); err != nil { 198 return err 199 } 200 201 defer func() { 202 // Clean up directories if setup fails 203 if !setupSuccess { 204 unmounter, unmountCreateErr := b.plugin.NewUnmounter(b.volName, b.podUID) 205 if unmountCreateErr != nil { 206 klog.Errorf("error cleaning up mount %s after failure. Create unmounter failed with %v", b.volName, unmountCreateErr) 207 return 208 } 209 tearDownErr := unmounter.TearDown() 210 if tearDownErr != nil { 211 klog.Errorf("error tearing down volume %s with : %v", b.volName, tearDownErr) 212 } 213 } 214 }() 215 216 writerContext := fmt.Sprintf("pod %v/%v volume %v", b.pod.Namespace, b.pod.Name, b.volName) 217 writer, err := volumeutil.NewAtomicWriter(dir, writerContext) 218 if err != nil { 219 klog.Errorf("Error creating atomic writer: %v", err) 220 return err 221 } 222 223 setPerms := func(_ string) error { 224 // This may be the first time writing and new files get created outside the timestamp subdirectory: 225 // change the permissions on the whole volume and not only in the timestamp directory. 226 return volume.SetVolumeOwnership(b, dir, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil)) 227 } 228 err = writer.Write(data, setPerms) 229 if err != nil { 230 klog.Errorf("Error writing payload to dir: %v", err) 231 return err 232 } 233 234 setupSuccess = true 235 return nil 236 } 237 238 // CollectData collects requested downwardAPI in data map. 239 // Map's key is the requested name of file to dump 240 // Map's value is the (sorted) content of the field to be dumped in the file. 241 // 242 // Note: this function is exported so that it can be called from the projection volume driver 243 func CollectData(items []v1.DownwardAPIVolumeFile, pod *v1.Pod, host volume.VolumeHost, defaultMode *int32) (map[string]volumeutil.FileProjection, error) { 244 if defaultMode == nil { 245 return nil, fmt.Errorf("no defaultMode used, not even the default value for it") 246 } 247 248 errlist := []error{} 249 data := make(map[string]volumeutil.FileProjection) 250 for _, fileInfo := range items { 251 var fileProjection volumeutil.FileProjection 252 fPath := filepath.Clean(fileInfo.Path) 253 if fileInfo.Mode != nil { 254 fileProjection.Mode = *fileInfo.Mode 255 } else { 256 fileProjection.Mode = *defaultMode 257 } 258 if fileInfo.FieldRef != nil { 259 // TODO: unify with Kubelet.podFieldSelectorRuntimeValue 260 if values, err := fieldpath.ExtractFieldPathAsString(pod, fileInfo.FieldRef.FieldPath); err != nil { 261 klog.Errorf("Unable to extract field %s: %s", fileInfo.FieldRef.FieldPath, err.Error()) 262 errlist = append(errlist, err) 263 } else { 264 fileProjection.Data = []byte(values) 265 } 266 } else if fileInfo.ResourceFieldRef != nil { 267 containerName := fileInfo.ResourceFieldRef.ContainerName 268 nodeAllocatable, err := host.GetNodeAllocatable() 269 if err != nil { 270 errlist = append(errlist, err) 271 } else if values, err := resource.ExtractResourceValueByContainerNameAndNodeAllocatable(fileInfo.ResourceFieldRef, pod, containerName, nodeAllocatable); err != nil { 272 klog.Errorf("Unable to extract field %s: %s", fileInfo.ResourceFieldRef.Resource, err.Error()) 273 errlist = append(errlist, err) 274 } else { 275 fileProjection.Data = []byte(values) 276 } 277 } 278 279 data[fPath] = fileProjection 280 } 281 return data, utilerrors.NewAggregate(errlist) 282 } 283 284 func (d *downwardAPIVolume) GetPath() string { 285 return d.plugin.host.GetPodVolumeDir(d.podUID, utilstrings.EscapeQualifiedName(downwardAPIPluginName), d.volName) 286 } 287 288 // downwardAPIVolumeCleaner handles cleaning up downwardAPI volumes 289 type downwardAPIVolumeUnmounter struct { 290 *downwardAPIVolume 291 } 292 293 // downwardAPIVolumeUnmounter implements volume.Unmounter interface 294 var _ volume.Unmounter = &downwardAPIVolumeUnmounter{} 295 296 func (c *downwardAPIVolumeUnmounter) TearDown() error { 297 return c.TearDownAt(c.GetPath()) 298 } 299 300 func (c *downwardAPIVolumeUnmounter) TearDownAt(dir string) error { 301 return volumeutil.UnmountViaEmptyDir(dir, c.plugin.host, c.volName, wrappedVolumeSpec(), c.podUID) 302 } 303 304 func getVolumeSource(spec *volume.Spec) (*v1.DownwardAPIVolumeSource, bool) { 305 var readOnly bool 306 var volumeSource *v1.DownwardAPIVolumeSource 307 308 if spec.Volume != nil && spec.Volume.DownwardAPI != nil { 309 volumeSource = spec.Volume.DownwardAPI 310 readOnly = spec.ReadOnly 311 } 312 313 return volumeSource, readOnly 314 }