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