...

Source file src/k8s.io/kubernetes/pkg/volume/git_repo/git_repo.go

Documentation: k8s.io/kubernetes/pkg/volume/git_repo

     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  }
   302  

View as plain text