Commit 70a9c0bf authored by Harry Zhang's avatar Harry Zhang Committed by harry zhang

Add directory in GitRepo and updated tests.

Update validate and gitRepo Update generated code
parent f4ea77be
......@@ -13251,8 +13251,7 @@
"id": "v1.GitRepoVolumeSource",
"description": "GitRepoVolumeSource represents a volume that is pulled from git when the pod is created.",
"required": [
"repository",
"revision"
"repository"
],
"properties": {
"repository": {
......@@ -13262,6 +13261,10 @@
"revision": {
"type": "string",
"description": "Commit hash for the specified revision."
},
"directory": {
"type": "string",
"description": "Target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name."
}
}
},
......
......@@ -3244,8 +3244,7 @@
"id": "v1.GitRepoVolumeSource",
"description": "GitRepoVolumeSource represents a volume that is pulled from git when the pod is created.",
"required": [
"repository",
"revision"
"repository"
],
"properties": {
"repository": {
......@@ -3255,6 +3254,10 @@
"revision": {
"type": "string",
"description": "Commit hash for the specified revision."
},
"directory": {
"type": "string",
"description": "Target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name."
}
}
},
......
......@@ -1436,7 +1436,14 @@ Both these may change in the future. Incoming requests are matched against the h
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">revision</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Commit hash for the specified revision.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">directory</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Target directory name. Must not contain or start with <em>..</em>. If <em>.</em> is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
......@@ -4261,7 +4268,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</div>
<div id="footer">
<div id="footer-text">
Last updated 2015-11-20 03:20:06 UTC
Last updated 2015-11-29 16:46:22 UTC
</div>
</div>
</body>
......
......@@ -1321,7 +1321,14 @@ Examples:<br>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">revision</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Commit hash for the specified revision.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">directory</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Target directory name. Must not contain or start with <em>..</em>. If <em>.</em> is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
......@@ -6894,7 +6901,7 @@ The resulting set of endpoints can be viewed as:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2015-11-20 03:19:59 UTC
Last updated 2015-11-29 16:46:14 UTC
</div>
</div>
</body>
......
......@@ -587,6 +587,7 @@ func deepCopy_api_GCEPersistentDiskVolumeSource(in GCEPersistentDiskVolumeSource
func deepCopy_api_GitRepoVolumeSource(in GitRepoVolumeSource, out *GitRepoVolumeSource, c *conversion.Cloner) error {
out.Repository = in.Repository
out.Revision = in.Revision
out.Directory = in.Directory
return nil
}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -502,7 +502,12 @@ type GitRepoVolumeSource struct {
// Repository URL
Repository string `json:"repository"`
// Commit hash, this is optional
Revision string `json:"revision"`
Revision string `json:"revision,omitempty"`
// Clone target, this is optional
// Must not contain or start with '..'. If '.' is supplied, the volume directory will be the
// git repository. Otherwise, if specified, the volume will contain the git repository in
// the subdirectory with the given name.
Directory string `json:"directory,omitempty"`
// TODO: Consider credentials here.
}
......
......@@ -826,6 +826,7 @@ func autoconvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *api.GitRe
}
out.Repository = in.Repository
out.Revision = in.Revision
out.Directory = in.Directory
return nil
}
......@@ -3851,6 +3852,7 @@ func autoconvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in *GitRepoVo
}
out.Repository = in.Repository
out.Revision = in.Revision
out.Directory = in.Directory
return nil
}
......
......@@ -623,6 +623,7 @@ func deepCopy_v1_GCEPersistentDiskVolumeSource(in GCEPersistentDiskVolumeSource,
func deepCopy_v1_GitRepoVolumeSource(in GitRepoVolumeSource, out *GitRepoVolumeSource, c *conversion.Cloner) error {
out.Repository = in.Repository
out.Revision = in.Revision
out.Directory = in.Directory
return nil
}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -673,7 +673,12 @@ type GitRepoVolumeSource struct {
// Repository URL
Repository string `json:"repository"`
// Commit hash for the specified revision.
Revision string `json:"revision"`
Revision string `json:"revision,omitempty"`
// Target directory name.
// Must not contain or start with '..'. If '.' is supplied, the volume directory will be the
// git repository. Otherwise, if specified, the volume will contain the git repository in
// the subdirectory with the given name.
Directory string `json:"directory,omitempty"`
}
// SecretVolumeSource adapts a Secret into a VolumeSource.
......
......@@ -416,6 +416,7 @@ var map_GitRepoVolumeSource = map[string]string{
"": "GitRepoVolumeSource represents a volume that is pulled from git when the pod is created.",
"repository": "Repository URL",
"revision": "Commit hash for the specified revision.",
"directory": "Target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name.",
}
func (GitRepoVolumeSource) SwaggerDoc() map[string]string {
......
......@@ -440,9 +440,12 @@ func validateHostPathVolumeSource(hostPath *api.HostPathVolumeSource) validation
func validateGitRepoVolumeSource(gitRepo *api.GitRepoVolumeSource) validation.ErrorList {
allErrs := validation.ErrorList{}
if gitRepo.Repository == "" {
if len(gitRepo.Repository) == 0 {
allErrs = append(allErrs, validation.NewRequiredError("repository"))
}
pathErrs := validateVolumeSourcePath(gitRepo.Directory, "directory")
allErrs = append(allErrs, pathErrs...)
return allErrs
}
......@@ -570,23 +573,34 @@ func validateDownwardAPIVolumeSource(downwardAPIVolume *api.DownwardAPIVolumeSou
if len(downwardAPIVolumeFile.Path) == 0 {
allErrs = append(allErrs, validation.NewRequiredError("path"))
}
if path.IsAbs(downwardAPIVolumeFile.Path) {
allErrs = append(allErrs, validation.NewForbiddenError("path", "must not be an absolute path"))
}
items := strings.Split(downwardAPIVolumeFile.Path, string(os.PathSeparator))
for _, item := range items {
if item == ".." {
allErrs = append(allErrs, validation.NewInvalidError("path", downwardAPIVolumeFile.Path, "must not contain \"..\"."))
}
}
if strings.HasPrefix(items[0], "..") && len(items[0]) > 2 {
allErrs = append(allErrs, validation.NewInvalidError("path", downwardAPIVolumeFile.Path, "must not start with \"..\"."))
}
allErrs = append(allErrs, validateVolumeSourcePath(downwardAPIVolumeFile.Path, "path")...)
allErrs = append(allErrs, validateObjectFieldSelector(&downwardAPIVolumeFile.FieldRef, &validDownwardAPIFieldPathExpressions).Prefix("FieldRef")...)
}
return allErrs
}
// This validate will make sure targetPath:
// 1. is not abs path
// 2. does not contain '..'
// 3. does not start with '..'
func validateVolumeSourcePath(targetPath string, field string) validation.ErrorList {
allErrs := validation.ErrorList{}
if path.IsAbs(targetPath) {
allErrs = append(allErrs, validation.NewForbiddenError(field, "must not be an absolute path"))
}
// TODO assume OS of api server & nodes are the same for now
items := strings.Split(targetPath, string(os.PathSeparator))
for _, item := range items {
if item == ".." {
allErrs = append(allErrs, validation.NewInvalidError(field, targetPath, "must not contain \"..\""))
}
}
if strings.HasPrefix(items[0], "..") && len(items[0]) > 2 {
allErrs = append(allErrs, validation.NewInvalidError(field, targetPath, "must not start with \"..\""))
}
return allErrs
}
func validateRBD(rbd *api.RBDVolumeSource) validation.ErrorList {
allErrs := validation.ErrorList{}
if len(rbd.CephMonitors) == 0 {
......
......@@ -453,7 +453,8 @@ func TestValidateVolumes(t *testing.T) {
{Name: "empty", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}},
{Name: "awsebs", VolumeSource: api.VolumeSource{AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{VolumeID: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}},
{Name: "gitrepo", VolumeSource: api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{Repository: "my-repo", Revision: "hashstring"}}},
{Name: "gitrepo", VolumeSource: api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{Repository: "my-repo", Revision: "hashstring", Directory: "target"}}},
{Name: "gitrepodot", VolumeSource: api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{Repository: "my-repo", Directory: "."}}},
{Name: "iscsidisk", VolumeSource: api.VolumeSource{ISCSI: &api.ISCSIVolumeSource{TargetPortal: "127.0.0.1", IQN: "iqn.2015-02.example.com:test", Lun: 1, FSType: "ext4", ReadOnly: false}}},
{Name: "secret", VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "my-secret"}}},
{Name: "glusterfs", VolumeSource: api.VolumeSource{Glusterfs: &api.GlusterfsVolumeSource{EndpointsName: "host1", Path: "path", ReadOnly: false}}},
......@@ -505,6 +506,9 @@ func TestValidateVolumes(t *testing.T) {
emptyMon := api.VolumeSource{RBD: &api.RBDVolumeSource{CephMonitors: []string{}, RBDImage: "bar", FSType: "ext4"}}
emptyImage := api.VolumeSource{RBD: &api.RBDVolumeSource{CephMonitors: []string{"foo"}, RBDImage: "", FSType: "ext4"}}
emptyCephFSMon := api.VolumeSource{CephFS: &api.CephFSVolumeSource{Monitors: []string{}}}
startsWithDots := api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{Repository: "foo", Directory: "..dots/bar"}}
containsDots := api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{Repository: "foo", Directory: "dots/../bar"}}
absPath := api.VolumeSource{GitRepo: &api.GitRepoVolumeSource{Repository: "foo", Directory: "/abstarget"}}
emptyPathName := api.VolumeSource{DownwardAPI: &api.DownwardAPIVolumeSource{Items: []api.DownwardAPIVolumeFile{{Path: "",
FieldRef: api.ObjectFieldSelector{
APIVersion: "v1",
......@@ -553,12 +557,15 @@ func TestValidateVolumes(t *testing.T) {
"empty cephfs mon": {[]api.Volume{{Name: "badmon", VolumeSource: emptyCephFSMon}}, validation.ErrorTypeRequired, "[0].source.cephfs.monitors", ""},
"empty metatada path": {[]api.Volume{{Name: "emptyname", VolumeSource: emptyPathName}}, validation.ErrorTypeRequired, "[0].source.downwardApi.path", ""},
"absolute path": {[]api.Volume{{Name: "absolutepath", VolumeSource: absolutePathName}}, validation.ErrorTypeForbidden, "[0].source.downwardApi.path", ""},
"dot dot path": {[]api.Volume{{Name: "dotdotpath", VolumeSource: dotDotInPath}}, validation.ErrorTypeInvalid, "[0].source.downwardApi.path", "must not contain \"..\"."},
"dot dot file name": {[]api.Volume{{Name: "dotdotfilename", VolumeSource: dotDotPathName}}, validation.ErrorTypeInvalid, "[0].source.downwardApi.path", "must not start with \"..\"."},
"dot dot first level dirent": {[]api.Volume{{Name: "dotdotdirfilename", VolumeSource: dotDotFirstLevelDirent}}, validation.ErrorTypeInvalid, "[0].source.downwardApi.path", "must not start with \"..\"."},
"dot dot path": {[]api.Volume{{Name: "dotdotpath", VolumeSource: dotDotInPath}}, validation.ErrorTypeInvalid, "[0].source.downwardApi.path", "must not contain \"..\""},
"dot dot file name": {[]api.Volume{{Name: "dotdotfilename", VolumeSource: dotDotPathName}}, validation.ErrorTypeInvalid, "[0].source.downwardApi.path", "must not start with \"..\""},
"dot dot first level dirent": {[]api.Volume{{Name: "dotdotdirfilename", VolumeSource: dotDotFirstLevelDirent}}, validation.ErrorTypeInvalid, "[0].source.downwardApi.path", "must not start with \"..\""},
"empty wwn": {[]api.Volume{{Name: "badimage", VolumeSource: zeroWWN}}, validation.ErrorTypeRequired, "[0].source.fc.targetWWNs", ""},
"empty lun": {[]api.Volume{{Name: "badimage", VolumeSource: emptyLun}}, validation.ErrorTypeRequired, "[0].source.fc.lun", ""},
"slash in datasetName": {[]api.Volume{{Name: "slashinname", VolumeSource: slashInName}}, validation.ErrorTypeInvalid, "[0].source.flocker.datasetName", "must not contain '/'"},
"starts with '..'": {[]api.Volume{{Name: "badprefix", VolumeSource: startsWithDots}}, validation.ErrorTypeInvalid, "[0].source.gitRepo.directory", "must not start with \"..\""},
"contains '..'": {[]api.Volume{{Name: "containsdots", VolumeSource: containsDots}}, validation.ErrorTypeInvalid, "[0].source.gitRepo.directory", "must not contain \"..\""},
"absolute target": {[]api.Volume{{Name: "absolutetarget", VolumeSource: absPath}}, validation.ErrorTypeForbidden, "[0].source.gitRepo.directory", ""},
}
for k, v := range errorCases {
_, errs := validateVolumes(v.V)
......
......@@ -290,6 +290,7 @@ func deepCopy_api_GCEPersistentDiskVolumeSource(in api.GCEPersistentDiskVolumeSo
func deepCopy_api_GitRepoVolumeSource(in api.GitRepoVolumeSource, out *api.GitRepoVolumeSource, c *conversion.Cloner) error {
out.Repository = in.Repository
out.Revision = in.Revision
out.Directory = in.Directory
return nil
}
......
......@@ -397,6 +397,7 @@ func autoconvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *api.GitRe
}
out.Repository = in.Repository
out.Revision = in.Revision
out.Directory = in.Directory
return nil
}
......@@ -1456,6 +1457,7 @@ func autoconvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in *v1.GitRep
}
out.Repository = in.Repository
out.Revision = in.Revision
out.Directory = in.Directory
return nil
}
......
......@@ -326,6 +326,7 @@ func deepCopy_v1_GCEPersistentDiskVolumeSource(in v1.GCEPersistentDiskVolumeSour
func deepCopy_v1_GitRepoVolumeSource(in v1.GitRepoVolumeSource, out *v1.GitRepoVolumeSource, c *conversion.Cloner) error {
out.Repository = in.Repository
out.Revision = in.Revision
out.Directory = in.Directory
return nil
}
......
......@@ -20,6 +20,7 @@ import (
"fmt"
"io/ioutil"
"path"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/types"
......@@ -66,6 +67,7 @@ func (plugin *gitRepoPlugin) NewBuilder(spec *volume.Spec, pod *api.Pod, opts vo
pod: *pod,
source: spec.Volume.GitRepo.Repository,
revision: spec.Volume.GitRepo.Revision,
target: spec.Volume.GitRepo.Directory,
exec: exec.New(),
opts: opts,
}, nil
......@@ -103,6 +105,7 @@ type gitRepoVolumeBuilder struct {
pod api.Pod
source string
revision string
target string
exec exec.Interface
opts volume.VolumeOptions
}
......@@ -143,24 +146,41 @@ func (b *gitRepoVolumeBuilder) SetUpAt(dir string) error {
return err
}
if output, err := b.execCommand("git", []string{"clone", b.source}, dir); err != nil {
return fmt.Errorf("failed to exec 'git clone %s': %s: %v", b.source, output, err)
args := []string{"clone", b.source}
if len(b.target) != 0 {
args = append(args, b.target)
}
if output, err := b.execCommand("git", args, dir); err != nil {
return fmt.Errorf("failed to exec 'git %s': %s: %v",
strings.Join(args, " "), output, err)
}
files, err := ioutil.ReadDir(dir)
if err != nil {
return err
}
if len(files) != 1 {
return fmt.Errorf("unexpected directory contents: %v", files)
}
if len(b.revision) == 0 {
// Done!
volumeutil.SetReady(b.getMetaDir())
return nil
}
subdir := path.Join(dir, files[0].Name())
var subdir string
switch {
case b.target == ".":
// if target dir is '.', use the current dir
subdir = path.Join(dir)
case len(files) == 1:
// if target is not '.', use the generated folder
subdir = path.Join(dir, files[0].Name())
default:
// if target is not '.', but generated many files, it's wrong
return fmt.Errorf("unexpected directory contents: %v", files)
}
if output, err := b.execCommand("git", []string{"checkout", b.revision}, subdir); err != nil {
return fmt.Errorf("failed to exec 'git checkout %s': %s: %v", b.revision, output, err)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment