diff --git a/pkg/api/types.go b/pkg/api/types.go index a3970c76208..4a192c2f17f 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -147,6 +147,8 @@ type VolumeSource struct { // GCEPersistentDisk represents a GCE Disk resource that is attached to a // kubelet's host machine and then exposed to the pod. GCEPersistentDisk *GCEPersistentDisk `json:"persistentDisk" yaml:"persistentDisk"` + // GitRepo represents a git repository at a particular revision. + GitRepo *GitRepo `json:"gitRepo" yaml:"gitRepo"` } // HostDir represents bare host directory volume. @@ -187,6 +189,15 @@ type GCEPersistentDisk struct { ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"` } +// GitRepo represents a volume that is pulled from git when the pod is created. +type GitRepo struct { + // Repository URL + Repository string `yaml:"repository" json:"repository"` + // Commit hash, this is optional + Revision string `yaml:"revision" json:"revision"` + // TODO: Consider credentials here. +} + // Port represents a network port in a single container type Port struct { // Optional: If specified, this must be a DNS_LABEL. Each named port diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 341b037a9ca..5e98687f115 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -93,6 +93,8 @@ type VolumeSource struct { // GCEPersistentDisk represents a GCE Disk resource that is attached to a // kubelet's host machine and then exposed to the pod. GCEPersistentDisk *GCEPersistentDisk `yaml:"persistentDisk" json:"persistentDisk"` + // GitRepo represents a git repository at a particular revision. + GitRepo *GitRepo `json:"gitRepo" yaml:"gitRepo"` } // HostDir represents bare host directory volume. @@ -133,6 +135,14 @@ type GCEPersistentDisk struct { ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"` } +// GitRepo represents a volume that is pulled from git when the pod is created. +type GitRepo struct { + // Repository URL + Repository string `yaml:"repository" json:"repository"` + // Commit hash, this is optional + Revision string `yaml:"revision" json:"revision"` +} + // Port represents a network port in a single container type Port struct { // Optional: If specified, this must be a DNS_LABEL. Each named port diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index e73a3db7bdf..e780228fdbd 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -69,6 +69,8 @@ type VolumeSource struct { // A persistent disk that is mounted to the // kubelet's host machine and then exposed to the pod. GCEPersistentDisk *GCEPersistentDisk `yaml:"persistentDisk" json:"persistentDisk"` + // GitRepo represents a git repository at a particular revision. + GitRepo *GitRepo `json:"gitRepo" yaml:"gitRepo"` } // HostDir represents bare host directory volume. @@ -124,6 +126,14 @@ type GCEPersistentDisk struct { ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"` } +// GitRepo represents a volume that is pulled from git when the pod is created. +type GitRepo struct { + // Repository URL + Repository string `yaml:"repository" json:"repository"` + // Commit hash, this is optional + Revision string `yaml:"revision" json:"revision"` +} + // VolumeMount describes a mounting of a Volume within a container. type VolumeMount struct { // Required: This must match the Name of a Volume [above]. diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index a510291172e..4a05aa59058 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -178,6 +178,8 @@ type VolumeSource struct { // GCEPersistentDisk represents a GCE Disk resource that is attached to a // kubelet's host machine and then exposed to the pod. GCEPersistentDisk *GCEPersistentDisk `yaml:"persistentDisk" json:"persistentDisk"` + // GitRepo represents a git repository at a particular revision. + GitRepo *GitRepo `json:"gitRepo" yaml:"gitRepo"` } // HostDir represents bare host directory volume. @@ -218,6 +220,14 @@ type GCEPersistentDisk struct { ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"` } +// GitRepo represents a volume that is pulled from git when the pod is created. +type GitRepo struct { + // Repository URL + Repository string `yaml:"repository" json:"repository"` + // Commit hash, this is optional + Revision string `yaml:"revision" json:"revision"` +} + // Port represents a network port in a single container. type Port struct { // Optional: If specified, this must be a DNS_LABEL. Each named port diff --git a/pkg/util/exec/exec.go b/pkg/util/exec/exec.go index 8052c3defe9..26f7a3d16c0 100644 --- a/pkg/util/exec/exec.go +++ b/pkg/util/exec/exec.go @@ -36,6 +36,7 @@ type Cmd interface { // CombinedOutput runs the command and returns its combined standard output // and standard error. This follows the pattern of package os/exec. CombinedOutput() ([]byte, error) + SetDir(dir string) } // ExitError is an interface that presents an API similar to os.ProcessState, which is @@ -64,6 +65,10 @@ func (executor *executor) Command(cmd string, args ...string) Cmd { // Wraps exec.Cmd so we can capture errors. type cmdWrapper osexec.Cmd +func (cmd *cmdWrapper) SetDir(dir string) { + cmd.Dir = dir +} + // CombinedOutput is part of the Cmd interface. func (cmd *cmdWrapper) CombinedOutput() ([]byte, error) { out, err := (*osexec.Cmd)(cmd).CombinedOutput() diff --git a/pkg/util/exec/fake_exec.go b/pkg/util/exec/fake_exec.go new file mode 100644 index 00000000000..bd13fba5963 --- /dev/null +++ b/pkg/util/exec/fake_exec.go @@ -0,0 +1,92 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package exec + +import ( + "fmt" +) + +// A simple scripted Interface type. +type FakeExec struct { + CommandScript []FakeCommandAction + CommandCalls int +} + +type FakeCommandAction func(cmd string, args ...string) Cmd + +func (fake *FakeExec) Command(cmd string, args ...string) Cmd { + if fake.CommandCalls > len(fake.CommandScript)-1 { + panic("ran out of Command() actions") + } + i := fake.CommandCalls + fake.CommandCalls++ + return fake.CommandScript[i](cmd, args...) +} + +// A simple scripted Cmd type. +type FakeCmd struct { + Argv []string + CombinedOutputScript []FakeCombinedOutputAction + CombinedOutputCalls int + CombinedOutputLog [][]string + Dirs []string +} + +func InitFakeCmd(fake *FakeCmd, cmd string, args ...string) Cmd { + fake.Argv = append([]string{cmd}, args...) + return fake +} + +type FakeCombinedOutputAction func() ([]byte, error) + +func (fake *FakeCmd) SetDir(dir string) { + fake.Dirs = append(fake.Dirs, dir) +} + +func (fake *FakeCmd) CombinedOutput() ([]byte, error) { + if fake.CombinedOutputCalls > len(fake.CombinedOutputScript)-1 { + panic("ran out of CombinedOutput() actions") + } + if fake.CombinedOutputLog == nil { + fake.CombinedOutputLog = [][]string{} + } + i := fake.CombinedOutputCalls + fake.CombinedOutputLog = append(fake.CombinedOutputLog, append([]string{}, fake.Argv...)) + fake.CombinedOutputCalls++ + return fake.CombinedOutputScript[i]() +} + +// A simple fake ExitError type. +type FakeExitError struct { + Status int +} + +func (fake *FakeExitError) String() string { + return fmt.Sprintf("exit %d", fake.Status) +} + +func (fake *FakeExitError) Error() string { + return fake.String() +} + +func (fake *FakeExitError) Exited() bool { + return true +} + +func (fake *FakeExitError) ExitStatus() int { + return fake.Status +} diff --git a/pkg/util/iptables/iptables_test.go b/pkg/util/iptables/iptables_test.go index 9270a6e31aa..f027ab351b0 100644 --- a/pkg/util/iptables/iptables_test.go +++ b/pkg/util/iptables/iptables_test.go @@ -17,95 +17,28 @@ limitations under the License. package iptables import ( - "fmt" "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" - utilexec "github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec" ) -// A simple scripted utilexec.Interface type. -type fakeExec struct { - commandScript []fakeCommandAction - commandCalls int -} - -type fakeCommandAction func(cmd string, args ...string) utilexec.Cmd - -func (fake *fakeExec) Command(cmd string, args ...string) utilexec.Cmd { - if fake.commandCalls > len(fake.commandScript)-1 { - panic("ran out of Command() actions") - } - i := fake.commandCalls - fake.commandCalls++ - return fake.commandScript[i](cmd, args...) -} - -// A simple scripted utilexec.Cmd type. -type fakeCmd struct { - argv []string - combinedOutputScript []fakeCombinedOutputAction - combinedOutputCalls int - combinedOutputLog [][]string -} - -func initFakeCmd(fake *fakeCmd, cmd string, args ...string) utilexec.Cmd { - fake.argv = append([]string{cmd}, args...) - return fake -} - -type fakeCombinedOutputAction func() ([]byte, error) - -func (fake *fakeCmd) CombinedOutput() ([]byte, error) { - if fake.combinedOutputCalls > len(fake.combinedOutputScript)-1 { - panic("ran out of CombinedOutput() actions") - } - if fake.combinedOutputLog == nil { - fake.combinedOutputLog = [][]string{} - } - i := fake.combinedOutputCalls - fake.combinedOutputLog = append(fake.combinedOutputLog, append([]string{}, fake.argv...)) - fake.combinedOutputCalls++ - return fake.combinedOutputScript[i]() -} - -// A simple fake utilexec.ExitError type. -type fakeExitError struct { - status int -} - -func (fake *fakeExitError) String() string { - return fmt.Sprintf("exit %d", fake.status) -} - -func (fake *fakeExitError) Error() string { - return fake.String() -} - -func (fake *fakeExitError) Exited() bool { - return true -} - -func (fake *fakeExitError) ExitStatus() int { - return fake.status -} - func TestEnsureChain(t *testing.T) { - fcmd := fakeCmd{ - combinedOutputScript: []fakeCombinedOutputAction{ + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ // Success. func() ([]byte, error) { return []byte{}, nil }, // Exists. - func() ([]byte, error) { return nil, &fakeExitError{1} }, + func() ([]byte, error) { return nil, &exec.FakeExitError{1} }, // Failure. - func() ([]byte, error) { return nil, &fakeExitError{2} }, + func() ([]byte, error) { return nil, &exec.FakeExitError{2} }, }, } - fexec := fakeExec{ - commandScript: []fakeCommandAction{ - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } runner := New(&fexec) @@ -117,11 +50,11 @@ func TestEnsureChain(t *testing.T) { if exists { t.Errorf("expected exists = false") } - if fcmd.combinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.combinedOutputCalls) + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) } - if !util.NewStringSet(fcmd.combinedOutputLog[0]...).HasAll("iptables", "-t", "nat", "-N", "FOOBAR") { - t.Errorf("wrong CombinedOutput() log, got %s", fcmd.combinedOutputLog[0]) + if !util.NewStringSet(fcmd.CombinedOutputLog[0]...).HasAll("iptables", "-t", "nat", "-N", "FOOBAR") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) } // Exists. exists, err = runner.EnsureChain(TableNAT, Chain("FOOBAR")) @@ -139,18 +72,18 @@ func TestEnsureChain(t *testing.T) { } func TestFlushChain(t *testing.T) { - fcmd := fakeCmd{ - combinedOutputScript: []fakeCombinedOutputAction{ + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ // Success. func() ([]byte, error) { return []byte{}, nil }, // Failure. - func() ([]byte, error) { return nil, &fakeExitError{1} }, + func() ([]byte, error) { return nil, &exec.FakeExitError{1} }, }, } - fexec := fakeExec{ - commandScript: []fakeCommandAction{ - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } runner := New(&fexec) @@ -159,11 +92,11 @@ func TestFlushChain(t *testing.T) { if err != nil { t.Errorf("expected success, got %+v", err) } - if fcmd.combinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.combinedOutputCalls) + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) } - if !util.NewStringSet(fcmd.combinedOutputLog[0]...).HasAll("iptables", "-t", "nat", "-F", "FOOBAR") { - t.Errorf("wrong CombinedOutput() log, got %s", fcmd.combinedOutputLog[0]) + if !util.NewStringSet(fcmd.CombinedOutputLog[0]...).HasAll("iptables", "-t", "nat", "-F", "FOOBAR") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) } // Failure. err = runner.FlushChain(TableNAT, Chain("FOOBAR")) @@ -173,16 +106,16 @@ func TestFlushChain(t *testing.T) { } func TestEnsureRuleAlreadyExists(t *testing.T) { - fcmd := fakeCmd{ - combinedOutputScript: []fakeCombinedOutputAction{ + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ // Success. func() ([]byte, error) { return []byte{}, nil }, }, } - fexec := fakeExec{ - commandScript: []fakeCommandAction{ + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ // The first Command() call is checking the rule. Success of that exec means "done". - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } runner := New(&fexec) @@ -193,28 +126,28 @@ func TestEnsureRuleAlreadyExists(t *testing.T) { if !exists { t.Errorf("expected exists = true") } - if fcmd.combinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.combinedOutputCalls) + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) } - if !util.NewStringSet(fcmd.combinedOutputLog[0]...).HasAll("iptables", "-t", "nat", "-C", "OUTPUT", "abc", "123") { - t.Errorf("wrong CombinedOutput() log, got %s", fcmd.combinedOutputLog[0]) + if !util.NewStringSet(fcmd.CombinedOutputLog[0]...).HasAll("iptables", "-t", "nat", "-C", "OUTPUT", "abc", "123") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) } } func TestEnsureRuleNew(t *testing.T) { - fcmd := fakeCmd{ - combinedOutputScript: []fakeCombinedOutputAction{ + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ // Status 1 on the first call. - func() ([]byte, error) { return nil, &fakeExitError{1} }, + func() ([]byte, error) { return nil, &exec.FakeExitError{1} }, // Success on the second call. func() ([]byte, error) { return []byte{}, nil }, }, } - fexec := fakeExec{ - commandScript: []fakeCommandAction{ + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ // The first Command() call is checking the rule. Failure of that means create it. - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } runner := New(&fexec) @@ -225,25 +158,25 @@ func TestEnsureRuleNew(t *testing.T) { if exists { t.Errorf("expected exists = false") } - if fcmd.combinedOutputCalls != 2 { - t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.combinedOutputCalls) + if fcmd.CombinedOutputCalls != 2 { + t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) } - if !util.NewStringSet(fcmd.combinedOutputLog[1]...).HasAll("iptables", "-t", "nat", "-A", "OUTPUT", "abc", "123") { - t.Errorf("wrong CombinedOutput() log, got %s", fcmd.combinedOutputLog[1]) + if !util.NewStringSet(fcmd.CombinedOutputLog[1]...).HasAll("iptables", "-t", "nat", "-A", "OUTPUT", "abc", "123") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[1]) } } func TestEnsureRuleErrorChecking(t *testing.T) { - fcmd := fakeCmd{ - combinedOutputScript: []fakeCombinedOutputAction{ + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ // Status 2 on the first call. - func() ([]byte, error) { return nil, &fakeExitError{2} }, + func() ([]byte, error) { return nil, &exec.FakeExitError{2} }, }, } - fexec := fakeExec{ - commandScript: []fakeCommandAction{ + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ // The first Command() call is checking the rule. Failure of that means create it. - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } runner := New(&fexec) @@ -251,25 +184,25 @@ func TestEnsureRuleErrorChecking(t *testing.T) { if err == nil { t.Errorf("expected failure") } - if fcmd.combinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.combinedOutputCalls) + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) } } func TestEnsureRuleErrorCreating(t *testing.T) { - fcmd := fakeCmd{ - combinedOutputScript: []fakeCombinedOutputAction{ + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ // Status 1 on the first call. - func() ([]byte, error) { return nil, &fakeExitError{1} }, + func() ([]byte, error) { return nil, &exec.FakeExitError{1} }, // Status 1 on the second call. - func() ([]byte, error) { return nil, &fakeExitError{1} }, + func() ([]byte, error) { return nil, &exec.FakeExitError{1} }, }, } - fexec := fakeExec{ - commandScript: []fakeCommandAction{ + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ // The first Command() call is checking the rule. Failure of that means create it. - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } runner := New(&fexec) @@ -277,22 +210,22 @@ func TestEnsureRuleErrorCreating(t *testing.T) { if err == nil { t.Errorf("expected failure") } - if fcmd.combinedOutputCalls != 2 { - t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.combinedOutputCalls) + if fcmd.CombinedOutputCalls != 2 { + t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) } } func TestDeleteRuleAlreadyExists(t *testing.T) { - fcmd := fakeCmd{ - combinedOutputScript: []fakeCombinedOutputAction{ + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ // Status 1 on the first call. - func() ([]byte, error) { return nil, &fakeExitError{1} }, + func() ([]byte, error) { return nil, &exec.FakeExitError{1} }, }, } - fexec := fakeExec{ - commandScript: []fakeCommandAction{ + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ // The first Command() call is checking the rule. Failure of that exec means "does not exist". - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } runner := New(&fexec) @@ -300,28 +233,28 @@ func TestDeleteRuleAlreadyExists(t *testing.T) { if err != nil { t.Errorf("expected success, got %+v", err) } - if fcmd.combinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.combinedOutputCalls) + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) } - if !util.NewStringSet(fcmd.combinedOutputLog[0]...).HasAll("iptables", "-t", "nat", "-C", "OUTPUT", "abc", "123") { - t.Errorf("wrong CombinedOutput() log, got %s", fcmd.combinedOutputLog[0]) + if !util.NewStringSet(fcmd.CombinedOutputLog[0]...).HasAll("iptables", "-t", "nat", "-C", "OUTPUT", "abc", "123") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) } } func TestDeleteRuleNew(t *testing.T) { - fcmd := fakeCmd{ - combinedOutputScript: []fakeCombinedOutputAction{ + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ // Success on the first call. func() ([]byte, error) { return []byte{}, nil }, // Success on the second call. func() ([]byte, error) { return []byte{}, nil }, }, } - fexec := fakeExec{ - commandScript: []fakeCommandAction{ + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ // The first Command() call is checking the rule. Success of that means delete it. - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } runner := New(&fexec) @@ -329,25 +262,25 @@ func TestDeleteRuleNew(t *testing.T) { if err != nil { t.Errorf("expected success, got %+v", err) } - if fcmd.combinedOutputCalls != 2 { - t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.combinedOutputCalls) + if fcmd.CombinedOutputCalls != 2 { + t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) } - if !util.NewStringSet(fcmd.combinedOutputLog[1]...).HasAll("iptables", "-t", "nat", "-D", "OUTPUT", "abc", "123") { - t.Errorf("wrong CombinedOutput() log, got %s", fcmd.combinedOutputLog[1]) + if !util.NewStringSet(fcmd.CombinedOutputLog[1]...).HasAll("iptables", "-t", "nat", "-D", "OUTPUT", "abc", "123") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[1]) } } func TestDeleteRuleErrorChecking(t *testing.T) { - fcmd := fakeCmd{ - combinedOutputScript: []fakeCombinedOutputAction{ + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ // Status 2 on the first call. - func() ([]byte, error) { return nil, &fakeExitError{2} }, + func() ([]byte, error) { return nil, &exec.FakeExitError{2} }, }, } - fexec := fakeExec{ - commandScript: []fakeCommandAction{ + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ // The first Command() call is checking the rule. Failure of that means create it. - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } runner := New(&fexec) @@ -355,25 +288,25 @@ func TestDeleteRuleErrorChecking(t *testing.T) { if err == nil { t.Errorf("expected failure") } - if fcmd.combinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.combinedOutputCalls) + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) } } func TestDeleteRuleErrorCreating(t *testing.T) { - fcmd := fakeCmd{ - combinedOutputScript: []fakeCombinedOutputAction{ + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ // Success on the first call. func() ([]byte, error) { return []byte{}, nil }, // Status 1 on the second call. - func() ([]byte, error) { return nil, &fakeExitError{1} }, + func() ([]byte, error) { return nil, &exec.FakeExitError{1} }, }, } - fexec := fakeExec{ - commandScript: []fakeCommandAction{ + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ // The first Command() call is checking the rule. Success of that means delete it. - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, - func(cmd string, args ...string) utilexec.Cmd { return initFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } runner := New(&fexec) @@ -381,7 +314,7 @@ func TestDeleteRuleErrorCreating(t *testing.T) { if err == nil { t.Errorf("expected failure") } - if fcmd.combinedOutputCalls != 2 { - t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.combinedOutputCalls) + if fcmd.CombinedOutputCalls != 2 { + t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) } } diff --git a/pkg/volume/volume.go b/pkg/volume/volume.go index 99d5adc3e37..24dfdcf5127 100644 --- a/pkg/volume/volume.go +++ b/pkg/volume/volume.go @@ -18,12 +18,14 @@ package volume import ( "errors" + "fmt" "io/ioutil" "os" "path" "strconv" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec" "github.com/golang/glog" ) @@ -82,6 +84,82 @@ func (hostVol *HostDir) GetPath() string { return hostVol.Path } +type execInterface interface { + ExecCommand(cmd []string, dir string) ([]byte, error) +} + +type GitDir struct { + Source string + Revision string + PodID string + RootDir string + Name string + exec exec.Interface +} + +func newGitRepo(volume *api.Volume, podID, rootDir string) *GitDir { + return &GitDir{ + Source: volume.Source.GitRepo.Repository, + Revision: volume.Source.GitRepo.Revision, + PodID: podID, + RootDir: rootDir, + Name: volume.Name, + exec: exec.New(), + } +} + +func (g *GitDir) ExecCommand(command string, args []string, dir string) ([]byte, error) { + cmd := g.exec.Command(command, args...) + cmd.SetDir(dir) + return cmd.CombinedOutput() +} + +func (g *GitDir) SetUp() error { + volumePath := g.GetPath() + if err := os.MkdirAll(volumePath, 0750); err != nil { + return err + } + if _, err := g.ExecCommand("git", []string{"clone", g.Source}, g.GetPath()); err != nil { + return err + } + files, err := ioutil.ReadDir(g.GetPath()) + if err != nil { + return err + } + if len(g.Revision) == 0 { + return nil + } + + if len(files) != 1 { + return fmt.Errorf("Unexpected directory contents: %v", files) + } + dir := path.Join(g.GetPath(), files[0].Name()) + if _, err := g.ExecCommand("git", []string{"checkout", g.Revision}, dir); err != nil { + return err + } + if _, err := g.ExecCommand("git", []string{"reset", "--hard"}, dir); err != nil { + return err + } + return nil +} + +func (g *GitDir) GetPath() string { + return path.Join(g.RootDir, g.PodID, "volumes", "git", g.Name) +} + +// TearDown simply deletes everything in the directory. +func (g *GitDir) TearDown() error { + tmpDir, err := renameDirectory(g.GetPath(), g.Name+"~deleting") + if err != nil { + return err + } + err = os.RemoveAll(tmpDir) + if err != nil { + return err + } + return nil +} + // EmptyDir volumes are temporary directories exposed to the pod. // These do not persist beyond the lifetime of a pod. type EmptyDir struct { @@ -93,20 +171,15 @@ type EmptyDir struct { // SetUp creates new directory. func (emptyDir *EmptyDir) SetUp() error { path := emptyDir.GetPath() - err := os.MkdirAll(path, 0750) - if err != nil { - return err - } - return nil + return os.MkdirAll(path, 0750) } func (emptyDir *EmptyDir) GetPath() string { return path.Join(emptyDir.RootDir, emptyDir.PodID, "volumes", "empty", emptyDir.Name) } -func (emptyDir *EmptyDir) renameDirectory() (string, error) { - oldPath := emptyDir.GetPath() - newPath, err := ioutil.TempDir(path.Dir(oldPath), emptyDir.Name+".deleting~") +func renameDirectory(oldPath, newName string) (string, error) { + newPath, err := ioutil.TempDir(path.Dir(oldPath), newName) if err != nil { return "", err } @@ -119,7 +192,7 @@ func (emptyDir *EmptyDir) renameDirectory() (string, error) { // TearDown simply deletes everything in the directory. func (emptyDir *EmptyDir) TearDown() error { - tmpDir, err := emptyDir.renameDirectory() + tmpDir, err := renameDirectory(emptyDir.GetPath(), emptyDir.Name+".deleting~") if err != nil { return err } @@ -279,6 +352,8 @@ func CreateVolumeBuilder(volume *api.Volume, podID string, rootDir string) (Buil if err != nil { return nil, err } + } else if source.GitRepo != nil { + vol = newGitRepo(volume, podID, rootDir) } else { return nil, ErrUnsupportedVolumeType } @@ -297,6 +372,12 @@ func CreateVolumeCleaner(kind string, name string, podID string, rootDir string) RootDir: rootDir, util: &GCEDiskUtil{}, mounter: &DiskMounter{}}, nil + case "git": + return &GitDir{ + Name: name, + PodID: podID, + RootDir: rootDir, + }, nil default: return nil, ErrUnsupportedVolumeType } diff --git a/pkg/volume/volume_test.go b/pkg/volume/volume_test.go index a62bda30db1..7c150b52d4f 100644 --- a/pkg/volume/volume_test.go +++ b/pkg/volume/volume_test.go @@ -24,6 +24,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec" ) type MockDiskUtil struct{} @@ -224,3 +225,71 @@ func TestGetActiveVolumes(t *testing.T) { } } } + +type fakeExec struct { + cmds [][]string + dirs []string + data []byte + err error + action func([]string, string) +} + +func (f *fakeExec) ExecCommand(cmd []string, dir string) ([]byte, error) { + f.cmds = append(f.cmds, cmd) + f.dirs = append(f.dirs, dir) + f.action(cmd, dir) + return f.data, f.err +} + +func TestGitVolume(t *testing.T) { + var fcmd exec.FakeCmd + fcmd = exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ + func() ([]byte, error) { + os.MkdirAll(path.Join(fcmd.Dirs[0], "kubernetes"), 0750) + return []byte{}, nil + }, + func() ([]byte, error) { return []byte{}, nil }, + func() ([]byte, error) { return []byte{}, nil }, + }, + } + fake := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + dir := os.TempDir() + "/git" + g := GitDir{ + Source: "https://github.com/GoogleCloudPlatform/kubernetes.git", + Revision: "2a30ce65c5ab586b98916d83385c5983edd353a1", + PodID: "foo", + RootDir: dir, + Name: "test-pod", + exec: &fake, + } + err := g.SetUp() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + expectedCmds := [][]string{ + {"git", "clone", g.Source}, + {"git", "checkout", g.Revision}, + {"git", "reset", "--hard"}, + } + if fake.CommandCalls != len(expectedCmds) { + t.Errorf("unexpected command calls: expected 3, saw: %d", fake.CommandCalls) + } + if !reflect.DeepEqual(expectedCmds, fcmd.CombinedOutputLog) { + t.Errorf("unexpected commands: %v, expected: %v", fcmd.CombinedOutputLog, expectedCmds) + } + expectedDirs := []string{g.GetPath(), g.GetPath() + "/kubernetes", g.GetPath() + "/kubernetes"} + if len(fcmd.Dirs) != 3 || !reflect.DeepEqual(expectedDirs, fcmd.Dirs) { + t.Errorf("unexpected directories: %v, expected: %v", fcmd.Dirs, expectedDirs) + } + err = g.TearDown() + if err != nil { + t.Errorf("unexpected error: %v", err) + } +}