From c1abc67fa8070b6cf742aa59f16e302f04017022 Mon Sep 17 00:00:00 2001 From: Josh Curl Date: Mon, 7 Nov 2016 11:36:37 -0800 Subject: [PATCH] Support string values for runcmd --- config/types.go | 15 ++++--- config/yaml/command.go | 44 +++++++++++++++++++ .../configuration/running-commands/index.md | 5 ++- tests/assets/test_26/cloud-config.yml | 1 + tests/start_commands_test.go | 2 +- util/util.go | 12 +++-- 6 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 config/yaml/command.go diff --git a/config/types.go b/config/types.go index 2039daed..c22ca59c 100644 --- a/config/types.go +++ b/config/types.go @@ -7,6 +7,7 @@ import ( "github.com/coreos/coreos-cloudinit/config" "github.com/docker/engine-api/types" composeConfig "github.com/docker/libcompose/config" + "github.com/rancher/os/config/yaml" ) const ( @@ -83,13 +84,13 @@ type Repository struct { type Repositories map[string]Repository type CloudConfig struct { - SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"` - WriteFiles []File `yaml:"write_files"` - Hostname string `yaml:"hostname"` - Mounts [][]string `yaml:"mounts,omitempty"` - Rancher RancherConfig `yaml:"rancher,omitempty"` - Runcmd [][]string `yaml:"runcmd,omitempty"` - Bootcmd [][]string `yaml:"bootcmd,omitempty"` + SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"` + WriteFiles []File `yaml:"write_files"` + Hostname string `yaml:"hostname"` + Mounts [][]string `yaml:"mounts,omitempty"` + Rancher RancherConfig `yaml:"rancher,omitempty"` + Runcmd []yaml.StringandSlice `yaml:"runcmd,omitempty"` + Bootcmd []yaml.StringandSlice `yaml:"bootcmd,omitempty"` } type File struct { diff --git a/config/yaml/command.go b/config/yaml/command.go new file mode 100644 index 00000000..5d47abee --- /dev/null +++ b/config/yaml/command.go @@ -0,0 +1,44 @@ +package yaml + +import "fmt" + +// StringandSlice stores either a string or slice depending on original type +// Differs from libcompose Stringorslice by being able to determine original type +type StringandSlice struct { + StringValue string + SliceValue []string +} + +// UnmarshalYAML implements the Unmarshaller interface. +// TODO: this needs to be ported to go-yaml +func (s *StringandSlice) UnmarshalYAML(tag string, value interface{}) error { + switch value := value.(type) { + case []interface{}: + parts, err := toStrings(value) + if err != nil { + return err + } + s.SliceValue = parts + case string: + s.StringValue = value + default: + return fmt.Errorf("Failed to unmarshal StringandSlice: %#v", value) + } + return nil +} + +// TODO: use this function from libcompose +func toStrings(s []interface{}) ([]string, error) { + if len(s) == 0 { + return nil, nil + } + r := make([]string, len(s)) + for k, v := range s { + if sv, ok := v.(string); ok { + r[k] = sv + } else { + return nil, fmt.Errorf("Cannot unmarshal '%v' of type %T into a string value", v, v) + } + } + return r, nil +} diff --git a/docs/os/configuration/running-commands/index.md b/docs/os/configuration/running-commands/index.md index 70177f61..b5e46134 100644 --- a/docs/os/configuration/running-commands/index.md +++ b/docs/os/configuration/running-commands/index.md @@ -7,12 +7,13 @@ layout: os-default ## Running Commands --- -You can automate running commands on boot using the `runcmd` cloud-config directive. Commands must be specified in a list syntax as in the following example. +You can automate running commands on boot using the `runcmd` cloud-config directive. Commands can be specified as either a list or a string. In the latter case, the command is executed with `sh`. ```yaml #cloud-config runcmd: -- [ touch, /home/rancher/test ] +- [ touch, /home/rancher/test1 ] +- echo "test" > /home/rancher/test2 ``` Commands specified using `runcmd` will be executed within the context of the `console` container. More details on the ordering of commands run in the `console` container can be found [here]({{site.baseurl}}/os/system-services/built-in-system-services/#console). diff --git a/tests/assets/test_26/cloud-config.yml b/tests/assets/test_26/cloud-config.yml index b74010dc..f4f15f8b 100644 --- a/tests/assets/test_26/cloud-config.yml +++ b/tests/assets/test_26/cloud-config.yml @@ -27,5 +27,6 @@ runcmd: - [] - [ test ] - [ touch, /home/rancher/test2 ] +- echo "" > /home/rancher/test5 ssh_authorized_keys: - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC85w9stZyiLQp/DkVO6fqwiShYcj1ClKdtCqgHtf+PLpJkFReSFu8y21y+ev09gsSMRRrjF7yt0pUHV6zncQhVeqsZtgc5WbELY2DOYUGmRn/CCvPbXovoBrQjSorqlBmpuPwsStYLr92Xn+VVsMNSUIegHY22DphGbDKG85vrKB8HxUxGIDxFBds/uE8FhSy+xsoyT/jUZDK6pgq2HnGl6D81ViIlKecpOpWlW3B+fea99ADNyZNVvDzbHE5pcI3VRw8u59WmpWOUgT6qacNVACl8GqpBvQk8sw7O/X9DSZHCKafeD9G5k+GYbAUz92fKWrx/lOXfUXPS3+c8dRIF diff --git a/tests/start_commands_test.go b/tests/start_commands_test.go index 1a393ddd..74871616 100644 --- a/tests/start_commands_test.go +++ b/tests/start_commands_test.go @@ -9,7 +9,7 @@ import ( func (s *QemuSuite) TestStartCommands(c *C) { s.RunQemu(c, "--cloud-config", "./tests/assets/test_26/cloud-config.yml") - for i := 1; i < 5; i++ { + for i := 1; i < 6; i++ { s.CheckCall(c, fmt.Sprintf("ls /home/rancher | grep test%d", i)) } s.CheckCall(c, "docker ps | grep nginx") diff --git a/util/util.go b/util/util.go index e89064f8..a631e23d 100644 --- a/util/util.go +++ b/util/util.go @@ -12,6 +12,7 @@ import ( "strings" yaml "github.com/cloudfoundry-incubator/candiedyaml" + osYaml "github.com/rancher/os/config/yaml" log "github.com/Sirupsen/logrus" ) @@ -269,13 +270,16 @@ func RunScript(path string) error { return cmd.Run() } -func RunCommandSequence(commandSequence [][]string) { +func RunCommandSequence(commandSequence []osYaml.StringandSlice) { for _, command := range commandSequence { - if len(command) == 0 { + var cmd *exec.Cmd + if command.StringValue != "" { + cmd = exec.Command("sh", "-c", command.StringValue) + } else if len(command.SliceValue) > 0 { + cmd = exec.Command(command.SliceValue[0], command.SliceValue[1:]...) + } else { continue } - - cmd := exec.Command(command[0], command[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil {