k8s.io/kubernetes@v1.29.3/pkg/volume/git_repo/git_repo.go (about) 1 /* 2 Copyright 2014 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 git_repo 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "path/filepath" 23 "strings" 24 25 v1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/types" 27 "k8s.io/kubernetes/pkg/volume" 28 volumeutil "k8s.io/kubernetes/pkg/volume/util" 29 "k8s.io/utils/exec" 30 utilstrings "k8s.io/utils/strings" 31 ) 32 33 // This is the primary entrypoint for volume plugins. 34 func ProbeVolumePlugins() []volume.VolumePlugin { 35 return []volume.VolumePlugin{&gitRepoPlugin{nil}} 36 } 37 38 type gitRepoPlugin struct { 39 host volume.VolumeHost 40 } 41 42 var _ volume.VolumePlugin = &gitRepoPlugin{} 43 44 func wrappedVolumeSpec() volume.Spec { 45 return volume.Spec{ 46 Volume: &v1.Volume{VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}}, 47 } 48 } 49 50 const ( 51 gitRepoPluginName = "kubernetes.io/git-repo" 52 ) 53 54 func (plugin *gitRepoPlugin) Init(host volume.VolumeHost) error { 55 plugin.host = host 56 return nil 57 } 58 59 func (plugin *gitRepoPlugin) GetPluginName() string { 60 return gitRepoPluginName 61 } 62 63 func (plugin *gitRepoPlugin) GetVolumeName(spec *volume.Spec) (string, error) { 64 volumeSource, _ := getVolumeSource(spec) 65 if volumeSource == nil { 66 return "", fmt.Errorf("Spec does not reference a Git repo volume type") 67 } 68 69 return fmt.Sprintf( 70 "%v:%v:%v", 71 volumeSource.Repository, 72 volumeSource.Revision, 73 volumeSource.Directory), nil 74 } 75 76 func (plugin *gitRepoPlugin) CanSupport(spec *volume.Spec) bool { 77 return spec.Volume != nil && spec.Volume.GitRepo != nil 78 } 79 80 func (plugin *gitRepoPlugin) RequiresRemount(spec *volume.Spec) bool { 81 return false 82 } 83 84 func (plugin *gitRepoPlugin) SupportsMountOption() bool { 85 return false 86 } 87 88 func (plugin *gitRepoPlugin) SupportsBulkVolumeVerification() bool { 89 return false 90 } 91 92 func (plugin *gitRepoPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { 93 return false, nil 94 } 95 96 func (plugin *gitRepoPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { 97 if err := validateVolume(spec.Volume.GitRepo); err != nil { 98 return nil, err 99 } 100 101 return &gitRepoVolumeMounter{ 102 gitRepoVolume: &gitRepoVolume{ 103 volName: spec.Name(), 104 podUID: pod.UID, 105 plugin: plugin, 106 }, 107 pod: *pod, 108 source: spec.Volume.GitRepo.Repository, 109 revision: spec.Volume.GitRepo.Revision, 110 target: spec.Volume.GitRepo.Directory, 111 exec: exec.New(), 112 opts: opts, 113 }, nil 114 } 115 116 func (plugin *gitRepoPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { 117 return &gitRepoVolumeUnmounter{ 118 &gitRepoVolume{ 119 volName: volName, 120 podUID: podUID, 121 plugin: plugin, 122 }, 123 }, nil 124 } 125 126 func (plugin *gitRepoPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { 127 gitVolume := &v1.Volume{ 128 Name: volumeName, 129 VolumeSource: v1.VolumeSource{ 130 GitRepo: &v1.GitRepoVolumeSource{}, 131 }, 132 } 133 return volume.ReconstructedVolume{ 134 Spec: volume.NewSpecFromVolume(gitVolume), 135 }, nil 136 } 137 138 // gitRepo volumes are directories which are pre-filled from a git repository. 139 // These do not persist beyond the lifetime of a pod. 140 type gitRepoVolume struct { 141 volName string 142 podUID types.UID 143 plugin *gitRepoPlugin 144 volume.MetricsNil 145 } 146 147 var _ volume.Volume = &gitRepoVolume{} 148 149 func (gr *gitRepoVolume) GetPath() string { 150 name := gitRepoPluginName 151 return gr.plugin.host.GetPodVolumeDir(gr.podUID, utilstrings.EscapeQualifiedName(name), gr.volName) 152 } 153 154 // gitRepoVolumeMounter builds git repo volumes. 155 type gitRepoVolumeMounter struct { 156 *gitRepoVolume 157 158 pod v1.Pod 159 source string 160 revision string 161 target string 162 exec exec.Interface 163 opts volume.VolumeOptions 164 } 165 166 var _ volume.Mounter = &gitRepoVolumeMounter{} 167 168 func (b *gitRepoVolumeMounter) GetAttributes() volume.Attributes { 169 return volume.Attributes{ 170 ReadOnly: false, 171 Managed: true, 172 SELinuxRelabel: true, // xattr change should be okay, TODO: double check 173 } 174 } 175 176 // SetUp creates new directory and clones a git repo. 177 func (b *gitRepoVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error { 178 return b.SetUpAt(b.GetPath(), mounterArgs) 179 } 180 181 // SetUpAt creates new directory and clones a git repo. 182 func (b *gitRepoVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { 183 if volumeutil.IsReady(b.getMetaDir()) { 184 return nil 185 } 186 187 // Wrap EmptyDir, let it do the setup. 188 wrapped, err := b.plugin.host.NewWrapperMounter(b.volName, wrappedVolumeSpec(), &b.pod, b.opts) 189 if err != nil { 190 return err 191 } 192 if err := wrapped.SetUpAt(dir, mounterArgs); err != nil { 193 return err 194 } 195 196 args := []string{"clone", "--", b.source} 197 198 if len(b.target) != 0 { 199 args = append(args, b.target) 200 } 201 if output, err := b.execCommand("git", args, dir); err != nil { 202 return fmt.Errorf("failed to exec 'git %s': %s: %v", 203 strings.Join(args, " "), output, err) 204 } 205 206 files, err := ioutil.ReadDir(dir) 207 if err != nil { 208 return err 209 } 210 211 if len(b.revision) == 0 { 212 // Done! 213 volumeutil.SetReady(b.getMetaDir()) 214 return nil 215 } 216 217 var subdir string 218 219 switch { 220 case len(b.target) != 0 && filepath.Clean(b.target) == ".": 221 // if target dir is '.', use the current dir 222 subdir = filepath.Join(dir) 223 case len(files) == 1: 224 // if target is not '.', use the generated folder 225 subdir = filepath.Join(dir, files[0].Name()) 226 default: 227 // if target is not '.', but generated many files, it's wrong 228 return fmt.Errorf("unexpected directory contents: %v", files) 229 } 230 231 if output, err := b.execCommand("git", []string{"checkout", b.revision}, subdir); err != nil { 232 return fmt.Errorf("failed to exec 'git checkout %s': %s: %v", b.revision, output, err) 233 } 234 if output, err := b.execCommand("git", []string{"reset", "--hard"}, subdir); err != nil { 235 return fmt.Errorf("failed to exec 'git reset --hard': %s: %v", output, err) 236 } 237 238 volume.SetVolumeOwnership(b, dir, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil)) 239 240 volumeutil.SetReady(b.getMetaDir()) 241 return nil 242 } 243 244 func (b *gitRepoVolumeMounter) getMetaDir() string { 245 return filepath.Join(b.plugin.host.GetPodPluginDir(b.podUID, utilstrings.EscapeQualifiedName(gitRepoPluginName)), b.volName) 246 } 247 248 func (b *gitRepoVolumeMounter) execCommand(command string, args []string, dir string) ([]byte, error) { 249 cmd := b.exec.Command(command, args...) 250 cmd.SetDir(dir) 251 return cmd.CombinedOutput() 252 } 253 254 func validateVolume(src *v1.GitRepoVolumeSource) error { 255 if err := validateNonFlagArgument(src.Repository, "repository"); err != nil { 256 return err 257 } 258 if err := validateNonFlagArgument(src.Revision, "revision"); err != nil { 259 return err 260 } 261 if err := validateNonFlagArgument(src.Directory, "directory"); err != nil { 262 return err 263 } 264 return nil 265 } 266 267 // gitRepoVolumeUnmounter cleans git repo volumes. 268 type gitRepoVolumeUnmounter struct { 269 *gitRepoVolume 270 } 271 272 var _ volume.Unmounter = &gitRepoVolumeUnmounter{} 273 274 // TearDown simply deletes everything in the directory. 275 func (c *gitRepoVolumeUnmounter) TearDown() error { 276 return c.TearDownAt(c.GetPath()) 277 } 278 279 // TearDownAt simply deletes everything in the directory. 280 func (c *gitRepoVolumeUnmounter) TearDownAt(dir string) error { 281 return volumeutil.UnmountViaEmptyDir(dir, c.plugin.host, c.volName, wrappedVolumeSpec(), c.podUID) 282 } 283 284 func getVolumeSource(spec *volume.Spec) (*v1.GitRepoVolumeSource, bool) { 285 var readOnly bool 286 var volumeSource *v1.GitRepoVolumeSource 287 288 if spec.Volume != nil && spec.Volume.GitRepo != nil { 289 volumeSource = spec.Volume.GitRepo 290 readOnly = spec.ReadOnly 291 } 292 293 return volumeSource, readOnly 294 } 295 296 func validateNonFlagArgument(arg, argName string) error { 297 if len(arg) > 0 && arg[0] == '-' { 298 return fmt.Errorf("%q is an invalid value for %s", arg, argName) 299 } 300 return nil 301 }