1
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
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
139
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
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,
173 }
174 }
175
176
177 func (b *gitRepoVolumeMounter) SetUp(mounterArgs volume.MounterArgs) error {
178 return b.SetUpAt(b.GetPath(), mounterArgs)
179 }
180
181
182 func (b *gitRepoVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
183 if volumeutil.IsReady(b.getMetaDir()) {
184 return nil
185 }
186
187
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
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
222 subdir = filepath.Join(dir)
223 case len(files) == 1:
224
225 subdir = filepath.Join(dir, files[0].Name())
226 default:
227
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 , 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
268 type gitRepoVolumeUnmounter struct {
269 *gitRepoVolume
270 }
271
272 var _ volume.Unmounter = &gitRepoVolumeUnmounter{}
273
274
275 func (c *gitRepoVolumeUnmounter) TearDown() error {
276 return c.TearDownAt(c.GetPath())
277 }
278
279
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