git_repo.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package git_repo
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "path"
  18. "strings"
  19. "k8s.io/kubernetes/pkg/api"
  20. "k8s.io/kubernetes/pkg/types"
  21. "k8s.io/kubernetes/pkg/util/exec"
  22. utilstrings "k8s.io/kubernetes/pkg/util/strings"
  23. "k8s.io/kubernetes/pkg/volume"
  24. volumeutil "k8s.io/kubernetes/pkg/volume/util"
  25. )
  26. // This is the primary entrypoint for volume plugins.
  27. func ProbeVolumePlugins() []volume.VolumePlugin {
  28. return []volume.VolumePlugin{&gitRepoPlugin{nil}}
  29. }
  30. type gitRepoPlugin struct {
  31. host volume.VolumeHost
  32. }
  33. var _ volume.VolumePlugin = &gitRepoPlugin{}
  34. func wrappedVolumeSpec() volume.Spec {
  35. return volume.Spec{
  36. Volume: &api.Volume{VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
  37. }
  38. }
  39. const (
  40. gitRepoPluginName = "kubernetes.io/git-repo"
  41. )
  42. func (plugin *gitRepoPlugin) Init(host volume.VolumeHost) error {
  43. plugin.host = host
  44. return nil
  45. }
  46. func (plugin *gitRepoPlugin) GetPluginName() string {
  47. return gitRepoPluginName
  48. }
  49. func (plugin *gitRepoPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
  50. volumeSource, _ := getVolumeSource(spec)
  51. if volumeSource == nil {
  52. return "", fmt.Errorf("Spec does not reference a Git repo volume type")
  53. }
  54. return fmt.Sprintf(
  55. "%v:%v:%v",
  56. volumeSource.Repository,
  57. volumeSource.Revision,
  58. volumeSource.Directory), nil
  59. }
  60. func (plugin *gitRepoPlugin) CanSupport(spec *volume.Spec) bool {
  61. return spec.Volume != nil && spec.Volume.GitRepo != nil
  62. }
  63. func (plugin *gitRepoPlugin) RequiresRemount() bool {
  64. return false
  65. }
  66. func (plugin *gitRepoPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
  67. return &gitRepoVolumeMounter{
  68. gitRepoVolume: &gitRepoVolume{
  69. volName: spec.Name(),
  70. podUID: pod.UID,
  71. plugin: plugin,
  72. },
  73. pod: *pod,
  74. source: spec.Volume.GitRepo.Repository,
  75. revision: spec.Volume.GitRepo.Revision,
  76. target: spec.Volume.GitRepo.Directory,
  77. exec: exec.New(),
  78. opts: opts,
  79. }, nil
  80. }
  81. func (plugin *gitRepoPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
  82. return &gitRepoVolumeUnmounter{
  83. &gitRepoVolume{
  84. volName: volName,
  85. podUID: podUID,
  86. plugin: plugin,
  87. },
  88. }, nil
  89. }
  90. func (plugin *gitRepoPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
  91. gitVolume := &api.Volume{
  92. Name: volumeName,
  93. VolumeSource: api.VolumeSource{
  94. GitRepo: &api.GitRepoVolumeSource{},
  95. },
  96. }
  97. return volume.NewSpecFromVolume(gitVolume), nil
  98. }
  99. // gitRepo volumes are directories which are pre-filled from a git repository.
  100. // These do not persist beyond the lifetime of a pod.
  101. type gitRepoVolume struct {
  102. volName string
  103. podUID types.UID
  104. plugin *gitRepoPlugin
  105. volume.MetricsNil
  106. }
  107. var _ volume.Volume = &gitRepoVolume{}
  108. func (gr *gitRepoVolume) GetPath() string {
  109. name := gitRepoPluginName
  110. return gr.plugin.host.GetPodVolumeDir(gr.podUID, utilstrings.EscapeQualifiedNameForDisk(name), gr.volName)
  111. }
  112. // gitRepoVolumeMounter builds git repo volumes.
  113. type gitRepoVolumeMounter struct {
  114. *gitRepoVolume
  115. pod api.Pod
  116. source string
  117. revision string
  118. target string
  119. exec exec.Interface
  120. opts volume.VolumeOptions
  121. }
  122. var _ volume.Mounter = &gitRepoVolumeMounter{}
  123. func (b *gitRepoVolumeMounter) GetAttributes() volume.Attributes {
  124. return volume.Attributes{
  125. ReadOnly: false,
  126. Managed: true,
  127. SupportsSELinux: true, // xattr change should be okay, TODO: double check
  128. }
  129. }
  130. // SetUp creates new directory and clones a git repo.
  131. func (b *gitRepoVolumeMounter) SetUp(fsGroup *int64) error {
  132. return b.SetUpAt(b.GetPath(), fsGroup)
  133. }
  134. // SetUpAt creates new directory and clones a git repo.
  135. func (b *gitRepoVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
  136. if volumeutil.IsReady(b.getMetaDir()) {
  137. return nil
  138. }
  139. // Wrap EmptyDir, let it do the setup.
  140. wrapped, err := b.plugin.host.NewWrapperMounter(b.volName, wrappedVolumeSpec(), &b.pod, b.opts)
  141. if err != nil {
  142. return err
  143. }
  144. if err := wrapped.SetUpAt(dir, fsGroup); err != nil {
  145. return err
  146. }
  147. args := []string{"clone", b.source}
  148. if len(b.target) != 0 {
  149. args = append(args, b.target)
  150. }
  151. if output, err := b.execCommand("git", args, dir); err != nil {
  152. return fmt.Errorf("failed to exec 'git %s': %s: %v",
  153. strings.Join(args, " "), output, err)
  154. }
  155. files, err := ioutil.ReadDir(dir)
  156. if err != nil {
  157. return err
  158. }
  159. if len(b.revision) == 0 {
  160. // Done!
  161. volumeutil.SetReady(b.getMetaDir())
  162. return nil
  163. }
  164. var subdir string
  165. switch {
  166. case b.target == ".":
  167. // if target dir is '.', use the current dir
  168. subdir = path.Join(dir)
  169. case len(files) == 1:
  170. // if target is not '.', use the generated folder
  171. subdir = path.Join(dir, files[0].Name())
  172. default:
  173. // if target is not '.', but generated many files, it's wrong
  174. return fmt.Errorf("unexpected directory contents: %v", files)
  175. }
  176. if output, err := b.execCommand("git", []string{"checkout", b.revision}, subdir); err != nil {
  177. return fmt.Errorf("failed to exec 'git checkout %s': %s: %v", b.revision, output, err)
  178. }
  179. if output, err := b.execCommand("git", []string{"reset", "--hard"}, subdir); err != nil {
  180. return fmt.Errorf("failed to exec 'git reset --hard': %s: %v", output, err)
  181. }
  182. volume.SetVolumeOwnership(b, fsGroup)
  183. volumeutil.SetReady(b.getMetaDir())
  184. return nil
  185. }
  186. func (b *gitRepoVolumeMounter) getMetaDir() string {
  187. return path.Join(b.plugin.host.GetPodPluginDir(b.podUID, utilstrings.EscapeQualifiedNameForDisk(gitRepoPluginName)), b.volName)
  188. }
  189. func (b *gitRepoVolumeMounter) execCommand(command string, args []string, dir string) ([]byte, error) {
  190. cmd := b.exec.Command(command, args...)
  191. cmd.SetDir(dir)
  192. return cmd.CombinedOutput()
  193. }
  194. // gitRepoVolumeUnmounter cleans git repo volumes.
  195. type gitRepoVolumeUnmounter struct {
  196. *gitRepoVolume
  197. }
  198. var _ volume.Unmounter = &gitRepoVolumeUnmounter{}
  199. // TearDown simply deletes everything in the directory.
  200. func (c *gitRepoVolumeUnmounter) TearDown() error {
  201. return c.TearDownAt(c.GetPath())
  202. }
  203. // TearDownAt simply deletes everything in the directory.
  204. func (c *gitRepoVolumeUnmounter) TearDownAt(dir string) error {
  205. // Wrap EmptyDir, let it do the teardown.
  206. wrapped, err := c.plugin.host.NewWrapperUnmounter(c.volName, wrappedVolumeSpec(), c.podUID)
  207. if err != nil {
  208. return err
  209. }
  210. return wrapped.TearDownAt(dir)
  211. }
  212. func getVolumeSource(spec *volume.Spec) (*api.GitRepoVolumeSource, bool) {
  213. var readOnly bool
  214. var volumeSource *api.GitRepoVolumeSource
  215. if spec.Volume != nil && spec.Volume.GitRepo != nil {
  216. volumeSource = spec.Volume.GitRepo
  217. readOnly = spec.ReadOnly
  218. }
  219. return volumeSource, readOnly
  220. }