diff --git a/hack/vendor.sh b/hack/vendor.sh index 2d598d9fc5..df1c45721b 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -13,6 +13,7 @@ clone git github.com/go-check/check 11d3bc7aa68e238947792f30573146a3231fc0f1 clone git github.com/gorilla/context 14f550f51a clone git github.com/gorilla/mux e444e69cbd clone git github.com/kr/pty 5cf931ef8f +clone git github.com/mattn/go-shellwords v1.0.0 clone git github.com/mattn/go-sqlite3 v1.1.0 clone git github.com/microsoft/hcsshim de43b42b5ce14dfdcbeedb0628b0032174d89caa clone git github.com/mistifyio/go-zfs v2.1.1 diff --git a/pkg/parsers/operatingsystem/operatingsystem_linux.go b/pkg/parsers/operatingsystem/operatingsystem_linux.go index ca8ea8f052..f6f0a7289f 100644 --- a/pkg/parsers/operatingsystem/operatingsystem_linux.go +++ b/pkg/parsers/operatingsystem/operatingsystem_linux.go @@ -3,9 +3,14 @@ package operatingsystem import ( + "bufio" "bytes" - "errors" + "fmt" "io/ioutil" + "os" + "strings" + + "github.com/mattn/go-shellwords" ) var ( @@ -18,15 +23,34 @@ var ( // GetOperatingSystem gets the name of the current operating system. func GetOperatingSystem() (string, error) { - b, err := ioutil.ReadFile(etcOsRelease) + osReleaseFile, err := os.Open(etcOsRelease) if err != nil { return "", err } - if i := bytes.Index(b, []byte("PRETTY_NAME")); i >= 0 { - b = b[i+13:] - return string(b[:bytes.IndexByte(b, '"')]), nil + defer osReleaseFile.Close() + + var prettyName string + scanner := bufio.NewScanner(osReleaseFile) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "PRETTY_NAME=") { + data := strings.SplitN(line, "=", 2) + prettyNames, err := shellwords.Parse(data[1]) + if err != nil { + return "", fmt.Errorf("PRETTY_NAME is invalid: %s", err.Error()) + } + if len(prettyNames) != 1 { + return "", fmt.Errorf("PRETTY_NAME needs to be enclosed by quotes if they have spaces: %s", data[1]) + } + prettyName = prettyNames[0] + } } - return "", errors.New("PRETTY_NAME not found") + if prettyName != "" { + return prettyName, nil + } + // If not set, defaults to PRETTY_NAME="Linux" + // c.f. http://www.freedesktop.org/software/systemd/man/os-release.html + return "Linux", nil } // IsContainerized returns true if we are running inside a container. diff --git a/pkg/parsers/operatingsystem/operatingsystem_unix_test.go b/pkg/parsers/operatingsystem/operatingsystem_unix_test.go index 3bc88e1f46..1bad093c95 100644 --- a/pkg/parsers/operatingsystem/operatingsystem_unix_test.go +++ b/pkg/parsers/operatingsystem/operatingsystem_unix_test.go @@ -10,9 +10,74 @@ import ( ) func TestGetOperatingSystem(t *testing.T) { - var ( - backup = etcOsRelease - ubuntuTrusty = []byte(`NAME="Ubuntu" + var backup = etcOsRelease + + invalids := []struct { + content string + errorExpected string + }{ + { + `PRETTY_NAME=Source Mage GNU/Linux +PRETTY_NAME=Ubuntu 14.04.LTS`, + "PRETTY_NAME needs to be enclosed by quotes if they have spaces: Source Mage GNU/Linux", + }, + { + `PRETTY_NAME="Ubuntu Linux +PRETTY_NAME=Ubuntu 14.04.LTS`, + "PRETTY_NAME is invalid: invalid command line string", + }, + { + `PRETTY_NAME=Ubuntu' +PRETTY_NAME=Ubuntu 14.04.LTS`, + "PRETTY_NAME is invalid: invalid command line string", + }, + { + `PRETTY_NAME' +PRETTY_NAME=Ubuntu 14.04.LTS`, + "PRETTY_NAME needs to be enclosed by quotes if they have spaces: Ubuntu 14.04.LTS", + }, + } + + valids := []struct { + content string + expected string + }{ + { + `NAME="Ubuntu" +PRETTY_NAME_AGAIN="Ubuntu 14.04.LTS" +VERSION="14.04, Trusty Tahr" +ID=ubuntu +ID_LIKE=debian +VERSION_ID="14.04" +HOME_URL="http://www.ubuntu.com/" +SUPPORT_URL="http://help.ubuntu.com/" +BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`, + "Linux", + }, + { + `NAME="Ubuntu" +VERSION="14.04, Trusty Tahr" +ID=ubuntu +ID_LIKE=debian +VERSION_ID="14.04" +HOME_URL="http://www.ubuntu.com/" +SUPPORT_URL="http://help.ubuntu.com/" +BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`, + "Linux", + }, + { + `NAME=Gentoo +ID=gentoo +PRETTY_NAME="Gentoo/Linux" +ANSI_COLOR="1;32" +HOME_URL="http://www.gentoo.org/" +SUPPORT_URL="http://www.gentoo.org/main/en/support.xml" +BUG_REPORT_URL="https://bugs.gentoo.org/" +`, + "Gentoo/Linux", + }, + { + `NAME="Ubuntu" VERSION="14.04, Trusty Tahr" ID=ubuntu ID_LIKE=debian @@ -20,24 +85,28 @@ PRETTY_NAME="Ubuntu 14.04 LTS" VERSION_ID="14.04" HOME_URL="http://www.ubuntu.com/" SUPPORT_URL="http://help.ubuntu.com/" -BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`) - gentoo = []byte(`NAME=Gentoo -ID=gentoo -PRETTY_NAME="Gentoo/Linux" -ANSI_COLOR="1;32" -HOME_URL="http://www.gentoo.org/" -SUPPORT_URL="http://www.gentoo.org/main/en/support.xml" -BUG_REPORT_URL="https://bugs.gentoo.org/" -`) - noPrettyName = []byte(`NAME="Ubuntu" +BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`, + "Ubuntu 14.04 LTS", + }, + { + `NAME="Ubuntu" VERSION="14.04, Trusty Tahr" ID=ubuntu ID_LIKE=debian -VERSION_ID="14.04" -HOME_URL="http://www.ubuntu.com/" -SUPPORT_URL="http://help.ubuntu.com/" -BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`) - ) +PRETTY_NAME='Ubuntu 14.04 LTS'`, + "Ubuntu 14.04 LTS", + }, + { + `PRETTY_NAME=Source +NAME="Source Mage"`, + "Source", + }, + { + `PRETTY_NAME=Source +PRETTY_NAME="Source Mage"`, + "Source Mage", + }, + } dir := os.TempDir() etcOsRelease = filepath.Join(dir, "etcOsRelease") @@ -47,21 +116,23 @@ BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`) etcOsRelease = backup }() - for expect, osRelease := range map[string][]byte{ - "Ubuntu 14.04 LTS": ubuntuTrusty, - "Gentoo/Linux": gentoo, - "": noPrettyName, - } { - if err := ioutil.WriteFile(etcOsRelease, osRelease, 0600); err != nil { + for _, elt := range invalids { + if err := ioutil.WriteFile(etcOsRelease, []byte(elt.content), 0600); err != nil { t.Fatalf("failed to write to %s: %v", etcOsRelease, err) } s, err := GetOperatingSystem() - if s != expect { - if expect == "" { - t.Fatalf("Expected error 'PRETTY_NAME not found', but got %v", err) - } else { - t.Fatalf("Expected '%s', but got '%s'. Err=%v", expect, s, err) - } + if err == nil || err.Error() != elt.errorExpected { + t.Fatalf("Expected an error %q, got %q (err: %v)", elt.errorExpected, s, err) + } + } + + for _, elt := range valids { + if err := ioutil.WriteFile(etcOsRelease, []byte(elt.content), 0600); err != nil { + t.Fatalf("failed to write to %s: %v", etcOsRelease, err) + } + s, err := GetOperatingSystem() + if err != nil || s != elt.expected { + t.Fatalf("Expected %q, got %q (err: %v)", elt.expected, s, err) } } } diff --git a/vendor/src/github.com/mattn/go-shellwords/.travis.yml b/vendor/src/github.com/mattn/go-shellwords/.travis.yml new file mode 100644 index 0000000000..d0796cea69 --- /dev/null +++ b/vendor/src/github.com/mattn/go-shellwords/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - tip +before_install: + - go get github.com/axw/gocov/gocov + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -repotoken 2FMhp57u8LcstKL9B190fLTcEnBtAAiEL diff --git a/vendor/src/github.com/mattn/go-shellwords/README.md b/vendor/src/github.com/mattn/go-shellwords/README.md new file mode 100644 index 0000000000..56f357fad7 --- /dev/null +++ b/vendor/src/github.com/mattn/go-shellwords/README.md @@ -0,0 +1,47 @@ +# go-shellwords + +[![Coverage Status](https://coveralls.io/repos/mattn/go-shellwords/badge.png?branch=master)](https://coveralls.io/r/mattn/go-shellwords?branch=master) +[![Build Status](https://travis-ci.org/mattn/go-shellwords.svg?branch=master)](https://travis-ci.org/mattn/go-shellwords) + +Parse line as shell words. + +## Usage + +```go +args, err := shellwords.Parse("./foo --bar=baz") +// args should be ["./foo", "--bar=baz"] +``` + +```go +os.Setenv("FOO", "bar") +p := shellwords.NewParser() +p.ParseEnv = true +args, err := p.Parse("./foo $FOO") +// args should be ["./foo", "bar"] +``` + +```go +p := shellwords.NewParser() +p.ParseBacktick = true +args, err := p.Parse("./foo `echo $SHELL`") +// args should be ["./foo", "/bin/bash"] +``` + +```go +shellwords.ParseBacktick = true +p := shellwords.NewParser() +args, err := p.Parse("./foo `echo $SHELL`") +// args should be ["./foo", "/bin/bash"] +``` + +# Thanks + +This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine). + +# License + +under the MIT License: http://mattn.mit-license.org/2014 + +# Author + +Yasuhiro Matsumoto (a.k.a mattn) diff --git a/vendor/src/github.com/mattn/go-shellwords/shellwords.go b/vendor/src/github.com/mattn/go-shellwords/shellwords.go new file mode 100644 index 0000000000..1abaa6c9df --- /dev/null +++ b/vendor/src/github.com/mattn/go-shellwords/shellwords.go @@ -0,0 +1,134 @@ +package shellwords + +import ( + "errors" + "os" + "regexp" + "strings" +) + +var ( + ParseEnv bool = false + ParseBacktick bool = false +) + +var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`) + +func isSpace(r rune) bool { + switch r { + case ' ', '\t', '\r', '\n': + return true + } + return false +} + +func replaceEnv(s string) string { + return envRe.ReplaceAllStringFunc(s, func(s string) string { + s = s[1:] + if s[0] == '{' { + s = s[1 : len(s)-1] + } + return os.Getenv(s) + }) +} + +type Parser struct { + ParseEnv bool + ParseBacktick bool +} + +func NewParser() *Parser { + return &Parser{ParseEnv, ParseBacktick} +} + +func (p *Parser) Parse(line string) ([]string, error) { + line = strings.TrimSpace(line) + + args := []string{} + buf := "" + var escaped, doubleQuoted, singleQuoted, backQuote bool + backtick := "" + + for _, r := range line { + if escaped { + buf += string(r) + escaped = false + continue + } + + if r == '\\' { + if singleQuoted { + buf += string(r) + } else { + escaped = true + } + continue + } + + if isSpace(r) { + if singleQuoted || doubleQuoted || backQuote { + buf += string(r) + backtick += string(r) + } else if buf != "" { + if p.ParseEnv { + buf = replaceEnv(buf) + } + args = append(args, buf) + buf = "" + } + continue + } + + switch r { + case '`': + if !singleQuoted && !doubleQuoted { + if p.ParseBacktick { + if backQuote { + out, err := shellRun(backtick) + if err != nil { + return nil, err + } + buf = out + } + backtick = "" + backQuote = !backQuote + continue + } + backtick = "" + backQuote = !backQuote + } + case '"': + if !singleQuoted { + doubleQuoted = !doubleQuoted + continue + } + case '\'': + if !doubleQuoted { + singleQuoted = !singleQuoted + continue + } + } + + buf += string(r) + if backQuote { + backtick += string(r) + } + } + + if buf != "" { + if p.ParseEnv { + buf = replaceEnv(buf) + } + args = append(args, buf) + } + + if escaped || singleQuoted || doubleQuoted || backQuote { + return nil, errors.New("invalid command line string") + } + + return args, nil +} + +func Parse(line string) ([]string, error) { + return NewParser().Parse(line) +} diff --git a/vendor/src/github.com/mattn/go-shellwords/util_posix.go b/vendor/src/github.com/mattn/go-shellwords/util_posix.go new file mode 100644 index 0000000000..4f8ac55e47 --- /dev/null +++ b/vendor/src/github.com/mattn/go-shellwords/util_posix.go @@ -0,0 +1,19 @@ +// +build !windows + +package shellwords + +import ( + "errors" + "os" + "os/exec" + "strings" +) + +func shellRun(line string) (string, error) { + shell := os.Getenv("SHELL") + b, err := exec.Command(shell, "-c", line).Output() + if err != nil { + return "", errors.New(err.Error() + ":" + string(b)) + } + return strings.TrimSpace(string(b)), nil +} diff --git a/vendor/src/github.com/mattn/go-shellwords/util_windows.go b/vendor/src/github.com/mattn/go-shellwords/util_windows.go new file mode 100644 index 0000000000..7cad4cf06f --- /dev/null +++ b/vendor/src/github.com/mattn/go-shellwords/util_windows.go @@ -0,0 +1,17 @@ +package shellwords + +import ( + "errors" + "os" + "os/exec" + "strings" +) + +func shellRun(line string) (string, error) { + shell := os.Getenv("COMSPEC") + b, err := exec.Command(shell, "/c", line).Output() + if err != nil { + return "", errors.New(err.Error() + ":" + string(b)) + } + return strings.TrimSpace(string(b)), nil +}