diff --git a/Gopkg.lock b/Gopkg.lock index 77745082d..ff9ed3e83 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -34,6 +34,12 @@ revision = "769d3a54c8f2689a66c02cd5018cdce9d9ef6bb9" source = "github.com/weaveworks/goketo" +[[projects]] + name = "github.com/Masterminds/semver" + packages = ["."] + revision = "c7af12943936e8c39859482e61f0574c2fd7fc75" + version = "v1.4.2" + [[projects]] branch = "v1" name = "github.com/Masterminds/squirrel" @@ -548,6 +554,15 @@ revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" +[[projects]] + branch = "master" + name = "github.com/pkg/term" + packages = [ + ".", + "termios" + ] + revision = "bffc007b7fd5a70e20e28f5b7649bb84671ef436" + [[projects]] name = "github.com/pmezard/go-difflib" packages = ["difflib"] @@ -784,7 +799,7 @@ "ssh", "update" ] - revision = "95fe29edd7325be2f1137dff790b0e2529a79b52" + revision = "4073b98bf3c2c04afc92717baa63667bd83a945a" [[projects]] branch = "master" diff --git a/vendor/github.com/Masterminds/semver/LICENSE.txt b/vendor/github.com/Masterminds/semver/LICENSE.txt new file mode 100644 index 000000000..0da4aeadb --- /dev/null +++ b/vendor/github.com/Masterminds/semver/LICENSE.txt @@ -0,0 +1,20 @@ +The Masterminds +Copyright (C) 2014-2015, Matt Butcher and Matt Farina + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/Masterminds/semver/collection.go b/vendor/github.com/Masterminds/semver/collection.go new file mode 100644 index 000000000..a78235895 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/collection.go @@ -0,0 +1,24 @@ +package semver + +// Collection is a collection of Version instances and implements the sort +// interface. See the sort package for more details. +// https://golang.org/pkg/sort/ +type Collection []*Version + +// Len returns the length of a collection. The number of Version instances +// on the slice. +func (c Collection) Len() int { + return len(c) +} + +// Less is needed for the sort interface to compare two Version objects on the +// slice. If checks if one is less than the other. +func (c Collection) Less(i, j int) bool { + return c[i].LessThan(c[j]) +} + +// Swap is needed for the sort interface to replace the Version objects +// at two different positions in the slice. +func (c Collection) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} diff --git a/vendor/github.com/Masterminds/semver/constraints.go b/vendor/github.com/Masterminds/semver/constraints.go new file mode 100644 index 000000000..a41a6a7a4 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/constraints.go @@ -0,0 +1,426 @@ +package semver + +import ( + "errors" + "fmt" + "regexp" + "strings" +) + +// Constraints is one or more constraint that a semantic version can be +// checked against. +type Constraints struct { + constraints [][]*constraint +} + +// NewConstraint returns a Constraints instance that a Version instance can +// be checked against. If there is a parse error it will be returned. +func NewConstraint(c string) (*Constraints, error) { + + // Rewrite - ranges into a comparison operation. + c = rewriteRange(c) + + ors := strings.Split(c, "||") + or := make([][]*constraint, len(ors)) + for k, v := range ors { + cs := strings.Split(v, ",") + result := make([]*constraint, len(cs)) + for i, s := range cs { + pc, err := parseConstraint(s) + if err != nil { + return nil, err + } + + result[i] = pc + } + or[k] = result + } + + o := &Constraints{constraints: or} + return o, nil +} + +// Check tests if a version satisfies the constraints. +func (cs Constraints) Check(v *Version) bool { + // loop over the ORs and check the inner ANDs + for _, o := range cs.constraints { + joy := true + for _, c := range o { + if !c.check(v) { + joy = false + break + } + } + + if joy { + return true + } + } + + return false +} + +// Validate checks if a version satisfies a constraint. If not a slice of +// reasons for the failure are returned in addition to a bool. +func (cs Constraints) Validate(v *Version) (bool, []error) { + // loop over the ORs and check the inner ANDs + var e []error + for _, o := range cs.constraints { + joy := true + for _, c := range o { + if !c.check(v) { + em := fmt.Errorf(c.msg, v, c.orig) + e = append(e, em) + joy = false + } + } + + if joy { + return true, []error{} + } + } + + return false, e +} + +var constraintOps map[string]cfunc +var constraintMsg map[string]string +var constraintRegex *regexp.Regexp + +func init() { + constraintOps = map[string]cfunc{ + "": constraintTildeOrEqual, + "=": constraintTildeOrEqual, + "!=": constraintNotEqual, + ">": constraintGreaterThan, + "<": constraintLessThan, + ">=": constraintGreaterThanEqual, + "=>": constraintGreaterThanEqual, + "<=": constraintLessThanEqual, + "=<": constraintLessThanEqual, + "~": constraintTilde, + "~>": constraintTilde, + "^": constraintCaret, + } + + constraintMsg = map[string]string{ + "": "%s is not equal to %s", + "=": "%s is not equal to %s", + "!=": "%s is equal to %s", + ">": "%s is less than or equal to %s", + "<": "%s is greater than or equal to %s", + ">=": "%s is less than %s", + "=>": "%s is less than %s", + "<=": "%s is greater than %s", + "=<": "%s is greater than %s", + "~": "%s does not have same major and minor version as %s", + "~>": "%s does not have same major and minor version as %s", + "^": "%s does not have same major version as %s", + } + + ops := make([]string, 0, len(constraintOps)) + for k := range constraintOps { + ops = append(ops, regexp.QuoteMeta(k)) + } + + constraintRegex = regexp.MustCompile(fmt.Sprintf( + `^\s*(%s)\s*(%s)\s*$`, + strings.Join(ops, "|"), + cvRegex)) + + constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( + `\s*(%s)\s+-\s+(%s)\s*`, + cvRegex, cvRegex)) +} + +// An individual constraint +type constraint struct { + // The callback function for the restraint. It performs the logic for + // the constraint. + function cfunc + + msg string + + // The version used in the constraint check. For example, if a constraint + // is '<= 2.0.0' the con a version instance representing 2.0.0. + con *Version + + // The original parsed version (e.g., 4.x from != 4.x) + orig string + + // When an x is used as part of the version (e.g., 1.x) + minorDirty bool + dirty bool + patchDirty bool +} + +// Check if a version meets the constraint +func (c *constraint) check(v *Version) bool { + return c.function(v, c) +} + +type cfunc func(v *Version, c *constraint) bool + +func parseConstraint(c string) (*constraint, error) { + m := constraintRegex.FindStringSubmatch(c) + if m == nil { + return nil, fmt.Errorf("improper constraint: %s", c) + } + + ver := m[2] + orig := ver + minorDirty := false + patchDirty := false + dirty := false + if isX(m[3]) { + ver = "0.0.0" + dirty = true + } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { + minorDirty = true + dirty = true + ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) + } else if isX(strings.TrimPrefix(m[5], ".")) { + dirty = true + patchDirty = true + ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) + } + + con, err := NewVersion(ver) + if err != nil { + + // The constraintRegex should catch any regex parsing errors. So, + // we should never get here. + return nil, errors.New("constraint Parser Error") + } + + cs := &constraint{ + function: constraintOps[m[1]], + msg: constraintMsg[m[1]], + con: con, + orig: orig, + minorDirty: minorDirty, + patchDirty: patchDirty, + dirty: dirty, + } + return cs, nil +} + +// Constraint functions +func constraintNotEqual(v *Version, c *constraint) bool { + if c.dirty { + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if c.con.Major() != v.Major() { + return true + } + if c.con.Minor() != v.Minor() && !c.minorDirty { + return true + } else if c.minorDirty { + return false + } + + return false + } + + return !v.Equal(c.con) +} + +func constraintGreaterThan(v *Version, c *constraint) bool { + + // An edge case the constraint is 0.0.0 and the version is 0.0.0-someprerelease + // exists. This that case. + if !isNonZero(c.con) && isNonZero(v) { + return true + } + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + return v.Compare(c.con) == 1 +} + +func constraintLessThan(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if !c.dirty { + return v.Compare(c.con) < 0 + } + + if v.Major() > c.con.Major() { + return false + } else if v.Minor() > c.con.Minor() && !c.minorDirty { + return false + } + + return true +} + +func constraintGreaterThanEqual(v *Version, c *constraint) bool { + // An edge case the constraint is 0.0.0 and the version is 0.0.0-someprerelease + // exists. This that case. + if !isNonZero(c.con) && isNonZero(v) { + return true + } + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + return v.Compare(c.con) >= 0 +} + +func constraintLessThanEqual(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if !c.dirty { + return v.Compare(c.con) <= 0 + } + + if v.Major() > c.con.Major() { + return false + } else if v.Minor() > c.con.Minor() && !c.minorDirty { + return false + } + + return true +} + +// ~*, ~>* --> >= 0.0.0 (any) +// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0 +// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0 +// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0 +// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0 +// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0 +func constraintTilde(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if v.LessThan(c.con) { + return false + } + + // ~0.0.0 is a special case where all constraints are accepted. It's + // equivalent to >= 0.0.0. + if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 && + !c.minorDirty && !c.patchDirty { + return true + } + + if v.Major() != c.con.Major() { + return false + } + + if v.Minor() != c.con.Minor() && !c.minorDirty { + return false + } + + return true +} + +// When there is a .x (dirty) status it automatically opts in to ~. Otherwise +// it's a straight = +func constraintTildeOrEqual(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if c.dirty { + c.msg = constraintMsg["~"] + return constraintTilde(v, c) + } + + return v.Equal(c.con) +} + +// ^* --> (any) +// ^2, ^2.x, ^2.x.x --> >=2.0.0, <3.0.0 +// ^2.0, ^2.0.x --> >=2.0.0, <3.0.0 +// ^1.2, ^1.2.x --> >=1.2.0, <2.0.0 +// ^1.2.3 --> >=1.2.3, <2.0.0 +// ^1.2.0 --> >=1.2.0, <2.0.0 +func constraintCaret(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if v.LessThan(c.con) { + return false + } + + if v.Major() != c.con.Major() { + return false + } + + return true +} + +var constraintRangeRegex *regexp.Regexp + +const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + +func isX(x string) bool { + switch x { + case "x", "*", "X": + return true + default: + return false + } +} + +func rewriteRange(i string) string { + m := constraintRangeRegex.FindAllStringSubmatch(i, -1) + if m == nil { + return i + } + o := i + for _, v := range m { + t := fmt.Sprintf(">= %s, <= %s", v[1], v[11]) + o = strings.Replace(o, v[0], t, 1) + } + + return o +} + +// Detect if a version is not zero (0.0.0) +func isNonZero(v *Version) bool { + if v.Major() != 0 || v.Minor() != 0 || v.Patch() != 0 || v.Prerelease() != "" { + return true + } + + return false +} diff --git a/vendor/github.com/Masterminds/semver/doc.go b/vendor/github.com/Masterminds/semver/doc.go new file mode 100644 index 000000000..e00f65eb7 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/doc.go @@ -0,0 +1,115 @@ +/* +Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go. + +Specifically it provides the ability to: + + * Parse semantic versions + * Sort semantic versions + * Check if a semantic version fits within a set of constraints + * Optionally work with a `v` prefix + +Parsing Semantic Versions + +To parse a semantic version use the `NewVersion` function. For example, + + v, err := semver.NewVersion("1.2.3-beta.1+build345") + +If there is an error the version wasn't parseable. The version object has methods +to get the parts of the version, compare it to other versions, convert the +version back into a string, and get the original string. For more details +please see the documentation at https://godoc.org/github.com/Masterminds/semver. + +Sorting Semantic Versions + +A set of versions can be sorted using the `sort` package from the standard library. +For example, + + raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} + vs := make([]*semver.Version, len(raw)) + for i, r := range raw { + v, err := semver.NewVersion(r) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + vs[i] = v + } + + sort.Sort(semver.Collection(vs)) + +Checking Version Constraints + +Checking a version against version constraints is one of the most featureful +parts of the package. + + c, err := semver.NewConstraint(">= 1.2.3") + if err != nil { + // Handle constraint not being parseable. + } + + v, _ := semver.NewVersion("1.3") + if err != nil { + // Handle version not being parseable. + } + // Check if the version meets the constraints. The a variable will be true. + a := c.Check(v) + +Basic Comparisons + +There are two elements to the comparisons. First, a comparison string is a list +of comma separated and comparisons. These are then separated by || separated or +comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a +comparison that's greater than or equal to 1.2 and less than 3.0.0 or is +greater than or equal to 4.2.3. + +The basic comparisons are: + + * `=`: equal (aliased to no operator) + * `!=`: not equal + * `>`: greater than + * `<`: less than + * `>=`: greater than or equal to + * `<=`: less than or equal to + +Hyphen Range Comparisons + +There are multiple methods to handle ranges and the first is hyphens ranges. +These look like: + + * `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` + * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5` + +Wildcards In Comparisons + +The `x`, `X`, and `*` characters can be used as a wildcard character. This works +for all comparison operators. When used on the `=` operator it falls +back to the pack level comparison (see tilde below). For example, + + * `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` + * `>= 1.2.x` is equivalent to `>= 1.2.0` + * `<= 2.x` is equivalent to `<= 3` + * `*` is equivalent to `>= 0.0.0` + +Tilde Range Comparisons (Patch) + +The tilde (`~`) comparison operator is for patch level ranges when a minor +version is specified and major level changes when the minor number is missing. +For example, + + * `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` + * `~1` is equivalent to `>= 1, < 2` + * `~2.3` is equivalent to `>= 2.3, < 2.4` + * `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` + * `~1.x` is equivalent to `>= 1, < 2` + +Caret Range Comparisons (Major) + +The caret (`^`) comparison operator is for major level changes. This is useful +when comparisons of API versions as a major change is API breaking. For example, + + * `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` + * `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` + * `^2.3` is equivalent to `>= 2.3, < 3` + * `^2.x` is equivalent to `>= 2.0.0, < 3` +*/ +package semver diff --git a/vendor/github.com/Masterminds/semver/version.go b/vendor/github.com/Masterminds/semver/version.go new file mode 100644 index 000000000..9d22ea630 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/version.go @@ -0,0 +1,421 @@ +package semver + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "regexp" + "strconv" + "strings" +) + +// The compiled version of the regex created at init() is cached here so it +// only needs to be created once. +var versionRegex *regexp.Regexp +var validPrereleaseRegex *regexp.Regexp + +var ( + // ErrInvalidSemVer is returned a version is found to be invalid when + // being parsed. + ErrInvalidSemVer = errors.New("Invalid Semantic Version") + + // ErrInvalidMetadata is returned when the metadata is an invalid format + ErrInvalidMetadata = errors.New("Invalid Metadata string") + + // ErrInvalidPrerelease is returned when the pre-release is an invalid format + ErrInvalidPrerelease = errors.New("Invalid Prerelease string") +) + +// SemVerRegex is the regular expression used to parse a semantic version. +const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + +// ValidPrerelease is the regular expression which validates +// both prerelease and metadata values. +const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)` + +// Version represents a single semantic version. +type Version struct { + major, minor, patch int64 + pre string + metadata string + original string +} + +func init() { + versionRegex = regexp.MustCompile("^" + SemVerRegex + "$") + validPrereleaseRegex = regexp.MustCompile(ValidPrerelease) +} + +// NewVersion parses a given version and returns an instance of Version or +// an error if unable to parse the version. +func NewVersion(v string) (*Version, error) { + m := versionRegex.FindStringSubmatch(v) + if m == nil { + return nil, ErrInvalidSemVer + } + + sv := &Version{ + metadata: m[8], + pre: m[5], + original: v, + } + + var temp int64 + temp, err := strconv.ParseInt(m[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + sv.major = temp + + if m[2] != "" { + temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + sv.minor = temp + } else { + sv.minor = 0 + } + + if m[3] != "" { + temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 64) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + sv.patch = temp + } else { + sv.patch = 0 + } + + return sv, nil +} + +// MustParse parses a given version and panics on error. +func MustParse(v string) *Version { + sv, err := NewVersion(v) + if err != nil { + panic(err) + } + return sv +} + +// String converts a Version object to a string. +// Note, if the original version contained a leading v this version will not. +// See the Original() method to retrieve the original value. Semantic Versions +// don't contain a leading v per the spec. Instead it's optional on +// impelementation. +func (v *Version) String() string { + var buf bytes.Buffer + + fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) + if v.pre != "" { + fmt.Fprintf(&buf, "-%s", v.pre) + } + if v.metadata != "" { + fmt.Fprintf(&buf, "+%s", v.metadata) + } + + return buf.String() +} + +// Original returns the original value passed in to be parsed. +func (v *Version) Original() string { + return v.original +} + +// Major returns the major version. +func (v *Version) Major() int64 { + return v.major +} + +// Minor returns the minor version. +func (v *Version) Minor() int64 { + return v.minor +} + +// Patch returns the patch version. +func (v *Version) Patch() int64 { + return v.patch +} + +// Prerelease returns the pre-release version. +func (v *Version) Prerelease() string { + return v.pre +} + +// Metadata returns the metadata on the version. +func (v *Version) Metadata() string { + return v.metadata +} + +// originalVPrefix returns the original 'v' prefix if any. +func (v *Version) originalVPrefix() string { + + // Note, only lowercase v is supported as a prefix by the parser. + if v.original != "" && v.original[:1] == "v" { + return v.original[:1] + } + return "" +} + +// IncPatch produces the next patch version. +// If the current version does not have prerelease/metadata information, +// it unsets metadata and prerelease values, increments patch number. +// If the current version has any of prerelease or metadata information, +// it unsets both values and keeps curent patch value +func (v Version) IncPatch() Version { + vNext := v + // according to http://semver.org/#spec-item-9 + // Pre-release versions have a lower precedence than the associated normal version. + // according to http://semver.org/#spec-item-10 + // Build metadata SHOULD be ignored when determining version precedence. + if v.pre != "" { + vNext.metadata = "" + vNext.pre = "" + } else { + vNext.metadata = "" + vNext.pre = "" + vNext.patch = v.patch + 1 + } + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMinor produces the next minor version. +// Sets patch to 0. +// Increments minor number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMinor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = v.minor + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMajor produces the next major version. +// Sets patch to 0. +// Sets minor to 0. +// Increments major number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMajor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = 0 + vNext.major = v.major + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// SetPrerelease defines the prerelease value. +// Value must not include the required 'hypen' prefix. +func (v Version) SetPrerelease(prerelease string) (Version, error) { + vNext := v + if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) { + return vNext, ErrInvalidPrerelease + } + vNext.pre = prerelease + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + +// SetMetadata defines metadata value. +// Value must not include the required 'plus' prefix. +func (v Version) SetMetadata(metadata string) (Version, error) { + vNext := v + if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) { + return vNext, ErrInvalidMetadata + } + vNext.metadata = metadata + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + +// LessThan tests if one version is less than another one. +func (v *Version) LessThan(o *Version) bool { + return v.Compare(o) < 0 +} + +// GreaterThan tests if one version is greater than another one. +func (v *Version) GreaterThan(o *Version) bool { + return v.Compare(o) > 0 +} + +// Equal tests if two versions are equal to each other. +// Note, versions can be equal with different metadata since metadata +// is not considered part of the comparable version. +func (v *Version) Equal(o *Version) bool { + return v.Compare(o) == 0 +} + +// Compare compares this version to another one. It returns -1, 0, or 1 if +// the version smaller, equal, or larger than the other version. +// +// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is +// lower than the version without a prerelease. +func (v *Version) Compare(o *Version) int { + // Compare the major, minor, and patch version for differences. If a + // difference is found return the comparison. + if d := compareSegment(v.Major(), o.Major()); d != 0 { + return d + } + if d := compareSegment(v.Minor(), o.Minor()); d != 0 { + return d + } + if d := compareSegment(v.Patch(), o.Patch()); d != 0 { + return d + } + + // At this point the major, minor, and patch versions are the same. + ps := v.pre + po := o.Prerelease() + + if ps == "" && po == "" { + return 0 + } + if ps == "" { + return 1 + } + if po == "" { + return -1 + } + + return comparePrerelease(ps, po) +} + +// UnmarshalJSON implements JSON.Unmarshaler interface. +func (v *Version) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + temp, err := NewVersion(s) + if err != nil { + return err + } + v.major = temp.major + v.minor = temp.minor + v.patch = temp.patch + v.pre = temp.pre + v.metadata = temp.metadata + v.original = temp.original + temp = nil + return nil +} + +// MarshalJSON implements JSON.Marshaler interface. +func (v *Version) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +func compareSegment(v, o int64) int { + if v < o { + return -1 + } + if v > o { + return 1 + } + + return 0 +} + +func comparePrerelease(v, o string) int { + + // split the prelease versions by their part. The separator, per the spec, + // is a . + sparts := strings.Split(v, ".") + oparts := strings.Split(o, ".") + + // Find the longer length of the parts to know how many loop iterations to + // go through. + slen := len(sparts) + olen := len(oparts) + + l := slen + if olen > slen { + l = olen + } + + // Iterate over each part of the prereleases to compare the differences. + for i := 0; i < l; i++ { + // Since the lentgh of the parts can be different we need to create + // a placeholder. This is to avoid out of bounds issues. + stemp := "" + if i < slen { + stemp = sparts[i] + } + + otemp := "" + if i < olen { + otemp = oparts[i] + } + + d := comparePrePart(stemp, otemp) + if d != 0 { + return d + } + } + + // Reaching here means two versions are of equal value but have different + // metadata (the part following a +). They are not identical in string form + // but the version comparison finds them to be equal. + return 0 +} + +func comparePrePart(s, o string) int { + // Fastpath if they are equal + if s == o { + return 0 + } + + // When s or o are empty we can use the other in an attempt to determine + // the response. + if s == "" { + if o != "" { + return -1 + } + return 1 + } + + if o == "" { + if s != "" { + return 1 + } + return -1 + } + + // When comparing strings "99" is greater than "103". To handle + // cases like this we need to detect numbers and compare them. + + oi, n1 := strconv.ParseInt(o, 10, 64) + si, n2 := strconv.ParseInt(s, 10, 64) + + // The case where both are strings compare the strings + if n1 != nil && n2 != nil { + if s > o { + return 1 + } + return -1 + } else if n1 != nil { + // o is a string and s is a number + return -1 + } else if n2 != nil { + // s is a string and o is a number + return 1 + } + // Both are numbers + if si > oi { + return 1 + } + return -1 + +} diff --git a/vendor/github.com/pkg/term/LICENSE b/vendor/github.com/pkg/term/LICENSE new file mode 100644 index 000000000..8b9d1574f --- /dev/null +++ b/vendor/github.com/pkg/term/LICENSE @@ -0,0 +1,10 @@ +Copyright (c) 2014, David Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/term/term.go b/vendor/github.com/pkg/term/term.go new file mode 100644 index 000000000..958a87b16 --- /dev/null +++ b/vendor/github.com/pkg/term/term.go @@ -0,0 +1,79 @@ +// Package term manages POSIX terminals. As POSIX terminals are connected to, +// or emulate, a UART, this package also provides control over the various +// UART and serial line parameters. +package term + +import ( + "errors" + "io" + "os" + "syscall" + + "github.com/pkg/term/termios" +) + +const ( + NONE = iota // flow control off + XONXOFF // software flow control + HARDWARE // hardware flow control +) + +var errNotSupported = errors.New("not supported") + +// Read reads up to len(b) bytes from the terminal. It returns the number of +// bytes read and an error, if any. EOF is signaled by a zero count with +// err set to io.EOF. +func (t *Term) Read(b []byte) (int, error) { + n, e := syscall.Read(t.fd, b) + if n < 0 { + n = 0 + } + if n == 0 && len(b) > 0 && e == nil { + return 0, io.EOF + } + if e != nil { + return n, &os.PathError{"read", t.name, e} + } + return n, nil +} + +// SetOption takes one or more option function and applies them in order to Term. +func (t *Term) SetOption(options ...func(*Term) error) error { + for _, opt := range options { + if err := opt(t); err != nil { + return err + } + } + return nil +} + +// Write writes len(b) bytes to the terminal. It returns the number of bytes +// written and an error, if any. Write returns a non-nil error when n != +// len(b). +func (t *Term) Write(b []byte) (int, error) { + n, e := syscall.Write(t.fd, b) + if n < 0 { + n = 0 + } + if n != len(b) { + return n, io.ErrShortWrite + } + if e != nil { + return n, &os.PathError{"write", t.name, e} + } + return n, nil +} + +// Available returns how many bytes are unused in the buffer. +func (t *Term) Available() (int, error) { + var n int + err := termios.Tiocinq(uintptr(t.fd), &n) + return n, err +} + +// Buffered returns the number of bytes that have been written into the current buffer. +func (t *Term) Buffered() (int, error) { + var n int + err := termios.Tiocoutq(uintptr(t.fd), &n) + return n, err +} diff --git a/vendor/github.com/pkg/term/term_bsd.go b/vendor/github.com/pkg/term/term_bsd.go new file mode 100644 index 000000000..4446b3bdb --- /dev/null +++ b/vendor/github.com/pkg/term/term_bsd.go @@ -0,0 +1,55 @@ +// +build freebsd openbsd netbsd + +package term + +import "syscall" + +type attr syscall.Termios + +func (a *attr) setSpeed(baud int) error { + var rate uint32 + switch baud { + case 50: + rate = syscall.B50 + case 75: + rate = syscall.B75 + case 110: + rate = syscall.B110 + case 134: + rate = syscall.B134 + case 150: + rate = syscall.B150 + case 200: + rate = syscall.B200 + case 300: + rate = syscall.B300 + case 600: + rate = syscall.B600 + case 1200: + rate = syscall.B1200 + case 1800: + rate = syscall.B1800 + case 2400: + rate = syscall.B2400 + case 4800: + rate = syscall.B4800 + case 9600: + rate = syscall.B9600 + case 19200: + rate = syscall.B19200 + case 38400: + rate = syscall.B38400 + case 57600: + rate = syscall.B57600 + case 115200: + rate = syscall.B115200 + case 230400: + rate = syscall.B230400 + default: + return syscall.EINVAL + } + (*syscall.Termios)(a).Cflag = syscall.CS8 | syscall.CREAD | syscall.CLOCAL | rate + (*syscall.Termios)(a).Ispeed = rate + (*syscall.Termios)(a).Ospeed = rate + return nil +} diff --git a/vendor/github.com/pkg/term/term_darwin.go b/vendor/github.com/pkg/term/term_darwin.go new file mode 100644 index 000000000..37d05dc04 --- /dev/null +++ b/vendor/github.com/pkg/term/term_darwin.go @@ -0,0 +1,53 @@ +package term + +import "syscall" + +type attr syscall.Termios + +func (a *attr) setSpeed(baud int) error { + var rate uint64 + switch baud { + case 50: + rate = syscall.B50 + case 75: + rate = syscall.B75 + case 110: + rate = syscall.B110 + case 134: + rate = syscall.B134 + case 150: + rate = syscall.B150 + case 200: + rate = syscall.B200 + case 300: + rate = syscall.B300 + case 600: + rate = syscall.B600 + case 1200: + rate = syscall.B1200 + case 1800: + rate = syscall.B1800 + case 2400: + rate = syscall.B2400 + case 4800: + rate = syscall.B4800 + case 9600: + rate = syscall.B9600 + case 19200: + rate = syscall.B19200 + case 38400: + rate = syscall.B38400 + case 57600: + rate = syscall.B57600 + case 115200: + rate = syscall.B115200 + case 230400: + rate = syscall.B230400 + default: + return syscall.EINVAL + } + (*syscall.Termios)(a).Cflag = syscall.CS8 | syscall.CREAD | syscall.CLOCAL | rate + (*syscall.Termios)(a).Ispeed = rate + (*syscall.Termios)(a).Ospeed = rate + return nil +} diff --git a/vendor/github.com/pkg/term/term_linux.go b/vendor/github.com/pkg/term/term_linux.go new file mode 100644 index 000000000..d614c473d --- /dev/null +++ b/vendor/github.com/pkg/term/term_linux.go @@ -0,0 +1,77 @@ +package term + +import "syscall" + +type attr syscall.Termios + +func (a *attr) setSpeed(baud int) error { + var rate uint32 + switch baud { + case 50: + rate = syscall.B50 + case 75: + rate = syscall.B75 + case 110: + rate = syscall.B110 + case 134: + rate = syscall.B134 + case 150: + rate = syscall.B150 + case 200: + rate = syscall.B200 + case 300: + rate = syscall.B300 + case 600: + rate = syscall.B600 + case 1200: + rate = syscall.B1200 + case 1800: + rate = syscall.B1800 + case 2400: + rate = syscall.B2400 + case 4800: + rate = syscall.B4800 + case 9600: + rate = syscall.B9600 + case 19200: + rate = syscall.B19200 + case 38400: + rate = syscall.B38400 + case 57600: + rate = syscall.B57600 + case 115200: + rate = syscall.B115200 + case 230400: + rate = syscall.B230400 + case 460800: + rate = syscall.B460800 + case 500000: + rate = syscall.B500000 + case 576000: + rate = syscall.B576000 + case 921600: + rate = syscall.B921600 + case 1000000: + rate = syscall.B1000000 + case 1152000: + rate = syscall.B1152000 + case 1500000: + rate = syscall.B1500000 + case 2000000: + rate = syscall.B2000000 + case 2500000: + rate = syscall.B2500000 + case 3000000: + rate = syscall.B3000000 + case 3500000: + rate = syscall.B3500000 + case 4000000: + rate = syscall.B4000000 + default: + return syscall.EINVAL + } + (*syscall.Termios)(a).Cflag = syscall.CS8 | syscall.CREAD | syscall.CLOCAL | rate + (*syscall.Termios)(a).Ispeed = rate + (*syscall.Termios)(a).Ospeed = rate + return nil +} diff --git a/vendor/github.com/pkg/term/term_open_posix.go b/vendor/github.com/pkg/term/term_open_posix.go new file mode 100644 index 000000000..5efea56b7 --- /dev/null +++ b/vendor/github.com/pkg/term/term_open_posix.go @@ -0,0 +1,33 @@ +// +build !windows,!solaris + +package term + +import ( + "os" + "syscall" + + "github.com/pkg/term/termios" +) + +// Open opens an asynchronous communications port. +func Open(name string, options ...func(*Term) error) (*Term, error) { + fd, e := syscall.Open(name, syscall.O_NOCTTY|syscall.O_CLOEXEC|syscall.O_NDELAY|syscall.O_RDWR, 0666) + if e != nil { + return nil, &os.PathError{"open", name, e} + } + + t := Term{name: name, fd: fd} + if err := termios.Tcgetattr(uintptr(t.fd), &t.orig); err != nil { + return nil, err + } + if err := t.SetOption(options...); err != nil { + return nil, err + } + return &t, syscall.SetNonblock(t.fd, false) +} + +// Restore restores the state of the terminal captured at the point that +// the terminal was originally opened. +func (t *Term) Restore() error { + return termios.Tcsetattr(uintptr(t.fd), termios.TCIOFLUSH, &t.orig) +} diff --git a/vendor/github.com/pkg/term/term_posix.go b/vendor/github.com/pkg/term/term_posix.go new file mode 100644 index 000000000..96f271a21 --- /dev/null +++ b/vendor/github.com/pkg/term/term_posix.go @@ -0,0 +1,197 @@ +// +build !windows + +package term + +import ( + "syscall" + "time" + + "github.com/pkg/term/termios" +) + +// Term represents an asynchronous communications port. +type Term struct { + name string + fd int + orig syscall.Termios // original state of the terminal, see Open and Restore +} + +// SetCbreak sets cbreak mode. +func (t *Term) SetCbreak() error { + return t.SetOption(CBreakMode) +} + +// CBreakMode places the terminal into cbreak mode. +func CBreakMode(t *Term) error { + var a attr + if err := termios.Tcgetattr(uintptr(t.fd), (*syscall.Termios)(&a)); err != nil { + return err + } + termios.Cfmakecbreak((*syscall.Termios)(&a)) + return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, (*syscall.Termios)(&a)) +} + +// SetRaw sets raw mode. +func (t *Term) SetRaw() error { + return t.SetOption(RawMode) +} + +// RawMode places the terminal into raw mode. +func RawMode(t *Term) error { + var a attr + if err := termios.Tcgetattr(uintptr(t.fd), (*syscall.Termios)(&a)); err != nil { + return err + } + termios.Cfmakeraw((*syscall.Termios)(&a)) + return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, (*syscall.Termios)(&a)) +} + +// Speed sets the baud rate option for the terminal. +func Speed(baud int) func(*Term) error { + return func(t *Term) error { + return t.setSpeed(baud) + } +} + +// SetSpeed sets the receive and transmit baud rates. +func (t *Term) SetSpeed(baud int) error { + return t.SetOption(Speed(baud)) +} + +func (t *Term) setSpeed(baud int) error { + var a attr + if err := termios.Tcgetattr(uintptr(t.fd), (*syscall.Termios)(&a)); err != nil { + return err + } + a.setSpeed(baud) + return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, (*syscall.Termios)(&a)) +} + +func clamp(v, lo, hi int64) int64 { + if v < lo { + return lo + } + if v > hi { + return hi + } + return v +} + +// timeoutVals converts d into values suitable for termios VMIN and VTIME ctrl chars +func timeoutVals(d time.Duration) (uint8, uint8) { + if d > 0 { + // VTIME is expressed in terms of deciseconds + vtimeDeci := d.Nanoseconds() / 1e6 / 100 + // ensure valid range + vtime := uint8(clamp(vtimeDeci, 1, 0xff)) + return 0, vtime + } + // block indefinitely until we receive at least 1 byte + return 1, 0 +} + +// ReadTimeout sets the read timeout option for the terminal. +func ReadTimeout(d time.Duration) func(*Term) error { + return func(t *Term) error { + return t.setReadTimeout(d) + } +} + +// SetReadTimeout sets the read timeout. +// A zero value for d means read operations will not time out. +func (t *Term) SetReadTimeout(d time.Duration) error { + return t.SetOption(ReadTimeout(d)) +} + +func (t *Term) setReadTimeout(d time.Duration) error { + var a attr + if err := termios.Tcgetattr(uintptr(t.fd), (*syscall.Termios)(&a)); err != nil { + return err + } + a.Cc[syscall.VMIN], a.Cc[syscall.VTIME] = timeoutVals(d) + return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, (*syscall.Termios)(&a)) +} + +// FlowControl sets the flow control option for the terminal. +func FlowControl(kind int) func(*Term) error { + return func(t *Term) error { + return t.setFlowControl(kind) + } +} + +// SetFlowControl sets whether hardware flow control is enabled. +func (t *Term) SetFlowControl(kind int) error { + return t.SetOption(FlowControl(kind)) +} + +func (t *Term) setFlowControl(kind int) error { + var a attr + if err := termios.Tcgetattr(uintptr(t.fd), (*syscall.Termios)(&a)); err != nil { + return err + } + switch kind { + case NONE: + a.Iflag &^= termios.IXON | termios.IXOFF | termios.IXANY + a.Cflag &^= termios.CRTSCTS + + case XONXOFF: + a.Cflag &^= termios.CRTSCTS + a.Iflag |= termios.IXON | termios.IXOFF | termios.IXANY + + case HARDWARE: + a.Iflag &^= termios.IXON | termios.IXOFF | termios.IXANY + a.Cflag |= termios.CRTSCTS + } + return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, (*syscall.Termios)(&a)) +} + +// Flush flushes both data received but not read, and data written but not transmitted. +func (t *Term) Flush() error { + return termios.Tcflush(uintptr(t.fd), termios.TCIOFLUSH) +} + +// SendBreak sends a break signal. +func (t *Term) SendBreak() error { + return termios.Tcsendbreak(uintptr(t.fd), 0) +} + +// SetDTR sets the DTR (data terminal ready) signal. +func (t *Term) SetDTR(v bool) error { + bits := syscall.TIOCM_DTR + if v { + return termios.Tiocmbis(uintptr(t.fd), &bits) + } else { + return termios.Tiocmbic(uintptr(t.fd), &bits) + } +} + +// DTR returns the state of the DTR (data terminal ready) signal. +func (t *Term) DTR() (bool, error) { + var status int + err := termios.Tiocmget(uintptr(t.fd), &status) + return status&syscall.TIOCM_DTR == syscall.TIOCM_DTR, err +} + +// SetRTS sets the RTS (data terminal ready) signal. +func (t *Term) SetRTS(v bool) error { + bits := syscall.TIOCM_RTS + if v { + return termios.Tiocmbis(uintptr(t.fd), &bits) + } else { + return termios.Tiocmbic(uintptr(t.fd), &bits) + } +} + +// RTS returns the state of the RTS (data terminal ready) signal. +func (t *Term) RTS() (bool, error) { + var status int + err := termios.Tiocmget(uintptr(t.fd), &status) + return status&syscall.TIOCM_RTS == syscall.TIOCM_RTS, err +} + +// Close closes the device and releases any associated resources. +func (t *Term) Close() error { + err := syscall.Close(t.fd) + t.fd = -1 + return err +} diff --git a/vendor/github.com/pkg/term/term_solaris.go b/vendor/github.com/pkg/term/term_solaris.go new file mode 100644 index 000000000..0a930703a --- /dev/null +++ b/vendor/github.com/pkg/term/term_solaris.go @@ -0,0 +1,108 @@ +package term + +// #include +import "C" + +import ( + "syscall" + "github.com/pkg/term/termios" + "os" + "golang.org/x/sys/unix" + "unsafe" +) + +type attr syscall.Termios + +func (a *attr) setSpeed(baud int) error { + var rate uint32 + switch baud { + case 50: + rate = syscall.B50 + case 75: + rate = syscall.B75 + case 110: + rate = syscall.B110 + case 134: + rate = syscall.B134 + case 150: + rate = syscall.B150 + case 200: + rate = syscall.B200 + case 300: + rate = syscall.B300 + case 600: + rate = syscall.B600 + case 1200: + rate = syscall.B1200 + case 1800: + rate = syscall.B1800 + case 2400: + rate = syscall.B2400 + case 4800: + rate = syscall.B4800 + case 9600: + rate = syscall.B9600 + case 19200: + rate = syscall.B19200 + case 38400: + rate = syscall.B38400 + case 57600: + rate = syscall.B57600 + case 115200: + rate = syscall.B115200 + case 230400: + rate = syscall.B230400 + case 460800: + rate = syscall.B460800 + case 921600: + rate = syscall.B921600 + default: + return syscall.EINVAL + } + + err := termios.Cfsetispeed((*syscall.Termios)(a), uintptr(rate)) + if err != nil { + return err + } + + err = termios.Cfsetospeed((*syscall.Termios)(a), uintptr(rate)) + if err != nil { + return err + } + + return nil +} + +// Open opens an asynchronous communications port. +func Open(name string, options ...func(*Term) error) (*Term, error) { + fd, e := syscall.Open(name, syscall.O_NOCTTY|syscall.O_CLOEXEC|syscall.O_NDELAY|syscall.O_RDWR, 0666) + if e != nil { + return nil, &os.PathError{"open", name, e} + } + + modules := [2]string{"ptem", "ldterm"} + for _, mod := range modules { + err := unix.IoctlSetInt(fd, C.I_PUSH, int(uintptr(unsafe.Pointer(syscall.StringBytePtr(mod))))) + if err != nil { + return nil, err + } + } + + t := Term{name: name, fd: fd} + termios.Tcgetattr(uintptr(t.fd), &t.orig) + if err := termios.Tcgetattr(uintptr(t.fd), &t.orig); err != nil { + return nil, err + } + + if err := t.SetOption(options...); err != nil { + return nil, err + } + + return &t, syscall.SetNonblock(t.fd, false) +} + +// Restore restores the state of the terminal captured at the point that +// the terminal was originally opened. +func (t *Term) Restore() error { + return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, &t.orig) +} diff --git a/vendor/github.com/pkg/term/term_windows.go b/vendor/github.com/pkg/term/term_windows.go new file mode 100644 index 000000000..570bacdf3 --- /dev/null +++ b/vendor/github.com/pkg/term/term_windows.go @@ -0,0 +1,172 @@ +package term + +import ( + "errors" + "io" + "os" + "syscall" + + "github.com/pkg/term/termios" +) + +type Term struct { +} + +var errNotSupported = errors.New("not supported") + +// Open opens an asynchronous communications port. +func Open(name string, options ...func(*Term) error) (*Term, error) { + return nil, errNotSupported +} + +// SetOption takes one or more option function and applies them in order to Term. +func (t *Term) SetOption(options ...func(*Term) error) error { + for _, opt := range options { + if err := opt(t); err != nil { + return err + } + } + return nil +} + +// Read reads up to len(b) bytes from the terminal. It returns the number of +// bytes read and an error, if any. EOF is signaled by a zero count with +// err set to io.EOF. +func (t *Term) Read(b []byte) (int, error) { + n, e := syscall.Read(t.fd, b) + if n < 0 { + n = 0 + } + if n == 0 && len(b) > 0 && e == nil { + return 0, io.EOF + } + if e != nil { + return n, &os.PathError{"read", t.name, e} + } + return n, nil +} + +// Write writes len(b) bytes to the terminal. It returns the number of bytes +// written and an error, if any. Write returns a non-nil error when n != +// len(b). +func (t *Term) Write(b []byte) (int, error) { + n, e := syscall.Write(t.fd, b) + if n < 0 { + n = 0 + } + if n != len(b) { + return n, io.ErrShortWrite + } + if e != nil { + return n, &os.PathError{"write", t.name, e} + } + return n, nil +} + +// Close closes the device and releases any associated resources. +func (t *Term) Close() error { + err := syscall.Close(t.fd) + t.fd = -1 + return err +} + +// SetCbreak sets cbreak mode. +func (t *Term) SetCbreak() error { + return t.SetOption(CBreakMode) +} + +// CBreakMode places the terminal into cbreak mode. +func CBreakMode(t *Term) error { + var a attr + if err := termios.Tcgetattr(uintptr(t.fd), (*syscall.Termios)(&a)); err != nil { + return err + } + termios.Cfmakecbreak((*syscall.Termios)(&a)) + return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, (*syscall.Termios)(&a)) +} + +// SetRaw sets raw mode. +func (t *Term) SetRaw() error { + return t.SetOption(RawMode) +} + +// RawMode places the terminal into raw mode. +func RawMode(t *Term) error { + var a attr + if err := termios.Tcgetattr(uintptr(t.fd), (*syscall.Termios)(&a)); err != nil { + return err + } + termios.Cfmakeraw((*syscall.Termios)(&a)) + return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, (*syscall.Termios)(&a)) +} + +// Speed sets the baud rate option for the terminal. +func Speed(baud int) func(*Term) error { + return func(t *Term) error { + return t.setSpeed(baud) + } +} + +// SetSpeed sets the receive and transmit baud rates. +func (t *Term) SetSpeed(baud int) error { + return t.SetOption(Speed(baud)) +} + +func (t *Term) setSpeed(baud int) error { + var a attr + if err := termios.Tcgetattr(uintptr(t.fd), (*syscall.Termios)(&a)); err != nil { + return err + } + a.setSpeed(baud) + return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, (*syscall.Termios)(&a)) +} + +// Flush flushes both data received but not read, and data written but not transmitted. +func (t *Term) Flush() error { + return termios.Tcflush(uintptr(t.fd), termios.TCIOFLUSH) +} + +// SendBreak sends a break signal. +func (t *Term) SendBreak() error { + return termios.Tcsendbreak(uintptr(t.fd), 0) +} + +// SetDTR sets the DTR (data terminal ready) signal. +func (t *Term) SetDTR(v bool) error { + bits := syscall.TIOCM_DTR + if v { + return termios.Tiocmbis(uintptr(t.fd), &bits) + } else { + return termios.Tiocmbic(uintptr(t.fd), &bits) + } +} + +// DTR returns the state of the DTR (data terminal ready) signal. +func (t *Term) DTR() (bool, error) { + var status int + err := termios.Tiocmget(uintptr(t.fd), &status) + return status&syscall.TIOCM_DTR == syscall.TIOCM_DTR, err +} + +// SetRTS sets the RTS (data terminal ready) signal. +func (t *Term) SetRTS(v bool) error { + bits := syscall.TIOCM_RTS + if v { + return termios.Tiocmbis(uintptr(t.fd), &bits) + } else { + return termios.Tiocmbic(uintptr(t.fd), &bits) + } +} + +// RTS returns the state of the RTS (data terminal ready) signal. +func (t *Term) RTS() (bool, error) { + var status int + err := termios.Tiocmget(uintptr(t.fd), &status) + return status&syscall.TIOCM_RTS == syscall.TIOCM_RTS, err +} + +// Restore restores the state of the terminal captured at the point that +// the terminal was originally opened. +func (t *Term) Restore() error { + return termios.Tcsetattr(uintptr(t.fd), termios.TCIOFLUSH, &t.orig) +} diff --git a/vendor/github.com/pkg/term/termios/doc.go b/vendor/github.com/pkg/term/termios/doc.go new file mode 100644 index 000000000..7b178c2df --- /dev/null +++ b/vendor/github.com/pkg/term/termios/doc.go @@ -0,0 +1,4 @@ +// Package termios implements the low level termios(3) terminal line discipline facilities. +// +// For a higher level interface please use the github.com/pkg/term package. +package termios diff --git a/vendor/github.com/pkg/term/termios/ioctl.go b/vendor/github.com/pkg/term/termios/ioctl.go new file mode 100644 index 000000000..3b59ea32c --- /dev/null +++ b/vendor/github.com/pkg/term/termios/ioctl.go @@ -0,0 +1,12 @@ +// +build !windows,!solaris + +package termios + +import "syscall" + +func ioctl(fd, request, argp uintptr) error { + if _, _, e := syscall.Syscall6(syscall.SYS_IOCTL, fd, request, argp, 0, 0, 0); e != 0 { + return e + } + return nil +} diff --git a/vendor/github.com/pkg/term/termios/ioctl_darwin.go b/vendor/github.com/pkg/term/termios/ioctl_darwin.go new file mode 100644 index 000000000..5d9b91ab2 --- /dev/null +++ b/vendor/github.com/pkg/term/termios/ioctl_darwin.go @@ -0,0 +1,10 @@ +package termios + +const ( + _IOC_PARAM_SHIFT = 13 + _IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1 +) + +func _IOC_PARM_LEN(ioctl uintptr) uintptr { + return (ioctl >> 16) & _IOC_PARAM_MASK +} diff --git a/vendor/github.com/pkg/term/termios/ioctl_solaris.go b/vendor/github.com/pkg/term/termios/ioctl_solaris.go new file mode 100644 index 000000000..1f737f5d4 --- /dev/null +++ b/vendor/github.com/pkg/term/termios/ioctl_solaris.go @@ -0,0 +1,7 @@ +package termios + +import "golang.org/x/sys/unix" + +func ioctl(fd, request, argp uintptr) error { + return unix.IoctlSetInt(int(fd), int(request), int(argp)) +} diff --git a/vendor/github.com/pkg/term/termios/pty.go b/vendor/github.com/pkg/term/termios/pty.go new file mode 100644 index 000000000..712eab9d9 --- /dev/null +++ b/vendor/github.com/pkg/term/termios/pty.go @@ -0,0 +1,45 @@ +package termios + +import ( + "fmt" + "os" + "syscall" +) + +func open_device(path string) (uintptr, error) { + fd, err := syscall.Open(path, syscall.O_NOCTTY|syscall.O_RDWR|syscall.O_CLOEXEC, 0666) + if err != nil { + return 0, fmt.Errorf("unable to open %q: %v", path, err) + } + return uintptr(fd), nil +} + +// Pty returns a UNIX 98 pseudoterminal device. +// Pty returns a pair of fds representing the master and slave pair. +func Pty() (*os.File, *os.File, error) { + ptm, err := open_pty_master() + if err != nil { + return nil, nil, err + } + + sname, err := Ptsname(ptm) + if err != nil { + return nil, nil, err + } + + err = grantpt(ptm) + if err != nil { + return nil, nil, err + } + + err = unlockpt(ptm) + if err != nil { + return nil, nil, err + } + + pts, err := open_device(sname) + if err != nil { + return nil, nil, err + } + return os.NewFile(uintptr(ptm), "ptm"), os.NewFile(uintptr(pts), sname), nil +} diff --git a/vendor/github.com/pkg/term/termios/pty_bsd.go b/vendor/github.com/pkg/term/termios/pty_bsd.go new file mode 100644 index 000000000..d1a3baf6a --- /dev/null +++ b/vendor/github.com/pkg/term/termios/pty_bsd.go @@ -0,0 +1,41 @@ +// +build freebsd openbsd netbsd + +package termios + +import ( + "fmt" + "syscall" + "unsafe" +) + +func posix_openpt(oflag int) (fd uintptr, err error) { + // Copied from debian-golang-pty/pty_freebsd.go. + r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0) + fd = uintptr(r0) + if e1 != 0 { + err = e1 + } + return +} + +func open_pty_master() (uintptr, error) { + return posix_openpt(syscall.O_NOCTTY|syscall.O_RDWR|syscall.O_CLOEXEC) +} + +func Ptsname(fd uintptr) (string, error) { + var n uintptr + err := ioctl(fd, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))) + if err != nil { + return "", err + } + return fmt.Sprintf("/dev/pts/%d", n), nil +} + +func grantpt(fd uintptr) error { + var n uintptr + return ioctl(fd, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))) +} + +func unlockpt(fd uintptr) error { + return nil +} diff --git a/vendor/github.com/pkg/term/termios/pty_darwin.go b/vendor/github.com/pkg/term/termios/pty_darwin.go new file mode 100644 index 000000000..33472b743 --- /dev/null +++ b/vendor/github.com/pkg/term/termios/pty_darwin.go @@ -0,0 +1,35 @@ +package termios + +import ( + "errors" + "syscall" + "unsafe" +) + +func open_pty_master() (uintptr, error) { + return open_device("/dev/ptmx") +} + +func Ptsname(fd uintptr) (string, error) { + n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME)) + + err := ioctl(fd, syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0]))) + if err != nil { + return "", err + } + + for i, c := range n { + if c == 0 { + return string(n[:i]), nil + } + } + return "", errors.New("TIOCPTYGNAME string not NUL-terminated") +} + +func grantpt(fd uintptr) error { + return ioctl(fd, syscall.TIOCPTYGRANT, 0) +} + +func unlockpt(fd uintptr) error { + return ioctl(fd, syscall.TIOCPTYUNLK, 0) +} diff --git a/vendor/github.com/pkg/term/termios/pty_linux.go b/vendor/github.com/pkg/term/termios/pty_linux.go new file mode 100644 index 000000000..662a197fc --- /dev/null +++ b/vendor/github.com/pkg/term/termios/pty_linux.go @@ -0,0 +1,30 @@ +package termios + +import ( + "fmt" + "syscall" + "unsafe" +) + +func open_pty_master() (uintptr, error) { + return open_device("/dev/ptmx") +} + +func Ptsname(fd uintptr) (string, error) { + var n uintptr + err := ioctl(fd, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))) + if err != nil { + return "", err + } + return fmt.Sprintf("/dev/pts/%d", n), nil +} + +func grantpt(fd uintptr) error { + var n uintptr + return ioctl(fd, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))) +} + +func unlockpt(fd uintptr) error { + var n uintptr + return ioctl(fd, syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&n))) +} diff --git a/vendor/github.com/pkg/term/termios/pty_solaris.go b/vendor/github.com/pkg/term/termios/pty_solaris.go new file mode 100644 index 000000000..6fccd1eed --- /dev/null +++ b/vendor/github.com/pkg/term/termios/pty_solaris.go @@ -0,0 +1,33 @@ +package termios + +// #include +import "C" + +import "syscall" + +func open_pty_master() (uintptr, error) { + return open_device("/dev/ptmx") +} + +func Ptsname(fd uintptr) (string, error) { + slavename := C.GoString(C.ptsname(C.int(fd))) + return slavename, nil +} + +func grantpt(fd uintptr) error { + rc := C.grantpt(C.int(fd)) + if rc == 0 { + return nil + } else { + return syscall.Errno(rc) + } +} + +func unlockpt(fd uintptr) error { + rc := C.unlockpt(C.int(fd)) + if rc == 0 { + return nil + } else { + return syscall.Errno(rc) + } +} diff --git a/vendor/github.com/pkg/term/termios/termios.go b/vendor/github.com/pkg/term/termios/termios.go new file mode 100644 index 000000000..7cafc685a --- /dev/null +++ b/vendor/github.com/pkg/term/termios/termios.go @@ -0,0 +1,46 @@ +// +build !windows + +package termios + +import ( + "syscall" + "unsafe" +) + +// Tiocmget returns the state of the MODEM bits. +func Tiocmget(fd uintptr, status *int) error { + return ioctl(fd, syscall.TIOCMGET, uintptr(unsafe.Pointer(status))) +} + +// Tiocmset sets the state of the MODEM bits. +func Tiocmset(fd uintptr, status *int) error { + return ioctl(fd, syscall.TIOCMSET, uintptr(unsafe.Pointer(status))) +} + +// Tiocmbis sets the indicated modem bits. +func Tiocmbis(fd uintptr, status *int) error { + return ioctl(fd, syscall.TIOCMBIS, uintptr(unsafe.Pointer(status))) +} + +// Tiocmbic clears the indicated modem bits. +func Tiocmbic(fd uintptr, status *int) error { + return ioctl(fd, syscall.TIOCMBIC, uintptr(unsafe.Pointer(status))) +} + +// Cfmakecbreak modifies attr for cbreak mode. +func Cfmakecbreak(attr *syscall.Termios) { + attr.Lflag &^= syscall.ECHO | syscall.ICANON + attr.Cc[syscall.VMIN] = 1 + attr.Cc[syscall.VTIME] = 0 +} + +// Cfmakeraw modifies attr for raw mode. +func Cfmakeraw(attr *syscall.Termios) { + attr.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON + attr.Oflag &^= syscall.OPOST + attr.Cflag &^= syscall.CSIZE | syscall.PARENB + attr.Cflag |= syscall.CS8 + attr.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG + attr.Cc[syscall.VMIN] = 1 + attr.Cc[syscall.VTIME] = 0 +} diff --git a/vendor/github.com/pkg/term/termios/termios_bsd.go b/vendor/github.com/pkg/term/termios/termios_bsd.go new file mode 100644 index 000000000..701506510 --- /dev/null +++ b/vendor/github.com/pkg/term/termios/termios_bsd.go @@ -0,0 +1,90 @@ +// +build darwin freebsd openbsd netbsd + +package termios + +import ( + "syscall" + "time" + "unsafe" +) + +const ( + FREAD = 0x0001 + FWRITE = 0x0002 + + IXON = 0x00000200 + IXOFF = 0x00000400 + IXANY = 0x00000800 + CCTS_OFLOW = 0x00010000 + CRTS_IFLOW = 0x00020000 + CRTSCTS = CCTS_OFLOW | CRTS_IFLOW +) + +// Tcgetattr gets the current serial port settings. +func Tcgetattr(fd uintptr, argp *syscall.Termios) error { + return ioctl(fd, syscall.TIOCGETA, uintptr(unsafe.Pointer(argp))) +} + +// Tcsetattr sets the current serial port settings. +func Tcsetattr(fd, opt uintptr, argp *syscall.Termios) error { + switch opt { + case TCSANOW: + opt = syscall.TIOCSETA + case TCSADRAIN: + opt = syscall.TIOCSETAW + case TCSAFLUSH: + opt = syscall.TIOCSETAF + default: + return syscall.EINVAL + } + return ioctl(fd, opt, uintptr(unsafe.Pointer(argp))) +} + +// Tcsendbreak function transmits a continuous stream of zero-valued bits for +// four-tenths of a second to the terminal referenced by fildes. The duration +// parameter is ignored in this implementation. +func Tcsendbreak(fd, duration uintptr) error { + if err := ioctl(fd, syscall.TIOCSBRK, 0); err != nil { + return err + } + time.Sleep(4 / 10 * time.Second) + return ioctl(fd, syscall.TIOCCBRK, 0) +} + +// Tcdrain waits until all output written to the terminal referenced by fd has been transmitted to the terminal. +func Tcdrain(fd uintptr) error { + return ioctl(fd, syscall.TIOCDRAIN, 0) +} + +// Tcflush discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of which. +func Tcflush(fd, which uintptr) error { + var com int + switch which { + case syscall.TCIFLUSH: + com = FREAD + case syscall.TCOFLUSH: + com = FWRITE + case syscall.TCIOFLUSH: + com = FREAD | FWRITE + default: + return syscall.EINVAL + } + return ioctl(fd, syscall.TIOCFLUSH, uintptr(unsafe.Pointer(&com))) +} + +// Cfgetispeed returns the input baud rate stored in the termios structure. +func Cfgetispeed(attr *syscall.Termios) uint32 { return uint32(attr.Ispeed) } + +// Cfgetospeed returns the output baud rate stored in the termios structure. +func Cfgetospeed(attr *syscall.Termios) uint32 { return uint32(attr.Ospeed) } + +// Tiocinq returns the number of bytes in the input buffer. +func Tiocinq(fd uintptr, argp *int) error { + *argp = 0 + return nil +} + +// Tiocoutq return the number of bytes in the output buffer. +func Tiocoutq(fd uintptr, argp *int) error { + return ioctl(fd, syscall.TIOCOUTQ, uintptr(unsafe.Pointer(argp))) +} diff --git a/vendor/github.com/pkg/term/termios/termios_const.go b/vendor/github.com/pkg/term/termios/termios_const.go new file mode 100644 index 000000000..7e319bc85 --- /dev/null +++ b/vendor/github.com/pkg/term/termios/termios_const.go @@ -0,0 +1,13 @@ +// +build !windows,!solaris + +package termios + +const ( + TCIFLUSH = 0 + TCOFLUSH = 1 + TCIOFLUSH = 2 + + TCSANOW = 0 + TCSADRAIN = 1 + TCSAFLUSH = 2 +) diff --git a/vendor/github.com/pkg/term/termios/termios_const_solaris.go b/vendor/github.com/pkg/term/termios/termios_const_solaris.go new file mode 100644 index 000000000..cd0f08266 --- /dev/null +++ b/vendor/github.com/pkg/term/termios/termios_const_solaris.go @@ -0,0 +1,14 @@ +package termios + +// #include +import "C" + +const ( + TCIFLUSH = C.TCIFLUSH + TCOFLUSH = C.TCOFLUSH + TCIOFLUSH = C.TCIOFLUSH + + TCSANOW = C.TCSANOW + TCSADRAIN = C.TCSADRAIN + TCSAFLUSH = C.TCSAFLUSH +) diff --git a/vendor/github.com/pkg/term/termios/termios_linux.go b/vendor/github.com/pkg/term/termios/termios_linux.go new file mode 100644 index 000000000..8ced0595f --- /dev/null +++ b/vendor/github.com/pkg/term/termios/termios_linux.go @@ -0,0 +1,81 @@ +package termios + +import ( + "syscall" + "unsafe" +) + +const ( + TCSETS = 0x5402 + TCSETSW = 0x5403 + TCSETSF = 0x5404 + TCFLSH = 0x540B + TCSBRK = 0x5409 + TCSBRKP = 0x5425 + + IXON = 0x00000400 + IXANY = 0x00000800 + IXOFF = 0x00001000 + CRTSCTS = 0x80000000 +) + +// Tcgetattr gets the current serial port settings. +func Tcgetattr(fd uintptr, argp *syscall.Termios) error { + return ioctl(fd, syscall.TCGETS, uintptr(unsafe.Pointer(argp))) +} + +// Tcsetattr sets the current serial port settings. +func Tcsetattr(fd, action uintptr, argp *syscall.Termios) error { + var request uintptr + switch action { + case TCSANOW: + request = TCSETS + case TCSADRAIN: + request = TCSETSW + case TCSAFLUSH: + request = TCSETSF + default: + return syscall.EINVAL + } + return ioctl(fd, request, uintptr(unsafe.Pointer(argp))) +} + +// Tcsendbreak transmits a continuous stream of zero-valued bits for a specific +// duration, if the terminal is using asynchronous serial data transmission. If +// duration is zero, it transmits zero-valued bits for at least 0.25 seconds, and not more that 0.5 seconds. +// If duration is not zero, it sends zero-valued bits for some +// implementation-defined length of time. +func Tcsendbreak(fd, duration uintptr) error { + return ioctl(fd, TCSBRKP, duration) +} + +// Tcdrain waits until all output written to the object referred to by fd has been transmitted. +func Tcdrain(fd uintptr) error { + // simulate drain with TCSADRAIN + var attr syscall.Termios + if err := Tcgetattr(fd, &attr); err != nil { + return err + } + return Tcsetattr(fd, TCSADRAIN, &attr) +} + +// Tcflush discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of selector. +func Tcflush(fd, selector uintptr) error { + return ioctl(fd, TCFLSH, selector) +} + +// Tiocinq returns the number of bytes in the input buffer. +func Tiocinq(fd uintptr, argp *int) error { + return ioctl(fd, syscall.TIOCINQ, uintptr(unsafe.Pointer(argp))) +} + +// Tiocoutq return the number of bytes in the output buffer. +func Tiocoutq(fd uintptr, argp *int) error { + return ioctl(fd, syscall.TIOCOUTQ, uintptr(unsafe.Pointer(argp))) +} + +// Cfgetispeed returns the input baud rate stored in the termios structure. +func Cfgetispeed(attr *syscall.Termios) uint32 { return attr.Ispeed } + +// Cfgetospeed returns the output baud rate stored in the termios structure. +func Cfgetospeed(attr *syscall.Termios) uint32 { return attr.Ospeed } diff --git a/vendor/github.com/pkg/term/termios/termios_solaris.go b/vendor/github.com/pkg/term/termios/termios_solaris.go new file mode 100644 index 000000000..f388194ef --- /dev/null +++ b/vendor/github.com/pkg/term/termios/termios_solaris.go @@ -0,0 +1,102 @@ +package termios + +// #include +// typedef struct termios termios_t; +import "C" + +import ( + "syscall" + + "golang.org/x/sys/unix" + "unsafe" +) + +const ( + TCSETS = 0x5402 + TCSETSW = 0x5403 + TCSETSF = 0x5404 + TCFLSH = 0x540B + TCSBRK = 0x5409 + TCSBRKP = 0x5425 + + IXON = 0x00000400 + IXANY = 0x00000800 + IXOFF = 0x00001000 + CRTSCTS = 0x80000000 +) + +// See /usr/include/sys/termios.h +const FIORDCHK = C.FIORDCHK + +// Tcgetattr gets the current serial port settings. +func Tcgetattr(fd uintptr, argp *syscall.Termios) error { + termios, err := unix.IoctlGetTermios(int(fd), unix.TCGETS) + *argp = syscall.Termios(*termios) + return err +} + +// Tcsetattr sets the current serial port settings. +func Tcsetattr(fd, action uintptr, argp *syscall.Termios) error { + return unix.IoctlSetTermios(int(fd), int(action), (*unix.Termios)(argp)) +} + +// Tcsendbreak transmits a continuous stream of zero-valued bits for a specific +// duration, if the terminal is using asynchronous serial data transmission. If +// duration is zero, it transmits zero-valued bits for at least 0.25 seconds, and not more that 0.5 seconds. +// If duration is not zero, it sends zero-valued bits for some +// implementation-defined length of time. +func Tcsendbreak(fd, duration uintptr) error { + return ioctl(fd, TCSBRKP, duration) +} + +// Tcdrain waits until all output written to the object referred to by fd has been transmitted. +func Tcdrain(fd uintptr) error { + // simulate drain with TCSADRAIN + var attr syscall.Termios + if err := Tcgetattr(fd, &attr); err != nil { + return err + } + return Tcsetattr(fd, TCSADRAIN, &attr) +} + +// Tcflush discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of selector. +func Tcflush(fd, selector uintptr) error { + return ioctl(fd, TCFLSH, selector) +} + +// Tiocinq returns the number of bytes in the input buffer. +func Tiocinq(fd uintptr, argp *int) (err error) { + *argp, err = unix.IoctlGetInt(int(fd), FIORDCHK) + return err +} + +// Tiocoutq return the number of bytes in the output buffer. +func Tiocoutq(fd uintptr, argp *int) error { + return ioctl(fd, syscall.TIOCOUTQ, uintptr(unsafe.Pointer(argp))) +} + +// Cfgetispeed returns the input baud rate stored in the termios structure. +func Cfgetispeed(attr *syscall.Termios) uint32 { + solTermios := (*unix.Termios)(attr) + return uint32(C.cfgetispeed((*C.termios_t)(unsafe.Pointer(solTermios)))) +} + +// Cfsetispeed sets the input baud rate stored in the termios structure. +func Cfsetispeed(attr *syscall.Termios, speed uintptr) error { + solTermios := (*unix.Termios)(attr) + _, err := C.cfsetispeed((*C.termios_t)(unsafe.Pointer(solTermios)), C.speed_t(speed)) + return err +} + +// Cfgetospeed returns the output baud rate stored in the termios structure. +func Cfgetospeed(attr *syscall.Termios) uint32 { + solTermios := (*unix.Termios)(attr) + return uint32(C.cfgetospeed((*C.termios_t)(unsafe.Pointer(solTermios)))) +} + +// Cfsetospeed sets the output baud rate stored in the termios structure. +func Cfsetospeed(attr *syscall.Termios, speed uintptr) error { + solTermios := (*unix.Termios)(attr) + _, err := C.cfsetospeed((*C.termios_t)(unsafe.Pointer(solTermios)), C.speed_t(speed)) + return err +} diff --git a/vendor/github.com/pkg/term/termios/termios_windows.go b/vendor/github.com/pkg/term/termios/termios_windows.go new file mode 100644 index 000000000..884378cb7 --- /dev/null +++ b/vendor/github.com/pkg/term/termios/termios_windows.go @@ -0,0 +1 @@ +package termios diff --git a/vendor/github.com/weaveworks/flux/api/v6/container.go b/vendor/github.com/weaveworks/flux/api/v6/container.go index b09bdda8c..1afc9ab62 100644 --- a/vendor/github.com/weaveworks/flux/api/v6/container.go +++ b/vendor/github.com/weaveworks/flux/api/v6/container.go @@ -3,6 +3,7 @@ package v6 import ( "github.com/pkg/errors" "github.com/weaveworks/flux/image" + "github.com/weaveworks/flux/policy" "github.com/weaveworks/flux/registry" "github.com/weaveworks/flux/update" ) @@ -15,10 +16,10 @@ type Container struct { LatestFiltered image.Info `json:",omitempty"` // All available images (ignoring tag filters) - Available []image.Info `json:",omitempty"` - AvailableError string `json:",omitempty"` - AvailableImagesCount int `json:",omitempty"` - NewAvailableImagesCount int `json:",omitempty"` + Available update.SortedImageInfos `json:",omitempty"` + AvailableError string `json:",omitempty"` + AvailableImagesCount int `json:",omitempty"` + NewAvailableImagesCount int `json:",omitempty"` // Filtered available images (matching tag filters) FilteredImagesCount int `json:",omitempty"` @@ -26,27 +27,29 @@ type Container struct { } // NewContainer creates a Container given a list of images and the current image -func NewContainer(name string, images update.ImageInfos, currentImage image.Info, tagPattern string, fields []string) (Container, error) { +func NewContainer(name string, images update.ImageInfos, currentImage image.Info, tagPattern policy.Pattern, fields []string) (Container, error) { + sorted := images.Sort(tagPattern) + // All images - imagesCount := len(images) + imagesCount := len(sorted) imagesErr := "" - if images == nil { + if sorted == nil { imagesErr = registry.ErrNoImageData.Error() } - var newImages []image.Info - for _, img := range images { - if img.CreatedAt.After(currentImage.CreatedAt) { + var newImages update.SortedImageInfos + for _, img := range sorted { + if tagPattern.Newer(&img, ¤tImage) { newImages = append(newImages, img) } } newImagesCount := len(newImages) // Filtered images - filteredImages := images.Filter(tagPattern) + filteredImages := sorted.Filter(tagPattern) filteredImagesCount := len(filteredImages) - var newFilteredImages []image.Info + var newFilteredImages update.SortedImageInfos for _, img := range filteredImages { - if img.CreatedAt.After(currentImage.CreatedAt) { + if tagPattern.Newer(&img, ¤tImage) { newFilteredImages = append(newFilteredImages, img) } } @@ -58,7 +61,7 @@ func NewContainer(name string, images update.ImageInfos, currentImage image.Info Current: currentImage, LatestFiltered: latestFiltered, - Available: images, + Available: sorted, AvailableError: imagesErr, AvailableImagesCount: imagesCount, NewAvailableImagesCount: newImagesCount, diff --git a/vendor/github.com/weaveworks/flux/cluster/manifests.go b/vendor/github.com/weaveworks/flux/cluster/manifests.go index c3448f784..8314dfe70 100644 --- a/vendor/github.com/weaveworks/flux/cluster/manifests.go +++ b/vendor/github.com/weaveworks/flux/cluster/manifests.go @@ -26,24 +26,22 @@ func ErrResourceNotFound(name string) error { type Manifests interface { // Update the image in a manifest's bytes to that given UpdateImage(def []byte, resourceID flux.ResourceID, container string, newImageID image.Ref) ([]byte, error) - // Load all the resource manifests under the path given. `baseDir` - // is used to relativise the paths, which are supplied as absolute - // paths to directories or files; at least one path must be - // supplied. - LoadManifests(baseDir, first string, rest ...string) (map[string]resource.Resource, error) + // Load all the resource manifests under the paths + // given. `baseDir` is used to relativise the paths, which are + // supplied as absolute paths to directories or files; at least + // one path should be supplied, even if it is the same as `baseDir`. + LoadManifests(baseDir string, paths []string) (map[string]resource.Resource, error) // Parse the manifests given in an exported blob ParseManifests([]byte) (map[string]resource.Resource, error) // UpdatePolicies modifies a manifest to apply the policy update specified UpdatePolicies([]byte, flux.ResourceID, policy.Update) ([]byte, error) - // ServicesWithPolicies returns all services with their associated policies - ServicesWithPolicies(path string) (policy.ResourceMap, error) } // UpdateManifest looks for the manifest for the identified resource, // reads its contents, applies f(contents), and writes the results // back to the file. -func UpdateManifest(m Manifests, root string, id flux.ResourceID, f func(manifest []byte) ([]byte, error)) error { - resources, err := m.LoadManifests(root, root) +func UpdateManifest(m Manifests, root string, paths []string, id flux.ResourceID, f func(manifest []byte) ([]byte, error)) error { + resources, err := m.LoadManifests(root, paths) if err != nil { return err } diff --git a/vendor/github.com/weaveworks/flux/cluster/mock.go b/vendor/github.com/weaveworks/flux/cluster/mock.go index 11826ad87..4943bce44 100644 --- a/vendor/github.com/weaveworks/flux/cluster/mock.go +++ b/vendor/github.com/weaveworks/flux/cluster/mock.go @@ -10,18 +10,17 @@ import ( // Doubles as a cluster.Cluster and cluster.Manifests implementation type Mock struct { - AllServicesFunc func(maybeNamespace string) ([]Controller, error) - SomeServicesFunc func([]flux.ResourceID) ([]Controller, error) - PingFunc func() error - ExportFunc func() ([]byte, error) - SyncFunc func(SyncDef) error - PublicSSHKeyFunc func(regenerate bool) (ssh.PublicKey, error) - UpdateImageFunc func(def []byte, id flux.ResourceID, container string, newImageID image.Ref) ([]byte, error) - LoadManifestsFunc func(base, first string, rest ...string) (map[string]resource.Resource, error) - ParseManifestsFunc func([]byte) (map[string]resource.Resource, error) - UpdateManifestFunc func(path, resourceID string, f func(def []byte) ([]byte, error)) error - UpdatePoliciesFunc func([]byte, flux.ResourceID, policy.Update) ([]byte, error) - ServicesWithPoliciesFunc func(path string) (policy.ResourceMap, error) + AllServicesFunc func(maybeNamespace string) ([]Controller, error) + SomeServicesFunc func([]flux.ResourceID) ([]Controller, error) + PingFunc func() error + ExportFunc func() ([]byte, error) + SyncFunc func(SyncDef) error + PublicSSHKeyFunc func(regenerate bool) (ssh.PublicKey, error) + UpdateImageFunc func(def []byte, id flux.ResourceID, container string, newImageID image.Ref) ([]byte, error) + LoadManifestsFunc func(base string, paths []string) (map[string]resource.Resource, error) + ParseManifestsFunc func([]byte) (map[string]resource.Resource, error) + UpdateManifestFunc func(path, resourceID string, f func(def []byte) ([]byte, error)) error + UpdatePoliciesFunc func([]byte, flux.ResourceID, policy.Update) ([]byte, error) } func (m *Mock) AllControllers(maybeNamespace string) ([]Controller, error) { @@ -52,8 +51,8 @@ func (m *Mock) UpdateImage(def []byte, id flux.ResourceID, container string, new return m.UpdateImageFunc(def, id, container, newImageID) } -func (m *Mock) LoadManifests(base, first string, rest ...string) (map[string]resource.Resource, error) { - return m.LoadManifestsFunc(base, first, rest...) +func (m *Mock) LoadManifests(base string, paths []string) (map[string]resource.Resource, error) { + return m.LoadManifestsFunc(base, paths) } func (m *Mock) ParseManifests(def []byte) (map[string]resource.Resource, error) { @@ -67,7 +66,3 @@ func (m *Mock) UpdateManifest(path string, resourceID string, f func(def []byte) func (m *Mock) UpdatePolicies(def []byte, id flux.ResourceID, p policy.Update) ([]byte, error) { return m.UpdatePoliciesFunc(def, id, p) } - -func (m *Mock) ServicesWithPolicies(path string) (policy.ResourceMap, error) { - return m.ServicesWithPoliciesFunc(path) -} diff --git a/vendor/github.com/weaveworks/flux/flux.go b/vendor/github.com/weaveworks/flux/flux.go index cd2f812c0..168d365f5 100644 --- a/vendor/github.com/weaveworks/flux/flux.go +++ b/vendor/github.com/weaveworks/flux/flux.go @@ -13,9 +13,14 @@ import ( var ( ErrInvalidServiceID = errors.New("invalid service ID") - LegacyServiceIDRegexp = regexp.MustCompile("^([a-zA-Z0-9_-]+)/([a-zA-Z0-9_-]+)$") - ResourceIDRegexp = regexp.MustCompile("^([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)/([a-zA-Z0-9_.-]+)$") - UnqualifiedResourceIDRegexp = regexp.MustCompile("^([a-zA-Z0-9_-]+)/([a-zA-Z0-9_.-]+)$") + LegacyServiceIDRegexp = regexp.MustCompile("^([a-zA-Z0-9_-]+)/([a-zA-Z0-9_-]+)$") + // The namespace and name commponents are (apparently + // non-normatively) defined in + // https://github.com/kubernetes/community/blob/master/contributors/design-proposals/architecture/identifiers.md + // In practice, more punctuation is used than allowed there; + // specifically, people use underscores as well as dashes and dots, and in names, colons. + ResourceIDRegexp = regexp.MustCompile("^([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)/([a-zA-Z0-9_.:-]+)$") + UnqualifiedResourceIDRegexp = regexp.MustCompile("^([a-zA-Z0-9_-]+)/([a-zA-Z0-9_.:-]+)$") ) // ResourceID is an opaque type which uniquely identifies a resource in an diff --git a/vendor/github.com/weaveworks/flux/git/export.go b/vendor/github.com/weaveworks/flux/git/export.go new file mode 100644 index 000000000..1015987ac --- /dev/null +++ b/vendor/github.com/weaveworks/flux/git/export.go @@ -0,0 +1,32 @@ +package git + +import ( + "context" + "os" +) + +type Export struct { + dir string +} + +func (e *Export) Dir() string { + return e.dir +} + +func (e *Export) Clean() { + if e.dir != "" { + os.RemoveAll(e.dir) + } +} + +// Export creates a minimal clone of the repo, at the ref given. +func (r *Repo) Export(ctx context.Context, ref string) (*Export, error) { + dir, err := r.workingClone(ctx, "") + if err != nil { + return nil, err + } + if err = checkout(ctx, dir, ref); err != nil { + return nil, err + } + return &Export{dir}, nil +} diff --git a/vendor/github.com/weaveworks/flux/git/operations.go b/vendor/github.com/weaveworks/flux/git/operations.go index 3ba49914e..732e306df 100644 --- a/vendor/github.com/weaveworks/flux/git/operations.go +++ b/vendor/github.com/weaveworks/flux/git/operations.go @@ -53,6 +53,10 @@ func mirror(ctx context.Context, workingDir, repoURL string) (path string, err e return repoPath, nil } +func checkout(ctx context.Context, workingDir, ref string) error { + return execGitCmd(ctx, workingDir, nil, "checkout", ref) +} + // checkPush sanity-checks that we can write to the upstream repo // (being able to `clone` is an adequate check that we can read the // upstream). @@ -186,21 +190,15 @@ func revlist(ctx context.Context, path, ref string) ([]string, error) { } // Return the revisions and one-line log commit messages -// subdir argument ... corresponds to the git-path flag supplied to weave-flux-agent -func onelinelog(ctx context.Context, path, refspec, subdir string) ([]Commit, error) { +func onelinelog(ctx context.Context, path, refspec string, subdirs []string) ([]Commit, error) { out := &bytes.Buffer{} - - // we need to distinguish whether subdir is populated or not, - // because supplying an empty string to execGitCmd results in git complaining about - // >> ambiguous argument '' << - if subdir != "" { - if err := execGitCmd(ctx, path, out, "log", "--oneline", "--no-abbrev-commit", refspec, "--", subdir); err != nil { - return nil, err - } - return splitLog(out.String()) + args := []string{"log", "--oneline", "--no-abbrev-commit", refspec} + if len(subdirs) > 0 { + args = append(args, "--") + args = append(args, subdirs...) } - if err := execGitCmd(ctx, path, out, "log", "--oneline", "--no-abbrev-commit", refspec); err != nil { + if err := execGitCmd(ctx, path, out, args...); err != nil { return nil, err } @@ -237,16 +235,18 @@ func moveTagAndPush(ctx context.Context, path string, tag, ref, msg, upstream st return nil } -func changedFiles(ctx context.Context, path, subPath, ref string) ([]string, error) { - // Remove leading slash if present. diff doesn't work when using github style root paths. - if len(subPath) > 0 && subPath[0] == '/' { - return []string{}, errors.New("git subdirectory should not have leading forward slash") - } +func changed(ctx context.Context, path, ref string, subPaths []string) ([]string, error) { out := &bytes.Buffer{} // This uses --diff-filter to only look at changes for file _in // the working dir_; i.e, we do not report on things that no // longer appear. - if err := execGitCmd(ctx, path, out, "diff", "--name-only", "--diff-filter=ACMRT", ref, "--", subPath); err != nil { + args := []string{"diff", "--name-only", "--diff-filter=ACMRT", ref} + if len(subPaths) > 0 { + args = append(args, "--") + args = append(args, subPaths...) + } + + if err := execGitCmd(ctx, path, out, args...); err != nil { return nil, err } return splitList(out.String()), nil @@ -293,9 +293,14 @@ func env() []string { } // check returns true if there are changes locally. -func check(ctx context.Context, workingDir, subdir string) bool { +func check(ctx context.Context, workingDir string, subdirs []string) bool { // `--quiet` means "exit with 1 if there are changes" - return execGitCmd(ctx, workingDir, nil, "diff", "--quiet", "--", subdir) != nil + args := []string{"diff", "--quiet"} + if len(subdirs) > 0 { + args = append(args, "--") + args = append(args, subdirs...) + } + return execGitCmd(ctx, workingDir, nil, args...) != nil } func findErrorMessage(output io.Reader) string { diff --git a/vendor/github.com/weaveworks/flux/git/repo.go b/vendor/github.com/weaveworks/flux/git/repo.go index 37fd2b7d6..88d3e9fcc 100644 --- a/vendor/github.com/weaveworks/flux/git/repo.go +++ b/vendor/github.com/weaveworks/flux/git/repo.go @@ -54,6 +54,7 @@ type Repo struct { // As supplied to constructor origin Remote interval time.Duration + readonly bool // State mu sync.RWMutex @@ -69,12 +70,22 @@ type Option interface { apply(*Repo) } +type optionFunc func(*Repo) + +func (f optionFunc) apply(r *Repo) { + f(r) +} + type PollInterval time.Duration func (p PollInterval) apply(r *Repo) { r.interval = time.Duration(p) } +var ReadOnly optionFunc = func(r *Repo) { + r.readonly = true +} + // NewRepo constructs a repo mirror which will sync itself. func NewRepo(origin Remote, opts ...Option) *Repo { status := RepoNew @@ -187,89 +198,123 @@ func (r *Repo) Revision(ctx context.Context, ref string) (string, error) { return refRevision(ctx, r.dir, ref) } -func (r *Repo) CommitsBefore(ctx context.Context, ref, path string) ([]Commit, error) { +func (r *Repo) CommitsBefore(ctx context.Context, ref string, paths ...string) ([]Commit, error) { r.mu.RLock() defer r.mu.RUnlock() if err := r.errorIfNotReady(); err != nil { return nil, err } - return onelinelog(ctx, r.dir, ref, path) + return onelinelog(ctx, r.dir, ref, paths) } -func (r *Repo) CommitsBetween(ctx context.Context, ref1, ref2, path string) ([]Commit, error) { +func (r *Repo) CommitsBetween(ctx context.Context, ref1, ref2 string, paths ...string) ([]Commit, error) { r.mu.RLock() defer r.mu.RUnlock() if err := r.errorIfNotReady(); err != nil { return nil, err } - return onelinelog(ctx, r.dir, ref1+".."+ref2, path) + return onelinelog(ctx, r.dir, ref1+".."+ref2, paths) } -// Start begins synchronising the repo by cloning it, then fetching -// the required tags and so on. -func (r *Repo) Start(shutdown <-chan struct{}, done *sync.WaitGroup) error { - defer done.Done() - - for { - - r.mu.RLock() - url := r.origin.URL - dir := r.dir - status := r.status - r.mu.RUnlock() - - bg := context.Background() - - switch status { +// step attempts to advance the repo state machine, and returns `true` +// if it has made progress, `false` otherwise. +func (r *Repo) step(bg context.Context) bool { + r.mu.RLock() + url := r.origin.URL + dir := r.dir + status := r.status + r.mu.RUnlock() - case RepoNoConfig: - // this is not going to change in the lifetime of this - // process, so just exit. - return nil - case RepoNew: + switch status { - rootdir, err := ioutil.TempDir(os.TempDir(), "flux-gitclone") - if err != nil { - return err - } + case RepoNoConfig: + // this is not going to change in the lifetime of this + // process, so just exit. + return false + + case RepoNew: + rootdir, err := ioutil.TempDir(os.TempDir(), "flux-gitclone") + if err != nil { + panic(err) + } + ctx, cancel := context.WithTimeout(bg, opTimeout) + dir, err = mirror(ctx, rootdir, url) + cancel() + if err == nil { + r.mu.Lock() + r.dir = dir ctx, cancel := context.WithTimeout(bg, opTimeout) - dir, err = mirror(ctx, rootdir, url) + err = r.fetch(ctx) cancel() - if err == nil { - r.mu.Lock() - r.dir = dir - ctx, cancel := context.WithTimeout(bg, opTimeout) - err = r.fetch(ctx) - cancel() - r.mu.Unlock() - } - if err == nil { - r.setUnready(RepoCloned, ErrClonedOnly) - continue // with new status, skipping timer - } - dir = "" - os.RemoveAll(rootdir) - r.setUnready(RepoNew, err) + r.mu.Unlock() + } + if err == nil { + r.setUnready(RepoCloned, ErrClonedOnly) + return true + } + dir = "" + os.RemoveAll(rootdir) + r.setUnready(RepoNew, err) + return false - case RepoCloned: + case RepoCloned: + if !r.readonly { ctx, cancel := context.WithTimeout(bg, opTimeout) err := checkPush(ctx, dir, url) cancel() - if err == nil { - r.setReady() - // Treat every transition to ready as a refresh, so - // that any listeners can respond in the same way. - r.refreshed() - continue // with new status, skipping timer + if err != nil { + r.setUnready(RepoCloned, err) + return false } - r.setUnready(RepoCloned, err) + } + + r.setReady() + // Treat every transition to ready as a refresh, so + // that any listeners can respond in the same way. + r.refreshed() + return true + + case RepoReady: + return false + } + + return false +} + +// Ready tries to advance the cloning process along as far as +// possible, and returns an error if it is not able to get to a ready +// state. +func (r *Repo) Ready(ctx context.Context) error { + for r.step(ctx) { + // keep going! + } + _, err := r.Status() + return err +} - case RepoReady: +// Start begins synchronising the repo by cloning it, then fetching +// the required tags and so on. +func (r *Repo) Start(shutdown <-chan struct{}, done *sync.WaitGroup) error { + defer done.Done() + + for { + ctx, cancel := context.WithTimeout(context.Background(), opTimeout) + advanced := r.step(ctx) + cancel() + + if advanced { + continue + } + + status, _ := r.Status() + if status == RepoReady { if err := r.refreshLoop(shutdown); err != nil { r.setUnready(RepoNew, err) continue // with new status, skipping timer } + } else if status == RepoNoConfig { + return nil } tryAgain := time.NewTimer(10 * time.Second) @@ -283,6 +328,7 @@ func (r *Repo) Start(shutdown <-chan struct{}, done *sync.WaitGroup) error { continue } } + return nil } func (r *Repo) Refresh(ctx context.Context) error { diff --git a/vendor/github.com/weaveworks/flux/git/working.go b/vendor/github.com/weaveworks/flux/git/working.go index dfe12e532..de8791ee7 100644 --- a/vendor/github.com/weaveworks/flux/git/working.go +++ b/vendor/github.com/weaveworks/flux/git/working.go @@ -2,15 +2,20 @@ package git import ( "context" + "errors" "os" "path/filepath" ) +var ( + ErrReadOnly = errors.New("cannot make a working clone of a read-only git repo") +) + // Config holds some values we use when working in the working clone of // a repo. type Config struct { - Branch string // branch we're syncing to - Path string // path within the repo containing files we care about + Branch string // branch we're syncing to + Paths []string // paths within the repo containing files we care about SyncTag string NotesRef string UserName string @@ -43,6 +48,10 @@ type CommitAction struct { // Clone returns a local working clone of the sync'ed `*Repo`, using // the config given. func (r *Repo) Clone(ctx context.Context, conf Config) (*Checkout, error) { + if r.readonly { + return nil, ErrReadOnly + } + upstream := r.Origin() repoDir, err := r.workingClone(ctx, conf.Branch) if err != nil { @@ -90,15 +99,25 @@ func (c *Checkout) Dir() string { return c.dir } -// ManifestDir returns the path to the manifests files -func (c *Checkout) ManifestDir() string { - return filepath.Join(c.dir, c.config.Path) +// ManifestDirs returns the paths to the manifests files. It ensures +// that at least one path is returned, so that it can be used with +// `Manifest.LoadManifests`. +func (c *Checkout) ManifestDirs() []string { + if len(c.config.Paths) == 0 { + return []string{c.dir} + } + + paths := make([]string, len(c.config.Paths), len(c.config.Paths)) + for i, p := range c.config.Paths { + paths[i] = filepath.Join(c.dir, p) + } + return paths } // CommitAndPush commits changes made in this checkout, along with any // extra data as a note, and pushes the commit and note to the remote repo. func (c *Checkout) CommitAndPush(ctx context.Context, commitAction CommitAction, note interface{}) error { - if !check(ctx, c.dir, c.config.Path) { + if !check(ctx, c.dir, c.config.Paths) { return ErrNoChanges } @@ -151,7 +170,7 @@ func (c *Checkout) MoveSyncTagAndPush(ctx context.Context, ref, msg string) erro // ChangedFiles does a git diff listing changed files func (c *Checkout) ChangedFiles(ctx context.Context, ref string) ([]string, error) { - list, err := changedFiles(ctx, c.dir, c.config.Path, ref) + list, err := changed(ctx, c.dir, ref, c.config.Paths) if err == nil { for i, file := range list { list[i] = filepath.Join(c.dir, file) diff --git a/vendor/github.com/weaveworks/flux/image/image.go b/vendor/github.com/weaveworks/flux/image/image.go index fbb6e179d..6311b6fa7 100644 --- a/vendor/github.com/weaveworks/flux/image/image.go +++ b/vendor/github.com/weaveworks/flux/image/image.go @@ -4,9 +4,11 @@ import ( "encoding/json" "fmt" "regexp" + "sort" "strings" "time" + "github.com/Masterminds/semver" "github.com/pkg/errors" ) @@ -276,20 +278,59 @@ func (im *Info) UnmarshalJSON(b []byte) error { return nil } -// ByCreatedDesc is a shim used to sort image info by creation date -type ByCreatedDesc []Info +// NewerByCreated returns true if lhs image should be sorted +// before rhs with regard to their creation date descending. +func NewerByCreated(lhs, rhs *Info) bool { + if lhs.CreatedAt.Equal(rhs.CreatedAt) { + return lhs.ID.String() < rhs.ID.String() + } + return lhs.CreatedAt.After(rhs.CreatedAt) +} -func (is ByCreatedDesc) Len() int { return len(is) } -func (is ByCreatedDesc) Swap(i, j int) { is[i], is[j] = is[j], is[i] } -func (is ByCreatedDesc) Less(i, j int) bool { - switch { - case is[i].CreatedAt.IsZero(): - return true - case is[j].CreatedAt.IsZero(): +// NewerBySemver returns true if lhs image should be sorted +// before rhs with regard to their semver order descending. +func NewerBySemver(lhs, rhs *Info) bool { + lv, lerr := semver.NewVersion(lhs.ID.Tag) + rv, rerr := semver.NewVersion(rhs.ID.Tag) + if (lerr != nil && rerr != nil) || (lv == rv) { + return lhs.ID.String() < rhs.ID.String() + } + if lerr != nil { return false - case is[i].CreatedAt.Equal(is[j].CreatedAt): - return is[i].ID.String() < is[j].ID.String() - default: - return is[i].CreatedAt.After(is[j].CreatedAt) } + if rerr != nil { + return true + } + cmp := lv.Compare(rv) + // In semver, `1.10` and `1.10.0` is the same but in favor of explicitness + // we should consider the latter newer. + if cmp == 0 { + return lhs.ID.String() > rhs.ID.String() + } + return cmp > 0 +} + +// Sort orders the given image infos according to `newer` func. +func Sort(infos []Info, newer func (a, b *Info) bool) { + if newer == nil { + newer = NewerByCreated + } + sort.Sort(&infoSort{infos: infos, newer: newer}) +} + +type infoSort struct { + infos []Info + newer func(a, b *Info) bool +} + +func (s *infoSort) Len() int { + return len(s.infos) +} + +func (s *infoSort) Swap(i, j int) { + s.infos[i], s.infos[j] = s.infos[j], s.infos[i] +} + +func (s *infoSort) Less(i, j int) bool { + return s.newer(&s.infos[i], &s.infos[j]) } diff --git a/vendor/github.com/weaveworks/flux/policy/pattern.go b/vendor/github.com/weaveworks/flux/policy/pattern.go new file mode 100644 index 000000000..c89864767 --- /dev/null +++ b/vendor/github.com/weaveworks/flux/policy/pattern.go @@ -0,0 +1,126 @@ +package policy + +import ( + "github.com/Masterminds/semver" + "github.com/ryanuber/go-glob" + "github.com/weaveworks/flux/image" + "strings" + "regexp" +) + +const ( + globPrefix = "glob:" + semverPrefix = "semver:" + regexpPrefix = "regexp:" +) + +var ( + // PatternAll matches everything. + PatternAll = NewPattern(globPrefix + "*") + PatternLatest = NewPattern(globPrefix + "latest") +) + +// Pattern provides an interface to match image tags. +type Pattern interface { + // Matches returns true if the given image tag matches the pattern. + Matches(tag string) bool + // String returns the prefixed string representation. + String() string + // Newer returns true if image `a` is newer than image `b`. + Newer(a, b *image.Info) bool + // Valid returns true if the pattern is considered valid. + Valid() bool +} + +type GlobPattern string + +// SemverPattern matches by semantic versioning. +// See https://semver.org/ +type SemverPattern struct { + pattern string // pattern without prefix + constraints *semver.Constraints +} + +// RegexpPattern matches by regular expression. +type RegexpPattern struct { + pattern string // pattern without prefix + regexp *regexp.Regexp +} + +// NewPattern instantiates a Pattern according to the prefix +// it finds. The prefix can be either `glob:` (default if omitted), +// `semver:` or `regexp:`. +func NewPattern(pattern string) Pattern { + switch { + case strings.HasPrefix(pattern, semverPrefix): + pattern = strings.TrimPrefix(pattern, semverPrefix) + c, _ := semver.NewConstraint(pattern) + return SemverPattern{pattern, c} + case strings.HasPrefix(pattern, regexpPrefix): + pattern = strings.TrimPrefix(pattern, regexpPrefix) + r, _ := regexp.Compile(pattern) + return RegexpPattern{pattern, r} + default: + return GlobPattern(strings.TrimPrefix(pattern, globPrefix)) + } +} + +func (g GlobPattern) Matches(tag string) bool { + return glob.Glob(string(g), tag) +} + +func (g GlobPattern) String() string { + return globPrefix + string(g) +} + +func (g GlobPattern) Newer(a, b *image.Info) bool { + return image.NewerByCreated(a, b) +} + +func (g GlobPattern) Valid() bool { + return true +} + +func (s SemverPattern) Matches(tag string) bool { + v, err := semver.NewVersion(tag) + if err != nil { + return false + } + if s.constraints == nil { + // Invalid constraints match anything + return true + } + return s.constraints.Check(v) +} + +func (s SemverPattern) String() string { + return semverPrefix + s.pattern +} + +func (s SemverPattern) Newer(a, b *image.Info) bool { + return image.NewerBySemver(a, b) +} + +func (s SemverPattern) Valid() bool { + return s.constraints != nil +} + +func (r RegexpPattern) Matches(tag string) bool { + if r.regexp == nil { + // Invalid regexp match anything + return true + } + return r.regexp.MatchString(tag) +} + +func (r RegexpPattern) String() string { + return regexpPrefix + r.pattern +} + +func (r RegexpPattern) Newer(a, b *image.Info) bool { + return image.NewerByCreated(a, b) +} + +func (r RegexpPattern) Valid() bool { + return r.regexp != nil +} diff --git a/vendor/github.com/weaveworks/flux/policy/policy.go b/vendor/github.com/weaveworks/flux/policy/policy.go index 105294fb7..ef846858a 100644 --- a/vendor/github.com/weaveworks/flux/policy/policy.go +++ b/vendor/github.com/weaveworks/flux/policy/policy.go @@ -36,15 +36,15 @@ func Tag(policy Policy) bool { return strings.HasPrefix(string(policy), "tag.") } -func GetTagPattern(services ResourceMap, service flux.ResourceID, container string) string { - if services == nil { - return "*" +func GetTagPattern(policies Set, container string) Pattern { + if policies == nil { + return PatternAll } - policies := services[service] - if pattern, ok := policies.Get(TagPrefix(container)); ok { - return strings.TrimPrefix(pattern, "glob:") + pattern, ok := policies.Get(TagPrefix(container)) + if !ok { + return PatternAll } - return "*" + return NewPattern(pattern) } type Updates map[flux.ResourceID]Update @@ -101,10 +101,14 @@ func clone(s Set) Set { return newMap } -// Contains method determines if a resource has a particular policy present -func (s Set) Contains(needle Policy) bool { - for p := range s { +// Has returns true if a resource has a particular policy present, and +// for boolean policies, if it is set to true. +func (s Set) Has(needle Policy) bool { + for p, v := range s { if p == needle { + if Boolean(needle) { + return v == "true" + } return true } } @@ -133,38 +137,3 @@ func (s Set) ToStringMap() map[string]string { } return m } - -type ResourceMap map[flux.ResourceID]Set - -func (s ResourceMap) ToSlice() []flux.ResourceID { - slice := []flux.ResourceID{} - for service, _ := range s { - slice = append(slice, service) - } - return slice -} - -func (s ResourceMap) Contains(id flux.ResourceID) bool { - _, ok := s[id] - return ok -} - -func (s ResourceMap) Without(other ResourceMap) ResourceMap { - newMap := ResourceMap{} - for k, v := range s { - if !other.Contains(k) { - newMap[k] = v - } - } - return newMap -} - -func (s ResourceMap) OnlyWithPolicy(p Policy) ResourceMap { - newMap := ResourceMap{} - for k, v := range s { - if _, ok := v[p]; ok { - newMap[k] = v - } - } - return newMap -} diff --git a/vendor/github.com/weaveworks/flux/registry/client.go b/vendor/github.com/weaveworks/flux/registry/client.go index 603bb3c1a..79ddad23a 100644 --- a/vendor/github.com/weaveworks/flux/registry/client.go +++ b/vendor/github.com/weaveworks/flux/registry/client.go @@ -18,13 +18,18 @@ import ( "github.com/weaveworks/flux/image" ) +type ImageEntry struct { + image.Info `json:",inline"` + ExcludedReason string `json:",omitempty"` +} + // Client is a remote registry client for a particular image // repository (e.g., for quay.io/weaveworks/flux). It is an interface // so we can wrap it in instrumentation, write fake implementations, // and so on. type Client interface { Tags(context.Context) ([]string, error) - Manifest(ctx context.Context, ref string) (image.Info, error) + Manifest(ctx context.Context, ref string) (ImageEntry, error) } // ClientFactory supplies Client implementations for a given repo, @@ -64,14 +69,14 @@ func (a *Remote) Tags(ctx context.Context) ([]string, error) { // Manifest fetches the metadata for an image reference; currently // assumed to be in the same repo as that provided to `NewRemote(...)` -func (a *Remote) Manifest(ctx context.Context, ref string) (image.Info, error) { +func (a *Remote) Manifest(ctx context.Context, ref string) (ImageEntry, error) { repository, err := client.NewRepository(named{a.repo}, a.base, a.transport) if err != nil { - return image.Info{}, err + return ImageEntry{}, err } manifests, err := repository.Manifests(ctx) if err != nil { - return image.Info{}, err + return ImageEntry{}, err } var manifestDigest digest.Digest digestOpt := client.ReturnContentDigest(&manifestDigest) @@ -79,7 +84,7 @@ func (a *Remote) Manifest(ctx context.Context, ref string) (image.Info, error) { interpret: if fetchErr != nil { - return image.Info{}, err + return ImageEntry{}, fetchErr } info := image.Info{ID: a.repo.ToRef(ref), Digest: manifestDigest.String()} @@ -98,7 +103,7 @@ interpret: } if err = json.Unmarshal([]byte(man.History[0].V1Compatibility), &v1); err != nil { - return image.Info{}, err + return ImageEntry{}, err } // This is not the ImageID that Docker uses, but assumed to // identify the image as it's the topmost layer. @@ -108,7 +113,7 @@ interpret: var man schema2.Manifest = deserialised.Manifest configBytes, err := repository.Blobs(ctx).Get(ctx, man.Config.Digest) if err != nil { - return image.Info{}, err + return ImageEntry{}, err } var config struct { @@ -117,7 +122,7 @@ interpret: OS string `json:"os"` } if err = json.Unmarshal(configBytes, &config); err != nil { - return image.Info{}, err + return ImageEntry{}, err } // This _is_ what Docker uses as its Image ID. info.ImageID = man.Config.Digest.String() @@ -131,10 +136,10 @@ interpret: goto interpret } } - return image.Info{}, errors.New("no suitable manifest (linux amd64) in manifestlist") + return ImageEntry{ExcludedReason: "no suitable manifest (linux amd64) in manifestlist"}, nil default: t := reflect.TypeOf(manifest) - return image.Info{}, errors.New("unknown manifest type: " + t.String()) + return ImageEntry{}, errors.New("unknown manifest type: " + t.String()) } - return info, nil + return ImageEntry{Info: info}, nil } diff --git a/vendor/github.com/weaveworks/flux/registry/monitoring.go b/vendor/github.com/weaveworks/flux/registry/monitoring.go index fcdef5444..8ef4cf189 100644 --- a/vendor/github.com/weaveworks/flux/registry/monitoring.go +++ b/vendor/github.com/weaveworks/flux/registry/monitoring.go @@ -46,9 +46,9 @@ func NewInstrumentedRegistry(next Registry) Registry { } } -func (m *instrumentedRegistry) GetSortedRepositoryImages(id image.Name) (res []image.Info, err error) { +func (m *instrumentedRegistry) GetRepositoryImages(id image.Name) (res []image.Info, err error) { start := time.Now() - res, err = m.next.GetSortedRepositoryImages(id) + res, err = m.next.GetRepositoryImages(id) registryDuration.With( fluxmetrics.LabelSuccess, strconv.FormatBool(err == nil), ).Observe(time.Since(start).Seconds()) @@ -74,7 +74,7 @@ func NewInstrumentedClient(next Client) Client { } } -func (m *instrumentedClient) Manifest(ctx context.Context, ref string) (res image.Info, err error) { +func (m *instrumentedClient) Manifest(ctx context.Context, ref string) (res ImageEntry, err error) { start := time.Now() res, err = m.next.Manifest(ctx, ref) remoteDuration.With( diff --git a/vendor/github.com/weaveworks/flux/registry/registry.go b/vendor/github.com/weaveworks/flux/registry/registry.go index 5794ee503..207db6905 100644 --- a/vendor/github.com/weaveworks/flux/registry/registry.go +++ b/vendor/github.com/weaveworks/flux/registry/registry.go @@ -12,7 +12,7 @@ var ( // Registry is a store of image metadata. type Registry interface { - GetSortedRepositoryImages(image.Name) ([]image.Info, error) + GetRepositoryImages(image.Name) ([]image.Info, error) GetImage(image.Ref) (image.Info, error) } diff --git a/vendor/github.com/weaveworks/flux/remote/rpc/clientV6.go b/vendor/github.com/weaveworks/flux/remote/rpc/clientV6.go index d563edb3a..d639c6a69 100644 --- a/vendor/github.com/weaveworks/flux/remote/rpc/clientV6.go +++ b/vendor/github.com/weaveworks/flux/remote/rpc/clientV6.go @@ -6,12 +6,12 @@ import ( "net/rpc" "net/rpc/jsonrpc" + "github.com/weaveworks/flux" "github.com/weaveworks/flux/api/v10" - "github.com/weaveworks/flux/policy" - "github.com/weaveworks/flux/api/v6" fluxerr "github.com/weaveworks/flux/errors" "github.com/weaveworks/flux/job" + "github.com/weaveworks/flux/policy" "github.com/weaveworks/flux/remote" "github.com/weaveworks/flux/update" ) @@ -131,44 +131,47 @@ type listImagesWithOptionsClient interface { // interface to dispatch .ListImages() and .ListServices() to the correct // API version. func listImagesWithOptions(ctx context.Context, client listImagesWithOptionsClient, opts v10.ListImagesOptions) ([]v6.ImageStatus, error) { - images, err := client.ListImages(ctx, opts.Spec) + statuses, err := client.ListImages(ctx, opts.Spec) if err != nil { - return images, err + return statuses, err } var ns string if opts.Spec != update.ResourceSpecAll { resourceID, err := opts.Spec.AsID() if err != nil { - return images, err + return statuses, err } ns, _, _ = resourceID.Components() } services, err := client.ListServices(ctx, ns) - policyMap := make(policy.ResourceMap) + policyMap := map[flux.ResourceID]map[string]string{} for _, service := range services { - s := policy.Set{} - for k, v := range service.Policies { - s[policy.Policy(k)] = v - } - policyMap[service.ID] = s + policyMap[service.ID] = service.Policies } // Polyfill container fields from v10 - for i, image := range images { - for j, container := range image.Containers { - tagPattern := policy.GetTagPattern(policyMap, image.ID, container.Name) + for i, status := range statuses { + for j, container := range status.Containers { + var p policy.Set + if policies, ok := policyMap[status.ID]; ok { + p = policy.Set{} + for k, v := range policies { + p[policy.Policy(k)] = v + } + } + tagPattern := policy.GetTagPattern(p, container.Name) // Create a new container using the same function used in v10 - newContainer, err := v6.NewContainer(container.Name, container.Available, container.Current, tagPattern, opts.OverrideContainerFields) + newContainer, err := v6.NewContainer(container.Name, update.ImageInfos(container.Available), container.Current, tagPattern, opts.OverrideContainerFields) if err != nil { - return images, err + return statuses, err } - images[i].Containers[j] = newContainer + statuses[i].Containers[j] = newContainer } } - return images, nil + return statuses, nil } func (p *RPCClientV6) UpdateManifests(ctx context.Context, u update.Spec) (job.ID, error) { diff --git a/vendor/github.com/weaveworks/flux/update/containers.go b/vendor/github.com/weaveworks/flux/update/containers.go index 2ad0c7289..75ea5e445 100644 --- a/vendor/github.com/weaveworks/flux/update/containers.go +++ b/vendor/github.com/weaveworks/flux/update/containers.go @@ -20,17 +20,23 @@ type ContainerSpecs struct { Kind ReleaseKind ContainerSpecs map[flux.ResourceID][]ContainerUpdate SkipMismatches bool + Force bool } // CalculateRelease computes required controller updates to satisfy this specification. // It returns an error if any spec calculation fails unless `SkipMismatches` is true. func (s ContainerSpecs) CalculateRelease(rc ReleaseContext, logger log.Logger) ([]*ControllerUpdate, Result, error) { - all, results, err := s.selectServices(rc) + results := Result{} + prefilter, postfilter := s.filters() + all, err := rc.SelectServices(results, prefilter, postfilter) if err != nil { return nil, results, err } updates := s.controllerUpdates(results, all) + return updates, results, s.resultsError(results) +} +func (s ContainerSpecs) resultsError(results Result) error { failures := 0 successes := 0 for _, res := range results { @@ -42,26 +48,25 @@ func (s ContainerSpecs) CalculateRelease(rc ReleaseContext, logger log.Logger) ( } } if failures > 0 { - return updates, results, errors.New("cannot satisfy specs") + return errors.New("cannot satisfy specs") } if successes == 0 { - return updates, results, errors.New("no changes found") + return errors.New("no changes found") } - - return updates, results, nil + return nil } -func (s ContainerSpecs) selectServices(rc ReleaseContext) ([]*ControllerUpdate, Result, error) { - results := Result{} +func (s ContainerSpecs) filters() ([]ControllerFilter, []ControllerFilter) { var rids []flux.ResourceID for rid := range s.ContainerSpecs { rids = append(rids, rid) } - all, err := rc.SelectServices(results, []ControllerFilter{&IncludeFilter{IDs: rids}}, nil) - if err != nil { - return nil, results, err + pre := []ControllerFilter{&IncludeFilter{IDs: rids}} + + if !s.Force { + return pre, []ControllerFilter{&LockedFilter{}} } - return all, results, nil + return pre, []ControllerFilter{} } func (s ContainerSpecs) controllerUpdates(results Result, all []*ControllerUpdate) []*ControllerUpdate { diff --git a/vendor/github.com/weaveworks/flux/update/filter.go b/vendor/github.com/weaveworks/flux/update/filter.go index 35c7acf44..c3f4556b1 100644 --- a/vendor/github.com/weaveworks/flux/update/filter.go +++ b/vendor/github.com/weaveworks/flux/update/filter.go @@ -3,6 +3,7 @@ package update import ( "github.com/weaveworks/flux" "github.com/weaveworks/flux/image" + "github.com/weaveworks/flux/policy" ) const ( @@ -77,16 +78,13 @@ func (f *IncludeFilter) Filter(u ControllerUpdate) ControllerResult { } type LockedFilter struct { - IDs []flux.ResourceID } func (f *LockedFilter) Filter(u ControllerUpdate) ControllerResult { - for _, id := range f.IDs { - if u.ResourceID == id { - return ControllerResult{ - Status: ReleaseStatusSkipped, - Error: Locked, - } + if u.Resource.Policy().Has(policy.Locked) { + return ControllerResult{ + Status: ReleaseStatusSkipped, + Error: Locked, } } return ControllerResult{} diff --git a/vendor/github.com/weaveworks/flux/update/images.go b/vendor/github.com/weaveworks/flux/update/images.go index 42e870a64..3e07c2e5d 100644 --- a/vendor/github.com/weaveworks/flux/update/images.go +++ b/vendor/github.com/weaveworks/flux/update/images.go @@ -6,10 +6,10 @@ import ( "github.com/go-kit/kit/log" "github.com/pkg/errors" - glob "github.com/ryanuber/go-glob" fluxerr "github.com/weaveworks/flux/errors" "github.com/weaveworks/flux/image" + "github.com/weaveworks/flux/policy" "github.com/weaveworks/flux/registry" "github.com/weaveworks/flux/resource" ) @@ -38,32 +38,26 @@ func (r ImageRepos) GetRepoImages(repo image.Name) ImageInfos { // ImageInfos is a list of image.Info which can be filtered. type ImageInfos []image.Info -// Filter returns only the images which match the tagGlob. -func (ii ImageInfos) Filter(tagGlob string) ImageInfos { - var filtered ImageInfos - for _, i := range ii { - tag := i.ID.Tag - // Ignore latest if and only if it's not what the user wants. - if !strings.EqualFold(tagGlob, "latest") && strings.EqualFold(tag, "latest") { - continue - } - if glob.Glob(tagGlob, tag) { - var im image.Info - im = i - filtered = append(filtered, im) - } - } - return filtered +// SortedImageInfos is a list of sorted image.Info +type SortedImageInfos []image.Info + +// Filter returns only the images that match the pattern, in a new list. +func (ii ImageInfos) Filter(pattern policy.Pattern) ImageInfos { + return filterImages(ii, pattern) } -// Latest returns the latest image from ImageInfos. If no such image exists, -// returns a zero value and `false`, and the caller can decide whether -// that's an error or not. -func (ii ImageInfos) Latest() (image.Info, bool) { - if len(ii) > 0 { - return ii[0], true - } - return image.Info{}, false +// Sort orders the images according to the pattern order in a new list. +func (ii ImageInfos) Sort(pattern policy.Pattern) SortedImageInfos { + return sortImages(ii, pattern) +} + +// FilterAndSort is an optimized helper function to compose filtering and sorting. +func (ii ImageInfos) FilterAndSort(pattern policy.Pattern) SortedImageInfos { + filtered := ii.Filter(pattern) + // Do not call sortImages() here which will clone the list that we already + // cloned in ImageInfos.Filter() + image.Sort(filtered, pattern.Newer) + return SortedImageInfos(filtered) } // FindWithRef returns image.Info given an image ref. If the image cannot be @@ -77,6 +71,51 @@ func (ii ImageInfos) FindWithRef(ref image.Ref) image.Info { return image.Info{ID: ref} } +// Latest returns the latest image from SortedImageInfos. If no such image exists, +// returns a zero value and `false`, and the caller can decide whether +// that's an error or not. +func (is SortedImageInfos) Latest() (image.Info, bool) { + if len(is) > 0 { + return is[0], true + } + return image.Info{}, false +} + +// Filter returns only the images that match the pattern, in a new list. +func (is SortedImageInfos) Filter(pattern policy.Pattern) SortedImageInfos { + return SortedImageInfos(filterImages(is, pattern)) +} + +// Sort orders the images according to the pattern order in a new list. +func (is SortedImageInfos) Sort(pattern policy.Pattern) SortedImageInfos { + return sortImages(is, pattern) +} + +func sortImages(images []image.Info, pattern policy.Pattern) SortedImageInfos { + var sorted SortedImageInfos + for _, i := range images { + sorted = append(sorted, i) + } + image.Sort(sorted, pattern.Newer) + return sorted +} + +// filterImages keeps the sort order pristine. +func filterImages(images []image.Info, pattern policy.Pattern) ImageInfos { + var filtered ImageInfos + for _, i := range images { + tag := i.ID.Tag + // Ignore latest if and only if it's not what the user wants. + if pattern != policy.PatternLatest && strings.EqualFold(tag, "latest") { + continue + } + if pattern.Matches(tag) { + filtered = append(filtered, i) + } + } + return filtered +} + // containers represents a collection of things that have containers type containers interface { Len() int @@ -109,7 +148,7 @@ func FetchImageRepos(reg registry.Registry, cs containers, logger log.Logger) (I } } for repo := range imageRepos { - sortedRepoImages, err := reg.GetSortedRepositoryImages(repo.Name) + images, err := reg.GetRepositoryImages(repo.Name) if err != nil { // Not an error if missing. Use empty images. if !fluxerr.IsMissing(err) { @@ -117,7 +156,7 @@ func FetchImageRepos(reg registry.Registry, cs containers, logger log.Logger) (I continue } } - imageRepos[repo] = sortedRepoImages + imageRepos[repo] = images } return ImageRepos{imageRepos}, nil } diff --git a/vendor/github.com/weaveworks/flux/update/menu.go b/vendor/github.com/weaveworks/flux/update/menu.go new file mode 100644 index 000000000..ce0b9a6c8 --- /dev/null +++ b/vendor/github.com/weaveworks/flux/update/menu.go @@ -0,0 +1,286 @@ +package update + +import ( + "bytes" + "errors" + "fmt" + "io" + "text/tabwriter" + + "github.com/weaveworks/flux" +) + +const ( + // Escape sequences. + moveCursorUp = "\033[%dA" + hideCursor = "\033[?25l" + showCursor = "\033[?25h" + + // Glyphs. + glyphSelected = "\u21d2" + glyphChecked = "\u25c9" + glyphUnchecked = "\u25ef" + + tableHeading = "CONTROLLER \tSTATUS \tUPDATES" +) + +type writer struct { + out io.Writer + tw *tabwriter.Writer + lines int // lines written since last clear + width uint16 // terminal width +} + +func newWriter(out io.Writer) *writer { + return &writer{ + out: out, + tw: tabwriter.NewWriter(out, 0, 2, 2, ' ', 0), + width: terminalWidth(), + } +} + +func (c *writer) hideCursor() { + fmt.Fprintf(c.out, hideCursor) +} + +func (c *writer) showCursor() { + fmt.Fprintf(c.out, showCursor) +} + +// writeln counts the lines we output. +func (c *writer) writeln(line string) error { + line += "\n" + c.lines += (len(line)-1)/int(c.width) + 1 + _, err := c.tw.Write([]byte(line)) + return err +} + +// clear moves the terminal cursor up to the beginning of the +// line where we started writing. +func (c *writer) clear() { + if c.lines != 0 { + fmt.Fprintf(c.out, moveCursorUp, c.lines) + } + c.lines = 0 +} + +func (c *writer) flush() error { + return c.tw.Flush() +} + +type menuItem struct { + id flux.ResourceID + status ControllerUpdateStatus + error string + update ContainerUpdate + + checked bool +} + +// Menu presents a list of controllers which can be interacted with. +type Menu struct { + wr *writer + items []menuItem + selectable int + cursor int +} + +// NewMenu creates a menu printer that outputs a result set to +// the `io.Writer` provided, at the given level of verbosity: +// - 2 = include skipped and ignored resources +// - 1 = include skipped resources, exclude ignored resources +// - 0 = exclude skipped and ignored resources +// +// It can print a one time listing with `Print()` or then enter +// interactive mode with `Run()`. +func NewMenu(out io.Writer, results Result, verbosity int) *Menu { + m := &Menu{wr: newWriter(out)} + m.fromResults(results, verbosity) + return m +} + +func (m *Menu) fromResults(results Result, verbosity int) { + for _, serviceID := range results.ServiceIDs() { + resourceID := flux.MustParseResourceID(serviceID) + result := results[resourceID] + switch result.Status { + case ReleaseStatusIgnored: + if verbosity < 2 { + continue + } + case ReleaseStatusSkipped: + if verbosity < 1 { + continue + } + } + + if result.Error != "" { + m.AddItem(menuItem{ + id: resourceID, + status: result.Status, + error: result.Error, + }) + } + for _, upd := range result.PerContainer { + m.AddItem(menuItem{ + id: resourceID, + status: result.Status, + update: upd, + }) + } + if result.Error == "" && len(result.PerContainer) == 0 { + m.AddItem(menuItem{ + id: resourceID, + status: result.Status, + }) + } + } + return +} + +func (m *Menu) AddItem(mi menuItem) { + if mi.checkable() { + mi.checked = true + m.selectable++ + } + m.items = append(m.items, mi) +} + +// Run starts the interactive menu mode. +func (m *Menu) Run() (map[flux.ResourceID][]ContainerUpdate, error) { + specs := make(map[flux.ResourceID][]ContainerUpdate) + if m.selectable == 0 { + return specs, errors.New("No changes found.") + } + + m.printInteractive() + m.wr.hideCursor() + defer m.wr.showCursor() + + for { + ascii, keyCode, err := getChar() + if err != nil { + return specs, err + } + + switch ascii { + case 3, 27, 'q': + return specs, errors.New("Aborted.") + case ' ': + m.toggleSelected() + case 13: + for _, item := range m.items { + if item.checked { + specs[item.id] = append(specs[item.id], item.update) + } + } + m.wr.writeln("") + return specs, nil + case 9, 'j': + m.cursorDown() + case 'k': + m.cursorUp() + default: + switch keyCode { + case 40: + m.cursorDown() + case 38: + m.cursorUp() + } + } + + } +} + +func (m *Menu) Print() { + m.wr.writeln(tableHeading) + var previd flux.ResourceID + for _, item := range m.items { + inline := previd == item.id + m.wr.writeln(m.renderItem(item, inline)) + previd = item.id + } + m.wr.flush() +} + +func (m *Menu) printInteractive() { + m.wr.clear() + m.wr.writeln(" " + tableHeading) + i := 0 + var previd flux.ResourceID + for _, item := range m.items { + inline := previd == item.id + m.wr.writeln(m.renderInteractiveItem(item, inline, i)) + previd = item.id + if item.checkable() { + i++ + } + } + m.wr.writeln("") + m.wr.writeln("Use arrow keys and [Space] to toggle updates; hit [Enter] to release selected.") + + m.wr.flush() +} + +func (m *Menu) renderItem(item menuItem, inline bool) string { + if inline { + return fmt.Sprintf("\t\t%s", item.updates()) + } else { + return fmt.Sprintf("%s\t%s\t%s", item.id, item.status, item.updates()) + } +} + +func (m *Menu) renderInteractiveItem(item menuItem, inline bool, index int) string { + pre := bytes.Buffer{} + if index == m.cursor { + pre.WriteString(glyphSelected) + } else { + pre.WriteString(" ") + } + pre.WriteString(" ") + pre.WriteString(item.checkbox()) + pre.WriteString(" ") + pre.WriteString(m.renderItem(item, inline)) + + return pre.String() +} + +func (m *Menu) toggleSelected() { + m.items[m.cursor].checked = !m.items[m.cursor].checked + m.printInteractive() +} + +func (m *Menu) cursorDown() { + m.cursor = (m.cursor + 1) % m.selectable + m.printInteractive() +} + +func (m *Menu) cursorUp() { + m.cursor = (m.cursor + m.selectable - 1) % m.selectable + m.printInteractive() +} + +func (i menuItem) checkbox() string { + switch { + case !i.checkable(): + return " " + case i.checked: + return glyphChecked + default: + return glyphUnchecked + } +} + +func (i menuItem) checkable() bool { + return i.update.Container != "" +} + +func (i menuItem) updates() string { + if i.update.Container != "" { + return fmt.Sprintf("%s: %s -> %s", + i.update.Container, + i.update.Current.String(), + i.update.Target.Tag) + } + return i.error +} diff --git a/vendor/github.com/weaveworks/flux/update/menu_unix.go b/vendor/github.com/weaveworks/flux/update/menu_unix.go new file mode 100644 index 000000000..8e622a483 --- /dev/null +++ b/vendor/github.com/weaveworks/flux/update/menu_unix.go @@ -0,0 +1,57 @@ +// +build !windows + +package update + +import ( + "os" + + "github.com/pkg/term" + "golang.org/x/sys/unix" +) + +func terminalWidth() uint16 { + ws, _ := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ) + if ws != nil && ws.Col != 0 { + return ws.Col + } + return 9999 +} + +// See https://github.com/paulrademacher/climenu/blob/master/getchar.go +func getChar() (ascii int, keyCode int, err error) { + t, _ := term.Open("/dev/tty") + term.RawMode(t) + bs := make([]byte, 3) + + var numRead int + numRead, err = t.Read(bs) + if err != nil { + return + } + if numRead == 3 && bs[0] == 27 && bs[1] == 91 { + // Three-character control sequence, beginning with "ESC-[". + + // Since there are no ASCII codes for arrow keys, we use + // Javascript key codes. + if bs[2] == 65 { + // Up + keyCode = 38 + } else if bs[2] == 66 { + // Down + keyCode = 40 + } else if bs[2] == 67 { + // Right + keyCode = 39 + } else if bs[2] == 68 { + // Left + keyCode = 37 + } + } else if numRead == 1 { + ascii = int(bs[0]) + } else { + // Two characters read?? + } + t.Restore() + t.Close() + return +} diff --git a/vendor/github.com/weaveworks/flux/update/menu_win.go b/vendor/github.com/weaveworks/flux/update/menu_win.go new file mode 100644 index 000000000..63e72ef76 --- /dev/null +++ b/vendor/github.com/weaveworks/flux/update/menu_win.go @@ -0,0 +1,13 @@ +// +build windows + +package update + +import "errors" + +func terminalWidth() uint16 { + return 9999 +} + +func getChar() (ascii int, keyCode int, err error) { + return 0, 0, errors.New("Error: Interactive mode is not supported on Windows") +} diff --git a/vendor/github.com/weaveworks/flux/update/print.go b/vendor/github.com/weaveworks/flux/update/print.go index ee21f7efe..d01c4e6ca 100644 --- a/vendor/github.com/weaveworks/flux/update/print.go +++ b/vendor/github.com/weaveworks/flux/update/print.go @@ -1,11 +1,7 @@ package update import ( - "fmt" "io" - "text/tabwriter" - - "github.com/weaveworks/flux" ) // PrintResults outputs a result set to the `io.Writer` provided, at @@ -14,38 +10,5 @@ import ( // - 1 = include skipped resources, exclude ignored resources // - 0 = exclude skipped and ignored resources func PrintResults(out io.Writer, results Result, verbosity int) { - w := tabwriter.NewWriter(out, 0, 2, 2, ' ', 0) - fmt.Fprintln(w, "CONTROLLER \tSTATUS \tUPDATES") - for _, serviceID := range results.ServiceIDs() { - result := results[flux.MustParseResourceID(serviceID)] - switch result.Status { - case ReleaseStatusIgnored: - if verbosity < 2 { - continue - } - case ReleaseStatusSkipped: - if verbosity < 1 { - continue - } - } - - var extraLines []string - if result.Error != "" { - extraLines = append(extraLines, result.Error) - } - for _, update := range result.PerContainer { - extraLines = append(extraLines, fmt.Sprintf("%s: %s -> %s", update.Container, update.Current.String(), update.Target.Tag)) - } - - var inline string - if len(extraLines) > 0 { - inline = extraLines[0] - extraLines = extraLines[1:] - } - fmt.Fprintf(w, "%s\t%s\t%s\n", serviceID, result.Status, inline) - for _, lines := range extraLines { - fmt.Fprintf(w, "\t\t%s\n", lines) - } - } - w.Flush() + NewMenu(out, results, verbosity).Print() } diff --git a/vendor/github.com/weaveworks/flux/update/release.go b/vendor/github.com/weaveworks/flux/update/release.go index ff5a23c50..863da1d5f 100644 --- a/vendor/github.com/weaveworks/flux/update/release.go +++ b/vendor/github.com/weaveworks/flux/update/release.go @@ -8,7 +8,6 @@ import ( "github.com/go-kit/kit/log" "github.com/weaveworks/flux" - "github.com/weaveworks/flux/cluster" "github.com/weaveworks/flux/image" "github.com/weaveworks/flux/policy" "github.com/weaveworks/flux/registry" @@ -47,9 +46,7 @@ const UserAutomated = "" type ReleaseContext interface { SelectServices(Result, []ControllerFilter, []ControllerFilter) ([]*ControllerUpdate, error) - ServicesWithPolicies() (policy.ResourceMap, error) Registry() registry.Registry - Manifests() cluster.Manifests } // NB: these get sent from fluxctl, so we have to maintain the json format of @@ -122,13 +119,13 @@ func (s ReleaseSpec) filters(rc ReleaseContext) ([]ControllerFilter, []Controlle var prefilters, postfilters []ControllerFilter ids := []flux.ResourceID{} - for _, s := range s.ServiceSpecs { - if s == ResourceSpecAll { + for _, ss := range s.ServiceSpecs { + if ss == ResourceSpecAll { // "" Overrides any other filters ids = []flux.ResourceID{} break } - id, err := flux.ParseResourceID(string(s)) + id, err := flux.ParseResourceID(string(ss)) if err != nil { return nil, nil, err } @@ -152,13 +149,10 @@ func (s ReleaseSpec) filters(rc ReleaseContext) ([]ControllerFilter, []Controlle postfilters = append(postfilters, &SpecificImageFilter{id}) } - // Locked filter - services, err := rc.ServicesWithPolicies() - if err != nil { - return nil, nil, err + // Filter out locked controllers unless given a specific controller(s) and forced + if !(len(ids) > 0 && s.Force) { + postfilters = append(postfilters, &LockedFilter{}) } - lockedSet := services.OnlyWithPolicy(policy.Locked) - postfilters = append(postfilters, &LockedFilter{lockedSet.ToSlice()}) return prefilters, postfilters, nil } @@ -231,7 +225,16 @@ func (s ReleaseSpec) calculateImageUpdates(rc ReleaseContext, candidates []*Cont for _, container := range containers { currentImageID := container.Image - filteredImages := imageRepos.GetRepoImages(currentImageID.Name).Filter("*") + tagPattern := policy.PatternAll + // Use the container's filter if the spec does not want to force release, or + // all images requested + if !s.Force || s.ImageSpec == ImageSpecLatest { + if pattern, ok := u.Resource.Policy().Get(policy.TagPrefix(container.Name)); ok { + tagPattern = policy.NewPattern(pattern) + } + } + + filteredImages := imageRepos.GetRepoImages(currentImageID.Name).FilterAndSort(tagPattern) latestImage, ok := filteredImages.Latest() if !ok { if currentImageID.CanonicalName() != singleRepo {