diff -Nru snapd-2.37.1+18.04/advisor/backend.go snapd-2.34.2+18.04/advisor/backend.go --- snapd-2.37.1+18.04/advisor/backend.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/advisor/backend.go 2018-07-19 10:05:50.000000000 +0000 @@ -22,14 +22,12 @@ import ( "encoding/json" "os" - "path/filepath" "time" "github.com/snapcore/bolt" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/strutil" ) var ( @@ -38,7 +36,6 @@ ) type writer struct { - fn string db *bolt.DB tx *bolt.Tx cmdBucket *bolt.Bucket @@ -64,24 +61,32 @@ // these closes the database again. func Create() (CommandDB, error) { var err error - t := &writer{ - fn: dirs.SnapCommandsDB + "." + strutil.MakeRandomString(12) + "~", - } + t := &writer{} - t.db, err = bolt.Open(t.fn, 0644, &bolt.Options{Timeout: 1 * time.Second}) + t.db, err = bolt.Open(dirs.SnapCommandsDB, 0644, &bolt.Options{Timeout: 1 * time.Second}) if err != nil { return nil, err } t.tx, err = t.db.Begin(true) if err == nil { - t.cmdBucket, err = t.tx.CreateBucket(cmdBucketKey) - if err == nil { - t.pkgBucket, err = t.tx.CreateBucket(pkgBucketKey) + err := t.tx.DeleteBucket(cmdBucketKey) + if err == nil || err == bolt.ErrBucketNotFound { + t.cmdBucket, err = t.tx.CreateBucket(cmdBucketKey) } - if err != nil { t.tx.Rollback() + + } + + if err == nil { + err := t.tx.DeleteBucket(pkgBucketKey) + if err == nil || err == bolt.ErrBucketNotFound { + t.pkgBucket, err = t.tx.CreateBucket(pkgBucketKey) + } + if err != nil { + t.tx.Rollback() + } } } @@ -132,35 +137,11 @@ } func (t *writer) Commit() error { - // either everything worked, and therefore this will fail, or something - // will fail, and that error is more important than this one if this one - // then fails as well. So, ignore the error. - defer os.Remove(t.fn) - - if err := t.done(true); err != nil { - return err - } - - dir, err := os.Open(filepath.Dir(dirs.SnapCommandsDB)) - if err != nil { - return err - } - defer dir.Close() - - if err := os.Rename(t.fn, dirs.SnapCommandsDB); err != nil { - return err - } - - return dir.Sync() + return t.done(true) } func (t *writer) Rollback() error { - e1 := t.done(false) - e2 := os.Remove(t.fn) - if e1 == nil { - return e2 - } - return e1 + return t.done(false) } func (t *writer) done(commit bool) error { diff -Nru snapd-2.37.1+18.04/arch/arch.go snapd-2.34.2+18.04/arch/arch.go --- snapd-2.37.1+18.04/arch/arch.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/arch/arch.go 2018-07-19 10:05:50.000000000 +0000 @@ -22,8 +22,9 @@ import ( "log" "runtime" + "syscall" - "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/release" ) // ArchitectureType is the type for a supported snappy architecture @@ -77,7 +78,8 @@ // arch mapping. The Go arch sadly doesn't map this out // for us so we have to fallback to uname here. if goarch == "arm" { - if osutil.MachineName() == "armv6l" { + machineName := release.Machine() + if machineName == "armv6l" { return "armel" } } @@ -95,7 +97,24 @@ // UbuntuArchitecture - however there maybe cases that you run e.g. // a snapd:i386 on an amd64 kernel. func UbuntuKernelArchitecture() string { - return ubuntuArchFromKernelArch(osutil.MachineName()) + var utsname syscall.Utsname + if err := syscall.Uname(&utsname); err != nil { + log.Panicf("cannot get kernel architecture: %v", err) + } + + // syscall.Utsname{} is using [65]int8 for all char[] inside it, + // this makes converting it so awkward. The alternative would be + // to use a unsafe.Pointer() to cast it to a [65]byte slice. + // see https://github.com/golang/go/issues/20753 + kernelArch := make([]byte, 0, len(utsname.Machine)) + for _, c := range utsname.Machine { + if c == 0 { + break + } + kernelArch = append(kernelArch, byte(c)) + } + + return ubuntuArchFromKernelArch(string(kernelArch)) } // ubuntuArchFromkernelArch maps the kernel architecture as reported diff -Nru snapd-2.37.1+18.04/asserts/asserts.go snapd-2.34.2+18.04/asserts/asserts.go --- snapd-2.37.1+18.04/asserts/asserts.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/asserts.go 2018-07-19 10:05:50.000000000 +0000 @@ -124,8 +124,7 @@ // 1: plugs and slots // 2: support for $SLOT()/$PLUG()/$MISSING - // 3: support for on-store/on-brand/on-model device scope constraints - maxSupportedFormat[SnapDeclarationType.Name] = 3 + maxSupportedFormat[SnapDeclarationType.Name] = 2 } func MockMaxSupportedFormat(assertType *AssertionType, maxFormat int) (restore func()) { diff -Nru snapd-2.37.1+18.04/asserts/device_asserts.go snapd-2.34.2+18.04/asserts/device_asserts.go --- snapd-2.37.1+18.04/asserts/device_asserts.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/device_asserts.go 2018-07-19 10:05:50.000000000 +0000 @@ -24,8 +24,6 @@ "regexp" "strings" "time" - - "github.com/snapcore/snapd/strutil" ) // Model holds a model assertion, which is a statement by a brand @@ -73,40 +71,14 @@ return mod.HeaderString("architecture") } -// snapWithTrack represents a snap that includes optional track -// information like `snapName=trackName` -type snapWithTrack string - -func (s snapWithTrack) Snap() string { - return strings.SplitN(string(s), "=", 2)[0] -} - -func (s snapWithTrack) Track() string { - l := strings.SplitN(string(s), "=", 2) - if len(l) > 1 { - return l[1] - } - return "" -} - // Gadget returns the gadget snap the model uses. func (mod *Model) Gadget() string { - return snapWithTrack(mod.HeaderString("gadget")).Snap() -} - -// GadgetTrack returns the gadget track the model uses. -func (mod *Model) GadgetTrack() string { - return snapWithTrack(mod.HeaderString("gadget")).Track() + return mod.HeaderString("gadget") } // Kernel returns the kernel snap the model uses. func (mod *Model) Kernel() string { - return snapWithTrack(mod.HeaderString("kernel")).Snap() -} - -// KernelTrack returns the kernel track the model uses. -func (mod *Model) KernelTrack() string { - return snapWithTrack(mod.HeaderString("kernel")).Track() + return mod.HeaderString("kernel") } // Base returns the base snap the model uses. @@ -146,40 +118,11 @@ // limit model to only lowercase for now var validModel = regexp.MustCompile("^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$") -func checkSnapWithTrackHeader(header string, headers map[string]interface{}) error { - _, ok := headers[header] - if !ok { - return nil - } - value, ok := headers[header].(string) - if !ok { - return fmt.Errorf(`%q header must be a string`, header) - } - l := strings.SplitN(value, "=", 2) - - if err := validateSnapName(l[0], header); err != nil { - return err - } - if len(l) == 1 { - return nil - } - track := l[1] - if strings.Count(track, "/") != 0 { - return fmt.Errorf(`%q channel selector must be a track name only`, header) - } - channelRisks := []string{"stable", "candidate", "beta", "edge"} - if strutil.ListContains(channelRisks, track) { - return fmt.Errorf(`%q channel selector must be a track name`, header) - } - return nil -} - func checkModel(headers map[string]interface{}) (string, error) { s, err := checkStringMatches(headers, "model", validModel) if err != nil { return "", err } - // TODO: support the concept of case insensitive/preserving string headers if strings.ToLower(s) != s { return "", fmt.Errorf(`"model" header cannot contain uppercase letters`) @@ -222,29 +165,6 @@ classicModelOptional = []string{"architecture", "gadget"} ) -var almostValidName = regexp.MustCompile("^[a-z0-9-]*[a-z][a-z0-9-]*$") - -// validateSnapName checks whether the name can be used as a snap name -// -// This function should be synchronized with the reference implementation -// snap.ValidateName() in snap/validate.go -func validateSnapName(name string, headerName string) error { - isValidName := func() bool { - if !almostValidName.MatchString(name) { - return false - } - if name[0] == '-' || name[len(name)-1] == '-' || strings.Contains(name, "--") { - return false - } - return true - } - - if len(name) > 40 || !isValidName() { - return fmt.Errorf("invalid snap name in %q header: %s", headerName, name) - } - return nil -} - func assembleModel(assert assertionBase) (Assertion, error) { err := checkAuthorityMatchesBrand(&assert) if err != nil { @@ -283,25 +203,6 @@ } } - // kernel/gadget must be valid snap names and can have (optional) tracks - // - validate those - if err := checkSnapWithTrackHeader("kernel", assert.headers); err != nil { - return nil, err - } - if err := checkSnapWithTrackHeader("gadget", assert.headers); err != nil { - return nil, err - } - // base, if provided, must be a valid snap name too - base, err := checkOptionalString(assert.headers, "base") - if err != nil { - return nil, err - } - if base != "" { - if err := validateSnapName(base, "base"); err != nil { - return nil, err - } - } - // store is optional but must be a string, defaults to the ubuntu store _, err = checkOptionalString(assert.headers, "store") if err != nil { @@ -314,16 +215,11 @@ return nil, err } - // required snap must be valid snap names + // TODO parallel-install: verify if snap names are valid store names reqSnaps, err := checkStringList(assert.headers, "required-snaps") if err != nil { return nil, err } - for _, name := range reqSnaps { - if err := validateSnapName(name, "required-snaps"); err != nil { - return nil, err - } - } sysUserAuthority, err := checkOptionalSystemUserAuthority(assert.headers, assert.HeaderString("brand-id")) if err != nil { diff -Nru snapd-2.37.1+18.04/asserts/device_asserts_test.go snapd-2.34.2+18.04/asserts/device_asserts_test.go --- snapd-2.37.1+18.04/asserts/device_asserts_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/device_asserts_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -20,7 +20,6 @@ package asserts_test import ( - "fmt" "strings" "time" @@ -102,9 +101,7 @@ c.Check(model.DisplayName(), Equals, "Baz 3000") c.Check(model.Architecture(), Equals, "amd64") c.Check(model.Gadget(), Equals, "brand-gadget") - c.Check(model.GadgetTrack(), Equals, "") c.Check(model.Kernel(), Equals, "baz-linux") - c.Check(model.KernelTrack(), Equals, "") c.Check(model.Base(), Equals, "core18") c.Check(model.Store(), Equals, "brand-store") c.Check(model.RequiredSnaps(), DeepEquals, []string{"foo", "bar"}) @@ -167,92 +164,6 @@ c.Check(model.RequiredSnaps(), HasLen, 0) } -func (mods *modelSuite) TestDecodeValidatesSnapNames(c *C) { - withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) - encoded := strings.Replace(withTimestamp, reqSnaps, "required-snaps:\n - foo_bar\n - bar\n", 1) - a, err := asserts.Decode([]byte(encoded)) - c.Assert(a, IsNil) - c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "required-snaps" header: foo_bar`) - - encoded = strings.Replace(withTimestamp, reqSnaps, "required-snaps:\n - foo\n - bar-;;''\n", 1) - a, err = asserts.Decode([]byte(encoded)) - c.Assert(a, IsNil) - c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "required-snaps" header: bar-;;''`) - - encoded = strings.Replace(withTimestamp, "kernel: baz-linux\n", "kernel: baz-linux_instance\n", 1) - a, err = asserts.Decode([]byte(encoded)) - c.Assert(a, IsNil) - c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "kernel" header: baz-linux_instance`) - - encoded = strings.Replace(withTimestamp, "gadget: brand-gadget\n", "gadget: brand-gadget_instance\n", 1) - a, err = asserts.Decode([]byte(encoded)) - c.Assert(a, IsNil) - c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "gadget" header: brand-gadget_instance`) - - encoded = strings.Replace(withTimestamp, "base: core18\n", "base: core18_instance\n", 1) - a, err = asserts.Decode([]byte(encoded)) - c.Assert(a, IsNil) - c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "base" header: core18_instance`) -} - -func (mods modelSuite) TestDecodeValidSnapNames(c *C) { - // reuse test cases for snap.ValidateName() - - withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) - - validNames := []string{ - "a", "aa", "aaa", "aaaa", - "a-a", "aa-a", "a-aa", "a-b-c", - "a0", "a-0", "a-0a", - "01game", "1-or-2", - // a regexp stresser - "u-94903713687486543234157734673284536758", - } - for _, name := range validNames { - encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", fmt.Sprintf("kernel: %s\n", name), 1) - a, err := asserts.Decode([]byte(encoded)) - c.Assert(err, IsNil) - model := a.(*asserts.Model) - c.Check(model.Kernel(), Equals, name) - } - invalidNames := []string{ - // name cannot be empty, never reaches snap name validation - "", - // names cannot be too long - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "xxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx", - "1111111111111111111111111111111111111111x", - "x1111111111111111111111111111111111111111", - "x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x", - // a regexp stresser - "u-9490371368748654323415773467328453675-", - // dashes alone are not a name - "-", "--", - // double dashes in a name are not allowed - "a--a", - // name should not end with a dash - "a-", - // name cannot have any spaces in it - "a ", " a", "a a", - // a number alone is not a name - "0", "123", - // identifier must be plain ASCII - "日本語", "한글", "ру́сский язы́к", - // instance names are invalid too - "foo_bar", "x_1", - } - for _, name := range invalidNames { - encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", fmt.Sprintf("kernel: %s\n", name), 1) - a, err := asserts.Decode([]byte(encoded)) - c.Assert(a, IsNil) - if name != "" { - c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "kernel" header: .*`) - } else { - c.Assert(err, ErrorMatches, `assertion model: "kernel" header should not be empty`) - } - } -} - func (mods *modelSuite) TestDecodeSystemUserAuthorityIsOptional(c *C) { withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) encoded := strings.Replace(withTimestamp, sysUserAuths, "", 1) @@ -269,26 +180,6 @@ c.Check(model.SystemUserAuthority(), DeepEquals, []string{"foo", "bar"}) } -func (mods *modelSuite) TestDecodeKernelTrack(c *C) { - withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) - encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", "kernel: baz-linux=18\n", 1) - a, err := asserts.Decode([]byte(encoded)) - c.Assert(err, IsNil) - model := a.(*asserts.Model) - c.Check(model.Kernel(), Equals, "baz-linux") - c.Check(model.KernelTrack(), Equals, "18") -} - -func (mods *modelSuite) TestDecodeGadgetTrack(c *C) { - withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) - encoded := strings.Replace(withTimestamp, "gadget: brand-gadget\n", "gadget: brand-gadget=18\n", 1) - a, err := asserts.Decode([]byte(encoded)) - c.Assert(err, IsNil) - model := a.(*asserts.Model) - c.Check(model.Gadget(), Equals, "brand-gadget") - c.Check(model.GadgetTrack(), Equals, "18") -} - const ( modelErrPrefix = "assertion model: " ) @@ -312,16 +203,8 @@ {"architecture: amd64\n", "architecture: \n", `"architecture" header should not be empty`}, {"gadget: brand-gadget\n", "", `"gadget" header is mandatory`}, {"gadget: brand-gadget\n", "gadget: \n", `"gadget" header should not be empty`}, - {"gadget: brand-gadget\n", "gadget: brand-gadget=x/x/x\n", `"gadget" channel selector must be a track name only`}, - {"gadget: brand-gadget\n", "gadget: brand-gadget=stable\n", `"gadget" channel selector must be a track name`}, - {"gadget: brand-gadget\n", "gadget: brand-gadget=18/beta\n", `"gadget" channel selector must be a track name only`}, - {"gadget: brand-gadget\n", "gadget:\n - xyz \n", `"gadget" header must be a string`}, {"kernel: baz-linux\n", "", `"kernel" header is mandatory`}, {"kernel: baz-linux\n", "kernel: \n", `"kernel" header should not be empty`}, - {"kernel: baz-linux\n", "kernel: baz-linux=x/x/x\n", `"kernel" channel selector must be a track name only`}, - {"kernel: baz-linux\n", "kernel: baz-linux=stable\n", `"kernel" channel selector must be a track name`}, - {"kernel: baz-linux\n", "kernel: baz-linux=18/beta\n", `"kernel" channel selector must be a track name only`}, - {"kernel: baz-linux\n", "kernel:\n - xyz \n", `"kernel" header must be a string`}, {"store: brand-store\n", "store:\n - xyz\n", `"store" header must be a string`}, {mods.tsLine, "", `"timestamp" header is mandatory`}, {mods.tsLine, "timestamp: \n", `"timestamp" header should not be empty`}, @@ -389,7 +272,6 @@ c.Check(model.Architecture(), Equals, "amd64") c.Check(model.Gadget(), Equals, "brand-gadget") c.Check(model.Kernel(), Equals, "") - c.Check(model.KernelTrack(), Equals, "") c.Check(model.Store(), Equals, "brand-store") c.Check(model.RequiredSnaps(), DeepEquals, []string{"foo", "bar"}) } diff -Nru snapd-2.37.1+18.04/asserts/header_checks.go snapd-2.34.2+18.04/asserts/header_checks.go --- snapd-2.37.1+18.04/asserts/header_checks.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/header_checks.go 2018-07-19 10:05:50.000000000 +0000 @@ -196,10 +196,8 @@ return b, nil } -// checkStringListInMap returns the `name` entry in the `m` map as a (possibly nil) `[]string` -// if `m` has an entry for `name` and it isn't a `[]string`, an error is returned -// if pattern is not nil, all the strings must match that pattern, otherwise an error is returned -// `what` is a descriptor, used for error messages +var anyString = regexp.MustCompile("") + func checkStringListInMap(m map[string]interface{}, name, what string, pattern *regexp.Regexp) ([]string, error) { value, ok := m[name] if !ok { @@ -218,7 +216,7 @@ if !ok { return nil, fmt.Errorf("%s must be a list of strings", what) } - if pattern != nil && !pattern.MatchString(s) { + if !pattern.MatchString(s) { return nil, fmt.Errorf("%s contains an invalid element: %q", what, s) } res[i] = s @@ -227,7 +225,7 @@ } func checkStringList(headers map[string]interface{}, name string) ([]string, error) { - return checkStringListMatches(headers, name, nil) + return checkStringListMatches(headers, name, anyString) } func checkStringListMatches(headers map[string]interface{}, name string, pattern *regexp.Regexp) ([]string, error) { diff -Nru snapd-2.37.1+18.04/asserts/ifacedecls.go snapd-2.34.2+18.04/asserts/ifacedecls.go --- snapd-2.37.1+18.04/asserts/ifacedecls.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/ifacedecls.go 2018-07-19 10:05:50.000000000 +0000 @@ -37,8 +37,6 @@ const ( // feature label for $SLOT()/$PLUG()/$MISSING dollarAttrConstraintsFeature = "dollar-attr-constraints" - // feature label for on-store/on-brand/on-model - deviceScopeConstraintsFeature = "device-scope-constraints" ) type attrMatcher interface { @@ -366,66 +364,6 @@ SystemIDs []string } -// DeviceScopeConstraint specifies a constraints based on which brand -// store, brand or model the device belongs to. -type DeviceScopeConstraint struct { - Store []string - Brand []string - // Model is a list of precise "/" constraints - Model []string -} - -var ( - validStoreID = regexp.MustCompile("^[-A-Z0-9a-z_]+$") - validBrandSlashModel = regexp.MustCompile("^(" + - strings.Trim(validAccountID.String(), "^$") + - ")/(" + - strings.Trim(validModel.String(), "^$") + - ")$") - deviceScopeConstraints = map[string]*regexp.Regexp{ - "on-store": validStoreID, - "on-brand": validAccountID, - // on-model constraints are of the form list of - // / strings where are account - // IDs as they appear in the respective model assertion - "on-model": validBrandSlashModel, - } -) - -func detectDeviceScopeConstraint(cMap map[string]interface{}) bool { - // for consistency and simplicity we support all of on-store, - // on-brand, and on-model to appear together. The interpretation - // layer will AND them as usual - for field := range deviceScopeConstraints { - if cMap[field] != nil { - return true - } - } - return false -} - -func compileDeviceScopeConstraint(cMap map[string]interface{}, context string) (constr *DeviceScopeConstraint, err error) { - // initial map size of 2: we expect usual cases to have just one of the - // constraints or rarely 2 - deviceConstr := make(map[string][]string, 2) - for field, validRegexp := range deviceScopeConstraints { - vals, err := checkStringListInMap(cMap, field, fmt.Sprintf("%s in %s", field, context), validRegexp) - if err != nil { - return nil, err - } - deviceConstr[field] = vals - } - - if len(deviceConstr) == 0 { - return nil, fmt.Errorf("internal error: misdetected device scope constraints in %s", context) - } - return &DeviceScopeConstraint{ - Store: deviceConstr["on-store"], - Brand: deviceConstr["on-brand"], - Model: deviceConstr["on-model"], - }, nil -} - // rules var ( @@ -463,7 +401,6 @@ setAttributeConstraints(field string, cstrs *AttributeConstraints) setIDConstraints(field string, cstrs []string) setOnClassicConstraint(onClassic *OnClassicConstraint) - setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) } func baseCompileConstraints(context string, cDef constraintsDef, target constraintsHolder, attrConstraints, idConstraints []string) error { @@ -528,21 +465,8 @@ } target.setOnClassicConstraint(c) } - if !detectDeviceScopeConstraint(cMap) { - defaultUsed++ - } else { - c, err := compileDeviceScopeConstraint(cMap, context) - if err != nil { - return err - } - target.setDeviceScopeConstraint(c) - } - // checks whether defaults have been used for everything, which is not - // well-formed - // +1+1 accounts for defaults for missing on-classic plus missing - // on-store/on-brand/on-model - if defaultUsed == len(attributeConstraints)+len(idConstraints)+1+1 { - return fmt.Errorf("%s must specify at least one of %s, %s, on-classic, on-store, on-brand, on-model", context, strings.Join(attrConstraints, ", "), strings.Join(idConstraints, ", ")) + if defaultUsed == len(attributeConstraints)+len(idConstraints)+1 { + return fmt.Errorf("%s must specify at least one of %s, %s, on-classic", context, strings.Join(attrConstraints, ", "), strings.Join(idConstraints, ", ")) } return nil } @@ -713,14 +637,9 @@ PlugAttributes *AttributeConstraints OnClassic *OnClassicConstraint - - DeviceScope *DeviceScopeConstraint } func (c *PlugInstallationConstraints) feature(flabel string) bool { - if flabel == deviceScopeConstraintsFeature { - return c.DeviceScope != nil - } return c.PlugAttributes.feature(flabel) } @@ -746,10 +665,6 @@ c.OnClassic = onClassic } -func (c *PlugInstallationConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { - c.DeviceScope = deviceScope -} - func compilePlugInstallationConstraints(context string, cDef constraintsDef) (constraintsHolder, error) { plugInstCstrs := &PlugInstallationConstraints{} err := baseCompileConstraints(context, cDef, plugInstCstrs, []string{"plug-attributes"}, []string{"plug-snap-type"}) @@ -771,14 +686,9 @@ SlotAttributes *AttributeConstraints OnClassic *OnClassicConstraint - - DeviceScope *DeviceScopeConstraint } func (c *PlugConnectionConstraints) feature(flabel string) bool { - if flabel == deviceScopeConstraintsFeature { - return c.DeviceScope != nil - } return c.PlugAttributes.feature(flabel) || c.SlotAttributes.feature(flabel) } @@ -810,10 +720,6 @@ c.OnClassic = onClassic } -func (c *PlugConnectionConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { - c.DeviceScope = deviceScope -} - var ( attributeConstraints = []string{"plug-attributes", "slot-attributes"} plugIDConstraints = []string{"slot-snap-type", "slot-publisher-id", "slot-snap-id"} @@ -963,14 +869,9 @@ SlotAttributes *AttributeConstraints OnClassic *OnClassicConstraint - - DeviceScope *DeviceScopeConstraint } func (c *SlotInstallationConstraints) feature(flabel string) bool { - if flabel == deviceScopeConstraintsFeature { - return c.DeviceScope != nil - } return c.SlotAttributes.feature(flabel) } @@ -996,10 +897,6 @@ c.OnClassic = onClassic } -func (c *SlotInstallationConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { - c.DeviceScope = deviceScope -} - func compileSlotInstallationConstraints(context string, cDef constraintsDef) (constraintsHolder, error) { slotInstCstrs := &SlotInstallationConstraints{} err := baseCompileConstraints(context, cDef, slotInstCstrs, []string{"slot-attributes"}, []string{"slot-snap-type"}) @@ -1021,14 +918,9 @@ PlugAttributes *AttributeConstraints OnClassic *OnClassicConstraint - - DeviceScope *DeviceScopeConstraint } func (c *SlotConnectionConstraints) feature(flabel string) bool { - if flabel == deviceScopeConstraintsFeature { - return c.DeviceScope != nil - } return c.PlugAttributes.feature(flabel) || c.SlotAttributes.feature(flabel) } @@ -1064,10 +956,6 @@ c.OnClassic = onClassic } -func (c *SlotConnectionConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { - c.DeviceScope = deviceScope -} - func compileSlotConnectionConstraints(context string, cDef constraintsDef) (constraintsHolder, error) { slotConnCstrs := &SlotConnectionConstraints{} err := baseCompileConstraints(context, cDef, slotConnCstrs, attributeConstraints, slotIDConstraints) diff -Nru snapd-2.37.1+18.04/asserts/ifacedecls_test.go snapd-2.34.2+18.04/asserts/ifacedecls_test.go --- snapd-2.37.1+18.04/asserts/ifacedecls_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/ifacedecls_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -777,65 +777,6 @@ c.Check(rule.AllowInstallation[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true, SystemIDs: []string{"ubuntu", "debian"}}) } -func (s *plugSlotRulesSuite) TestCompilePlugRuleInstallationConstraintsDeviceScope(c *C) { - m, err := asserts.ParseHeaders([]byte(`iface: - allow-installation: true`)) - c.Assert(err, IsNil) - - rule, err := asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{})) - c.Assert(err, IsNil) - - c.Check(rule.AllowInstallation[0].DeviceScope, IsNil) - - tests := []struct { - rule string - expected asserts.DeviceScopeConstraint - }{ - {`iface: - allow-installation: - on-store: - - my-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store"}}}, - {`iface: - allow-installation: - on-store: - - my-store - - other-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store", "other-store"}}}, - {`iface: - allow-installation: - on-brand: - - my-brand - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT`, asserts.DeviceScopeConstraint{Brand: []string{"my-brand", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT"}}}, - {`iface: - allow-installation: - on-model: - - my-brand/bar - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}}, - {`iface: - allow-installation: - on-store: - - store1 - - store2 - on-brand: - - my-brand - on-model: - - my-brand/bar - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{ - Store: []string{"store1", "store2"}, - Brand: []string{"my-brand"}, - Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}}, - } - - for _, t := range tests { - m, err = asserts.ParseHeaders([]byte(t.rule)) - c.Assert(err, IsNil) - - rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{})) - c.Assert(err, IsNil) - - c.Check(rule.AllowInstallation[0].DeviceScope, DeepEquals, &t.expected) - } -} - func (s *plugSlotRulesSuite) TestCompilePlugRuleConnectionConstraintsIDConstraints(c *C) { rule, err := asserts.CompilePlugRule("iface", map[string]interface{}{ "allow-connection": map[string]interface{}{ @@ -897,65 +838,6 @@ c.Check(rule.AllowConnection[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true, SystemIDs: []string{"ubuntu", "debian"}}) } -func (s *plugSlotRulesSuite) TestCompilePlugRuleConnectionConstraintsDeviceScope(c *C) { - m, err := asserts.ParseHeaders([]byte(`iface: - allow-connection: true`)) - c.Assert(err, IsNil) - - rule, err := asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{})) - c.Assert(err, IsNil) - - c.Check(rule.AllowInstallation[0].DeviceScope, IsNil) - - tests := []struct { - rule string - expected asserts.DeviceScopeConstraint - }{ - {`iface: - allow-connection: - on-store: - - my-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store"}}}, - {`iface: - allow-connection: - on-store: - - my-store - - other-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store", "other-store"}}}, - {`iface: - allow-connection: - on-brand: - - my-brand - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT`, asserts.DeviceScopeConstraint{Brand: []string{"my-brand", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT"}}}, - {`iface: - allow-connection: - on-model: - - my-brand/bar - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}}, - {`iface: - allow-connection: - on-store: - - store1 - - store2 - on-brand: - - my-brand - on-model: - - my-brand/bar - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{ - Store: []string{"store1", "store2"}, - Brand: []string{"my-brand"}, - Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}}, - } - - for _, t := range tests { - m, err = asserts.ParseHeaders([]byte(t.rule)) - c.Assert(err, IsNil) - - rule, err = asserts.CompilePlugRule("iface", m["iface"].(map[string]interface{})) - c.Assert(err, IsNil) - - c.Check(rule.AllowConnection[0].DeviceScope, DeepEquals, &t.expected) - } -} - func (s *plugSlotRulesSuite) TestCompilePlugRuleConnectionConstraintsAttributesDefault(c *C) { rule, err := asserts.CompilePlugRule("iface", map[string]interface{}{ "allow-connection": map[string]interface{}{ @@ -1018,51 +900,21 @@ {`iface: allow-connection: slot-snap-ids: - - foo`, `allow-connection in plug rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, on-classic, on-store, on-brand, on-model`}, + - foo`, `allow-connection in plug rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, on-classic`}, {`iface: deny-connection: slot-snap-ids: - - foo`, `deny-connection in plug rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, on-classic, on-store, on-brand, on-model`}, + - foo`, `deny-connection in plug rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, on-classic`}, {`iface: allow-auto-connection: slot-snap-ids: - - foo`, `allow-auto-connection in plug rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, on-classic, on-store, on-brand, on-model`}, + - foo`, `allow-auto-connection in plug rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, on-classic`}, {`iface: deny-auto-connection: slot-snap-ids: - - foo`, `deny-auto-connection in plug rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, on-classic, on-store, on-brand, on-model`}, + - foo`, `deny-auto-connection in plug rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, slot-snap-type, slot-publisher-id, slot-snap-id, on-classic`}, {`iface: allow-connect: true`, `plug rule for interface "iface" must specify at least one of allow-installation, deny-installation, allow-connection, deny-connection, allow-auto-connection, deny-auto-connection`}, - {`iface: - allow-installation: - on-store: true`, `on-store in allow-installation in plug rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-installation: - on-store: store1`, `on-store in allow-installation in plug rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-installation: - on-store: - - zoom!`, `on-store in allow-installation in plug rule for interface \"iface\" contains an invalid element: \"zoom!\"`}, - {`iface: - allow-connection: - on-brand: true`, `on-brand in allow-connection in plug rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-connection: - on-brand: brand1`, `on-brand in allow-connection in plug rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-connection: - on-brand: - - zoom!`, `on-brand in allow-connection in plug rule for interface \"iface\" contains an invalid element: \"zoom!\"`}, - {`iface: - allow-auto-connection: - on-model: true`, `on-model in allow-auto-connection in plug rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-auto-connection: - on-model: foo/bar`, `on-model in allow-auto-connection in plug rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-auto-connection: - on-model: - - foo/!qz`, `on-model in allow-auto-connection in plug rule for interface \"iface\" contains an invalid element: \"foo/!qz"`}, } for _, t := range tests { @@ -1074,14 +926,6 @@ } } -var ( - deviceScopeConstrs = map[string][]interface{}{ - "on-store": {"store"}, - "on-brand": {"brand"}, - "on-model": {"brand/model"}, - } -) - func (s *plugSlotRulesSuite) TestPlugRuleFeatures(c *C) { combos := []struct { subrule string @@ -1111,8 +955,6 @@ c.Assert(err, IsNil) c.Check(asserts.RuleFeature(rule, "dollar-attr-constraints"), Equals, false, Commentf("%v", ruleMap)) - c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, false, Commentf("%v", ruleMap)) - attrConstraintMap["a"] = "$MISSING" rule, err = asserts.CompilePlugRule("iface", ruleMap) c.Assert(err, IsNil) @@ -1124,20 +966,6 @@ c.Assert(err, IsNil) c.Check(asserts.RuleFeature(rule, "dollar-attr-constraints"), Equals, true, Commentf("%v", ruleMap)) - c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, false, Commentf("%v", ruleMap)) - - } - - for deviceScopeConstr, value := range deviceScopeConstrs { - ruleMap := map[string]interface{}{ - combo.subrule: map[string]interface{}{ - deviceScopeConstr: value, - }, - } - - rule, err := asserts.CompilePlugRule("iface", ruleMap) - c.Assert(err, IsNil) - c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, true, Commentf("%v", ruleMap)) } } } @@ -1395,65 +1223,6 @@ c.Check(rule.AllowInstallation[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true, SystemIDs: []string{"ubuntu", "debian"}}) } -func (s *plugSlotRulesSuite) TestCompileSlotRuleInstallationConstraintsDeviceScope(c *C) { - m, err := asserts.ParseHeaders([]byte(`iface: - allow-installation: true`)) - c.Assert(err, IsNil) - - rule, err := asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{})) - c.Assert(err, IsNil) - - c.Check(rule.AllowInstallation[0].DeviceScope, IsNil) - - tests := []struct { - rule string - expected asserts.DeviceScopeConstraint - }{ - {`iface: - allow-installation: - on-store: - - my-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store"}}}, - {`iface: - allow-installation: - on-store: - - my-store - - other-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store", "other-store"}}}, - {`iface: - allow-installation: - on-brand: - - my-brand - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT`, asserts.DeviceScopeConstraint{Brand: []string{"my-brand", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT"}}}, - {`iface: - allow-installation: - on-model: - - my-brand/bar - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}}, - {`iface: - allow-installation: - on-store: - - store1 - - store2 - on-brand: - - my-brand - on-model: - - my-brand/bar - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{ - Store: []string{"store1", "store2"}, - Brand: []string{"my-brand"}, - Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}}, - } - - for _, t := range tests { - m, err = asserts.ParseHeaders([]byte(t.rule)) - c.Assert(err, IsNil) - - rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{})) - c.Assert(err, IsNil) - - c.Check(rule.AllowInstallation[0].DeviceScope, DeepEquals, &t.expected) - } -} - func (s *plugSlotRulesSuite) TestCompileSlotRuleConnectionConstraintsIDConstraints(c *C) { rule, err := asserts.CompileSlotRule("iface", map[string]interface{}{ "allow-connection": map[string]interface{}{ @@ -1514,65 +1283,6 @@ c.Check(rule.AllowConnection[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: true, SystemIDs: []string{"ubuntu", "debian"}}) } -func (s *plugSlotRulesSuite) TestCompileSlotRuleConnectionConstraintsDeviceScope(c *C) { - m, err := asserts.ParseHeaders([]byte(`iface: - allow-connection: true`)) - c.Assert(err, IsNil) - - rule, err := asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{})) - c.Assert(err, IsNil) - - c.Check(rule.AllowConnection[0].DeviceScope, IsNil) - - tests := []struct { - rule string - expected asserts.DeviceScopeConstraint - }{ - {`iface: - allow-connection: - on-store: - - my-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store"}}}, - {`iface: - allow-connection: - on-store: - - my-store - - other-store`, asserts.DeviceScopeConstraint{Store: []string{"my-store", "other-store"}}}, - {`iface: - allow-connection: - on-brand: - - my-brand - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT`, asserts.DeviceScopeConstraint{Brand: []string{"my-brand", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT"}}}, - {`iface: - allow-connection: - on-model: - - my-brand/bar - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}}, - {`iface: - allow-connection: - on-store: - - store1 - - store2 - on-brand: - - my-brand - on-model: - - my-brand/bar - - s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz`, asserts.DeviceScopeConstraint{ - Store: []string{"store1", "store2"}, - Brand: []string{"my-brand"}, - Model: []string{"my-brand/bar", "s9zGdwb16ysLeRW6nRivwZS5r9puP8JT/baz"}}}, - } - - for _, t := range tests { - m, err = asserts.ParseHeaders([]byte(t.rule)) - c.Assert(err, IsNil) - - rule, err = asserts.CompileSlotRule("iface", m["iface"].(map[string]interface{})) - c.Assert(err, IsNil) - - c.Check(rule.AllowConnection[0].DeviceScope, DeepEquals, &t.expected) - } -} - func (s *plugSlotRulesSuite) TestCompileSlotRuleErrors(c *C) { tests := []struct { stanza string @@ -1629,51 +1339,21 @@ {`iface: allow-connection: plug-snap-ids: - - foo`, `allow-connection in slot rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, on-classic, on-store, on-brand, on-model`}, + - foo`, `allow-connection in slot rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, on-classic`}, {`iface: deny-connection: plug-snap-ids: - - foo`, `deny-connection in slot rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, on-classic, on-store, on-brand, on-model`}, + - foo`, `deny-connection in slot rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, on-classic`}, {`iface: allow-auto-connection: plug-snap-ids: - - foo`, `allow-auto-connection in slot rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, on-classic, on-store, on-brand, on-model`}, + - foo`, `allow-auto-connection in slot rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, on-classic`}, {`iface: deny-auto-connection: plug-snap-ids: - - foo`, `deny-auto-connection in slot rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, on-classic, on-store, on-brand, on-model`}, + - foo`, `deny-auto-connection in slot rule for interface "iface" must specify at least one of plug-attributes, slot-attributes, plug-snap-type, plug-publisher-id, plug-snap-id, on-classic`}, {`iface: allow-connect: true`, `slot rule for interface "iface" must specify at least one of allow-installation, deny-installation, allow-connection, deny-connection, allow-auto-connection, deny-auto-connection`}, - {`iface: - allow-installation: - on-store: true`, `on-store in allow-installation in slot rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-installation: - on-store: store1`, `on-store in allow-installation in slot rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-installation: - on-store: - - zoom!`, `on-store in allow-installation in slot rule for interface \"iface\" contains an invalid element: \"zoom!\"`}, - {`iface: - allow-connection: - on-brand: true`, `on-brand in allow-connection in slot rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-connection: - on-brand: brand1`, `on-brand in allow-connection in slot rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-connection: - on-brand: - - zoom!`, `on-brand in allow-connection in slot rule for interface \"iface\" contains an invalid element: \"zoom!\"`}, - {`iface: - allow-auto-connection: - on-model: true`, `on-model in allow-auto-connection in slot rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-auto-connection: - on-model: foo/bar`, `on-model in allow-auto-connection in slot rule for interface \"iface\" must be a list of strings`}, - {`iface: - allow-auto-connection: - on-model: - - foo//bar`, `on-model in allow-auto-connection in slot rule for interface \"iface\" contains an invalid element: \"foo//bar"`}, } for _, t := range tests { @@ -1712,101 +1392,11 @@ c.Assert(err, IsNil) c.Check(asserts.RuleFeature(rule, "dollar-attr-constraints"), Equals, false, Commentf("%v", ruleMap)) - c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, false, Commentf("%v", ruleMap)) - attrConstraintMap["a"] = "$PLUG(a)" rule, err = asserts.CompileSlotRule("iface", ruleMap) c.Assert(err, IsNil) c.Check(asserts.RuleFeature(rule, "dollar-attr-constraints"), Equals, true, Commentf("%v", ruleMap)) - c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, false, Commentf("%v", ruleMap)) - } - - for deviceScopeConstr, value := range deviceScopeConstrs { - ruleMap := map[string]interface{}{ - combo.subrule: map[string]interface{}{ - deviceScopeConstr: value, - }, - } - - rule, err := asserts.CompileSlotRule("iface", ruleMap) - c.Assert(err, IsNil) - c.Check(asserts.RuleFeature(rule, "device-scope-constraints"), Equals, true, Commentf("%v", ruleMap)) - } - } -} - -func (s *plugSlotRulesSuite) TestValidOnStoreBrandModel(c *C) { - tests := []struct { - constr string - value string - valid bool - }{ - {"on-store", "", false}, - {"on-store", "foo", true}, - {"on-store", "F_o-O88", true}, - {"on-store", "foo!", false}, - {"on-store", "foo.", false}, - {"on-store", "foo/", false}, - {"on-brand", "", false}, - // custom set brands (length 2-28) - {"on-brand", "dwell", true}, - {"on-brand", "Dwell", false}, - {"on-brand", "dwell-88", true}, - {"on-brand", "dwell_88", false}, - {"on-brand", "dwell.88", false}, - {"on-brand", "dwell:88", false}, - {"on-brand", "dwell!88", false}, - {"on-brand", "a", false}, - {"on-brand", "ab", true}, - {"on-brand", "0123456789012345678901234567", true}, - // snappy id brands (fixed length 32) - {"on-brand", "01234567890123456789012345678", false}, - {"on-brand", "012345678901234567890123456789", false}, - {"on-brand", "0123456789012345678901234567890", false}, - {"on-brand", "01234567890123456789012345678901", true}, - {"on-brand", "abcdefghijklmnopqrstuvwxyz678901", true}, - {"on-brand", "ABCDEFGHIJKLMNOPQRSTUVWCYZ678901", true}, - {"on-brand", "ABCDEFGHIJKLMNOPQRSTUVWCYZ678901X", false}, - {"on-brand", "ABCDEFGHIJKLMNOPQ!STUVWCYZ678901", false}, - {"on-brand", "ABCDEFGHIJKLMNOPQ_STUVWCYZ678901", false}, - {"on-brand", "ABCDEFGHIJKLMNOPQ-STUVWCYZ678901", false}, - {"on-model", "", false}, - {"on-model", "/", false}, - {"on-model", "dwell/dwell1", true}, - {"on-model", "dwell", false}, - {"on-model", "dwell/", false}, - {"on-model", "dwell//dwell1", false}, - {"on-model", "dwell/-dwell1", false}, - {"on-model", "dwell/dwell1-", false}, - {"on-model", "dwell/dwell1-23", true}, - {"on-model", "dwell/dwell1!", false}, - {"on-model", "dwell/dwe_ll1", false}, - {"on-model", "dwell/dwe.ll1", false}, - } - - check := func(constr, value string, valid bool) { - ruleMap := map[string]interface{}{ - "allow-auto-connection": map[string]interface{}{ - constr: []interface{}{value}, - }, - } - - _, err := asserts.CompilePlugRule("iface", ruleMap) - if valid { - c.Check(err, IsNil, Commentf("%v", ruleMap)) - } else { - c.Check(err, ErrorMatches, fmt.Sprintf(`%s in allow-auto-connection in plug rule for interface "iface" contains an invalid element: %q`, constr, value), Commentf("%v", ruleMap)) - } - } - - for _, t := range tests { - check(t.constr, t.value, t.valid) - - if t.constr == "on-brand" { - // reuse and double check all brands also in the context of on-model! - - check("on-model", t.value+"/foo", t.valid) } } } diff -Nru snapd-2.37.1+18.04/asserts/snapasserts/snapasserts.go snapd-2.34.2+18.04/asserts/snapasserts/snapasserts.go --- snapd-2.37.1+18.04/asserts/snapasserts/snapasserts.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/snapasserts/snapasserts.go 2018-07-19 10:05:50.000000000 +0000 @@ -53,34 +53,34 @@ return snapDecl, nil } -// CrossCheck tries to cross check the instance name, hash digest and size of a snap plus its metadata in a SideInfo with the relevant snap assertions in a database that should have been populated with them. -func CrossCheck(instanceName, snapSHA3_384 string, snapSize uint64, si *snap.SideInfo, db Finder) error { +// CrossCheck tries to cross check the name, hash digest and size of a snap plus its metadata in a SideInfo with the relevant snap assertions in a database that should have been populated with them. +func CrossCheck(name, snapSHA3_384 string, snapSize uint64, si *snap.SideInfo, db Finder) error { // get relevant assertions and do cross checks a, err := db.Find(asserts.SnapRevisionType, map[string]string{ "snap-sha3-384": snapSHA3_384, }) if err != nil { - return fmt.Errorf("internal error: cannot find pre-populated snap-revision assertion for %q: %s", instanceName, snapSHA3_384) + return fmt.Errorf("internal error: cannot find pre-populated snap-revision assertion for %q: %s", name, snapSHA3_384) } snapRev := a.(*asserts.SnapRevision) if snapRev.SnapSize() != snapSize { - return fmt.Errorf("snap %q file does not have expected size according to signatures (download is broken or tampered): %d != %d", instanceName, snapSize, snapRev.SnapSize()) + return fmt.Errorf("snap %q file does not have expected size according to signatures (download is broken or tampered): %d != %d", name, snapSize, snapRev.SnapSize()) } snapID := si.SnapID if snapRev.SnapID() != snapID || snapRev.SnapRevision() != si.Revision.N { - return fmt.Errorf("snap %q does not have expected ID or revision according to assertions (metadata is broken or tampered): %s / %s != %d / %s", instanceName, si.Revision, snapID, snapRev.SnapRevision(), snapRev.SnapID()) + return fmt.Errorf("snap %q does not have expected ID or revision according to assertions (metadata is broken or tampered): %s / %s != %d / %s", name, si.Revision, snapID, snapRev.SnapRevision(), snapRev.SnapID()) } - snapDecl, err := findSnapDeclaration(snapID, instanceName, db) + snapDecl, err := findSnapDeclaration(snapID, name, db) if err != nil { return err } - if snapDecl.SnapName() != snap.InstanceSnap(instanceName) { - return fmt.Errorf("cannot install %q, snap %q is undergoing a rename to %q", instanceName, snap.InstanceSnap(instanceName), snapDecl.SnapName()) + if snapDecl.SnapName() != name { + return fmt.Errorf("cannot install snap %q that is undergoing a rename to %q", name, snapDecl.SnapName()) } return nil @@ -142,14 +142,4 @@ } return f.Fetch(ref) -} - -// FetchStore fetches the store assertion and its prerequisites for the given store id using the given fetcher. -func FetchStore(f asserts.Fetcher, storeID string) error { - ref := &asserts.Ref{ - Type: asserts.StoreType, - PrimaryKey: []string{storeID}, - } - - return f.Fetch(ref) } diff -Nru snapd-2.37.1+18.04/asserts/snapasserts/snapasserts_test.go snapd-2.34.2+18.04/asserts/snapasserts/snapasserts_test.go --- snapd-2.37.1+18.04/asserts/snapasserts/snapasserts_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/snapasserts/snapasserts_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -119,12 +119,9 @@ Revision: snap.R(12), } - // everything cross checks, with the regular snap name + // everything cross checks err = snapasserts.CrossCheck("foo", digest, size, si, s.localDB) c.Check(err, IsNil) - // and a snap instance name - err = snapasserts.CrossCheck("foo_instance", digest, size, si, s.localDB) - c.Check(err, IsNil) } func (s *snapassertsSuite) TestCrossCheckErrors(c *C) { @@ -151,8 +148,6 @@ // different size err = snapasserts.CrossCheck("foo", digest, size+1, si, s.localDB) c.Check(err, ErrorMatches, fmt.Sprintf(`snap "foo" file does not have expected size according to signatures \(download is broken or tampered\): %d != %d`, size+1, size)) - err = snapasserts.CrossCheck("foo_instance", digest, size+1, si, s.localDB) - c.Check(err, ErrorMatches, fmt.Sprintf(`snap "foo_instance" file does not have expected size according to signatures \(download is broken or tampered\): %d != %d`, size+1, size)) // mismatched revision vs what we got from store original info err = snapasserts.CrossCheck("foo", digest, size, &snap.SideInfo{ @@ -160,11 +155,6 @@ Revision: snap.R(21), }, s.localDB) c.Check(err, ErrorMatches, `snap "foo" does not have expected ID or revision according to assertions \(metadata is broken or tampered\): 21 / snap-id-1 != 12 / snap-id-1`) - err = snapasserts.CrossCheck("foo_instance", digest, size, &snap.SideInfo{ - SnapID: "snap-id-1", - Revision: snap.R(21), - }, s.localDB) - c.Check(err, ErrorMatches, `snap "foo_instance" does not have expected ID or revision according to assertions \(metadata is broken or tampered\): 21 / snap-id-1 != 12 / snap-id-1`) // mismatched snap id vs what we got from store original info err = snapasserts.CrossCheck("foo", digest, size, &snap.SideInfo{ @@ -172,17 +162,10 @@ Revision: snap.R(12), }, s.localDB) c.Check(err, ErrorMatches, `snap "foo" does not have expected ID or revision according to assertions \(metadata is broken or tampered\): 12 / snap-id-other != 12 / snap-id-1`) - err = snapasserts.CrossCheck("foo_instance", digest, size, &snap.SideInfo{ - SnapID: "snap-id-other", - Revision: snap.R(12), - }, s.localDB) - c.Check(err, ErrorMatches, `snap "foo_instance" does not have expected ID or revision according to assertions \(metadata is broken or tampered\): 12 / snap-id-other != 12 / snap-id-1`) // changed name err = snapasserts.CrossCheck("baz", digest, size, si, s.localDB) - c.Check(err, ErrorMatches, `cannot install "baz", snap "baz" is undergoing a rename to "foo"`) - err = snapasserts.CrossCheck("baz_instance", digest, size, si, s.localDB) - c.Check(err, ErrorMatches, `cannot install "baz_instance", snap "baz" is undergoing a rename to "foo"`) + c.Check(err, ErrorMatches, `cannot install snap "baz" that is undergoing a rename to "foo"`) } @@ -223,8 +206,6 @@ err = snapasserts.CrossCheck("foo", digest, size, si, s.localDB) c.Check(err, ErrorMatches, `cannot install snap "foo" with a revoked snap declaration`) - err = snapasserts.CrossCheck("foo_instance", digest, size, si, s.localDB) - c.Check(err, ErrorMatches, `cannot install snap "foo_instance" with a revoked snap declaration`) } func (s *snapassertsSuite) TestDeriveSideInfoHappy(c *C) { diff -Nru snapd-2.37.1+18.04/asserts/snap_asserts.go snapd-2.34.2+18.04/asserts/snap_asserts.go --- snapd-2.37.1+18.04/asserts/snap_asserts.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/snap_asserts.go 2018-07-19 10:05:50.000000000 +0000 @@ -152,13 +152,7 @@ if !(plugsOk || slotsOk) { return 0, nil } - formatnum = 1 - setFormatNum := func(num int) { - if num > formatnum { - formatnum = num - } - } plugs, err := checkMap(headers, "plugs") if err != nil { @@ -166,10 +160,7 @@ } err = compilePlugRules(plugs, func(_ string, rule *PlugRule) { if rule.feature(dollarAttrConstraintsFeature) { - setFormatNum(2) - } - if rule.feature(deviceScopeConstraintsFeature) { - setFormatNum(3) + formatnum = 2 } }) if err != nil { @@ -182,10 +173,7 @@ } err = compileSlotRules(slots, func(_ string, rule *SlotRule) { if rule.feature(dollarAttrConstraintsFeature) { - setFormatNum(2) - } - if rule.feature(deviceScopeConstraintsFeature) { - setFormatNum(3) + formatnum = 2 } }) if err != nil { diff -Nru snapd-2.37.1+18.04/asserts/snap_asserts_test.go snapd-2.34.2+18.04/asserts/snap_asserts_test.go --- snapd-2.37.1+18.04/asserts/snap_asserts_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/snap_asserts_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -406,87 +406,6 @@ c.Assert(err, IsNil) c.Check(fmtnum, Equals, 2) - // combinations with on-store/on-brand/on-model => format 3 - for _, side := range []string{"plugs", "slots"} { - for k, vals := range deviceScopeConstrs { - - headers := map[string]interface{}{ - side: map[string]interface{}{ - "interface3": map[string]interface{}{ - "allow-installation": map[string]interface{}{ - k: vals, - }, - }, - }, - } - fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil) - c.Assert(err, IsNil) - c.Check(fmtnum, Equals, 3) - - for _, conn := range []string{"connection", "auto-connection"} { - - headers = map[string]interface{}{ - side: map[string]interface{}{ - "interface3": map[string]interface{}{ - "allow-" + conn: map[string]interface{}{ - k: vals, - }, - }, - }, - } - fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil) - c.Assert(err, IsNil) - c.Check(fmtnum, Equals, 3) - } - } - } - - // higher format features win - - headers = map[string]interface{}{ - "plugs": map[string]interface{}{ - "interface3": map[string]interface{}{ - "allow-auto-connection": map[string]interface{}{ - "on-store": []interface{}{"store"}, - }, - }, - }, - "slots": map[string]interface{}{ - "interface4": map[string]interface{}{ - "allow-auto-connection": map[string]interface{}{ - "plug-attributes": map[string]interface{}{ - "x": "$SLOT(x)", - }, - }, - }, - }, - } - fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil) - c.Assert(err, IsNil) - c.Check(fmtnum, Equals, 3) - - headers = map[string]interface{}{ - "plugs": map[string]interface{}{ - "interface4": map[string]interface{}{ - "allow-auto-connection": map[string]interface{}{ - "slot-attributes": map[string]interface{}{ - "x": "$SLOT(x)", - }, - }, - }, - }, - "slots": map[string]interface{}{ - "interface3": map[string]interface{}{ - "allow-auto-connection": map[string]interface{}{ - "on-store": []interface{}{"store"}, - }, - }, - }, - } - fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil) - c.Assert(err, IsNil) - c.Check(fmtnum, Equals, 3) - // errors headers = map[string]interface{}{ "plugs": "what", diff -Nru snapd-2.37.1+18.04/asserts/store_asserts.go snapd-2.34.2+18.04/asserts/store_asserts.go --- snapd-2.37.1+18.04/asserts/store_asserts.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/store_asserts.go 2018-07-19 10:05:50.000000000 +0000 @@ -26,12 +26,11 @@ ) // Store holds a store assertion, defining the configuration needed to connect -// a device to the store or relative to a non-default store. +// a device to the store. type Store struct { assertionBase - url *url.URL - friendlyStores []string - timestamp time.Time + url *url.URL + timestamp time.Time } // Store returns the identifying name of the operator's store. @@ -49,12 +48,6 @@ return store.url } -// FriendlyStores returns stores holding snaps that are also exposed -// through this one. -func (store *Store) FriendlyStores() []string { - return store.friendlyStores -} - // Location returns a summary of the store's location/purpose. func (store *Store) Location() string { return store.HeaderString("location") @@ -66,9 +59,7 @@ } func (store *Store) checkConsistency(db RODatabase, acck *AccountKey) error { - // Will be applied to a system's snapd or influence snapd - // policy decisions (via friendly-stores) so must be signed by a trusted - // authority! + // Will be applied to a system's snapd so must be signed by a trusted authority. if !db.IsTrustedAccount(store.AuthorityID()) { return fmt.Errorf("store assertion %q is not signed by a directly trusted authority: %s", store.Store(), store.AuthorityID()) @@ -138,11 +129,6 @@ return nil, err } - friendlyStores, err := checkStringList(assert.headers, "friendly-stores") - if err != nil { - return nil, err - } - _, err = checkOptionalString(assert.headers, "location") if err != nil { return nil, err @@ -154,9 +140,8 @@ } return &Store{ - assertionBase: assert, - url: url, - friendlyStores: friendlyStores, - timestamp: timestamp, + assertionBase: assert, + url: url, + timestamp: timestamp, }, nil } diff -Nru snapd-2.37.1+18.04/asserts/store_asserts_test.go snapd-2.34.2+18.04/asserts/store_asserts_test.go --- snapd-2.37.1+18.04/asserts/store_asserts_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/store_asserts_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -63,7 +63,6 @@ c.Check(store.URL().String(), Equals, "https://store.example.com") c.Check(store.Location(), Equals, "upstairs") c.Check(store.Timestamp().Equal(s.ts), Equals, true) - c.Check(store.FriendlyStores(), HasLen, 0) } var storeErrPrefix = "assertion store: " @@ -79,7 +78,6 @@ {s.tsLine, "", `"timestamp" header is mandatory`}, {s.tsLine, "timestamp: \n", `"timestamp" header should not be empty`}, {s.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, - {"url: https://store.example.com\n", "friendly-stores: foo\n", `"friendly-stores" header must be a list of strings`}, } for _, test := range tests { @@ -189,19 +187,6 @@ c.Assert(err, IsNil) } -func (s *storeSuite) TestFriendlyStores(c *C) { - encoded := strings.Replace(s.validExample, "url: https://store.example.com\n", `friendly-stores: - - store1 - - store2 - - store3 -`, 1) - assert, err := asserts.Decode([]byte(encoded)) - c.Assert(err, IsNil) - store := assert.(*asserts.Store) - c.Check(store.URL(), IsNil) - c.Check(store.FriendlyStores(), DeepEquals, []string{"store1", "store2", "store3"}) -} - func (s *storeSuite) TestCheckOperatorAccount(c *C) { storeDB, db := makeStoreAndCheckDB(c) diff -Nru snapd-2.37.1+18.04/asserts/user.go snapd-2.34.2+18.04/asserts/user.go --- snapd-2.37.1+18.04/asserts/user.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/user.go 2018-07-19 10:05:50.000000000 +0000 @@ -39,8 +39,6 @@ sshKeys []string since time.Time until time.Time - - forcePasswordChange bool } // BrandID returns the brand identifier that signed this assertion. @@ -79,12 +77,6 @@ return su.HeaderString("password") } -// ForcePasswordChange returns true if the user needs to change the password -// after the first login. -func (su *SystemUser) ForcePasswordChange() bool { - return su.forcePasswordChange -} - // SSHKeys returns the ssh keys for the user. func (su *SystemUser) SSHKeys() []string { return su.sshKeys @@ -158,9 +150,6 @@ }, nil } -// see crypt(3) for the legal chars -var isValidSaltAndHash = regexp.MustCompile(`^[a-zA-Z0-9./]+$`).MatchString - func checkHashedPassword(headers map[string]interface{}, name string) (string, error) { pw, err := checkOptionalString(headers, name) if err != nil { @@ -200,10 +189,12 @@ } } - if !isValidSaltAndHash(shd.Salt) { + // see crypt(3) for the legal chars + validSaltAndHash := regexp.MustCompile(`^[a-zA-Z0-9./]+$`) + if !validSaltAndHash.MatchString(shd.Salt) { return "", fmt.Errorf("%q header has invalid chars in salt %q", name, shd.Salt) } - if !isValidSaltAndHash(shd.Hash) { + if !validSaltAndHash.MatchString(shd.Hash) { return "", fmt.Errorf("%q header has invalid chars in hash %q", name, shd.Hash) } @@ -236,17 +227,9 @@ if _, err := checkStringMatches(assert.headers, "username", validSystemUserUsernames); err != nil { return nil, err } - password, err := checkHashedPassword(assert.headers, "password") - if err != nil { + if _, err := checkHashedPassword(assert.headers, "password"); err != nil { return nil, err } - forcePasswordChange, err := checkOptionalBool(assert.headers, "force-password-change") - if err != nil { - return nil, err - } - if forcePasswordChange && password == "" { - return nil, fmt.Errorf(`cannot use "force-password-change" with an empty "password"`) - } sshKeys, err := checkStringList(assert.headers, "ssh-keys") if err != nil { @@ -270,12 +253,11 @@ } return &SystemUser{ - assertionBase: assert, - series: series, - models: models, - sshKeys: sshKeys, - since: since, - until: until, - forcePasswordChange: forcePasswordChange, + assertionBase: assert, + series: series, + models: models, + sshKeys: sshKeys, + since: since, + until: until, }, nil } diff -Nru snapd-2.37.1+18.04/asserts/user_test.go snapd-2.34.2+18.04/asserts/user_test.go --- snapd-2.37.1+18.04/asserts/user_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/asserts/user_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -102,18 +102,6 @@ } } -func (s *systemUserSuite) TestDecodeForcePasswdChange(c *C) { - - old := "password: $6$salt$hash\n" - new := "password: $6$salt$hash\nforce-password-change: true\n" - - valid := strings.Replace(s.systemUserStr, old, new, 1) - a, err := asserts.Decode([]byte(valid)) - c.Check(err, IsNil) - systemUser := a.(*asserts.SystemUser) - c.Check(systemUser.ForcePasswordChange(), Equals, true) -} - func (s *systemUserSuite) TestValidAt(c *C) { a, err := asserts.Decode([]byte(s.systemUserStr)) c.Assert(err, IsNil) @@ -176,8 +164,6 @@ {"password: $6$salt$hash\n", "password: $8$rounds=xxx$salt$hash\n", `"password" header has invalid number of rounds:.*`}, {"password: $6$salt$hash\n", "password: $8$rounds=1$salt$hash\n", `"password" header rounds parameter out of bounds: 1`}, {"password: $6$salt$hash\n", "password: $8$rounds=1999999999$salt$hash\n", `"password" header rounds parameter out of bounds: 1999999999`}, - {"password: $6$salt$hash\n", "force-password-change: true\n", `cannot use "force-password-change" with an empty "password"`}, - {"password: $6$salt$hash\n", "password: $6$salt$hash\nforce-password-change: xxx\n", `"force-password-change" header must be 'true' or 'false'`}, {s.sinceLine, "since: \n", `"since" header should not be empty`}, {s.sinceLine, "since: 12:30\n", `"since" header is not a RFC3339 date: .*`}, {s.untilLine, "until: \n", `"until" header should not be empty`}, diff -Nru snapd-2.37.1+18.04/boot/kernel_os.go snapd-2.34.2+18.04/boot/kernel_os.go --- snapd-2.37.1+18.04/boot/kernel_os.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/boot/kernel_os.go 2018-07-19 10:05:50.000000000 +0000 @@ -22,6 +22,7 @@ import ( "fmt" "os" + "os/exec" "path/filepath" "github.com/snapcore/snapd/logger" @@ -48,6 +49,13 @@ return nil } +func copyAll(src, dst string) error { + if output, err := exec.Command("cp", "-aLv", src, dst).CombinedOutput(); err != nil { + return fmt.Errorf("cannot copy %q -> %q: %s (%s)", src, dst, err, output) + } + return nil +} + // ExtractKernelAssets extracts kernel/initrd/dtb data from the given // kernel snap, if required, to a versioned bootloader directory so // that the bootloader can use it. diff -Nru snapd-2.37.1+18.04/client/apps.go snapd-2.34.2+18.04/client/apps.go --- snapd-2.37.1+18.04/client/apps.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/apps.go 2018-07-19 10:05:50.000000000 +0000 @@ -31,26 +31,15 @@ "time" ) -// AppActivator is a thing that activates the app that is a service in the -// system. -type AppActivator struct { - Name string - // Type describes the type of the unit, either timer or socket - Type string - Active bool - Enabled bool -} - // AppInfo describes a single snap application. type AppInfo struct { - Snap string `json:"snap,omitempty"` - Name string `json:"name"` - DesktopFile string `json:"desktop-file,omitempty"` - Daemon string `json:"daemon,omitempty"` - Enabled bool `json:"enabled,omitempty"` - Active bool `json:"active,omitempty"` - CommonID string `json:"common-id,omitempty"` - Activators []AppActivator `json:"activators,omitempty"` + Snap string `json:"snap,omitempty"` + Name string `json:"name"` + DesktopFile string `json:"desktop-file,omitempty"` + Daemon string `json:"daemon,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Active bool `json:"active,omitempty"` + CommonID string `json:"common-id,omitempty"` } // IsService returns true if the application is a background daemon. @@ -124,15 +113,6 @@ return nil, err } - if rsp.StatusCode != 200 { - var r response - defer rsp.Body.Close() - if err := decodeInto(rsp.Body, &r); err != nil { - return nil, err - } - return nil, r.err(client) - } - ch := make(chan Log, 20) go func() { // logs come in application/json-seq, described in RFC7464: it's diff -Nru snapd-2.37.1+18.04/client/apps_test.go snapd-2.34.2+18.04/client/apps_test.go --- snapd-2.37.1+18.04/client/apps_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/apps_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -204,14 +204,6 @@ } } -func (cs *clientSuite) TestClientLogsNotFound(c *check.C) { - cs.rsp = `{"type":"error","status-code":404,"status":"Not Found","result":{"message":"snap \"foo\" not found","kind":"snap-not-found","value":"foo"}}` - cs.status = 404 - actual, err := testClientLogs(cs, c) - c.Assert(err, check.ErrorMatches, `snap "foo" not found`) - c.Check(actual, check.HasLen, 0) -} - func (cs *clientSuite) TestClientServiceStart(c *check.C) { cs.rsp = `{"type": "async", "status-code": 202, "change": "24"}` diff -Nru snapd-2.37.1+18.04/client/buy.go snapd-2.34.2+18.04/client/buy.go --- snapd-2.37.1+18.04/client/buy.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/buy.go 2018-07-19 10:05:50.000000000 +0000 @@ -22,23 +22,13 @@ import ( "bytes" "encoding/json" -) - -// BuyOptions specifies parameters to buy from the store. -type BuyOptions struct { - SnapID string `json:"snap-id"` - Price float64 `json:"price"` - Currency string `json:"currency"` // ISO 4217 code as string -} -// BuyResult holds the state of a buy attempt. -type BuyResult struct { - State string `json:"state,omitempty"` -} + "github.com/snapcore/snapd/store" +) -func (client *Client) Buy(opts *BuyOptions) (*BuyResult, error) { +func (client *Client) Buy(opts *store.BuyOptions) (*store.BuyResult, error) { if opts == nil { - opts = &BuyOptions{} + opts = &store.BuyOptions{} } var body bytes.Buffer @@ -46,7 +36,7 @@ return nil, err } - var result BuyResult + var result store.BuyResult _, err := client.doSync("POST", "/v2/buy", nil, nil, &body, &result) if err != nil { diff -Nru snapd-2.37.1+18.04/client/client.go snapd-2.34.2+18.04/client/client.go --- snapd-2.37.1+18.04/client/client.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/client.go 2018-07-19 10:05:50.000000000 +0000 @@ -66,10 +66,6 @@ // Socket is the path to the unix socket to use Socket string - - // DisableKeepAlive indicates whether the connections should not be kept - // alive for later reuse - DisableKeepAlive bool } // A Client knows how to talk to the snappy daemon. @@ -81,9 +77,6 @@ interactive bool maintenance error - - warningCount int - warningTimestamp time.Time } // New returns a new instance of Client @@ -94,13 +87,14 @@ // By default talk over an UNIX socket. if config.BaseURL == "" { - transport := &http.Transport{Dial: unixDialer(config.Socket), DisableKeepAlives: config.DisableKeepAlive} return &Client{ baseURL: url.URL{ Scheme: "http", Host: "localhost", }, - doer: &http.Client{Transport: transport}, + doer: &http.Client{ + Transport: &http.Transport{Dial: unixDialer(config.Socket)}, + }, disableAuth: config.DisableAuth, interactive: config.Interactive, } @@ -112,7 +106,7 @@ } return &Client{ baseURL: *baseURL, - doer: &http.Client{Transport: &http.Transport{DisableKeepAlives: config.DisableKeepAlive}}, + doer: &http.Client{}, disableAuth: config.DisableAuth, interactive: config.Interactive, } @@ -123,13 +117,6 @@ return client.maintenance } -// WarningsSummary returns the number of warnings that are ready to be shown to -// the user, and the timestamp of the most recently added warning (useful for -// silencing the warning alerts, and OKing the returned warnings). -func (client *Client) WarningsSummary() (count int, timestamp time.Time) { - return client.warningCount, client.warningTimestamp -} - func (client *Client) WhoAmI() (string, error) { user, err := readAuthData() if os.IsNotExist(err) { @@ -236,19 +223,6 @@ } } -type hijacked struct { - do func(*http.Request) (*http.Response, error) -} - -func (h hijacked) Do(req *http.Request) (*http.Response, error) { - return h.do(req) -} - -// Hijack lets the caller take over the raw http request -func (client *Client) Hijack(f func(*http.Request) (*http.Response, error)) { - client.doer = hijacked{f} -} - // do performs a request and decodes the resulting json into the given // value. It's low-level, for testing/experimenting only; you should // usually use a higher level interface that builds on this. @@ -276,27 +250,20 @@ defer rsp.Body.Close() if v != nil { - if err := decodeInto(rsp.Body, v); err != nil { - return err + dec := json.NewDecoder(rsp.Body) + if err := dec.Decode(v); err != nil { + r := dec.Buffered() + buf, err1 := ioutil.ReadAll(r) + if err1 != nil { + buf = []byte(fmt.Sprintf("error reading buffered response body: %s", err1)) + } + return fmt.Errorf("cannot decode %q: %s", buf, err) } } return nil } -func decodeInto(reader io.Reader, v interface{}) error { - dec := json.NewDecoder(reader) - if err := dec.Decode(v); err != nil { - r := dec.Buffered() - buf, err1 := ioutil.ReadAll(r) - if err1 != nil { - buf = []byte(fmt.Sprintf("error reading buffered response body: %s", err1)) - } - return fmt.Errorf("cannot decode %q: %s", buf, err) - } - return nil -} - // doSync performs a request to the given path using the specified HTTP method. // It expects a "sync" response from the API and on success decodes the JSON // response payload into the given value using the "UseNumber" json decoding @@ -319,9 +286,6 @@ } } - client.warningCount = rsp.WarningCount - client.warningTimestamp = rsp.WarningTimestamp - return &rsp.ResultInfo, nil } @@ -388,9 +352,6 @@ Type string `json:"type"` Change string `json:"change"` - WarningCount int `json:"warning-count"` - WarningTimestamp time.Time `json:"warning-timestamp"` - ResultInfo Maintenance *Error `json:"maintenance"` @@ -413,7 +374,6 @@ ErrorKindTwoFactorRequired = "two-factor-required" ErrorKindTwoFactorFailed = "two-factor-failed" ErrorKindLoginRequired = "login-required" - ErrorKindInvalidAuthData = "invalid-auth-data" ErrorKindTermsNotAccepted = "terms-not-accepted" ErrorKindNoPaymentMethods = "no-payment-methods" ErrorKindPaymentDeclined = "payment-declined" @@ -422,44 +382,27 @@ ErrorKindSnapAlreadyInstalled = "snap-already-installed" ErrorKindSnapNotInstalled = "snap-not-installed" ErrorKindSnapNotFound = "snap-not-found" - ErrorKindAppNotFound = "app-not-found" ErrorKindSnapLocal = "snap-local" ErrorKindSnapNeedsDevMode = "snap-needs-devmode" ErrorKindSnapNeedsClassic = "snap-needs-classic" ErrorKindSnapNeedsClassicSystem = "snap-needs-classic-system" - ErrorKindSnapNotClassic = "snap-not-classic" ErrorKindNoUpdateAvailable = "snap-no-update-available" ErrorKindRevisionNotAvailable = "snap-revision-not-available" ErrorKindChannelNotAvailable = "snap-channel-not-available" ErrorKindArchitectureNotAvailable = "snap-architecture-not-available" - ErrorKindChangeConflict = "snap-change-conflict" - ErrorKindNotSnap = "snap-not-a-snap" - ErrorKindNetworkTimeout = "network-timeout" - ErrorKindDNSFailure = "dns-failure" - + ErrorKindNetworkTimeout = "network-timeout" ErrorKindInterfacesUnchanged = "interfaces-unchanged" - ErrorKindBadQuery = "bad-query" ErrorKindConfigNoSuchOption = "option-not-found" ErrorKindSystemRestart = "system-restart" ErrorKindDaemonRestart = "daemon-restart" ) -// IsRetryable returns true if the given error is an error -// that can be retried later. -func IsRetryable(err error) bool { - switch e := err.(type) { - case *Error: - return e.Kind == ErrorKindChangeConflict - } - return false -} - // IsTwoFactorError returns whether the given error is due to problems // in two-factor authentication. func IsTwoFactorError(err error) bool { diff -Nru snapd-2.37.1+18.04/client/client_test.go snapd-2.34.2+18.04/client/client_test.go --- snapd-2.37.1+18.04/client/client_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/client_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -51,7 +51,6 @@ doCalls int header http.Header status int - restore func() } var _ = Suite(&clientSuite{}) @@ -71,13 +70,10 @@ cs.doCalls = 0 dirs.SetRootDir(c.MkDir()) - - cs.restore = client.MockDoRetry(time.Millisecond, 10*time.Millisecond) } func (cs *clientSuite) TearDownTest(c *C) { os.Unsetenv(client.TestAuthFileEnvKey) - cs.restore() } func (cs *clientSuite) Do(req *http.Request) (*http.Response, error) { @@ -103,6 +99,8 @@ } func (cs *clientSuite) TestClientDoReportsErrors(c *C) { + restore := client.MockDoRetry(10*time.Millisecond, 100*time.Millisecond) + defer restore() cs.err = errors.New("ouchie") err := cs.cli.Do("GET", "/", nil, nil, nil) c.Check(err, ErrorMatches, "cannot communicate with server: ouchie") @@ -422,15 +420,6 @@ c.Check(client.IsTwoFactorError((*client.Error)(nil)), Equals, false) } -func (cs *clientSuite) TestIsRetryable(c *C) { - // unhappy - c.Check(client.IsRetryable(nil), Equals, false) - c.Check(client.IsRetryable(errors.New("some-error")), Equals, false) - c.Check(client.IsRetryable(&client.Error{Kind: "something-else"}), Equals, false) - // happy - c.Check(client.IsRetryable(&client.Error{Kind: client.ErrorKindChangeConflict}), Equals, true) -} - func (cs *clientSuite) TestClientCreateUser(c *C) { _, err := cs.cli.CreateUser(&client.CreateUserOptions{}) c.Assert(err, ErrorMatches, "cannot create a user without providing an email") diff -Nru snapd-2.37.1+18.04/client/icons.go snapd-2.34.2+18.04/client/icons.go --- snapd-2.37.1+18.04/client/icons.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/icons.go 2018-07-19 10:05:50.000000000 +0000 @@ -31,8 +31,6 @@ Content []byte } -var contentDispositionMatcher = regexp.MustCompile(`attachment; filename=(.+)`).FindStringSubmatch - // Icon returns the Icon belonging to an installed snap func (c *Client) Icon(pkgID string) (*Icon, error) { const errPrefix = "cannot retrieve icon" @@ -47,7 +45,8 @@ return nil, fmt.Errorf("%s: Not Found", errPrefix) } - matches := contentDispositionMatcher(response.Header.Get("Content-Disposition")) + re := regexp.MustCompile(`attachment; filename=(.+)`) + matches := re.FindStringSubmatch(response.Header.Get("Content-Disposition")) if matches == nil || matches[1] == "" { return nil, fmt.Errorf("%s: cannot determine filename", errPrefix) diff -Nru snapd-2.37.1+18.04/client/packages.go snapd-2.34.2+18.04/client/packages.go --- snapd-2.37.1+18.04/client/packages.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/packages.go 2018-07-19 10:05:50.000000000 +0000 @@ -64,9 +64,8 @@ CommonIDs []string `json:"common-ids,omitempty"` MountedFrom string `json:"mounted-from,omitempty"` - Prices map[string]float64 `json:"prices,omitempty"` - Screenshots []snap.ScreenshotInfo `json:"screenshots,omitempty"` - Media snap.MediaInfos `json:"media,omitempty"` + Prices map[string]float64 `json:"prices,omitempty"` + Screenshots []Screenshot `json:"screenshots,omitempty"` // The flattended channel map with $track/$risk Channels map[string]*snap.ChannelSnapInfo `json:"channels,omitempty"` @@ -90,6 +89,12 @@ return json.Marshal(&m) } +type Screenshot struct { + URL string `json:"url"` + Width int64 `json:"width,omitempty"` + Height int64 `json:"height,omitempty"` +} + // Statuses and types a snap may have. const ( StatusAvailable = "available" diff -Nru snapd-2.37.1+18.04/client/packages_test.go snapd-2.34.2+18.04/client/packages_test.go --- snapd-2.37.1+18.04/client/packages_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/packages_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -231,11 +231,6 @@ {"url":"http://example.com/shot1.png", "width":640, "height":480}, {"url":"http://example.com/shot2.png"} ], - "media": [ - {"type": "icon", "url":"http://example.com/icon.png"}, - {"type": "screenshot", "url":"http://example.com/shot1.png", "width":640, "height":480}, - {"type": "screenshot", "url":"http://example.com/shot2.png"} - ], "common-ids": ["org.funky.snap"] } }` @@ -269,15 +264,10 @@ Private: true, DevMode: true, TryMode: true, - Screenshots: []snap.ScreenshotInfo{ + Screenshots: []client.Screenshot{ {URL: "http://example.com/shot1.png", Width: 640, Height: 480}, {URL: "http://example.com/shot2.png"}, }, - Media: []snap.MediaInfo{ - {Type: "icon", URL: "http://example.com/icon.png"}, - {Type: "screenshot", URL: "http://example.com/shot1.png", Width: 640, Height: 480}, - {Type: "screenshot", URL: "http://example.com/shot2.png"}, - }, CommonIDs: []string{"org.funky.snap"}, }) } diff -Nru snapd-2.37.1+18.04/client/snap_op.go snapd-2.34.2+18.04/client/snap_op.go --- snapd-2.37.1+18.04/client/snap_op.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/snap_op.go 2018-07-19 10:05:50.000000000 +0000 @@ -43,13 +43,6 @@ Users []string `json:"users,omitempty"` } -func writeFieldBool(mw *multipart.Writer, key string, val bool) error { - if !val { - return nil - } - return mw.WriteField(key, "true") -} - func (opts *SnapOptions) writeModeFields(mw *multipart.Writer) error { fields := []struct { f string @@ -61,7 +54,11 @@ {"dangerous", opts.Dangerous}, } for _, o := range fields { - if err := writeFieldBool(mw, o.f, o.b); err != nil { + if !o.b { + continue + } + err := mw.WriteField(o.f, "true") + if err != nil { return err } } @@ -69,10 +66,6 @@ return nil } -func (opts *SnapOptions) writeOptionFields(mw *multipart.Writer) error { - return writeFieldBool(mw, "unaliased", opts.Unaliased) -} - type actionData struct { Action string `json:"action"` Name string `json:"name,omitempty"` @@ -203,9 +196,9 @@ return client.doAsyncFull("POST", "/v2/snaps", nil, headers, bytes.NewBuffer(data)) } -// InstallPath sideloads the snap with the given path under optional provided name, -// returning the UUID of the background operation upon success. -func (client *Client) InstallPath(path, name string, options *SnapOptions) (changeID string, err error) { +// InstallPath sideloads the snap with the given path, returning the UUID +// of the background operation upon success. +func (client *Client) InstallPath(path string, options *SnapOptions) (changeID string, err error) { f, err := os.Open(path) if err != nil { return "", fmt.Errorf("cannot open: %q", path) @@ -213,7 +206,6 @@ action := actionData{ Action: "install", - Name: name, SnapPath: path, SnapOptions: options, } @@ -281,11 +273,6 @@ pw.CloseWithError(err) return } - - if err := action.writeOptionFields(mw); err != nil { - pw.CloseWithError(err) - return - } fw, err := mw.CreateFormFile("snap", filepath.Base(snapPath)) if err != nil { diff -Nru snapd-2.37.1+18.04/client/snap_op_test.go snapd-2.34.2+18.04/client/snap_op_test.go --- snapd-2.37.1+18.04/client/snap_op_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/snap_op_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -214,7 +214,7 @@ err := ioutil.WriteFile(snap, bodyData, 0644) c.Assert(err, check.IsNil) - id, err := cs.cli.InstallPath(snap, "", nil) + id, err := cs.cli.InstallPath(snap, nil) c.Assert(err, check.IsNil) body, err := ioutil.ReadAll(cs.req.Body) @@ -229,34 +229,6 @@ c.Check(id, check.Equals, "66b3") } -func (cs *clientSuite) TestClientOpInstallPathInstance(c *check.C) { - cs.rsp = `{ - "change": "66b3", - "status-code": 202, - "type": "async" - }` - bodyData := []byte("snap-data") - - snap := filepath.Join(c.MkDir(), "foo.snap") - err := ioutil.WriteFile(snap, bodyData, 0644) - c.Assert(err, check.IsNil) - - id, err := cs.cli.InstallPath(snap, "foo_bar", nil) - c.Assert(err, check.IsNil) - - body, err := ioutil.ReadAll(cs.req.Body) - c.Assert(err, check.IsNil) - - c.Assert(string(body), check.Matches, "(?s).*\r\nsnap-data\r\n.*") - c.Assert(string(body), check.Matches, "(?s).*Content-Disposition: form-data; name=\"action\"\r\n\r\ninstall\r\n.*") - c.Assert(string(body), check.Matches, "(?s).*Content-Disposition: form-data; name=\"name\"\r\n\r\nfoo_bar\r\n.*") - - c.Check(cs.req.Method, check.Equals, "POST") - c.Check(cs.req.URL.Path, check.Equals, fmt.Sprintf("/v2/snaps")) - c.Assert(cs.req.Header.Get("Content-Type"), check.Matches, "multipart/form-data; boundary=.*") - c.Check(id, check.Equals, "66b3") -} - func (cs *clientSuite) TestClientOpInstallDangerous(c *check.C) { cs.rsp = `{ "change": "66b3", @@ -274,7 +246,7 @@ } // InstallPath takes Dangerous - _, err = cs.cli.InstallPath(snap, "", &opts) + _, err = cs.cli.InstallPath(snap, &opts) c.Assert(err, check.IsNil) body, err := ioutil.ReadAll(cs.req.Body) @@ -293,41 +265,6 @@ c.Assert(err, check.NotNil) } -func (cs *clientSuite) TestClientOpInstallUnaliased(c *check.C) { - cs.rsp = `{ - "change": "66b3", - "status-code": 202, - "type": "async" - }` - bodyData := []byte("snap-data") - - snap := filepath.Join(c.MkDir(), "foo.snap") - err := ioutil.WriteFile(snap, bodyData, 0644) - c.Assert(err, check.IsNil) - - opts := client.SnapOptions{ - Unaliased: true, - } - - _, err = cs.cli.Install("foo", &opts) - c.Assert(err, check.IsNil) - - body, err := ioutil.ReadAll(cs.req.Body) - c.Assert(err, check.IsNil) - jsonBody := make(map[string]interface{}) - err = json.Unmarshal(body, &jsonBody) - c.Assert(err, check.IsNil, check.Commentf("body: %v", string(body))) - c.Check(jsonBody["unaliased"], check.Equals, true, check.Commentf("body: %v", string(body))) - - _, err = cs.cli.InstallPath(snap, "", &opts) - c.Assert(err, check.IsNil) - - body, err = ioutil.ReadAll(cs.req.Body) - c.Assert(err, check.IsNil) - - c.Assert(string(body), check.Matches, "(?s).*Content-Disposition: form-data; name=\"unaliased\"\r\n\r\ntrue\r\n.*") -} - func formToMap(c *check.C, mr *multipart.Reader) map[string]string { formData := map[string]string{} for { diff -Nru snapd-2.37.1+18.04/client/snapshot.go snapd-2.34.2+18.04/client/snapshot.go --- snapd-2.37.1+18.04/client/snapshot.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/snapshot.go 2018-07-19 10:05:50.000000000 +0000 @@ -56,7 +56,6 @@ // information about the snap this data is for Snap string `json:"snap"` Revision snap.Revision `json:"revision"` - SnapID string `json:"snap-id,omitempty"` Epoch snap.Epoch `json:"epoch,omitempty"` Summary string `json:"summary"` Version string `json:"version"` diff -Nru snapd-2.37.1+18.04/client/warnings.go snapd-2.34.2+18.04/client/warnings.go --- snapd-2.37.1+18.04/client/warnings.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/warnings.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,89 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package client - -import ( - "bytes" - "encoding/json" - "net/url" - "time" -) - -// A Warning is a short messages that's meant to alert about system events. -// There'll only ever be one Warning with the same message, and it can be -// silenced for a while before repeating. After a (supposedly longer) while -// it'll go away on its own (unless it recurrs). -type Warning struct { - Message string `json:"message"` - FirstAdded time.Time `json:"first-added"` - LastAdded time.Time `json:"last-added"` - LastShown time.Time `json:"last-shown,omitempty"` - ExpireAfter time.Duration `json:"expire-after,omitempty"` - RepeatAfter time.Duration `json:"repeat-after,omitempty"` -} - -type jsonWarning struct { - Warning - ExpireAfter string `json:"expire-after,omitempty"` - RepeatAfter string `json:"repeat-after,omitempty"` -} - -// WarningsOptions contains options for querying snapd for warnings -// supported options: -// - All: return all warnings, instead of only the un-okayed ones. -type WarningsOptions struct { - All bool -} - -// Warnings returns the list of un-okayed warnings. -func (client *Client) Warnings(opts WarningsOptions) ([]*Warning, error) { - var jws []*jsonWarning - q := make(url.Values) - if opts.All { - q.Add("select", "all") - } - _, err := client.doSync("GET", "/v2/warnings", q, nil, nil, &jws) - - ws := make([]*Warning, len(jws)) - for i, jw := range jws { - ws[i] = &jw.Warning - ws[i].ExpireAfter, _ = time.ParseDuration(jw.ExpireAfter) - ws[i].RepeatAfter, _ = time.ParseDuration(jw.RepeatAfter) - } - - return ws, err -} - -type warningsAction struct { - Action string `json:"action"` - Timestamp time.Time `json:"timestamp"` -} - -// Okay asks snapd to chill about the warnings that would have been returned by -// Warnings at the given time. -func (client *Client) Okay(t time.Time) error { - var body bytes.Buffer - var op = warningsAction{Action: "okay", Timestamp: t} - if err := json.NewEncoder(&body).Encode(op); err != nil { - return err - } - _, err := client.doSync("POST", "/v2/warnings", nil, nil, &body, nil) - return err -} diff -Nru snapd-2.37.1+18.04/client/warnings_test.go snapd-2.34.2+18.04/client/warnings_test.go --- snapd-2.37.1+18.04/client/warnings_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/client/warnings_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,121 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package client_test - -import ( - "encoding/json" - "time" - - "gopkg.in/check.v1" - - "github.com/snapcore/snapd/client" -) - -func (cs *clientSuite) testWarnings(c *check.C, all bool) { - t1 := time.Date(2018, 9, 19, 12, 41, 18, 505007495, time.UTC) - t2 := time.Date(2018, 9, 19, 12, 44, 19, 680362867, time.UTC) - cs.rsp = `{ - "result": [ - { - "expire-after": "672h0m0s", - "first-added": "2018-09-19T12:41:18.505007495Z", - "last-added": "2018-09-19T12:41:18.505007495Z", - "message": "hello world number one", - "repeat-after": "24h0m0s" - }, - { - "expire-after": "672h0m0s", - "first-added": "2018-09-19T12:44:19.680362867Z", - "last-added": "2018-09-19T12:44:19.680362867Z", - "message": "hello world number two", - "repeat-after": "24h0m0s" - } - ], - "status": "OK", - "status-code": 200, - "type": "sync", - "warning-count": 2, - "warning-timestamp": "2018-09-19T12:44:19.680362867Z" - }` - - ws, err := cs.cli.Warnings(client.WarningsOptions{All: all}) - c.Assert(err, check.IsNil) - c.Check(ws, check.DeepEquals, []*client.Warning{ - { - Message: "hello world number one", - FirstAdded: t1, - LastAdded: t1, - ExpireAfter: time.Hour * 24 * 28, - RepeatAfter: time.Hour * 24, - }, - { - Message: "hello world number two", - FirstAdded: t2, - LastAdded: t2, - ExpireAfter: time.Hour * 24 * 28, - RepeatAfter: time.Hour * 24, - }, - }) - c.Check(cs.req.Method, check.Equals, "GET") - c.Check(cs.req.URL.Path, check.Equals, "/v2/warnings") - query := cs.req.URL.Query() - if all { - c.Check(query, check.HasLen, 1) - c.Check(query.Get("select"), check.Equals, "all") - } else { - c.Check(query, check.HasLen, 0) - } - - // this could be done at the end of any sync method - count, stamp := cs.cli.WarningsSummary() - c.Check(count, check.Equals, 2) - c.Check(stamp, check.Equals, t2) -} - -func (cs *clientSuite) TestWarningsAll(c *check.C) { - cs.testWarnings(c, true) -} - -func (cs *clientSuite) TestWarnings(c *check.C) { - cs.testWarnings(c, false) -} - -func (cs *clientSuite) TestOkay(c *check.C) { - cs.rsp = `{ - "type": "sync", - "status-code": 200, - "result": { } - }` - t0 := time.Now() - err := cs.cli.Okay(t0) - c.Assert(err, check.IsNil) - c.Check(cs.req.Method, check.Equals, "POST") - c.Check(cs.req.URL.Query(), check.HasLen, 0) - var body map[string]interface{} - c.Assert(json.NewDecoder(cs.req.Body).Decode(&body), check.IsNil) - c.Check(body, check.HasLen, 2) - c.Check(body["action"], check.Equals, "okay") - c.Check(body["timestamp"], check.Equals, t0.Format(time.RFC3339Nano)) - - // note there's no warnings summary in the response - count, stamp := cs.cli.WarningsSummary() - c.Check(count, check.Equals, 0) - c.Check(stamp, check.Equals, time.Time{}) -} diff -Nru snapd-2.37.1+18.04/cmd/appinfo.go snapd-2.34.2+18.04/cmd/appinfo.go --- snapd-2.37.1+18.04/cmd/appinfo.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/appinfo.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,133 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package cmd - -import ( - "fmt" - "path/filepath" - "strings" - - "github.com/snapcore/snapd/client" - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/progress" - "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/systemd" -) - -func ClientAppInfoNotes(app *client.AppInfo) string { - if !app.IsService() { - return "-" - } - - var notes = make([]string, 0, 2) - var seenTimer, seenSocket bool - for _, act := range app.Activators { - switch act.Type { - case "timer": - seenTimer = true - case "socket": - seenSocket = true - } - } - if seenTimer { - notes = append(notes, "timer-activated") - } - if seenSocket { - notes = append(notes, "socket-activated") - } - if len(notes) == 0 { - return "-" - } - return strings.Join(notes, ",") -} - -func ClientAppInfosFromSnapAppInfos(apps []*snap.AppInfo) ([]client.AppInfo, error) { - // TODO: pass in an actual notifier here instead of null - // (Status doesn't _need_ it, but benefits from it) - sysd := systemd.New(dirs.GlobalRootDir, progress.Null) - - out := make([]client.AppInfo, 0, len(apps)) - for _, app := range apps { - appInfo := client.AppInfo{ - Snap: app.Snap.InstanceName(), - Name: app.Name, - CommonID: app.CommonID, - } - if fn := app.DesktopFile(); osutil.FileExists(fn) { - appInfo.DesktopFile = fn - } - - appInfo.Daemon = app.Daemon - if !app.IsService() || !app.Snap.IsActive() { - out = append(out, appInfo) - continue - } - - // collect all services for a single call to systemctl - serviceNames := make([]string, 0, 1+len(app.Sockets)+1) - serviceNames = append(serviceNames, app.ServiceName()) - - sockSvcFileToName := make(map[string]string, len(app.Sockets)) - for _, sock := range app.Sockets { - sockUnit := filepath.Base(sock.File()) - sockSvcFileToName[sockUnit] = sock.Name - serviceNames = append(serviceNames, sockUnit) - } - if app.Timer != nil { - timerUnit := filepath.Base(app.Timer.File()) - serviceNames = append(serviceNames, timerUnit) - } - - // sysd.Status() makes sure that we get only the units we asked - // for and raises an error otherwise - sts, err := sysd.Status(serviceNames...) - if err != nil { - return nil, fmt.Errorf("cannot get status of services of app %q: %v", app.Name, err) - } - if len(sts) != len(serviceNames) { - return nil, fmt.Errorf("cannot get status of services of app %q: expected %v results, got %v", app.Name, len(serviceNames), len(sts)) - } - for _, st := range sts { - switch filepath.Ext(st.UnitName) { - case ".service": - appInfo.Enabled = st.Enabled - appInfo.Active = st.Active - case ".timer": - appInfo.Activators = append(appInfo.Activators, client.AppActivator{ - Name: app.Name, - Enabled: st.Enabled, - Active: st.Active, - Type: "timer", - }) - case ".socket": - appInfo.Activators = append(appInfo.Activators, client.AppActivator{ - Name: sockSvcFileToName[st.UnitName], - Enabled: st.Enabled, - Active: st.Active, - Type: "socket", - }) - } - } - out = append(out, appInfo) - } - - return out, nil -} diff -Nru snapd-2.37.1+18.04/cmd/appinfo_test.go snapd-2.34.2+18.04/cmd/appinfo_test.go --- snapd-2.37.1+18.04/cmd/appinfo_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/appinfo_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,71 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package cmd_test - -import ( - "gopkg.in/check.v1" - - "github.com/snapcore/snapd/client" - "github.com/snapcore/snapd/cmd" -) - -func (*cmdSuite) TestAppStatusNotes(c *check.C) { - ai := client.AppInfo{} - c.Check(cmd.ClientAppInfoNotes(&ai), check.Equals, "-") - - ai = client.AppInfo{ - Daemon: "oneshot", - } - c.Check(cmd.ClientAppInfoNotes(&ai), check.Equals, "-") - - ai = client.AppInfo{ - Daemon: "oneshot", - Activators: []client.AppActivator{ - {Type: "timer"}, - }, - } - c.Check(cmd.ClientAppInfoNotes(&ai), check.Equals, "timer-activated") - - ai = client.AppInfo{ - Daemon: "oneshot", - Activators: []client.AppActivator{ - {Type: "socket"}, - }, - } - c.Check(cmd.ClientAppInfoNotes(&ai), check.Equals, "socket-activated") - - // check that the output is stable regardless of the order of activators - ai = client.AppInfo{ - Daemon: "oneshot", - Activators: []client.AppActivator{ - {Type: "timer"}, - {Type: "socket"}, - }, - } - c.Check(cmd.ClientAppInfoNotes(&ai), check.Equals, "timer-activated,socket-activated") - ai = client.AppInfo{ - Daemon: "oneshot", - Activators: []client.AppActivator{ - {Type: "socket"}, - {Type: "timer"}, - }, - } - c.Check(cmd.ClientAppInfoNotes(&ai), check.Equals, "timer-activated,socket-activated") -} diff -Nru snapd-2.37.1+18.04/cmd/autogen.sh snapd-2.34.2+18.04/cmd/autogen.sh --- snapd-2.37.1+18.04/cmd/autogen.sh 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/autogen.sh 2018-07-19 10:05:50.000000000 +0000 @@ -27,7 +27,7 @@ . /etc/os-release case "$ID" in arch) - extra_opts="--libexecdir=/usr/lib/snapd --with-snap-mount-dir=/var/lib/snapd/snap --enable-apparmor --enable-nvidia-biarch --enable-merged-usr" + extra_opts="--libexecdir=/usr/lib/snapd --with-snap-mount-dir=/var/lib/snapd/snap --disable-apparmor --enable-nvidia-biarch --enable-merged-usr" ;; debian) extra_opts="--libexecdir=/usr/lib/snapd" @@ -41,8 +41,8 @@ fedora|centos|rhel) extra_opts="--libexecdir=/usr/libexec/snapd --with-snap-mount-dir=/var/lib/snapd/snap --enable-merged-usr --disable-apparmor" ;; - opensuse|opensuse-tumbleweed) - extra_opts="--libexecdir=/usr/lib/snapd --enable-nvidia-biarch --with-32bit-libdir=/usr/lib --enable-merged-usr" + opensuse) + extra_opts="--libexecdir=/usr/lib/snapd" ;; solus) extra_opts="--enable-nvidia-biarch" diff -Nru snapd-2.37.1+18.04/cmd/cmd.go snapd-2.34.2+18.04/cmd/cmd.go --- snapd-2.37.1+18.04/cmd/cmd.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/cmd.go 2018-07-19 10:05:50.000000000 +0000 @@ -0,0 +1,205 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package cmd + +import ( + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" + "strings" + "syscall" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/strutil" +) + +// The SNAP_REEXEC environment variable controls whether the command +// will attempt to re-exec itself from inside an ubuntu-core snap +// present on the system. If not present in the environ it's assumed +// to be set to 1 (do re-exec); that is: set it to 0 to disable. +const reExecKey = "SNAP_REEXEC" + +var ( + // newCore is the place to look for the core snap; everything in this + // location will be new enough to re-exec into. + newCore = "/snap/core/current" + + // oldCore is the previous location of the core snap. Only things + // newer than minOldRevno will be ok to re-exec into. + oldCore = "/snap/ubuntu-core/current" + + // selfExe is the path to a symlink pointing to the current executable + selfExe = "/proc/self/exe" + + syscallExec = syscall.Exec + osReadlink = os.Readlink +) + +// distroSupportsReExec returns true if the distribution we are running on can use re-exec. +// +// This is true by default except for a "core/all" snap system where it makes +// no sense and in certain distributions that we don't want to enable re-exec +// yet because of missing validation or other issues. +func distroSupportsReExec() bool { + if !release.OnClassic { + return false + } + if !release.DistroLike("debian", "ubuntu") { + logger.Debugf("re-exec not supported on distro %q yet", release.ReleaseInfo.ID) + return false + } + return true +} + +// coreSupportsReExec returns true if the given core snap should be used as re-exec target. +// +// Ensure we do not use older version of snapd, look for info file and ignore +// version of core that do not yet have it. +func coreSupportsReExec(corePath string) bool { + fullInfo := filepath.Join(corePath, filepath.Join(dirs.CoreLibExecDir, "info")) + if !osutil.FileExists(fullInfo) { + return false + } + content, err := ioutil.ReadFile(fullInfo) + if err != nil { + logger.Noticef("cannot read snapd info file %q: %s", fullInfo, err) + return false + } + ver := regexp.MustCompile("(?m)^VERSION=(.*)$").FindStringSubmatch(string(content)) + if len(ver) != 2 { + logger.Noticef("cannot find snapd version information in %q", content) + return false + } + // > 0 means our Version is bigger than the version of snapd in core + res, err := strutil.VersionCompare(Version, ver[1]) + if err != nil { + logger.Debugf("cannot version compare %q and %q: %s", Version, ver[1], res) + return false + } + if res > 0 { + logger.Debugf("core snap (at %q) is older (%q) than distribution package (%q)", corePath, ver[1], Version) + return false + } + return true +} + +// InternalToolPath returns the path of an internal snapd tool. The tool +// *must* be located inside /usr/lib/snapd/. +// +// The return value is either the path of the tool in the current distribution +// or in the core snap (or the ubuntu-core snap). This handles spiritual +// "re-exec" where we run the tool from the core snap if the environment allows +// us to do so. +func InternalToolPath(tool string) string { + distroTool := filepath.Join(dirs.DistroLibExecDir, tool) + + // find the internal path relative to the running snapd, this + // ensure we don't rely on the state of the system (like + // having a valid "current" symlink). + exe, err := osReadlink("/proc/self/exe") + if err != nil { + logger.Noticef("cannot read /proc/self/exe: %v, using tool outside core", err) + return distroTool + } + + // ensure we never use this helper from anything but + if !strings.HasSuffix(exe, "/snapd") && !strings.HasSuffix(exe, ".test") { + log.Panicf("InternalToolPath can only be used from snapd, got: %s", exe) + } + + if !strings.HasPrefix(exe, dirs.SnapMountDir) { + logger.Debugf("exe doesn't have snap mount dir prefix: %q vs %q", exe, dirs.SnapMountDir) + return distroTool + } + + // if we are re-execed, then the tool is at the same location + // as snapd + return filepath.Join(filepath.Dir(exe), tool) +} + +// mustUnsetenv will unset the given environment key or panic if it +// cannot do that +func mustUnsetenv(key string) { + if err := os.Unsetenv(key); err != nil { + log.Panicf("cannot unset %s: %s", key, err) + } +} + +// ExecInCoreSnap makes sure you're executing the binary that ships in +// the core snap. +func ExecInCoreSnap() { + // Which executable are we? + exe, err := os.Readlink(selfExe) + if err != nil { + logger.Noticef("cannot read /proc/self/exe: %v", err) + return + } + + // Special case for snapd re-execing from 2.21. In this + // version of snap/snapd we did set SNAP_REEXEC=0 when we + // re-execed. In this case we need to unset the reExecKey to + // ensure that subsequent run of snap/snapd (e.g. when using + // classic confinement) will *not* prevented from re-execing. + if strings.HasPrefix(exe, dirs.SnapMountDir) && !osutil.GetenvBool(reExecKey, true) { + mustUnsetenv(reExecKey) + return + } + + // If we are asked not to re-execute use distribution packages. This is + // "spiritual" re-exec so use the same environment variable to decide. + if !osutil.GetenvBool(reExecKey, true) { + logger.Debugf("re-exec disabled by user") + return + } + + // Did we already re-exec? + if strings.HasPrefix(exe, dirs.SnapMountDir) { + return + } + + // If the distribution doesn't support re-exec or run-from-core then don't do it. + if !distroSupportsReExec() { + return + } + + // Is this executable in the core snap too? + corePath := newCore + full := filepath.Join(newCore, exe) + if !osutil.FileExists(full) { + corePath = oldCore + full = filepath.Join(oldCore, exe) + if !osutil.FileExists(full) { + return + } + } + + // If the core snap doesn't support re-exec or run-from-core then don't do it. + if !coreSupportsReExec(corePath) { + return + } + + logger.Debugf("restarting into %q", full) + panic(syscallExec(full, os.Args, os.Environ())) +} diff -Nru snapd-2.37.1+18.04/cmd/cmd_linux.go snapd-2.34.2+18.04/cmd/cmd_linux.go --- snapd-2.37.1+18.04/cmd/cmd_linux.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/cmd_linux.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,214 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2016 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package cmd - -import ( - "bytes" - "io/ioutil" - "log" - "os" - "path/filepath" - "strings" - "syscall" - - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/logger" - "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/release" - "github.com/snapcore/snapd/strutil" -) - -// The SNAP_REEXEC environment variable controls whether the command -// will attempt to re-exec itself from inside an ubuntu-core snap -// present on the system. If not present in the environ it's assumed -// to be set to 1 (do re-exec); that is: set it to 0 to disable. -const reExecKey = "SNAP_REEXEC" - -var ( - // snapdSnap is the place to look for the snapd snap; we will re-exec - // here - snapdSnap = "/snap/snapd/current" - - // coreSnap is the place to look for the core snap; we will re-exec - // here if there is no snapd snap - coreSnap = "/snap/core/current" - - // selfExe is the path to a symlink pointing to the current executable - selfExe = "/proc/self/exe" - - syscallExec = syscall.Exec - osReadlink = os.Readlink -) - -// distroSupportsReExec returns true if the distribution we are running on can use re-exec. -// -// This is true by default except for a "core/all" snap system where it makes -// no sense and in certain distributions that we don't want to enable re-exec -// yet because of missing validation or other issues. -func distroSupportsReExec() bool { - if !release.OnClassic { - return false - } - if !release.DistroLike("debian", "ubuntu") { - logger.Debugf("re-exec not supported on distro %q yet", release.ReleaseInfo.ID) - return false - } - return true -} - -// coreSupportsReExec returns true if the given core snap should be used as re-exec target. -// -// Ensure we do not use older version of snapd, look for info file and ignore -// version of core that do not yet have it. -func coreSupportsReExec(corePath string) bool { - fullInfo := filepath.Join(corePath, filepath.Join(dirs.CoreLibExecDir, "info")) - content, err := ioutil.ReadFile(fullInfo) - if err != nil { - if !os.IsNotExist(err) { - logger.Noticef("cannot open snapd info file %q: %s", fullInfo, err) - } - return false - } - - if !bytes.HasPrefix(content, []byte("VERSION=")) { - idx := bytes.Index(content, []byte("\nVERSION=")) - if idx < 0 { - logger.Noticef("cannot find snapd version information in %q", content) - return false - } - content = content[idx+1:] - } - content = content[8:] - idx := bytes.IndexByte(content, '\n') - if idx > -1 { - content = content[:idx] - } - ver := string(content) - // > 0 means our Version is bigger than the version of snapd in core - res, err := strutil.VersionCompare(Version, ver) - if err != nil { - logger.Debugf("cannot version compare %q and %q: %v", Version, ver, err) - return false - } - if res > 0 { - logger.Debugf("core snap (at %q) is older (%q) than distribution package (%q)", corePath, ver, Version) - return false - } - return true -} - -// InternalToolPath returns the path of an internal snapd tool. The tool -// *must* be located inside /usr/lib/snapd/. -// -// The return value is either the path of the tool in the current distribution -// or in the core snap (or the ubuntu-core snap). This handles spiritual -// "re-exec" where we run the tool from the core snap if the environment allows -// us to do so. -func InternalToolPath(tool string) string { - distroTool := filepath.Join(dirs.DistroLibExecDir, tool) - - // find the internal path relative to the running snapd, this - // ensure we don't rely on the state of the system (like - // having a valid "current" symlink). - exe, err := osReadlink("/proc/self/exe") - if err != nil { - logger.Noticef("cannot read /proc/self/exe: %v, using tool outside core", err) - return distroTool - } - - // ensure we never use this helper from anything but - if !strings.HasSuffix(exe, "/snapd") && !strings.HasSuffix(exe, ".test") { - log.Panicf("InternalToolPath can only be used from snapd, got: %s", exe) - } - - if !strings.HasPrefix(exe, dirs.SnapMountDir) { - logger.Debugf("exe doesn't have snap mount dir prefix: %q vs %q", exe, dirs.SnapMountDir) - return distroTool - } - - // if we are re-execed, then the tool is at the same location - // as snapd - return filepath.Join(filepath.Dir(exe), tool) -} - -// mustUnsetenv will unset the given environment key or panic if it -// cannot do that -func mustUnsetenv(key string) { - if err := os.Unsetenv(key); err != nil { - log.Panicf("cannot unset %s: %s", key, err) - } -} - -// ExecInSnapdOrCoreSnap makes sure you're executing the binary that ships in -// the snapd/core snap. -func ExecInSnapdOrCoreSnap() { - // Which executable are we? - exe, err := os.Readlink(selfExe) - if err != nil { - logger.Noticef("cannot read /proc/self/exe: %v", err) - return - } - - // Special case for snapd re-execing from 2.21. In this - // version of snap/snapd we did set SNAP_REEXEC=0 when we - // re-execed. In this case we need to unset the reExecKey to - // ensure that subsequent run of snap/snapd (e.g. when using - // classic confinement) will *not* prevented from re-execing. - if strings.HasPrefix(exe, dirs.SnapMountDir) && !osutil.GetenvBool(reExecKey, true) { - mustUnsetenv(reExecKey) - return - } - - // If we are asked not to re-execute use distribution packages. This is - // "spiritual" re-exec so use the same environment variable to decide. - if !osutil.GetenvBool(reExecKey, true) { - logger.Debugf("re-exec disabled by user") - return - } - - // Did we already re-exec? - if strings.HasPrefix(exe, dirs.SnapMountDir) { - return - } - - // If the distribution doesn't support re-exec or run-from-core then don't do it. - if !distroSupportsReExec() { - return - } - - // Is this executable in the core snap too? - corePath := snapdSnap - full := filepath.Join(snapdSnap, exe) - if !osutil.FileExists(full) { - corePath = coreSnap - full = filepath.Join(coreSnap, exe) - if !osutil.FileExists(full) { - return - } - } - - // If the core snap doesn't support re-exec or run-from-core then don't do it. - if !coreSupportsReExec(corePath) { - return - } - - logger.Debugf("restarting into %q", full) - panic(syscallExec(full, os.Args, os.Environ())) -} diff -Nru snapd-2.37.1+18.04/cmd/cmd_linux_test.go snapd-2.34.2+18.04/cmd/cmd_linux_test.go --- snapd-2.37.1+18.04/cmd/cmd_linux_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/cmd_linux_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,117 +0,0 @@ -package cmd - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/snapcore/snapd/dirs" -) - -const dataOK = `one line -another line -yadda yadda -VERSION=42 -potatoes -` - -const dataNOK = `a line -another -this is a very long line -that wasn't long what are you talking about long lines are like, so long you need to add things like commas to them for them to even make sense -a short one -and another -what is this -why -no -stop -` - -const dataHuge = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. -Quisque euismod ac elit ac auctor. -Proin malesuada diam ac tellus maximus aliquam. -Aenean tincidunt mi et tortor bibendum fringilla. -Phasellus finibus, urna id convallis vestibulum, metus metus venenatis massa, et efficitur nisi elit in massa. -Mauris at nisl leo. -Nulla ullamcorper risus venenatis massa venenatis, ac finibus lacus aliquam. -Nunc tempor convallis cursus. -Maecenas id rhoncus orci, eget pretium eros. - -Donec et consectetur lacus. -Nam nec mattis elit, id sollicitudin magna. -Aenean sit amet diam vitae tellus finibus tristique. -Duis et pharetra tortor, id pharetra erat. -Suspendisse commodo venenatis blandit. -Morbi tellus est, iaculis et tincidunt nec, semper ut ipsum. -Mauris quis condimentum risus. -Lorem ipsum dolor sit amet, consectetur adipiscing elit. -Mauris gravida turpis ut urna laoreet, sit amet tempor odio porttitor. - -Aliquam nibh libero, venenatis ac vehicula at, blandit id odio. -Etiam malesuada consectetur porta. -Fusce consectetur ligula et metus interdum sollicitudin. -Pellentesque odio neque, pharetra et gravida non, vestibulum nec lorem. -Sed condimentum velit ex, sit amet viverra lectus aliquet quis. -Aliquam tincidunt eu elit at condimentum. -Donec feugiat urna tortor, pellentesque tincidunt quam congue eu. - -Phasellus vel libero molestie, semper erat at, suscipit nisi. -Nullam euismod neque ut turpis molestie, eu fringilla elit volutpat. -Phasellus maximus, urna eget porta congue, diam enim volutpat diam, nec ultrices lorem risus ac metus. -Vivamus convallis eros non nunc pretium bibendum. -Maecenas consectetur metus metus. -Morbi scelerisque urna at arcu tristique feugiat. -Vestibulum condimentum odio sed tortor vulputate, eget hendrerit mi consequat. -Integer egestas finibus augue, ac scelerisque ex pretium aliquam. -Aliquam erat volutpat. -Suspendisse a nulla ultrices, porttitor tellus ut, bibendum diam. -In nibh dui, tempus eget vestibulum in, euismod in ex. -In tempus felis lectus. - -Maecenas suscipit turpis eget velit molestie, quis luctus nibh placerat. -Nulla semper eleifend nisi ut dignissim. -Donec eu massa maximus, blandit massa ac, lobortis risus. -Donec id condimentum libero, vel fringilla diam. -Praesent ultrices, ante congue sollicitudin sagittis, orci ex maximus ipsum, at convallis nunc nisl nec lorem. -Duis iaculis finibus fermentum. -Curabitur quis pharetra metus. -Donec nisl ipsum, faucibus vitae odio sed, mattis feugiat nisl. -Pellentesque nec justo in magna volutpat accumsan. -Pellentesque porttitor justo non velit porta rhoncus. -Nulla ut lectus quis lectus rutrum dignissim. -Pellentesque posuere sagittis felis, quis varius purus pharetra eu. -Nam blandit diam ullamcorper, auctor massa at, aliquet dui. -Aliquam erat volutpat. -Nullam sit amet augue nec diam sollicitudin ullamcorper a vitae neque. -VERSION=42 -` - -func benchmarkCSRE(b *testing.B, data string) { - tempdir, err := ioutil.TempDir("", "") - if err != nil { - b.Fatalf("tempdir: %v", err) - } - defer os.RemoveAll(tempdir) - if err = os.MkdirAll(filepath.Join(tempdir, dirs.CoreLibExecDir), 0755); err != nil { - b.Fatalf("mkdirall: %v", err) - } - - if err = ioutil.WriteFile(filepath.Join(tempdir, dirs.CoreLibExecDir, "info"), []byte(data), 0600); err != nil { - b.Fatalf("%v", err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - coreSupportsReExec(tempdir) - } -} - -func BenchmarkCSRE_fakeOK(b *testing.B) { benchmarkCSRE(b, dataOK) } -func BenchmarkCSRE_fakeNOK(b *testing.B) { benchmarkCSRE(b, dataNOK) } -func BenchmarkCSRE_fakeHuge(b *testing.B) { benchmarkCSRE(b, dataHuge) } - -func BenchmarkCSRE_real(b *testing.B) { - for i := 0; i < b.N; i++ { - coreSupportsReExec("/snap/core/current") - } -} diff -Nru snapd-2.37.1+18.04/cmd/cmd_other.go snapd-2.34.2+18.04/cmd/cmd_other.go --- snapd-2.37.1+18.04/cmd/cmd_other.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/cmd_other.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- -// +build !linux - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package cmd - -// ExecInSnapdOrCoreSnap makes sure you're executing the binary that ships in -// the snapd/core snap. -// On this OS this is a stub. -func ExecInSnapdOrCoreSnap() { - return -} diff -Nru snapd-2.37.1+18.04/cmd/cmd_test.go snapd-2.34.2+18.04/cmd/cmd_test.go --- snapd-2.37.1+18.04/cmd/cmd_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/cmd_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -44,8 +44,8 @@ lastExecArgv []string lastExecEnvv []string fakeroot string - snapdPath string - corePath string + newCore string + oldCore string } var _ = Suite(&cmdSuite{}) @@ -59,8 +59,8 @@ s.lastExecEnvv = nil s.fakeroot = c.MkDir() dirs.SetRootDir(s.fakeroot) - s.snapdPath = filepath.Join(dirs.SnapMountDir, "/snapd/42") - s.corePath = filepath.Join(dirs.SnapMountDir, "/core/21") + s.newCore = filepath.Join(dirs.SnapMountDir, "/core/42") + s.oldCore = filepath.Join(dirs.SnapMountDir, "/ubuntu-core/21") c.Assert(os.MkdirAll(filepath.Join(s.fakeroot, "proc/self"), 0755), IsNil) } @@ -95,7 +95,7 @@ restore := []func(){ release.MockOnClassic(true), release.MockReleaseInfo(&release.OS{ID: "ubuntu"}), - cmd.MockCoreSnapdPaths(s.corePath, s.snapdPath), + cmd.MockCorePaths(s.oldCore, s.newCore), cmd.MockVersion("2"), } @@ -148,7 +148,7 @@ // no distro supports re-exec when not on classic :-) for _, id := range []string{ "fedora", "centos", "rhel", "opensuse", "suse", "poky", - "debian", "ubuntu", "arch", "archlinux", + "debian", "ubuntu", "arch", } { restore = release.MockReleaseInfo(&release.OS{ID: id}) defer restore() @@ -163,41 +163,41 @@ func (s *cmdSuite) TestCoreSupportsReExecBadInfo(c *C) { // can't read snapd/info if it's a directory - p := s.snapdPath + "/usr/lib/snapd/info" + p := s.newCore + "/usr/lib/snapd/info" c.Assert(os.MkdirAll(p, 0755), IsNil) - c.Check(cmd.CoreSupportsReExec(s.snapdPath), Equals, false) + c.Check(cmd.CoreSupportsReExec(s.newCore), Equals, false) } func (s *cmdSuite) TestCoreSupportsReExecBadInfoContent(c *C) { // can't understand snapd/info if all it holds are potatoes - p := s.snapdPath + "/usr/lib/snapd" + p := s.newCore + "/usr/lib/snapd" c.Assert(os.MkdirAll(p, 0755), IsNil) c.Assert(ioutil.WriteFile(p+"/info", []byte("potatoes"), 0644), IsNil) - c.Check(cmd.CoreSupportsReExec(s.snapdPath), Equals, false) + c.Check(cmd.CoreSupportsReExec(s.newCore), Equals, false) } func (s *cmdSuite) TestCoreSupportsReExecBadVersion(c *C) { // can't understand snapd/info if all its version is gibberish - s.fakeCoreVersion(c, s.snapdPath, "0:") + s.fakeCoreVersion(c, s.newCore, "0:") - c.Check(cmd.CoreSupportsReExec(s.snapdPath), Equals, false) + c.Check(cmd.CoreSupportsReExec(s.newCore), Equals, false) } func (s *cmdSuite) TestCoreSupportsReExecOldVersion(c *C) { // can't re-exec if core version is too old defer cmd.MockVersion("2")() - s.fakeCoreVersion(c, s.snapdPath, "0") + s.fakeCoreVersion(c, s.newCore, "0") - c.Check(cmd.CoreSupportsReExec(s.snapdPath), Equals, false) + c.Check(cmd.CoreSupportsReExec(s.newCore), Equals, false) } func (s *cmdSuite) TestCoreSupportsReExec(c *C) { defer cmd.MockVersion("2")() - s.fakeCoreVersion(c, s.snapdPath, "9999") + s.fakeCoreVersion(c, s.newCore, "9999") - c.Check(cmd.CoreSupportsReExec(s.snapdPath), Equals, true) + c.Check(cmd.CoreSupportsReExec(s.newCore), Equals, true) } func (s *cmdSuite) TestInternalToolPathNoReexec(c *C) { @@ -210,13 +210,13 @@ } func (s *cmdSuite) TestInternalToolPathWithReexec(c *C) { - s.fakeInternalTool(c, s.snapdPath, "potato") + s.fakeInternalTool(c, s.newCore, "potato") restore := cmd.MockOsReadlink(func(string) (string, error) { - return filepath.Join(s.snapdPath, "/usr/lib/snapd/snapd"), nil + return filepath.Join(s.newCore, "/usr/lib/snapd/snapd"), nil }) defer restore() - c.Check(cmd.InternalToolPath("potato"), Equals, filepath.Join(dirs.SnapMountDir, "snapd/42/usr/lib/snapd/potato")) + c.Check(cmd.InternalToolPath("potato"), Equals, filepath.Join(dirs.SnapMountDir, "core/42/usr/lib/snapd/potato")) } func (s *cmdSuite) TestInternalToolPathFromIncorrectHelper(c *C) { @@ -228,80 +228,80 @@ c.Check(func() { cmd.InternalToolPath("potato") }, PanicMatches, "InternalToolPath can only be used from snapd, got: /usr/bin/potato") } -func (s *cmdSuite) TestExecInSnapdOrCoreSnap(c *C) { - defer s.mockReExecFor(c, s.snapdPath, "potato")() +func (s *cmdSuite) TestExecInCoreSnap(c *C) { + defer s.mockReExecFor(c, s.newCore, "potato")() - c.Check(cmd.ExecInSnapdOrCoreSnap, PanicMatches, `>exec of "[^"]+/potato" in tests<`) + c.Check(cmd.ExecInCoreSnap, PanicMatches, `>exec of "[^"]+/potato" in tests<`) c.Check(s.execCalled, Equals, 1) - c.Check(s.lastExecArgv0, Equals, filepath.Join(s.snapdPath, "/usr/lib/snapd/potato")) + c.Check(s.lastExecArgv0, Equals, filepath.Join(s.newCore, "/usr/lib/snapd/potato")) c.Check(s.lastExecArgv, DeepEquals, os.Args) } func (s *cmdSuite) TestExecInOldCoreSnap(c *C) { - defer s.mockReExecFor(c, s.corePath, "potato")() + defer s.mockReExecFor(c, s.oldCore, "potato")() - c.Check(cmd.ExecInSnapdOrCoreSnap, PanicMatches, `>exec of "[^"]+/potato" in tests<`) + c.Check(cmd.ExecInCoreSnap, PanicMatches, `>exec of "[^"]+/potato" in tests<`) c.Check(s.execCalled, Equals, 1) - c.Check(s.lastExecArgv0, Equals, filepath.Join(s.corePath, "/usr/lib/snapd/potato")) + c.Check(s.lastExecArgv0, Equals, filepath.Join(s.oldCore, "/usr/lib/snapd/potato")) c.Check(s.lastExecArgv, DeepEquals, os.Args) } -func (s *cmdSuite) TestExecInSnapdOrCoreSnapBailsNoCoreSupport(c *C) { - defer s.mockReExecFor(c, s.snapdPath, "potato")() +func (s *cmdSuite) TestExecInCoreSnapBailsNoCoreSupport(c *C) { + defer s.mockReExecFor(c, s.newCore, "potato")() // no "info" -> no core support: - c.Assert(os.Remove(filepath.Join(s.snapdPath, "/usr/lib/snapd/info")), IsNil) + c.Assert(os.Remove(filepath.Join(s.newCore, "/usr/lib/snapd/info")), IsNil) - cmd.ExecInSnapdOrCoreSnap() + cmd.ExecInCoreSnap() c.Check(s.execCalled, Equals, 0) } -func (s *cmdSuite) TestExecInSnapdOrCoreSnapMissingExe(c *C) { - defer s.mockReExecFor(c, s.snapdPath, "potato")() +func (s *cmdSuite) TestExecInCoreSnapMissingExe(c *C) { + defer s.mockReExecFor(c, s.newCore, "potato")() // missing exe: - c.Assert(os.Remove(filepath.Join(s.snapdPath, "/usr/lib/snapd/potato")), IsNil) + c.Assert(os.Remove(filepath.Join(s.newCore, "/usr/lib/snapd/potato")), IsNil) - cmd.ExecInSnapdOrCoreSnap() + cmd.ExecInCoreSnap() c.Check(s.execCalled, Equals, 0) } -func (s *cmdSuite) TestExecInSnapdOrCoreSnapBadSelfExe(c *C) { - defer s.mockReExecFor(c, s.snapdPath, "potato")() +func (s *cmdSuite) TestExecInCoreSnapBadSelfExe(c *C) { + defer s.mockReExecFor(c, s.newCore, "potato")() // missing self/exe: c.Assert(os.Remove(filepath.Join(s.fakeroot, "proc/self/exe")), IsNil) - cmd.ExecInSnapdOrCoreSnap() + cmd.ExecInCoreSnap() c.Check(s.execCalled, Equals, 0) } -func (s *cmdSuite) TestExecInSnapdOrCoreSnapBailsNoDistroSupport(c *C) { - defer s.mockReExecFor(c, s.snapdPath, "potato")() +func (s *cmdSuite) TestExecInCoreSnapBailsNoDistroSupport(c *C) { + defer s.mockReExecFor(c, s.newCore, "potato")() // no distro support: defer release.MockOnClassic(false)() - cmd.ExecInSnapdOrCoreSnap() + cmd.ExecInCoreSnap() c.Check(s.execCalled, Equals, 0) } -func (s *cmdSuite) TestExecInSnapdOrCoreSnapNoDouble(c *C) { +func (s *cmdSuite) TestExecInCoreSnapNoDouble(c *C) { selfExe := filepath.Join(s.fakeroot, "proc/self/exe") err := os.Symlink(filepath.Join(s.fakeroot, "/snap/core/42/usr/lib/snapd"), selfExe) c.Assert(err, IsNil) cmd.MockSelfExe(selfExe) - cmd.ExecInSnapdOrCoreSnap() + cmd.ExecInCoreSnap() c.Check(s.execCalled, Equals, 0) } -func (s *cmdSuite) TestExecInSnapdOrCoreSnapDisabled(c *C) { - defer s.mockReExecFor(c, s.snapdPath, "potato")() +func (s *cmdSuite) TestExecInCoreSnapDisabled(c *C) { + defer s.mockReExecFor(c, s.newCore, "potato")() os.Setenv("SNAP_REEXEC", "0") defer os.Unsetenv("SNAP_REEXEC") - cmd.ExecInSnapdOrCoreSnap() + cmd.ExecInCoreSnap() c.Check(s.execCalled, Equals, 0) } diff -Nru snapd-2.37.1+18.04/cmd/configure.ac snapd-2.34.2+18.04/cmd/configure.ac --- snapd-2.37.1+18.04/cmd/configure.ac 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/configure.ac 2018-07-19 10:05:50.000000000 +0000 @@ -228,13 +228,5 @@ AC_SUBST(HOST_ARCH32_TRIPLET) AC_DEFINE_UNQUOTED([HOST_ARCH32_TRIPLET], "${HOST_ARCH32_TRIPLET}", [Arch triplet for 32bit libraries]) -SYSTEMD_SYSTEM_GENERATOR_DIR="$($PKG_CONFIG --variable=systemdsystemgeneratordir systemd)" -AS_IF([test "x$SYSTEMD_SYSTEM_GENERATOR_DIR" = "x"], [SYSTEMD_SYSTEM_GENERATOR_DIR=/lib/systemd/system-generators]) -AC_SUBST(SYSTEMD_SYSTEM_GENERATOR_DIR) - -# FIXME: get this via something like pkgconf once it is defined there -SYSTEMD_SYSTEM_ENV_GENERATOR_DIR="${prefix}/lib/systemd/system-environment-generators" -AC_SUBST(SYSTEMD_SYSTEM_ENV_GENERATOR_DIR) - AC_CONFIG_FILES([Makefile]) AC_OUTPUT diff -Nru snapd-2.37.1+18.04/cmd/export_test.go snapd-2.34.2+18.04/cmd/export_test.go --- snapd-2.37.1+18.04/cmd/export_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/export_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -24,14 +24,14 @@ CoreSupportsReExec = coreSupportsReExec ) -func MockCoreSnapdPaths(newCoreSnap, newSnapdSnap string) func() { - oldOldCore := coreSnap - oldNewCore := snapdSnap - snapdSnap = newSnapdSnap - coreSnap = newCoreSnap +func MockCorePaths(newOldCore, newNewCore string) func() { + oldOldCore := oldCore + oldNewCore := newCore + newCore = newNewCore + oldCore = newOldCore return func() { - snapdSnap = oldNewCore - coreSnap = oldOldCore + newCore = oldNewCore + oldCore = oldOldCore } } diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/apparmor-support.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/apparmor-support.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/apparmor-support.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/apparmor-support.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2016 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "apparmor-support.h" - -#include -#include -#ifdef HAVE_APPARMOR -#include -#endif // ifdef HAVE_APPARMOR - -#include "../libsnap-confine-private/cleanup-funcs.h" -#include "../libsnap-confine-private/utils.h" - -// NOTE: Those constants map exactly what apparmor is returning and cannot be -// changed without breaking apparmor functionality. -#define SC_AA_ENFORCE_STR "enforce" -#define SC_AA_COMPLAIN_STR "complain" -#define SC_AA_MIXED_STR "mixed" -#define SC_AA_UNCONFINED_STR "unconfined" - -void sc_init_apparmor_support(struct sc_apparmor *apparmor) -{ -#ifdef HAVE_APPARMOR - // Use aa_is_enabled() to see if apparmor is available in the kernel and - // enabled at boot time. If it isn't log a diagnostic message and assume - // we're not confined. - if (aa_is_enabled() != true) { - switch (errno) { - case ENOSYS: - debug - ("apparmor extensions to the system are not available"); - break; - case ECANCELED: - debug - ("apparmor is available on the system but has been disabled at boot"); - break; - case ENOENT: - debug - ("apparmor is available but the interface but the interface is not available"); - break; - case EPERM: - // NOTE: fall-through - case EACCES: - debug - ("insufficient permissions to determine if apparmor is enabled"); - break; - default: - debug("apparmor is not enabled: %s", strerror(errno)); - break; - } - apparmor->is_confined = false; - apparmor->mode = SC_AA_NOT_APPLICABLE; - return; - } - // Use aa_getcon() to check the label of the current process and - // confinement type. Note that the returned label must be released with - // free() but the mode is a constant string that must not be freed. - char *label SC_CLEANUP(sc_cleanup_string) = NULL; - char *mode = NULL; - if (aa_getcon(&label, &mode) < 0) { - die("cannot query current apparmor profile"); - } - debug("apparmor label on snap-confine is: %s", label); - debug("apparmor mode is: %s", mode); - // The label has a special value "unconfined" that is applied to all - // processes without a dedicated profile. If that label is used then the - // current process is not confined. All other labels imply confinement. - if (label != NULL && strcmp(label, SC_AA_UNCONFINED_STR) == 0) { - apparmor->is_confined = false; - } else { - apparmor->is_confined = true; - } - // There are several possible results for the confinement type (mode) that - // are checked for below. - if (mode != NULL && strcmp(mode, SC_AA_COMPLAIN_STR) == 0) { - apparmor->mode = SC_AA_COMPLAIN; - } else if (mode != NULL && strcmp(mode, SC_AA_ENFORCE_STR) == 0) { - apparmor->mode = SC_AA_ENFORCE; - } else if (mode != NULL && strcmp(mode, SC_AA_MIXED_STR) == 0) { - apparmor->mode = SC_AA_MIXED; - } else { - apparmor->mode = SC_AA_INVALID; - } -#else - apparmor->mode = SC_AA_NOT_APPLICABLE; - apparmor->is_confined = false; -#endif // ifdef HAVE_APPARMOR -} - -void -sc_maybe_aa_change_onexec(struct sc_apparmor *apparmor, const char *profile) -{ -#ifdef HAVE_APPARMOR - if (apparmor->mode == SC_AA_NOT_APPLICABLE) { - return; - } - debug("requesting changing of apparmor profile on next exec to %s", - profile); - if (aa_change_onexec(profile) < 0) { - if (secure_getenv("SNAPPY_LAUNCHER_INSIDE_TESTS") == NULL) { - die("cannot change profile for the next exec call"); - } - } -#endif // ifdef HAVE_APPARMOR -} - -void -sc_maybe_aa_change_hat(struct sc_apparmor *apparmor, - const char *subprofile, unsigned long magic_token) -{ -#ifdef HAVE_APPARMOR - if (apparmor->mode == SC_AA_NOT_APPLICABLE) { - return; - } - if (apparmor->is_confined) { - debug("changing apparmor hat to %s", subprofile); - if (aa_change_hat(subprofile, magic_token) < 0) { - die("cannot change apparmor hat"); - } - } -#endif // ifdef HAVE_APPARMOR -} diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/apparmor-support.h snapd-2.34.2+18.04/cmd/libsnap-confine-private/apparmor-support.h --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/apparmor-support.h 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/apparmor-support.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2016 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#ifndef SNAP_CONFINE_APPARMOR_SUPPORT_H -#define SNAP_CONFINE_APPARMOR_SUPPORT_H - -#include - -/** - * Type of apparmor confinement. - **/ -enum sc_apparmor_mode { - // The enforcement mode was not recognized. - SC_AA_INVALID = -1, - // The enforcement mode is not applicable because apparmor is disabled. - SC_AA_NOT_APPLICABLE = 0, - // The enforcement mode is "enforcing" - SC_AA_ENFORCE = 1, - // The enforcement mode is "complain" - SC_AA_COMPLAIN, - // The enforcement mode is "mixed" - SC_AA_MIXED, -}; - -/** - * Data required to manage apparmor wrapper. - **/ -struct sc_apparmor { - // The mode of enforcement. In addition to the two apparmor defined modes - // can be also SC_AA_INVALID (unknown mode reported by apparmor) and - // SC_AA_NOT_APPLICABLE (when we're not linked with apparmor). - enum sc_apparmor_mode mode; - // Flag indicating that the current process is confined. - bool is_confined; -}; - -/** - * Initialize apparmor support. - * - * This operation should be done even when apparmor support is disabled at - * compile time. Internally the supplied structure is initialized based on the - * information returned from aa_getcon(2) or if apparmor is disabled at compile - * time, with built-in constants. - * - * The main action performed here is to check if snap-confine is currently - * confined, this information is used later in sc_maybe_change_apparmor_hat() - * - * As with many functions in the snap-confine tree, all errors result in - * process termination. - **/ -void sc_init_apparmor_support(struct sc_apparmor *apparmor); - -/** - * Maybe call aa_change_onexec(2) - * - * This function does nothing when apparmor support is not enabled at compile - * time. If apparmor is enabled then profile change request is attempted. - * - * As with many functions in the snap-confine tree, all errors result in - * process termination. As an exception, when SNAPPY_LAUNCHER_INSIDE_TESTS - * environment variable is set then the process is not terminated. - **/ -void -sc_maybe_aa_change_onexec(struct sc_apparmor *apparmor, const char *profile); - -/** - * Maybe call aa_change_hat(2) - * - * This function does nothing when apparmor support is not enabled at compile - * time. If apparmor is enabled then hat change is attempted. - * - * As with many functions in the snap-confine tree, all errors result in - * process termination. - **/ -void -sc_maybe_aa_change_hat(struct sc_apparmor *apparmor, - const char *subprofile, unsigned long magic_token); - -#endif diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/classic.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/classic.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/classic.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/classic.c 2018-07-19 10:05:50.000000000 +0000 @@ -1,63 +1,25 @@ #include "config.h" #include "classic.h" #include "../libsnap-confine-private/cleanup-funcs.h" -#include "../libsnap-confine-private/string-utils.h" -#include -#include #include +#include #include -static const char *os_release = "/etc/os-release"; -static const char *meta_snap_yaml = "/meta/snap.yaml"; +char *os_release = "/etc/os-release"; -sc_distro sc_classify_distro(void) +bool is_running_on_classic_distribution() { FILE *f SC_CLEANUP(sc_cleanup_file) = fopen(os_release, "r"); if (f == NULL) { - return SC_DISTRO_CLASSIC; + return true; } - bool is_core = false; - int core_version = 0; char buf[255] = { 0 }; - while (fgets(buf, sizeof buf, f) != NULL) { - size_t len = strlen(buf); - if (len > 0 && buf[len - 1] == '\n') { - buf[len - 1] = '\0'; - } - if (sc_streq(buf, "ID=\"ubuntu-core\"") - || sc_streq(buf, "ID=ubuntu-core")) { - is_core = true; - } else if (sc_streq(buf, "VERSION_ID=\"16\"") - || sc_streq(buf, "VERSION_ID=16")) { - core_version = 16; - } else if (sc_streq(buf, "VARIANT_ID=\"snappy\"") - || sc_streq(buf, "VARIANT_ID=snappy")) { - is_core = true; - } - } - - if (!is_core) { - /* Since classic systems don't have a /meta/snap.yaml file the simple - presence of that file qualifies as SC_DISTRO_CORE_OTHER. */ - if (access(meta_snap_yaml, F_OK) == 0) { - is_core = true; + if (strcmp(buf, "ID=ubuntu-core\n") == 0) { + return false; } } - - if (is_core) { - if (core_version == 16) { - return SC_DISTRO_CORE16; - } - return SC_DISTRO_CORE_OTHER; - } else { - return SC_DISTRO_CLASSIC; - } -} - -bool sc_should_use_normal_mode(sc_distro distro, const char *base_snap_name) -{ - return distro != SC_DISTRO_CORE16 || !sc_streq(base_snap_name, "core"); + return true; } diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/classic.h snapd-2.34.2+18.04/cmd/libsnap-confine-private/classic.h --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/classic.h 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/classic.h 2018-07-19 10:05:50.000000000 +0000 @@ -22,14 +22,6 @@ // Location of the host filesystem directory in the core snap. #define SC_HOSTFS_DIR "/var/lib/snapd/hostfs" -typedef enum sc_distro { - SC_DISTRO_CORE16, // As present in both "core" and later on in "core16" - SC_DISTRO_CORE_OTHER, // Any core distribution. - SC_DISTRO_CLASSIC, // Any classic distribution. -} sc_distro; - -sc_distro sc_classify_distro(void); - -bool sc_should_use_normal_mode(sc_distro distro, const char *base_snap_name); +bool is_running_on_classic_distribution(void); #endif diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/classic-test.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/classic-test.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/classic-test.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/classic-test.c 2018-07-19 10:05:50.000000000 +0000 @@ -20,104 +20,32 @@ #include -/* restore_os_release is an internal helper for mock_os_release */ -static void restore_os_release(gpointer * old) -{ - unlink(os_release); - os_release = (const char *)old; -} - -/* mock_os_release replaces the presence and contents of /etc/os-release - as seen by classic.c. The mocked value may be NULL to have the code refer - to an absent file. */ -static void mock_os_release(const char *mocked) -{ - const char *old = os_release; - if (mocked != NULL) { - os_release = "os-release.test"; - g_file_set_contents(os_release, mocked, -1, NULL); - } else { - os_release = "os-release.missing"; - } - g_test_queue_destroy((GDestroyNotify) restore_os_release, - (gpointer) old); -} - -/* restore_meta_snap_yaml is an internal helper for mock_meta_snap_yaml */ -static void restore_meta_snap_yaml(gpointer * old) -{ - unlink(meta_snap_yaml); - meta_snap_yaml = (const char *)old; -} - -/* mock_meta_snap_yaml replaces the presence and contents of /meta/snap.yaml - as seen by classic.c. The mocked value may be NULL to have the code refer - to an absent file. */ -static void mock_meta_snap_yaml(const char *mocked) -{ - const char *old = meta_snap_yaml; - if (mocked != NULL) { - meta_snap_yaml = "snap-yaml.test"; - g_file_set_contents(meta_snap_yaml, mocked, -1, NULL); - } else { - meta_snap_yaml = "snap-yaml.missing"; - } - g_test_queue_destroy((GDestroyNotify) restore_meta_snap_yaml, - (gpointer) old); -} - -static const char *os_release_classic = "" +const char *os_release_classic = "" "NAME=\"Ubuntu\"\n" "VERSION=\"17.04 (Zesty Zapus)\"\n" "ID=ubuntu\n" "ID_LIKE=debian\n"; static void test_is_on_classic(void) { - mock_os_release(os_release_classic); - mock_meta_snap_yaml(NULL); - g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC); -} - -static const char *os_release_core16 = "" - "NAME=\"Ubuntu Core\"\n" "VERSION_ID=\"16\"\n" "ID=ubuntu-core\n"; - -static const char *meta_snap_yaml_core16 = "" - "name: core\n" - "version: 16-something\n" "type: core\n" "architectures: [amd64]\n"; - -static void test_is_on_core_on16(void) -{ - mock_os_release(os_release_core16); - mock_meta_snap_yaml(meta_snap_yaml_core16); - g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE16); + g_file_set_contents("os-release.classic", os_release_classic, + strlen(os_release_classic), NULL); + os_release = "os-release.classic"; + g_assert_true(is_running_on_classic_distribution()); + unlink("os-release.classic"); } -static const char *os_release_core18 = "" - "NAME=\"Ubuntu Core\"\n" "VERSION_ID=\"18\"\n" "ID=ubuntu-core\n"; - -static const char *meta_snap_yaml_core18 = "" - "name: core18\n" "type: base\n" "architectures: [amd64]\n"; +const char *os_release_core = "" + "NAME=\"Ubuntu Core\"\n" "VERSION=\"16\"\n" "ID=ubuntu-core\n"; -static void test_is_on_core_on18(void) +static void test_is_on_core(void) { - mock_os_release(os_release_core18); - mock_meta_snap_yaml(meta_snap_yaml_core18); - g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER); + g_file_set_contents("os-release.core", os_release_core, + strlen(os_release_core), NULL); + os_release = "os-release.core"; + g_assert_false(is_running_on_classic_distribution()); + unlink("os-release.core"); } -const char *os_release_core20 = "" - "NAME=\"Ubuntu Core\"\n" "VERSION_ID=\"20\"\n" "ID=ubuntu-core\n"; - -static const char *meta_snap_yaml_core20 = "" - "name: core20\n" "type: base\n" "architectures: [amd64]\n"; - -static void test_is_on_core_on20(void) -{ - mock_os_release(os_release_core20); - mock_meta_snap_yaml(meta_snap_yaml_core20); - g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER); -} - -static const char *os_release_classic_with_long_line = "" +const char *os_release_classic_with_long_line = "" "NAME=\"Ubuntu\"\n" "VERSION=\"17.04 (Zesty Zapus)\"\n" "ID=ubuntu\n" @@ -126,66 +54,12 @@ static void test_is_on_classic_with_long_line(void) { - mock_os_release(os_release_classic_with_long_line); - mock_meta_snap_yaml(NULL); - g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC); -} - -static const char *os_release_fedora_base = "" - "NAME=Fedora\nID=fedora\nVARIANT_ID=snappy\n"; - -static const char *meta_snap_yaml_fedora_base = "" - "name: fedora29\n" "type: base\n" "architectures: [amd64]\n"; - -static void test_is_on_fedora_base(void) -{ - mock_os_release(os_release_fedora_base); - mock_meta_snap_yaml(meta_snap_yaml_fedora_base); - g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER); -} - -static const char *os_release_fedora_ws = "" - "NAME=Fedora\nID=fedora\nVARIANT_ID=workstation\n"; - -static void test_is_on_fedora_ws(void) -{ - mock_os_release(os_release_fedora_ws); - mock_meta_snap_yaml(NULL); - g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC); -} - -static const char *os_release_custom = "" - "NAME=\"Custom Distribution\"\nID=custom\n"; - -static const char *meta_snap_yaml_custom = "" - "name: custom\n" - "version: rolling\n" - "summary: Runtime environment based on Custom Distribution\n" - "type: base\n" "architectures: [amd64]\n"; - -static void test_is_on_custom_base(void) -{ - mock_os_release(os_release_custom); - - /* Without /meta/snap.yaml we treat "Custom Distribution" as classic. */ - mock_meta_snap_yaml(NULL); - g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CLASSIC); - - /* With /meta/snap.yaml we treat it as core instead. */ - mock_meta_snap_yaml(meta_snap_yaml_custom); - g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER); -} - -static void test_should_use_normal_mode(void) -{ - g_assert_false(sc_should_use_normal_mode(SC_DISTRO_CORE16, "core")); - g_assert_true(sc_should_use_normal_mode(SC_DISTRO_CORE_OTHER, "core")); - g_assert_true(sc_should_use_normal_mode(SC_DISTRO_CLASSIC, "core")); - - g_assert_true(sc_should_use_normal_mode(SC_DISTRO_CORE16, "core18")); - g_assert_true(sc_should_use_normal_mode - (SC_DISTRO_CORE_OTHER, "core18")); - g_assert_true(sc_should_use_normal_mode(SC_DISTRO_CLASSIC, "core18")); + g_file_set_contents("os-release.classic-with-long-line", + os_release_classic, strlen(os_release_classic), + NULL); + os_release = "os-release.classic-with-long-line"; + g_assert_true(is_running_on_classic_distribution()); + unlink("os-release.classic-with-long-line"); } static void __attribute__ ((constructor)) init(void) @@ -193,12 +67,5 @@ g_test_add_func("/classic/on-classic", test_is_on_classic); g_test_add_func("/classic/on-classic-with-long-line", test_is_on_classic_with_long_line); - g_test_add_func("/classic/on-core-on16", test_is_on_core_on16); - g_test_add_func("/classic/on-core-on18", test_is_on_core_on18); - g_test_add_func("/classic/on-core-on20", test_is_on_core_on20); - g_test_add_func("/classic/on-fedora-base", test_is_on_fedora_base); - g_test_add_func("/classic/on-fedora-ws", test_is_on_fedora_ws); - g_test_add_func("/classic/on-custom-base", test_is_on_custom_base); - g_test_add_func("/classic/should-use-normal-mode", - test_should_use_normal_mode); + g_test_add_func("/classic/on-core", test_is_on_core); } diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/feature.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/feature.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/feature.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/feature.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#define _GNU_SOURCE - -#include "feature.h" - -#include -#include -#include -#include -#include - -#include "cleanup-funcs.h" -#include "utils.h" - -static const char *feature_flag_dir = "/var/lib/snapd/features"; - -bool sc_feature_enabled(sc_feature_flag flag) -{ - const char *file_name; - switch (flag) { - case SC_PER_USER_MOUNT_NAMESPACE: - file_name = "per-user-mount-namespace"; - break; - default: - die("unknown feature flag code %d", flag); - } - - int dirfd SC_CLEANUP(sc_cleanup_close) = -1; - dirfd = open(feature_flag_dir, O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW | O_PATH); - if (dirfd < 0 && errno == ENOENT) { - return false; - } - if (dirfd < 0) { - die("cannot open path %s", feature_flag_dir); - } - - struct stat file_info; - if (fstatat(dirfd, file_name, &file_info, AT_SYMLINK_NOFOLLOW) < 0) { - if (errno == ENOENT) { - return false; - } - die("cannot inspect file %s/%s", feature_flag_dir, file_name); - } - - return S_ISREG(file_info.st_mode); -} diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/feature.h snapd-2.34.2+18.04/cmd/libsnap-confine-private/feature.h --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/feature.h 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/feature.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#ifndef SNAP_CONFINE_FEATURE_H -#define SNAP_CONFINE_FEATURE_H - -#include - -typedef enum sc_feature_flag { - SC_PER_USER_MOUNT_NAMESPACE, -} sc_feature_flag; - -/** - * sc_feature_enabled returns true if a given feature flag has been activated - * by the user via "snap set core experimental.xxx=true". This is determined by - * testing the presence of a file in /var/lib/snapd/features/ that is named - * after the flag name. -**/ -bool sc_feature_enabled(sc_feature_flag flag); - -#endif diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/feature-test.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/feature-test.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/feature-test.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/feature-test.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#include "feature.h" -#include "feature.c" - -#include - -#include - -#include "string-utils.h" -#include "test-utils.h" - -static char *sc_testdir(void) -{ - char *d = g_dir_make_tmp(NULL, NULL); - g_assert_nonnull(d); - g_test_queue_free(d); - g_test_queue_destroy((GDestroyNotify) rm_rf_tmp, d); - return d; -} - -// Set the feature flag directory to given value, useful for cleanup handlers. -static void set_feature_flag_dir(const char *dir) -{ - feature_flag_dir = dir; -} - -// Mock the location of the feature flag directory. -static void sc_mock_feature_flag_dir(const char *d) -{ - g_test_queue_destroy((GDestroyNotify) set_feature_flag_dir, - (void *)feature_flag_dir); - set_feature_flag_dir(d); -} - -static void test_feature_enabled__missing_dir(void) -{ - const char *d = sc_testdir(); - char subd[PATH_MAX]; - sc_must_snprintf(subd, sizeof subd, "%s/absent", d); - sc_mock_feature_flag_dir(subd); - g_assert(!sc_feature_enabled(SC_PER_USER_MOUNT_NAMESPACE)); -} - -static void test_feature_enabled__missing_file(void) -{ - const char *d = sc_testdir(); - sc_mock_feature_flag_dir(d); - g_assert(!sc_feature_enabled(SC_PER_USER_MOUNT_NAMESPACE)); -} - -static void test_feature_enabled__present_file(void) -{ - const char *d = sc_testdir(); - sc_mock_feature_flag_dir(d); - char pname[PATH_MAX]; - sc_must_snprintf(pname, sizeof pname, "%s/per-user-mount-namespace", d); - g_file_set_contents(pname, "", -1, NULL); - - g_assert(sc_feature_enabled(SC_PER_USER_MOUNT_NAMESPACE)); -} - -static void __attribute__ ((constructor)) init(void) -{ - g_test_add_func("/feature/missing_dir", - test_feature_enabled__missing_dir); - g_test_add_func("/feature/missing_file", - test_feature_enabled__missing_file); - g_test_add_func("/feature/present_file", - test_feature_enabled__present_file); -} diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/locking.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/locking.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/locking.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/locking.c 2018-07-19 10:05:50.000000000 +0000 @@ -21,7 +21,6 @@ #include "locking.h" -#include #include #include #include @@ -85,7 +84,7 @@ static const char *sc_lock_dir = SC_LOCK_DIR; -static int get_lock_directory(void) +static int sc_lock_generic(const char *scope, uid_t uid) { // Create (if required) and open the lock directory. debug("creating lock directory %s (if missing)", sc_lock_dir); @@ -93,50 +92,33 @@ die("cannot create lock directory %s", sc_lock_dir); } debug("opening lock directory %s", sc_lock_dir); - int dir_fd = + int dir_fd SC_CLEANUP(sc_cleanup_close) = -1; + dir_fd = open(sc_lock_dir, O_DIRECTORY | O_PATH | O_CLOEXEC | O_NOFOLLOW); if (dir_fd < 0) { die("cannot open lock directory"); } - return dir_fd; -} - -static void get_lock_name(char *lock_fname, size_t size, const char *scope, - uid_t uid) -{ + // Construct the name of the lock file. + char lock_fname[PATH_MAX] = { 0 }; if (uid == 0) { // The root user doesn't have a per-user mount namespace. // Doing so would be confusing for services which use $SNAP_DATA // as home, and not in $SNAP_USER_DATA. - sc_must_snprintf(lock_fname, size, "%s.lock", scope ? : ""); + sc_must_snprintf(lock_fname, sizeof lock_fname, "%s/%s.lock", + sc_lock_dir, scope ? : ""); } else { - sc_must_snprintf(lock_fname, size, "%s.%d.lock", - scope ? : "", uid); + sc_must_snprintf(lock_fname, sizeof lock_fname, "%s/%s.%d.lock", + sc_lock_dir, scope ? : "", uid); } -} - -static int open_lock(const char *scope, uid_t uid) -{ - int dir_fd SC_CLEANUP(sc_cleanup_close) = -1; - char lock_fname[PATH_MAX] = { 0 }; - int lock_fd; - - dir_fd = get_lock_directory(); - get_lock_name(lock_fname, sizeof lock_fname, scope, uid); // Open the lock file and acquire an exclusive lock. - debug("opening lock file: %s/%s", sc_lock_dir, lock_fname); - lock_fd = openat(dir_fd, lock_fname, - O_CREAT | O_RDWR | O_CLOEXEC | O_NOFOLLOW, 0600); + debug("opening lock file: %s", lock_fname); + int lock_fd = openat(dir_fd, lock_fname, + O_CREAT | O_RDWR | O_CLOEXEC | O_NOFOLLOW, 0600); if (lock_fd < 0) { - die("cannot open lock file: %s/%s", sc_lock_dir, lock_fname); + die("cannot open lock file: %s", lock_fname); } - return lock_fd; -} -static int sc_lock_generic(const char *scope, uid_t uid) -{ - int lock_fd = open_lock(scope, uid); sc_enable_sanity_timeout(); debug("acquiring exclusive lock (scope %s, uid %d)", scope ? : "(global)", uid); @@ -161,28 +143,6 @@ return sc_lock_generic(snap_name, 0); } -void sc_verify_snap_lock(const char *snap_name) -{ - int lock_fd, retval; - - lock_fd = open_lock(snap_name, 0); - debug("trying to verify whether exclusive lock over snap %s is held", - snap_name); - retval = flock(lock_fd, LOCK_EX | LOCK_NB); - if (retval == 0) { - /* We managed to grab the lock, the lock was not held! */ - flock(lock_fd, LOCK_UN); - close(lock_fd); - errno = 0; - die("unexpectedly managed to acquire exclusive lock over snap %s", snap_name); - } - if (retval < 0 && errno != EWOULDBLOCK) { - die("cannot verify exclusive lock over snap %s", snap_name); - } - /* We tried but failed to grab the lock because the file is already locked. - * Good, this is what we expected. */ -} - int sc_lock_snap_user(const char *snap_name, uid_t uid) { return sc_lock_generic(snap_name, uid); diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/locking.h snapd-2.34.2+18.04/cmd/libsnap-confine-private/locking.h --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/locking.h 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/locking.h 2018-07-19 10:05:50.000000000 +0000 @@ -54,14 +54,6 @@ int sc_lock_snap(const char *snap_name); /** - * Verify that a flock-based, exclusive, snap-scoped, lock is held. - * - * If the lock is not held the process dies. The details about the lock - * are exactly the same as for sc_lock_snap(). - **/ -void sc_verify_snap_lock(const char *snap_name); - -/** * Obtain a flock-based, exclusive, snap-scoped, lock. * * The actual lock is placed in "/run/snapd/ns/$SNAP_NAME.$UID.lock" diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/locking-test.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/locking-test.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/locking-test.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/locking-test.c 2018-07-19 10:05:50.000000000 +0000 @@ -92,29 +92,6 @@ g_assert_cmpint(err, ==, 0); } -// Check that holding a lock is properly detected. -static void test_sc_verify_snap_lock__locked(void) -{ - (void)sc_test_use_fake_lock_dir(); - int fd = sc_lock_snap("foo"); - sc_verify_snap_lock("foo"); - sc_unlock(fd); -} - -// Check that holding a lock is properly detected. -static void test_sc_verify_snap_lock__unlocked(void) -{ - (void)sc_test_use_fake_lock_dir(); - if (g_test_subprocess()) { - sc_verify_snap_lock("foo"); - return; - } - g_test_trap_subprocess(NULL, 0, 0); - g_test_trap_assert_failed(); - g_test_trap_assert_stderr - ("unexpectedly managed to acquire exclusive lock over snap foo\n"); -} - static void test_sc_enable_sanity_timeout(void) { if (g_test_subprocess()) { @@ -135,8 +112,4 @@ g_test_add_func("/locking/sc_lock_unlock", test_sc_lock_unlock); g_test_add_func("/locking/sc_enable_sanity_timeout", test_sc_enable_sanity_timeout); - g_test_add_func("/locking/sc_verify_snap_lock__locked", - test_sc_verify_snap_lock__locked); - g_test_add_func("/locking/sc_verify_snap_lock__unlocked", - test_sc_verify_snap_lock__unlocked); } diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/mount-opt.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/mount-opt.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/mount-opt.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/mount-opt.c 2018-07-19 10:05:50.000000000 +0000 @@ -256,10 +256,9 @@ "(disabled) use debug build to see details"; #endif -static bool sc_do_mount_ex(const char *source, const char *target, - const char *fs_type, - unsigned long mountflags, const void *data, - bool optional) +void sc_do_mount(const char *source, const char *target, + const char *fs_type, unsigned long mountflags, + const void *data) { char buf[10000] = { 0 }; const char *mount_cmd = NULL; @@ -275,11 +274,9 @@ } if (sc_faulty("mount", NULL) || mount(source, target, fs_type, mountflags, data) < 0) { + // Save errno as ensure can clobber it. int saved_errno = errno; - if (optional && saved_errno == ENOENT) { - // The special-cased value that is allowed to fail. - return false; - } + // Drop privileges so that we can compute our nice error message // without risking an attack on one of the string functions there. sc_privs_drop(); @@ -291,21 +288,6 @@ errno = saved_errno; die("cannot perform operation: %s", mount_cmd); } - return true; -} - -void sc_do_mount(const char *source, const char *target, - const char *fs_type, unsigned long mountflags, - const void *data) -{ - (void)sc_do_mount_ex(source, target, fs_type, mountflags, data, false); -} - -bool sc_do_optional_mount(const char *source, const char *target, - const char *fs_type, unsigned long mountflags, - const void *data) -{ - return sc_do_mount_ex(source, target, fs_type, mountflags, data, true); } void sc_do_umount(const char *target, int flags) diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/mount-opt.h snapd-2.34.2+18.04/cmd/libsnap-confine-private/mount-opt.h --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/mount-opt.h 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/mount-opt.h 2018-07-19 10:05:50.000000000 +0000 @@ -18,7 +18,6 @@ #ifndef SNAP_CONFINE_MOUNT_OPT_H #define SNAP_CONFINE_MOUNT_OPT_H -#include #include /** @@ -69,19 +68,6 @@ const void *data); /** - * A thin wrapper around mount(2) with logging and error checks. - * - * This variant is allowed to silently fail when mount fails with ENOENT. - * That is, it can be used to perform mount operations and if either the source - * or the destination is not present, carry on as if nothing had happened. - * - * The return value indicates if the operation was successful or not. - **/ -bool sc_do_optional_mount(const char *source, const char *target, - const char *fs_type, unsigned long mountflags, - const void *data); - -/** * A thin wrapper around umount(2) with logging and error checks. **/ void sc_do_umount(const char *target, int flags); diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/mount-opt-test.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/mount-opt-test.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/mount-opt-test.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/mount-opt-test.c 2018-07-19 10:05:50.000000000 +0000 @@ -275,50 +275,6 @@ } } -static bool missing_mount(struct sc_fault_state *state, void *ptr) -{ - errno = ENOENT; - return true; -} - -static void test_sc_do_optional_mount_missing(void) -{ - sc_break("mount", missing_mount); - bool ok = sc_do_optional_mount("/foo", "/bar", "ext4", MS_RDONLY, NULL); - g_assert_false(ok); - sc_reset_faults(); -} - -static void test_sc_do_optional_mount_failure(gconstpointer snap_debug) -{ - if (g_test_subprocess()) { - sc_break("mount", broken_mount); - if (GPOINTER_TO_INT(snap_debug) == 1) { - g_setenv("SNAP_CONFINE_DEBUG", "1", true); - } - (void)sc_do_optional_mount("/foo", "/bar", "ext4", MS_RDONLY, - NULL); - - g_test_message("expected sc_do_mount not to return"); - sc_reset_faults(); - g_test_fail(); - return; - } - g_test_trap_subprocess(NULL, 0, 0); - g_test_trap_assert_failed(); - if (GPOINTER_TO_INT(snap_debug) == 0) { - g_test_trap_assert_stderr - ("cannot perform operation: mount -t ext4 -o ro /foo /bar: Permission denied\n"); - } else { - /* with snap_debug the debug output hides the actual mount commands *but* - * they are still shown if there was an error - */ - g_test_trap_assert_stderr - ("DEBUG: performing operation: (disabled) use debug build to see details\n" - "cannot perform operation: mount -t ext4 -o ro /foo /bar: Permission denied\n"); - } -} - static void __attribute__ ((constructor)) init(void) { g_test_add_func("/mount/sc_mount_opt2str", test_sc_mount_opt2str); @@ -332,12 +288,4 @@ GINT_TO_POINTER(1), test_sc_do_mount); g_test_add_data_func("/mount/sc_do_umount_with_debug", GINT_TO_POINTER(1), test_sc_do_umount); - g_test_add_func("/mount/sc_do_optional_mount_missing", - test_sc_do_optional_mount_missing); - g_test_add_data_func("/mount/sc_do_optional_mount_failure", - GINT_TO_POINTER(0), - test_sc_do_optional_mount_failure); - g_test_add_data_func("/mount/sc_do_optional_mount_failure_with_debug", - GINT_TO_POINTER(1), - test_sc_do_optional_mount_failure); } diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/snap.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/snap.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/snap.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/snap.c 2018-07-19 10:05:50.000000000 +0000 @@ -22,7 +22,6 @@ #include #include #include -#include #include "utils.h" #include "string-utils.h" @@ -31,7 +30,7 @@ bool verify_security_tag(const char *security_tag, const char *snap_name) { const char *whitelist_re = - "^snap\\.([a-z0-9](-?[a-z0-9])*(_[a-z0-9]{1,10})?)\\.([a-zA-Z0-9](-?[a-zA-Z0-9])*|hook\\.[a-z](-?[a-z])*)$"; + "^snap\\.([a-z0-9](-?[a-z0-9])*)\\.([a-zA-Z0-9](-?[a-zA-Z0-9])*|hook\\.[a-z](-?[a-z])*)$"; regex_t re; if (regcomp(&re, whitelist_re, REG_EXTENDED) != 0) die("can not compile regex %s", whitelist_re); @@ -57,7 +56,7 @@ bool sc_is_hook_security_tag(const char *security_tag) { const char *whitelist_re = - "^snap\\.[a-z](-?[a-z0-9])*(_[a-z0-9]{1,10})?\\.(hook\\.[a-z](-?[a-z])*)$"; + "^snap\\.[a-z](-?[a-z0-9])*\\.(hook\\.[a-z](-?[a-z])*)$"; regex_t re; if (regcomp(&re, whitelist_re, REG_EXTENDED | REG_NOSUB) != 0) @@ -98,94 +97,6 @@ return 0; } -void sc_instance_name_validate(const char *instance_name, - struct sc_error **errorp) -{ - // NOTE: This function should be synchronized with the two other - // implementations: validate_instance_name and snap.ValidateInstanceName. - struct sc_error *err = NULL; - - // Ensure that name is not NULL - if (instance_name == NULL) { - err = - sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME, - "snap instance name cannot be NULL"); - goto out; - } - // 40 char snap_name + '_' + 10 char instance_key + 1 extra overflow + 1 - // NULL - char s[53] = { 0 }; - strncpy(s, instance_name, sizeof(s) - 1); - - char *t = s; - const char *snap_name = strsep(&t, "_"); - const char *instance_key = strsep(&t, "_"); - const char *third_separator = strsep(&t, "_"); - if (third_separator != NULL) { - err = - sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME, - "snap instance name can contain only one underscore"); - goto out; - } - - sc_snap_name_validate(snap_name, &err); - if (err != NULL) { - goto out; - } - // When the instance_name is a normal snap name, instance_key will be - // NULL, so only validate instance_key when we found one. - if (instance_key != NULL) { - sc_instance_key_validate(instance_key, &err); - } - - out: - sc_error_forward(errorp, err); -} - -void sc_instance_key_validate(const char *instance_key, - struct sc_error **errorp) -{ - // NOTE: see snap.ValidateInstanceName for reference of a valid instance key - // format - struct sc_error *err = NULL; - - // Ensure that name is not NULL - if (instance_key == NULL) { - err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME, - "instance key cannot be NULL"); - goto out; - } - // This is a regexp-free routine hand-coding the following pattern: - // - // "^[a-z]{1,10}$" - // - // The only motivation for not using regular expressions is so that we don't - // run untrusted input against a potentially complex regular expression - // engine. - int i = 0; - for (i = 0; instance_key[i] != '\0'; i++) { - if (islower(instance_key[i]) || isdigit(instance_key[i])) { - continue; - } - err = - sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY, - "instance key must use lower case letters or digits"); - goto out; - } - - if (i == 0) { - err = - sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY, - "instance key must contain at least one letter or digit"); - } else if (i > 10) { - err = - sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY, - "instance key must be shorter than 10 characters"); - } - out: - sc_error_forward(errorp, err); -} - void sc_snap_name_validate(const char *snap_name, struct sc_error **errorp) { // NOTE: This function should be synchronized with the two other @@ -248,17 +159,10 @@ if (!got_letter) { err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME, "snap name must contain at least one letter"); - goto out; - } - if (n < 2) { - err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME, - "snap name must be longer than 1 character"); - goto out; } if (n > 40) { err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME, "snap name must be shorter than 40 characters"); - goto out; } out: diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/snap.h snapd-2.34.2+18.04/cmd/libsnap-confine-private/snap.h --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/snap.h 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/snap.h 2018-07-19 10:05:50.000000000 +0000 @@ -31,10 +31,6 @@ enum { /** The name of the snap is not valid. */ SC_SNAP_INVALID_NAME = 1, - /** The instance key of the snap is not valid. */ - SC_SNAP_INVALID_INSTANCE_KEY = 2, - /** The instance of the snap is not valid. */ - SC_SNAP_INVALID_INSTANCE_NAME = 3, }; /** @@ -49,31 +45,6 @@ void sc_snap_name_validate(const char *snap_name, struct sc_error **errorp); /** - * Validate the given instance key. - * - * Valid instance key cannot be NULL and must match a regular expression - * describing the strict naming requirements. Please refer to snapd source code - * for details. - * - * The error protocol is observed so if the caller doesn't provide an outgoing - * error pointer the function will die on any error. - **/ -void sc_instance_key_validate(const char *instance_key, - struct sc_error **errorp); - -/** - * Validate the given snap instance name. - * - * Valid instance name must be composed of a valid snap name and a valid - * instance key. - * - * The error protocol is observed so if the caller doesn't provide an outgoing - * error pointer the function will die on any error. - **/ -void sc_instance_name_validate(const char *instance_name, - struct sc_error **errorp); - -/** * Validate security tag against strict naming requirements and snap name. * * The executable name is of form: diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/snap-test.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/snap-test.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/snap-test.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/snap-test.c 2018-07-19 10:05:50.000000000 +0000 @@ -30,12 +30,6 @@ g_assert_true(verify_security_tag("snap.f00.bar-baz1", "f00")); g_assert_true(verify_security_tag("snap.foo.hook.bar", "foo")); g_assert_true(verify_security_tag("snap.foo.hook.bar-baz", "foo")); - g_assert_true(verify_security_tag - ("snap.foo_instance.bar-baz", "foo_instance")); - g_assert_true(verify_security_tag - ("snap.foo_instance.hook.bar-baz", "foo_instance")); - g_assert_true(verify_security_tag - ("snap.foo_bar.hook.bar-baz", "foo_bar")); // Now, test the names we know are bad g_assert_false(verify_security_tag @@ -68,63 +62,31 @@ g_assert_false(verify_security_tag("snap..name.app", ".name")); g_assert_false(verify_security_tag("snap.name..app", "name.")); g_assert_false(verify_security_tag("snap.name.app..", "name")); - // These contain invalid instance key - g_assert_false(verify_security_tag("snap.foo_.bar-baz", "foo")); - g_assert_false(verify_security_tag - ("snap.foo_toolonginstance.bar-baz", "foo")); - g_assert_false(verify_security_tag - ("snap.foo_inst@nace.bar-baz", "foo")); - g_assert_false(verify_security_tag - ("snap.foo_in-stan-ce.bar-baz", "foo")); - g_assert_false(verify_security_tag("snap.foo_in stan.bar-baz", "foo")); // Test names that are both good, but snap name doesn't match security tag g_assert_false(verify_security_tag("snap.foo.hook.bar", "fo")); g_assert_false(verify_security_tag("snap.foo.hook.bar", "fooo")); g_assert_false(verify_security_tag("snap.foo.hook.bar", "snap")); g_assert_false(verify_security_tag("snap.foo.hook.bar", "bar")); - g_assert_false(verify_security_tag("snap.foo_instance.bar", "foo_bar")); // Regression test 12to8 g_assert_true(verify_security_tag("snap.12to8.128to8", "12to8")); g_assert_true(verify_security_tag("snap.123test.123test", "123test")); g_assert_true(verify_security_tag ("snap.123test.hook.configure", "123test")); + } -static void test_sc_is_hook_security_tag(void) +static void test_sc_snap_name_validate(void) { - // First, test the names we know are good - g_assert_true(sc_is_hook_security_tag("snap.foo.hook.bar")); - g_assert_true(sc_is_hook_security_tag("snap.foo.hook.bar-baz")); - g_assert_true(sc_is_hook_security_tag - ("snap.foo_instance.hook.bar-baz")); - g_assert_true(sc_is_hook_security_tag("snap.foo_bar.hook.bar-baz")); - - // Now, test the names we know are not valid hook security tags - g_assert_false(sc_is_hook_security_tag("snap.foo_instance.bar-baz")); - g_assert_false(sc_is_hook_security_tag("snap.name.app!hook.foo")); - g_assert_false(sc_is_hook_security_tag("snap.name.app.hook!foo")); - g_assert_false(sc_is_hook_security_tag("snap.name.app.hook.-foo")); - g_assert_false(sc_is_hook_security_tag("snap.name.app.hook.f00")); -} - -static void test_sc_snap_or_instance_name_validate(gconstpointer data) -{ - typedef void (*validate_func_t) (const char *, struct sc_error **); - - validate_func_t validate = (validate_func_t) data; - bool is_instance = - (validate == sc_instance_name_validate) ? true : false; - struct sc_error *err = NULL; // Smoke test, a valid snap name - validate("hello-world", &err); + sc_snap_name_validate("hello-world", &err); g_assert_null(err); // Smoke test: invalid character - validate("hello world", &err); + sc_snap_name_validate("hello world", &err); g_assert_nonnull(err); g_assert_true(sc_error_match (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); @@ -133,7 +95,7 @@ sc_error_free(err); // Smoke test: no letters - validate("", &err); + sc_snap_name_validate("", &err); g_assert_nonnull(err); g_assert_true(sc_error_match (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); @@ -142,7 +104,7 @@ sc_error_free(err); // Smoke test: leading dash - validate("-foo", &err); + sc_snap_name_validate("-foo", &err); g_assert_nonnull(err); g_assert_true(sc_error_match (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); @@ -151,7 +113,7 @@ sc_error_free(err); // Smoke test: trailing dash - validate("foo-", &err); + sc_snap_name_validate("foo-", &err); g_assert_nonnull(err); g_assert_true(sc_error_match (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); @@ -160,7 +122,7 @@ sc_error_free(err); // Smoke test: double dash - validate("f--oo", &err); + sc_snap_name_validate("f--oo", &err); g_assert_nonnull(err); g_assert_true(sc_error_match (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); @@ -169,40 +131,27 @@ sc_error_free(err); // Smoke test: NULL name is not valid - validate(NULL, &err); + sc_snap_name_validate(NULL, &err); g_assert_nonnull(err); - // the only case when instance name validation diverges from snap name - // validation - if (!is_instance) { - g_assert_true(sc_error_match - (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); - g_assert_cmpstr(sc_error_msg(err), ==, - "snap name cannot be NULL"); - } else { - g_assert_true(sc_error_match - (err, SC_SNAP_DOMAIN, - SC_SNAP_INVALID_INSTANCE_NAME)); - g_assert_cmpstr(sc_error_msg(err), ==, - "snap instance name cannot be NULL"); - } + g_assert_true(sc_error_match + (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); + g_assert_cmpstr(sc_error_msg(err), ==, "snap name cannot be NULL"); sc_error_free(err); const char *valid_names[] = { - "aa", "aaa", "aaaa", + "a", "aa", "aaa", "aaaa", "a-a", "aa-a", "a-aa", "a-b-c", "a0", "a-0", "a-0a", "01game", "1-or-2" }; for (size_t i = 0; i < sizeof valid_names / sizeof *valid_names; ++i) { g_test_message("checking valid snap name: %s", valid_names[i]); - validate(valid_names[i], &err); + sc_snap_name_validate(valid_names[i], &err); g_assert_null(err); } const char *invalid_names[] = { // name cannot be empty "", - // too short - "a", // names cannot be too long "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "xxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx", @@ -226,16 +175,16 @@ ++i) { g_test_message("checking invalid snap name: >%s<", invalid_names[i]); - validate(invalid_names[i], &err); + sc_snap_name_validate(invalid_names[i], &err); g_assert_nonnull(err); g_assert_true(sc_error_match (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); sc_error_free(err); } // Regression test: 12to8 and 123test - validate("12to8", &err); + sc_snap_name_validate("12to8", &err); g_assert_null(err); - validate("123test", &err); + sc_snap_name_validate("123test", &err); g_assert_null(err); // In case we switch to a regex, here's a test that could break things. @@ -245,7 +194,7 @@ g_assert_nonnull(memcpy(varname, good_bad_name, i)); varname[i] = 0; g_test_message("checking valid snap name: >%s<", varname); - validate(varname, &err); + sc_snap_name_validate(varname, &err); g_assert_null(err); sc_error_free(err); } @@ -265,96 +214,6 @@ ("snap name must use lower case letters, digits or dashes\n"); } -static void test_sc_instance_name_validate(void) -{ - struct sc_error *err = NULL; - - sc_instance_name_validate("hello-world", &err); - g_assert_null(err); - sc_instance_name_validate("hello-world_foo", &err); - g_assert_null(err); - - // just the separator - sc_instance_name_validate("_", &err); - g_assert_nonnull(err); - g_assert_true(sc_error_match - (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); - g_assert_cmpstr(sc_error_msg(err), ==, - "snap name must contain at least one letter"); - sc_error_free(err); - - // just name, with separator, missing instance key - sc_instance_name_validate("hello-world_", &err); - g_assert_nonnull(err); - g_assert_true(sc_error_match - (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY)); - g_assert_cmpstr(sc_error_msg(err), ==, - "instance key must contain at least one letter or digit"); - sc_error_free(err); - - // only separator and instance key, missing name - sc_instance_name_validate("_bar", &err); - g_assert_nonnull(err); - g_assert_true(sc_error_match - (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); - g_assert_cmpstr(sc_error_msg(err), ==, - "snap name must contain at least one letter"); - sc_error_free(err); - - sc_instance_name_validate("", &err); - g_assert_nonnull(err); - g_assert_true(sc_error_match - (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); - g_assert_cmpstr(sc_error_msg(err), ==, - "snap name must contain at least one letter"); - sc_error_free(err); - - // third separator - sc_instance_name_validate("foo_bar_baz", &err); - g_assert_nonnull(err); - g_assert_true(sc_error_match - (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_NAME)); - g_assert_cmpstr(sc_error_msg(err), ==, - "snap instance name can contain only one underscore"); - sc_error_free(err); - - const char *valid_names[] = { - "aa", "aaa", "aaaa", - "aa_a", "aa_1", "aa_123", "aa_0123456789", - }; - for (size_t i = 0; i < sizeof valid_names / sizeof *valid_names; ++i) { - g_test_message("checking valid instance name: %s", - valid_names[i]); - sc_instance_name_validate(valid_names[i], &err); - g_assert_null(err); - } - const char *invalid_names[] = { - // too short - "a", - // only letters and digits in the instance key - "a_--23))", "a_ ", "a_091234#", "a_123_456", - // up to 10 characters for the instance key - "a_01234567891", "a_0123456789123", - // snap name must not be more than 40 characters, regardless of instance - // key - "01234567890123456789012345678901234567890_foobar", - "01234567890123456789-01234567890123456789_foobar", - // instance key must be plain ASCII - "foobar_日本語", - // way too many underscores - "foobar_baz_zed_daz", - "foobar______", - }; - for (size_t i = 0; i < sizeof invalid_names / sizeof *invalid_names; - ++i) { - g_test_message("checking invalid instance name: >%s<", - invalid_names[i]); - sc_instance_name_validate(invalid_names[i], &err); - g_assert_nonnull(err); - sc_error_free(err); - } -} - static void test_sc_snap_drop_instance_key_no_dest(void) { if (g_test_subprocess()) { @@ -533,21 +392,10 @@ static void __attribute__ ((constructor)) init(void) { g_test_add_func("/snap/verify_security_tag", test_verify_security_tag); - g_test_add_func("/snap/sc_is_hook_security_tag", - test_sc_is_hook_security_tag); - - g_test_add_data_func("/snap/sc_snap_name_validate", - sc_snap_name_validate, - test_sc_snap_or_instance_name_validate); + g_test_add_func("/snap/sc_snap_name_validate", + test_sc_snap_name_validate); g_test_add_func("/snap/sc_snap_name_validate/respects_error_protocol", test_sc_snap_name_validate__respects_error_protocol); - - g_test_add_data_func("/snap/sc_instance_name_validate/just_name", - sc_instance_name_validate, - test_sc_snap_or_instance_name_validate); - g_test_add_func("/snap/sc_instance_name_validate/full", - test_sc_instance_name_validate); - g_test_add_func("/snap/sc_snap_drop_instance_key/basic", test_sc_snap_drop_instance_key_basic); g_test_add_func("/snap/sc_snap_drop_instance_key/no_dest", @@ -558,7 +406,6 @@ test_sc_snap_drop_instance_key_short_dest); g_test_add_func("/snap/sc_snap_drop_instance_key/short_dest2", test_sc_snap_drop_instance_key_short_dest2); - g_test_add_func("/snap/sc_snap_split_instance_name/basic", test_sc_snap_split_instance_name_basic); g_test_add_func("/snap/sc_snap_split_instance_name/trailing_nil", diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/string-utils.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/string-utils.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/string-utils.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/string-utils.c 2018-07-19 10:05:50.000000000 +0000 @@ -56,22 +56,6 @@ return strncmp(str - xlen + slen, suffix, xlen) == 0; } -char *sc_strdup(const char *str) -{ - size_t len; - char *copy; - if (str == NULL) { - die("cannot duplicate NULL string"); - } - len = strlen(str); - copy = malloc(len + 1); - if (copy == NULL) { - die("cannot allocate string copy (len: %zd)", len); - } - memcpy(copy, str, len + 1); - return copy; -} - int sc_must_snprintf(char *str, size_t size, const char *format, ...) { int n; diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/string-utils.h snapd-2.34.2+18.04/cmd/libsnap-confine-private/string-utils.h --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/string-utils.h 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/string-utils.h 2018-07-19 10:05:50.000000000 +0000 @@ -32,11 +32,6 @@ bool sc_endswith(const char *str, const char *suffix); /** - * Allocate and return a copy of a string. -**/ -char *sc_strdup(const char *str); - -/** * Safer version of snprintf. * * This version dies on any error condition. diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/string-utils-test.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/string-utils-test.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/string-utils-test.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/string-utils-test.c 2018-07-19 10:05:50.000000000 +0000 @@ -780,14 +780,6 @@ #undef DQ } -static void test_sc_strdup(void) -{ - char *s = sc_strdup("snap install everything"); - g_assert_nonnull(s); - g_assert_cmpstr(s, ==, "snap install everything"); - free(s); -} - static void __attribute__ ((constructor)) init(void) { g_test_add_func("/string-utils/sc_streq", test_sc_streq); @@ -844,5 +836,4 @@ ("/string-utils/sc_string_append_char_pair__uninitialized_buf", test_sc_string_append_char_pair__uninitialized_buf); g_test_add_func("/string-utils/sc_string_quote", test_sc_string_quote); - g_test_add_func("/string-utils/sc_strdup", test_sc_strdup); } diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/tool.c snapd-2.34.2+18.04/cmd/libsnap-confine-private/tool.c --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/tool.c 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/tool.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,230 +0,0 @@ -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "tool.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "../libsnap-confine-private/apparmor-support.h" -#include "../libsnap-confine-private/cleanup-funcs.h" -#include "../libsnap-confine-private/string-utils.h" -#include "../libsnap-confine-private/utils.h" - -/** - * sc_open_snapd_tool returns a file descriptor of the given internal executable. - * - * The executable is located based on the location of the currently executing process. - * The returning file descriptor can be used with fexecve function, like in sc_call_snapd_tool. -**/ -static int sc_open_snapd_tool(const char *tool_name); - -/** - * sc_call_snapd_tool calls a snapd tool by file descriptor. - * - * The idea with calling with an open file descriptor is to allow calling executables - * across mount namespaces, where the executable may not be visible in the new filesystem - * anymore. The caller establishes an open file descriptor in one namespace and later on - * performs the call in another mount namespace. - * - * The environment vector has special support for expanding the string "SNAPD_DEBUG=x". - * If such string is present, the "x" is replaced with either "0" or "1" depending on - * the result of is_sc_debug_enabled(). - **/ -static void sc_call_snapd_tool(int tool_fd, const char *tool_name, char **argv, - char **envp); - -/** - * sc_call_snapd_tool_with_apparmor calls a snapd tool by file descriptor, - * possibly confining the program with a specific apparmor profile. -**/ -static void sc_call_snapd_tool_with_apparmor(int tool_fd, const char *tool_name, - struct sc_apparmor *apparmor, - const char *aa_profile, - char **argv, char **envp); - -int sc_open_snap_update_ns(void) -{ - return sc_open_snapd_tool("snap-update-ns"); -} - -void sc_call_snap_update_ns(int snap_update_ns_fd, const char *snap_name, - struct sc_apparmor *apparmor) -{ - char *snap_name_copy SC_CLEANUP(sc_cleanup_string) = NULL; - snap_name_copy = sc_strdup(snap_name); - - char aa_profile[PATH_MAX] = { 0 }; - sc_must_snprintf(aa_profile, sizeof aa_profile, "snap-update-ns.%s", - snap_name); - - char *argv[] = { - "snap-update-ns", - /* This tells snap-update-ns we are calling from snap-confine and locking is in place */ - "--from-snap-confine", - snap_name_copy, NULL - }; - char *envp[] = { "SNAPD_DEBUG=x", NULL }; - sc_call_snapd_tool_with_apparmor(snap_update_ns_fd, - "snap-update-ns", apparmor, - aa_profile, argv, envp); -} - -void sc_call_snap_update_ns_as_user(int snap_update_ns_fd, - const char *snap_name, - struct sc_apparmor *apparmor) -{ - char *snap_name_copy SC_CLEANUP(sc_cleanup_string) = NULL; - snap_name_copy = sc_strdup(snap_name); - - char aa_profile[PATH_MAX] = { 0 }; - sc_must_snprintf(aa_profile, sizeof aa_profile, "snap-update-ns.%s", - snap_name); - - const char *xdg_runtime_dir = getenv("XDG_RUNTIME_DIR"); - char xdg_runtime_dir_env[PATH_MAX+strlen("XDG_RUNTIME_DIR=")]; - if (xdg_runtime_dir != NULL) { - sc_must_snprintf(xdg_runtime_dir_env, - sizeof(xdg_runtime_dir_env), - "XDG_RUNTIME_DIR=%s", xdg_runtime_dir); - } - - char *argv[] = { - "snap-update-ns", - /* This tells snap-update-ns we are calling from snap-confine and locking is in place */ - /* TODO: enable this in sync with snap-update-ns changes, "--from-snap-confine", */ - /* This tells snap-update-ns that we want to process the per-user profile */ - "--user-mounts", snap_name_copy, NULL - }; - char *envp[] = { - /* SNAPD_DEBUG=x is replaced by sc_call_snapd_tool_with_apparmor - * with either SNAPD_DEBUG=0 or SNAPD_DEBUG=1, see that function - * for details. */ - "SNAPD_DEBUG=x", - xdg_runtime_dir_env, NULL - }; - sc_call_snapd_tool_with_apparmor(snap_update_ns_fd, - "snap-update-ns", apparmor, - aa_profile, argv, envp); -} - -int sc_open_snap_discard_ns(void) -{ - return sc_open_snapd_tool("snap-discard-ns"); -} - -void sc_call_snap_discard_ns(int snap_discard_ns_fd, const char *snap_name) -{ - char *snap_name_copy SC_CLEANUP(sc_cleanup_string) = NULL; - snap_name_copy = sc_strdup(snap_name); - char *argv[] = - { "snap-discard-ns", "--from-snap-confine", snap_name_copy, NULL }; - /* SNAPD_DEBUG=x is replaced by sc_call_snapd_tool_with_apparmor with - * either SNAPD_DEBUG=0 or SNAPD_DEBUG=1, see that function for details. */ - char *envp[] = { "SNAPD_DEBUG=x", NULL }; - sc_call_snapd_tool(snap_discard_ns_fd, "snap-discard-ns", argv, envp); -} - -static int sc_open_snapd_tool(const char *tool_name) -{ - // +1 is for the case where the link is exactly PATH_MAX long but we also - // want to store the terminating '\0'. The readlink system call doesn't add - // terminating null, but our initialization of buf handles this for us. - char buf[PATH_MAX + 1] = { 0 }; - if (readlink("/proc/self/exe", buf, sizeof buf) < 0) { - die("cannot readlink /proc/self/exe"); - } - if (buf[0] != '/') { // this shouldn't happen, but make sure have absolute path - die("readlink /proc/self/exe returned relative path"); - } - char *dir_name = dirname(buf); - int dir_fd SC_CLEANUP(sc_cleanup_close) = 1; - dir_fd = open(dir_name, O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); - if (dir_fd < 0) { - die("cannot open path %s", dir_name); - } - int tool_fd = -1; - tool_fd = openat(dir_fd, tool_name, O_PATH | O_NOFOLLOW | O_CLOEXEC); - if (tool_fd < 0) { - die("cannot open path %s/%s", dir_name, tool_name); - } - debug("opened %s executable as file descriptor %d", tool_name, tool_fd); - return tool_fd; -} - -static void sc_call_snapd_tool(int tool_fd, const char *tool_name, char **argv, - char **envp) -{ - sc_call_snapd_tool_with_apparmor(tool_fd, tool_name, NULL, NULL, argv, - envp); -} - -static void sc_call_snapd_tool_with_apparmor(int tool_fd, const char *tool_name, - struct sc_apparmor *apparmor, - const char *aa_profile, - char **argv, char **envp) -{ - debug("calling snapd tool %s", tool_name); - pid_t child = fork(); - if (child < 0) { - die("cannot fork to run snapd tool %s", tool_name); - } - if (child == 0) { - /* If the caller provided template environment entry for SNAPD_DEBUG - * then expand it to the actual value. */ - for (char **env = envp; - /* Mama mia, that's a spicy meatball. */ - env != NULL && *env != NULL && **env != '\0'; env++) { - if (sc_streq(*env, "SNAPD_DEBUG=x")) { - /* NOTE: this is not released, on purpose. */ - char *entry = sc_strdup(*env); - entry[strlen("SNAPD_DEBUG=x") - 1] = - sc_is_debug_enabled()? '1' : '0'; - *env = entry; - } - } - /* Switch apparmor profile for the process after exec. */ - if (apparmor != NULL && aa_profile != NULL) { - sc_maybe_aa_change_onexec(apparmor, aa_profile); - } - fexecve(tool_fd, argv, envp); - die("cannot execute snapd tool %s", tool_name); - } else { - int status = 0; - debug("waiting for snapd tool %s to terminate", tool_name); - if (waitpid(child, &status, 0) < 0) { - die("cannot get snapd tool %s termination status via waitpid", tool_name); - } - if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { - die("%s failed with code %i", tool_name, - WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - die("%s killed by signal %i", tool_name, - WTERMSIG(status)); - } - debug("%s finished successfully", tool_name); - } -} diff -Nru snapd-2.37.1+18.04/cmd/libsnap-confine-private/tool.h snapd-2.34.2+18.04/cmd/libsnap-confine-private/tool.h --- snapd-2.37.1+18.04/cmd/libsnap-confine-private/tool.h 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/libsnap-confine-private/tool.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#ifndef SNAP_CONFINE_TOOL_H -#define SNAP_CONFINE_TOOL_H - -/* Forward declaration, for real see apparmor-support.h */ -struct sc_apparmor; - -/** - * sc_open_snap_update_ns returns a file descriptor for the snap-update-ns tool. -**/ -int sc_open_snap_update_ns(void); - -/** - * sc_call_snap_update_ns calls snap-update-ns from snap-confine - **/ -void sc_call_snap_update_ns(int snap_update_ns_fd, const char *snap_name, - struct sc_apparmor *apparmor); - -/** - * sc_call_snap_update_ns calls snap-update-ns --user-mounts from snap-confine - **/ -void sc_call_snap_update_ns_as_user(int snap_update_ns_fd, - const char *snap_name, - struct sc_apparmor *apparmor); - -/** - * sc_open_snap_update_ns returns a file descriptor for the snap-discard-ns tool. -**/ -int sc_open_snap_discard_ns(void); - -/** - * sc_call_snap_discard_ns calls the snap-discard-ns from snap confine. -**/ -void sc_call_snap_discard_ns(int snap_discard_ns_fd, const char *snap_name); - -#endif diff -Nru snapd-2.37.1+18.04/cmd/Makefile.am snapd-2.34.2+18.04/cmd/Makefile.am --- snapd-2.37.1+18.04/cmd/Makefile.am 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/Makefile.am 2018-07-19 10:05:50.000000000 +0000 @@ -15,15 +15,7 @@ CHECK_CFLAGS += -Werror endif -subdirs = \ - libsnap-confine-private \ - snap-confine \ - snap-discard-ns \ - snap-gdb-shim \ - snap-update-ns \ - snapd-env-generator \ - snapd-generator \ - system-shutdown +subdirs = snap-confine snap-discard-ns system-shutdown libsnap-confine-private snap-gdb-shim snapd-generator # Run check-syntax when checking # TODO: conver those to autotools-style tests later @@ -47,31 +39,26 @@ if WITH_UNIT_TESTS check-unit-tests: snap-confine/unit-tests system-shutdown/unit-tests libsnap-confine-private/unit-tests $(HAVE_VALGRIND) ./libsnap-confine-private/unit-tests - SNAP_DEVICE_HELPER=$(srcdir)/snap-confine/snap-device-helper $(HAVE_VALGRIND) ./snap-confine/unit-tests + $(HAVE_VALGRIND) ./snap-confine/unit-tests $(HAVE_VALGRIND) ./system-shutdown/unit-tests else check-unit-tests: echo "unit tests are disabled (rebuild with --enable-unit-tests)" endif -new_format = snap-discard-ns/snap-discard-ns.c .PHONY: fmt -fmt:: $(filter $(addprefix %,$(new_format)),$(foreach dir,$(subdirs),$(wildcard $(srcdir)/$(dir)/*.[ch]))) - clang-format -style='{BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 120}' -i $^ - -fmt:: $(filter-out $(addprefix %,$(new_format)),$(foreach dir,$(subdirs),$(wildcard $(srcdir)/$(dir)/*.[ch]))) +fmt: $(foreach dir,$(subdirs),$(wildcard $(srcdir)/$(dir)/*.[ch])) HOME=$(srcdir) indent $^ # The hack target helps devlopers work on snap-confine on their live system by # installing a fresh copy of snap confine and the appropriate apparmor profile. .PHONY: hack -hack: snap-confine/snap-confine-debug snap-confine/snap-confine.apparmor snap-update-ns/snap-update-ns snap-seccomp/snap-seccomp snap-discard-ns/snap-discard-ns - sudo install -D -m 6755 snap-confine/snap-confine-debug $(DESTDIR)$(libexecdir)/snap-confine +hack: snap-confine/snap-confine snap-confine/snap-confine.apparmor snap-update-ns/snap-update-ns snap-seccomp/snap-seccomp + sudo install -D -m 6755 snap-confine/snap-confine $(DESTDIR)$(libexecdir)/snap-confine sudo install -m 644 snap-confine/snap-confine.apparmor $(DESTDIR)/etc/apparmor.d/$(patsubst .%,%,$(subst /,.,$(libexecdir))).snap-confine.real sudo install -d -m 755 $(DESTDIR)/var/lib/snapd/apparmor/snap-confine/ sudo apparmor_parser -r snap-confine/snap-confine.apparmor sudo install -m 755 snap-update-ns/snap-update-ns $(DESTDIR)$(libexecdir)/snap-update-ns - sudo install -m 755 snap-discard-ns/snap-discard-ns $(DESTDIR)$(libexecdir)/snap-discard-ns sudo install -m 755 snap-seccomp/snap-seccomp $(DESTDIR)$(libexecdir)/snap-seccomp # for the hack target also: @@ -87,8 +74,6 @@ noinst_LIBRARIES += libsnap-confine-private.a libsnap_confine_private_a_SOURCES = \ - libsnap-confine-private/apparmor-support.c \ - libsnap-confine-private/apparmor-support.h \ libsnap-confine-private/cgroup-freezer-support.c \ libsnap-confine-private/cgroup-freezer-support.h \ libsnap-confine-private/classic.c \ @@ -99,8 +84,6 @@ libsnap-confine-private/error.h \ libsnap-confine-private/fault-injection.c \ libsnap-confine-private/fault-injection.h \ - libsnap-confine-private/feature.c \ - libsnap-confine-private/feature.h \ libsnap-confine-private/locking.c \ libsnap-confine-private/locking.h \ libsnap-confine-private/mount-opt.c \ @@ -115,8 +98,6 @@ libsnap-confine-private/snap.h \ libsnap-confine-private/string-utils.c \ libsnap-confine-private/string-utils.h \ - libsnap-confine-private/tool.c \ - libsnap-confine-private/tool.h \ libsnap-confine-private/utils.c \ libsnap-confine-private/utils.h libsnap_confine_private_a_CFLAGS = $(CHECK_CFLAGS) @@ -132,7 +113,6 @@ libsnap-confine-private/cleanup-funcs-test.c \ libsnap-confine-private/error-test.c \ libsnap-confine-private/fault-injection-test.c \ - libsnap-confine-private/feature-test.c \ libsnap-confine-private/locking-test.c \ libsnap-confine-private/mount-opt-test.c \ libsnap-confine-private/mountinfo-test.c \ @@ -140,8 +120,8 @@ libsnap-confine-private/secure-getenv-test.c \ libsnap-confine-private/snap-test.c \ libsnap-confine-private/string-utils-test.c \ - libsnap-confine-private/test-utils-test.c \ libsnap-confine-private/test-utils.c \ + libsnap-confine-private/test-utils-test.c \ libsnap-confine-private/unit-tests-main.c \ libsnap-confine-private/unit-tests.c \ libsnap-confine-private/unit-tests.h \ @@ -198,13 +178,15 @@ libexec_PROGRAMS += snap-confine/snap-confine if HAVE_RST2MAN -dist_man_MANS += snap-confine/snap-confine.8 -CLEANFILES += snap-confine/snap-confine.8 +dist_man_MANS += snap-confine/snap-confine.1 +CLEANFILES += snap-confine/snap-confine.1 endif EXTRA_DIST += snap-confine/snap-confine.rst EXTRA_DIST += snap-confine/snap-confine.apparmor.in snap_confine_snap_confine_SOURCES = \ + snap-confine/apparmor-support.c \ + snap-confine/apparmor-support.h \ snap-confine/cookie-support.c \ snap-confine/cookie-support.h \ snap-confine/mount-support-nvidia.c \ @@ -213,6 +195,8 @@ snap-confine/mount-support.h \ snap-confine/ns-support.c \ snap-confine/ns-support.h \ + snap-confine/quirks.c \ + snap-confine/quirks.h \ snap-confine/snap-confine-args.c \ snap-confine/snap-confine-args.h \ snap-confine/snap-confine.c \ @@ -258,7 +242,7 @@ snap-confine/seccomp-support.h snap_confine_snap_confine_CFLAGS += $(SECCOMP_CFLAGS) if STATIC_LIBSECCOMP -snap_confine_snap_confine_STATIC += $(shell $(PKG_CONFIG) --static --libs libseccomp) +snap_confine_snap_confine_STATIC += $(shell pkg-config --static --libs libseccomp) else snap_confine_snap_confine_extra_libs += $(SECCOMP_LIBS) endif # STATIC_LIBSECCOMP @@ -267,7 +251,7 @@ if APPARMOR snap_confine_snap_confine_CFLAGS += $(APPARMOR_CFLAGS) if STATIC_LIBAPPARMOR -snap_confine_snap_confine_STATIC += $(shell $(PKG_CONFIG) --static --libs libapparmor) +snap_confine_snap_confine_STATIC += $(shell pkg-config --static --libs libapparmor) else snap_confine_snap_confine_extra_libs += $(APPARMOR_LIBS) endif # STATIC_LIBAPPARMOR @@ -297,11 +281,14 @@ libsnap-confine-private/unit-tests-main.c \ libsnap-confine-private/unit-tests.c \ libsnap-confine-private/unit-tests.h \ + snap-confine/apparmor-support.c \ + snap-confine/apparmor-support.h \ snap-confine/cookie-support-test.c \ snap-confine/mount-support-test.c \ snap-confine/ns-support-test.c \ - snap-confine/snap-confine-args-test.c \ - snap-confine/snap-device-helper-test.c + snap-confine/quirks.c \ + snap-confine/quirks.h \ + snap-confine/snap-confine-args-test.c snap_confine_unit_tests_CFLAGS = $(snap_confine_snap_confine_CFLAGS) $(GLIB_CFLAGS) snap_confine_unit_tests_LDADD = $(snap_confine_snap_confine_LDADD) $(GLIB_LIBS) snap_confine_unit_tests_LDFLAGS = $(snap_confine_snap_confine_LDFLAGS) @@ -316,7 +303,8 @@ endif # WITH_UNIT_TESTS if HAVE_RST2MAN -%.8: %.rst +snap-confine/%.1: snap-confine/%.rst + mkdir -p snap-confine $(HAVE_RST2MAN) $^ > $@ endif @@ -352,12 +340,7 @@ ## snap-mgmt ## -libexec_SCRIPTS = snap-mgmt/snap-mgmt -CLEANFILES += snap-mgmt/$(am__dirstamp) snap-mgmt/snap-mgmt - -snap-mgmt/$(am__dirstamp): - mkdir -p $$(dirname $@) - touch $@ +libexec_PROGRAMS += snap-mgmt/snap-mgmt snap-mgmt/snap-mgmt: snap-mgmt/snap-mgmt.sh.in Makefile snap-mgmt/$(am__dirstamp) sed -e 's,[@]SNAP_MOUNT_DIR[@],$(SNAP_MOUNT_DIR),' <$< >$@ @@ -392,18 +375,37 @@ libexec_PROGRAMS += snap-discard-ns/snap-discard-ns if HAVE_RST2MAN -dist_man_MANS += snap-discard-ns/snap-discard-ns.8 -CLEANFILES += snap-discard-ns/snap-discard-ns.8 +dist_man_MANS += snap-discard-ns/snap-discard-ns.5 +CLEANFILES += snap-discard-ns/snap-discard-ns.5 endif EXTRA_DIST += snap-discard-ns/snap-discard-ns.rst snap_discard_ns_snap_discard_ns_SOURCES = \ + snap-confine/ns-support.c \ + snap-confine/ns-support.h \ + snap-confine/apparmor-support.c \ + snap-confine/apparmor-support.h \ snap-discard-ns/snap-discard-ns.c snap_discard_ns_snap_discard_ns_CFLAGS = $(CHECK_CFLAGS) $(AM_CFLAGS) snap_discard_ns_snap_discard_ns_LDFLAGS = $(AM_LDFLAGS) snap_discard_ns_snap_discard_ns_LDADD = libsnap-confine-private.a snap_discard_ns_snap_discard_ns_STATIC = +if APPARMOR +snap_discard_ns_snap_discard_ns_CFLAGS += $(APPARMOR_CFLAGS) +if STATIC_LIBAPPARMOR +snap_discard_ns_snap_discard_ns_STATIC += $(shell pkg-config --static --libs libapparmor) +else +snap_discard_ns_snap_discard_ns_LDADD += $(APPARMOR_LIBS) +endif # STATIC_LIBAPPARMOR +endif # APPARMOR + +if STATIC_LIBCAP +snap_discard_ns_snap_discard_ns_STATIC += -lcap +else +snap_discard_ns_snap_discard_ns_LDADD += -lcap +endif # STATIC_LIBCAP + # Use a hacked rule if we're doing static build. This allows us to inject the LIBS += .. rule below. snap-discard-ns/snap-discard-ns$(EXEEXT): $(snap_discard_ns_snap_discard_ns_OBJECTS) $(snap_discard_ns_snap_discard_ns_DEPENDENCIES) $(EXTRA_snap_discard_ns_snap_discard_ns_DEPENDENCIES) snap-discard-ns/$(am__dirstamp) @rm -f snap-discard-ns/snap-discard-ns$(EXEEXT) @@ -411,6 +413,12 @@ snap-discard-ns/snap-discard-ns$(EXEEXT): LIBS += -Wl,-Bstatic $(snap_discard_ns_snap_discard_ns_STATIC) -Wl,-Bdynamic -pthread +if HAVE_RST2MAN +snap-discard-ns/%.5: snap-discard-ns/%.rst + mkdir -p snap-discard-ns + $(HAVE_RST2MAN) $^ > $@ +endif + ## ## system-shutdown ## @@ -451,29 +459,12 @@ ## snapd-generator ## -systemdsystemgeneratordir = $(SYSTEMD_SYSTEM_GENERATOR_DIR) -systemdsystemgenerator_PROGRAMS = snapd-generator/snapd-generator +libexec_PROGRAMS += snapd-generator/snapd-generator snapd_generator_snapd_generator_SOURCES = snapd-generator/main.c snapd_generator_snapd_generator_LDADD = libsnap-confine-private.a ## -## snapd-env-generator -## - -systemdsystemenvgeneratordir=$(SYSTEMD_SYSTEM_ENV_GENERATOR_DIR) -systemdsystemenvgenerator_PROGRAMS = snapd-env-generator/snapd-env-generator - -snapd_env_generator_snapd_env_generator_SOURCES = snapd-env-generator/main.c -snapd_env_generator_snapd_env_generator_LDADD = libsnap-confine-private.a -EXTRA_DIST += snapd-env-generator/snapd-env-generator.rst - -if HAVE_RST2MAN -dist_man_MANS += snapd-env-generator/snapd-env-generator.8 -CLEANFILES += snapd-env-generator/snapd-env-generator.8 -endif - -## ## snapd-apparmor ## diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_abort.go snapd-2.34.2+18.04/cmd/snap/cmd_abort.go --- snapd-2.37.1+18.04/cmd/snap/cmd_abort.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_abort.go 2018-07-19 10:05:50.000000000 +0000 @@ -50,13 +50,11 @@ return ErrExtraArgs } - id, err := x.GetChangeID() + cli := Client() + id, err := x.GetChangeID(cli) if err != nil { - if err == noChangeFoundOK { - return nil - } return err } - _, err = x.client.Abort(id) + _, err = cli.Abort(id) return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_abort_test.go snapd-2.34.2+18.04/cmd/snap/cmd_abort_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_abort_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_abort_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,89 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main_test - -import ( - "fmt" - "net/http" - - "gopkg.in/check.v1" - - snap "github.com/snapcore/snapd/cmd/snap" -) - -func (s *SnapSuite) TestAbortLast(c *check.C) { - n := 0 - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - n++ - switch n { - case 1: - c.Check(r.Method, check.Equals, "GET") - c.Check(r.URL.Path, check.Equals, "/v2/changes") - fmt.Fprintln(w, mockChangesJSON) - case 2: - c.Check(r.Method, check.Equals, "POST") - c.Check(r.URL.Path, check.Equals, "/v2/changes/two") - c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{"action": "abort"}) - fmt.Fprintln(w, mockChangeJSON) - default: - c.Errorf("expected 2 queries, currently on %d", n) - } - }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"abort", "--last=install"}) - c.Assert(err, check.IsNil) - c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Equals, "") - c.Check(s.Stderr(), check.Equals, "") - - c.Assert(n, check.Equals, 2) -} - -func (s *SnapSuite) TestAbortLastQuestionmark(c *check.C) { - n := 0 - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - n++ - c.Check(r.Method, check.Equals, "GET") - c.Assert(r.URL.Path, check.Equals, "/v2/changes") - switch n { - case 1, 2: - fmt.Fprintln(w, `{"type": "sync", "result": []}`) - case 3, 4: - fmt.Fprintln(w, mockChangesJSON) - default: - c.Errorf("expected 4 calls, now on %d", n) - } - }) - for i := 0; i < 2; i++ { - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"abort", "--last=foobar?"}) - c.Assert(err, check.IsNil) - c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, "") - c.Check(s.Stderr(), check.Equals, "") - - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"abort", "--last=foobar"}) - if i == 0 { - c.Assert(err, check.ErrorMatches, `no changes found`) - } else { - c.Assert(err, check.ErrorMatches, `no changes of type "foobar" found`) - } - } - - c.Check(n, check.Equals, 4) -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_ack.go snapd-2.34.2+18.04/cmd/snap/cmd_ack.go --- snapd-2.37.1+18.04/cmd/snap/cmd_ack.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_ack.go 2018-07-19 10:05:50.000000000 +0000 @@ -23,14 +23,12 @@ "fmt" "io/ioutil" - "github.com/jessevdk/go-flags" - - "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/i18n" + + "github.com/jessevdk/go-flags" ) type cmdAck struct { - clientMixin AckOptions struct { AssertionFile flags.Filename } `positional-args:"true" required:"true"` @@ -52,27 +50,27 @@ addCommand("ack", shortAckHelp, longAckHelp, func() flags.Commander { return &cmdAck{} }, nil, []argDesc{{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Assertion file"), }}) } -func ackFile(cli *client.Client, assertFile string) error { +func ackFile(assertFile string) error { assertData, err := ioutil.ReadFile(assertFile) if err != nil { return err } - return cli.Ack(assertData) + return Client().Ack(assertData) } func (x *cmdAck) Execute(args []string) error { if len(args) > 0 { return ErrExtraArgs } - if err := ackFile(x.client, string(x.AckOptions.AssertionFile)); err != nil { + if err := ackFile(string(x.AckOptions.AssertionFile)); err != nil { return fmt.Errorf("cannot assert: %v", err) } return nil diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_advise.go snapd-2.34.2+18.04/cmd/snap/cmd_advise.go --- snapd-2.37.1+18.04/cmd/snap/cmd_advise.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_advise.go 2018-07-19 10:05:50.000000000 +0000 @@ -20,33 +20,22 @@ package main import ( - "bufio" "encoding/json" "fmt" - "io" - "net" - "os" - "strconv" "github.com/jessevdk/go-flags" "github.com/snapcore/snapd/advisor" "github.com/snapcore/snapd/i18n" - "github.com/snapcore/snapd/osutil" ) type cmdAdviseSnap struct { Positionals struct { - CommandOrPkg string + CommandOrPkg string `required:"yes"` } `positional-args:"true"` - Format string `long:"format" default:"pretty" choice:"pretty" choice:"json"` - // Command makes advise try to find snaps that provide this command - Command bool `long:"command"` - - // FromApt tells advise that it got started from an apt hook - // and needs to communicate over a socket - FromApt bool `long:"from-apt"` + Format string `long:"format" default:"pretty" choice:"pretty" choice:"json"` + Command bool `long:"command"` } var shortAdviseSnapHelp = i18n.G("Advise on available snaps") @@ -61,29 +50,23 @@ cmd := addCommand("advise-snap", shortAdviseSnapHelp, longAdviseSnapHelp, func() flags.Commander { return &cmdAdviseSnap{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. "command": i18n.G("Advise on snaps that provide the given command"), - // TRANSLATORS: This should not start with a lowercase letter. - "from-apt": i18n.G("Advise will talk to apt via an apt hook"), - // TRANSLATORS: This should not start with a lowercase letter. - "format": i18n.G("Use the given output format"), + "format": i18n.G("Use the given output format"), }, []argDesc{ - // TRANSLATORS: This needs to begin with < and end with > - {name: i18n.G("")}, + {name: ""}, }) cmd.hidden = true } func outputAdviseExactText(command string, result []advisor.Command) error { fmt.Fprintf(Stdout, "\n") - // TRANSLATORS: %q is a command name (like "gimp" or "loimpress") fmt.Fprintf(Stdout, i18n.G("Command %q not found, but can be installed with:\n"), command) fmt.Fprintf(Stdout, "\n") for _, snap := range result { fmt.Fprintf(Stdout, "sudo snap install %s\n", snap.Snap) } fmt.Fprintf(Stdout, "\n") - fmt.Fprintln(Stdout, i18n.G("See 'snap info ' for additional versions.")) + fmt.Fprintf(Stdout, "See 'snap info ' for additional versions.\n") fmt.Fprintf(Stdout, "\n") return nil } @@ -93,10 +76,10 @@ fmt.Fprintf(Stdout, i18n.G("Command %q not found, did you mean:\n"), command) fmt.Fprintf(Stdout, "\n") for _, snap := range result { - fmt.Fprintf(Stdout, i18n.G(" command %q from snap %q\n"), snap.Command, snap.Snap) + fmt.Fprintf(Stdout, " command %q from snap %q\n", snap.Command, snap.Snap) } fmt.Fprintf(Stdout, "\n") - fmt.Fprintln(Stdout, i18n.G("See 'snap info ' for additional versions.")) + fmt.Fprintf(Stdout, "See 'snap info ' for additional versions.\n") fmt.Fprintf(Stdout, "\n") return nil } @@ -107,126 +90,11 @@ return nil } -type jsonRPC struct { - JsonRPC string `json:"jsonrpc"` - Method string `json:"method"` - Params struct { - Command string `json:"command"` - SearchTerms []string `json:"search-terms"` - UnknownPackages []string `json:"unknown-packages"` - } -} - -// readRpc reads a apt json rpc protocol 0.1 message as described in -// https://salsa.debian.org/apt-team/apt/blob/master/doc/json-hooks-protocol.md#wire-protocol -func readRpc(r *bufio.Reader) (*jsonRPC, error) { - line, err := r.ReadBytes('\n') - if err != nil && err != io.EOF { - return nil, fmt.Errorf("cannot read json-rpc: %v", err) - } - if osutil.GetenvBool("SNAP_APT_HOOK_DEBUG") { - fmt.Fprintf(os.Stderr, "%s\n", line) - } - - var rpc jsonRPC - if err := json.Unmarshal(line, &rpc); err != nil { - return nil, err - } - // empty \n - emptyNL, _, err := r.ReadLine() - if err != nil { - return nil, err - } - if string(emptyNL) != "" { - return nil, fmt.Errorf("unexpected line: %q (empty)", emptyNL) - } - - return &rpc, nil -} - -func adviseViaAptHook() error { - sockFd := os.Getenv("APT_HOOK_SOCKET") - if sockFd == "" { - return fmt.Errorf("cannot find APT_HOOK_SOCKET env") - } - fd, err := strconv.Atoi(sockFd) - if err != nil { - return fmt.Errorf("expected APT_HOOK_SOCKET to be a decimal integer, found %q", sockFd) - } - - f := os.NewFile(uintptr(fd), "apt-hook-socket") - if f == nil { - return fmt.Errorf("cannot open file descriptor %v", fd) - } - defer f.Close() - - conn, err := net.FileConn(f) - if err != nil { - return fmt.Errorf("cannot connect to %v: %v", fd, err) - } - defer conn.Close() - - r := bufio.NewReader(conn) - - // handshake - rpc, err := readRpc(r) - if err != nil { - return err - } - if rpc.Method != "org.debian.apt.hooks.hello" { - return fmt.Errorf("expected 'hello' method, got: %v", rpc.Method) - } - if _, err := conn.Write([]byte(`{"jsonrpc":"2.0","id":0,"result":{"version":"0.1"}}` + "\n\n")); err != nil { - return err - } - - // payload - rpc, err = readRpc(r) - if err != nil { - return err - } - if rpc.Method == "org.debian.apt.hooks.install.fail" { - for _, pkgName := range rpc.Params.UnknownPackages { - match, err := advisor.FindPackage(pkgName) - if err == nil && match != nil { - fmt.Fprintf(Stdout, "\n") - fmt.Fprintf(Stdout, i18n.G("No apt package %q, but there is a snap with that name.\n"), pkgName) - fmt.Fprintf(Stdout, i18n.G("Try \"snap install %s\"\n"), pkgName) - fmt.Fprintf(Stdout, "\n") - } - } - - } - // if rpc.Method == "org.debian.apt.hooks.search.post" { - // // FIXME: do a snap search here - // // FIXME2: figure out why apt does not tell us the search results - // } - - // bye - rpc, err = readRpc(r) - if err != nil { - return err - } - if rpc.Method != "org.debian.apt.hooks.bye" { - return fmt.Errorf("expected 'bye' method, got: %v", rpc.Method) - } - - return nil -} - func (x *cmdAdviseSnap) Execute(args []string) error { if len(args) > 0 { return ErrExtraArgs } - if x.FromApt { - return adviseViaAptHook() - } - - if len(x.Positionals.CommandOrPkg) == 0 { - return fmt.Errorf("the required argument `` was not provided") - } - if x.Command { return adviseCommand(x.Positionals.CommandOrPkg, x.Format) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_advise_test.go snapd-2.34.2+18.04/cmd/snap/cmd_advise_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_advise_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_advise_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -20,14 +20,7 @@ package main_test import ( - "bufio" - "bytes" "fmt" - "net" - "os" - "strconv" - "strings" - "syscall" . "gopkg.in/check.v1" @@ -72,7 +65,7 @@ restore := advisor.ReplaceCommandsFinder(mkSillyFinder) defer restore() - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"advise-snap", "--command", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"advise-snap", "--command", "hello"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Assert(s.Stdout(), Equals, ` @@ -91,7 +84,7 @@ restore := advisor.ReplaceCommandsFinder(mkSillyFinder) defer restore() - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"advise-snap", "--command", "--format=json", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"advise-snap", "--command", "--format=json", "hello"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Assert(s.Stdout(), Equals, `[{"Snap":"hello","Command":"hello"},{"Snap":"hello-wcm","Command":"hello"}]`+"\n") @@ -120,124 +113,3 @@ s.stderr.Reset() } } - -func (s *SnapSuite) TestAdviseFromAptIntegrationNoAptPackage(c *C) { - restore := advisor.ReplaceCommandsFinder(mkSillyFinder) - defer restore() - - fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) - c.Assert(err, IsNil) - - os.Setenv("APT_HOOK_SOCKET", strconv.Itoa(int(fds[1]))) - // note we don't close fds[1] ourselves; adviseViaAptHook might, or we might leak it - // (we don't close it here to avoid accidentally closing an arbitrary file descriptor that reused the number) - - done := make(chan bool, 1) - go func() { - f := os.NewFile(uintptr(fds[0]), "advise-sock") - conn, err := net.FileConn(f) - c.Assert(err, IsNil) - defer conn.Close() - defer f.Close() - - // handshake - _, err = conn.Write([]byte(`{"jsonrpc":"2.0","method":"org.debian.apt.hooks.hello","id":0,"params":{"versions":["0.1"]}}` + "\n\n")) - c.Assert(err, IsNil) - - // reply from snap - r := bufio.NewReader(conn) - buf, _, err := r.ReadLine() - c.Assert(err, IsNil) - c.Assert(string(buf), Equals, `{"jsonrpc":"2.0","id":0,"result":{"version":"0.1"}}`) - // plus empty line - buf, _, err = r.ReadLine() - c.Assert(err, IsNil) - c.Assert(string(buf), Equals, ``) - - // payload - _, err = conn.Write([]byte(`{"jsonrpc":"2.0","method":"org.debian.apt.hooks.install.fail","params":{"command":"install","search-terms":["aws-cli"],"unknown-packages":["hello"],"packages":[]}}` + "\n\n")) - c.Assert(err, IsNil) - - // bye - _, err = conn.Write([]byte(`{"jsonrpc":"2.0","method":"org.debian.apt.hooks.bye","params":{}}` + "\n\n")) - c.Assert(err, IsNil) - - done <- true - }() - - cmd := snap.CmdAdviseSnap() - cmd.FromApt = true - err = cmd.Execute(nil) - c.Assert(err, IsNil) - c.Assert(s.Stdout(), Equals, ` -No apt package "hello", but there is a snap with that name. -Try "snap install hello" - -`) - c.Assert(s.Stderr(), Equals, "") - c.Assert(<-done, Equals, true) -} - -func (s *SnapSuite) TestReadRpc(c *C) { - rpc := strings.Replace(` -{ - "jsonrpc": "2.0", - "method": "org.debian.apt.hooks.install.pre-prompt", - "params": { - "command": "install", - "packages": [ - { - "architecture": "amd64", - "automatic": false, - "id": 38033, - "mode": "install", - "name": "hello", - "versions": { - "candidate": { - "architecture": "amd64", - "id": 22712, - "pin": 500, - "version": "4:17.12.3-1ubuntu1" - }, - "install": { - "architecture": "amd64", - "id": 22712, - "pin": 500, - "version": "4:17.12.3-1ubuntu1" - } - } - }, - { - "architecture": "amd64", - "automatic": true, - "id": 38202, - "mode": "install", - "name": "hello-kpart", - "versions": { - "candidate": { - "architecture": "amd64", - "id": 22713, - "pin": 500, - "version": "4:17.12.3-1ubuntu1" - }, - "install": { - "architecture": "amd64", - "id": 22713, - "pin": 500, - "version": "4:17.12.3-1ubuntu1" - } - } - } - ], - "search-terms": [ - "hello" - ], - "unknown-packages": [] - } -}`, "\n", "", -1) - // all apt rpc ends with \n\n - rpc = rpc + "\n\n" - // this can be parsed without errors - _, err := snap.ReadRpc(bufio.NewReader(bytes.NewBufferString(rpc))) - c.Assert(err, IsNil) -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_aliases.go snapd-2.34.2+18.04/cmd/snap/cmd_aliases.go --- snapd-2.37.1+18.04/cmd/snap/cmd_aliases.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_aliases.go 2018-07-19 10:05:50.000000000 +0000 @@ -31,7 +31,6 @@ ) type cmdAliases struct { - clientMixin Positionals struct { Snap installedSnapName `positional-arg-name:""` } `positional-args:"true"` @@ -90,7 +89,7 @@ return ErrExtraArgs } - allStatuses, err := x.client.Aliases() + allStatuses, err := Client().Aliases() if err != nil { return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_aliases_test.go snapd-2.34.2+18.04/cmd/snap/cmd_aliases_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_aliases_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_aliases_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -43,7 +43,9 @@ not defined in the current revision of the snap, possibly temporarily (e.g. because of a revert). This can cleared with 'snap alias --reset'. ` - s.testSubCommandHelp(c, "aliases", msg) + rest, err := Parser().ParseArgs([]string{"aliases", "--help"}) + c.Assert(err.Error(), Equals, msg) + c.Assert(rest, DeepEquals, []string{}) } func (s *SnapSuite) TestAliases(c *C) { @@ -68,7 +70,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"aliases"}) + rest, err := Parser().ParseArgs([]string{"aliases"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -103,7 +105,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"aliases", "foo"}) + rest, err := Parser().ParseArgs([]string{"aliases", "foo"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -126,7 +128,7 @@ "result": map[string]map[string]client.AliasStatus{}, }) }) - _, err := Parser(Client()).ParseArgs([]string{"aliases"}) + _, err := Parser().ParseArgs([]string{"aliases"}) c.Assert(err, IsNil) c.Assert(s.Stdout(), Equals, "") c.Assert(s.Stderr(), Equals, "No aliases are currently defined.\n\nUse 'snap help alias' to learn how to create aliases manually.\n") @@ -147,7 +149,7 @@ }}, }) }) - _, err := Parser(Client()).ParseArgs([]string{"aliases", "not-bar"}) + _, err := Parser().ParseArgs([]string{"aliases", "not-bar"}) c.Assert(err, IsNil) c.Assert(s.Stdout(), Equals, "") c.Assert(s.Stderr(), Equals, "No aliases are currently defined for snap \"not-bar\".\n\nUse 'snap help alias' to learn how to create aliases manually.\n") diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_alias.go snapd-2.34.2+18.04/cmd/snap/cmd_alias.go --- snapd-2.37.1+18.04/cmd/snap/cmd_alias.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_alias.go 2018-07-19 10:05:50.000000000 +0000 @@ -54,7 +54,7 @@ return &cmdAlias{} }, waitDescs, []argDesc{ {name: ""}, - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. {name: i18n.G("")}, }) } @@ -67,11 +67,12 @@ snapName, appName := snap.SplitSnapApp(string(x.Positionals.SnapApp)) alias := x.Positionals.Alias - id, err := x.client.Alias(snapName, appName, alias) + cli := Client() + id, err := cli.Alias(snapName, appName, alias) if err != nil { return err } - chg, err := x.wait(id) + chg, err := x.wait(cli, id) if err != nil { if err == noWait { return nil @@ -98,11 +99,9 @@ } w := tabwriter.NewWriter(Stdout, 2, 2, 1, ' ', 0) if len(added) != 0 { - // TRANSLATORS: this is used to introduce a list of aliases that were added printChangedAliases(w, i18n.G("Added"), added) } if len(removed) != 0 { - // TRANSLATORS: this is used to introduce a list of aliases that were removed printChangedAliases(w, i18n.G("Removed"), removed) } w.Flush() @@ -112,7 +111,6 @@ func printChangedAliases(w io.Writer, label string, changed []*changedAlias) { fmt.Fprintf(w, "%s:\n", label) for _, a := range changed { - // TRANSLATORS: the first %s is a snap command (e.g. "hello-world.echo"), the second is the alias - fmt.Fprintf(w, i18n.G("\t- %s as %s\n"), snap.JoinSnapApp(a.Snap, a.App), a.Alias) + fmt.Fprintf(w, "\t- %s as %s\n", snap.JoinSnapApp(a.Snap, a.App), a.Alias) } } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_alias_test.go snapd-2.34.2+18.04/cmd/snap/cmd_alias_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_alias_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_alias_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -38,10 +38,12 @@ invoked just using the alias. [alias command options] - --no-wait Do not wait for the operation to finish but just print - the change id. + --no-wait Do not wait for the operation to finish but just print + the change id. ` - s.testSubCommandHelp(c, "alias", msg) + rest, err := Parser().ParseArgs([]string{"alias", "--help"}) + c.Assert(err.Error(), Equals, msg) + c.Assert(rest, DeepEquals, []string{}) } func (s *SnapSuite) TestAlias(c *C) { @@ -63,7 +65,7 @@ c.Fatalf("unexpected path %q", r.URL.Path) } }) - rest, err := Parser(Client()).ParseArgs([]string{"alias", "alias-snap.cmd1", "alias1"}) + rest, err := Parser().ParseArgs([]string{"alias", "alias-snap.cmd1", "alias1"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Assert(s.Stdout(), Equals, ""+ diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_auto_import.go snapd-2.34.2+18.04/cmd/snap/cmd_auto_import.go --- snapd-2.37.1+18.04/cmd/snap/cmd_auto_import.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_auto_import.go 2018-07-19 10:05:50.000000000 +0000 @@ -127,7 +127,7 @@ return osutil.CopyFile(src, dst, osutil.CopyFlagOverwrite) } -func autoImportFromSpool(cli *client.Client) (added int, err error) { +func autoImportFromSpool() (added int, err error) { files, err := ioutil.ReadDir(dirs.SnapAssertsSpoolDir) if os.IsNotExist(err) { return 0, nil @@ -138,7 +138,7 @@ for _, fi := range files { cand := filepath.Join(dirs.SnapAssertsSpoolDir, fi.Name()) - if err := ackFile(cli, cand); err != nil { + if err := ackFile(cand); err != nil { logger.Noticef("error: cannot import %s: %s", cand, err) continue } else { @@ -154,7 +154,7 @@ return added, nil } -func autoImportFromAllMounts(cli *client.Client) (int, error) { +func autoImportFromAllMounts() (int, error) { cands, err := autoImportCandidates() if err != nil { return 0, err @@ -162,7 +162,7 @@ added := 0 for _, cand := range cands { - err := ackFile(cli, cand) + err := ackFile(cand) // the server is not ready yet if _, ok := err.(client.ConnectionError); ok { logger.Noticef("queuing for later %s", cand) @@ -214,7 +214,6 @@ } type cmdAutoImport struct { - clientMixin Mount []string `long:"mount" arg-name:""` ForceClassic bool `long:"force-classic"` @@ -242,19 +241,15 @@ func() flags.Commander { return &cmdAutoImport{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "mount": i18n.G("Temporarily mount device before inspecting"), - // TRANSLATORS: This should not start with a lowercase letter. + "mount": i18n.G("Temporarily mount device before inspecting"), "force-classic": i18n.G("Force import on classic systems"), }, nil) cmd.hidden = true } -func (x *cmdAutoImport) autoAddUsers() error { +func autoAddUsers() error { cmd := cmdCreateUser{ - clientMixin: x.clientMixin, - Known: true, - Sudoer: true, + Known: true, Sudoer: true, } return cmd.Execute(nil) } @@ -287,18 +282,18 @@ defer doUmount(mp) } - added1, err := autoImportFromSpool(x.client) + added1, err := autoImportFromSpool() if err != nil { return err } - added2, err := autoImportFromAllMounts(x.client) + added2, err := autoImportFromAllMounts() if err != nil { return err } if added1+added2 > 0 { - return x.autoAddUsers() + return autoAddUsers() } return nil diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_auto_import_test.go snapd-2.34.2+18.04/cmd/snap/cmd_auto_import_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_auto_import_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_auto_import_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -88,7 +88,7 @@ logbuf, restore := logger.MockLogger() defer restore() - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) + rest, err := snap.Parser().ParseArgs([]string{"auto-import"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Check(s.Stdout(), Equals, `created user "foo"`+"\n") @@ -120,7 +120,7 @@ restore = snap.MockMountInfoPath(makeMockMountInfo(c, content)) defer restore() - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) + rest, err := snap.Parser().ParseArgs([]string{"auto-import"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Check(s.Stdout(), Equals, "") @@ -175,7 +175,7 @@ restore = snap.MockMountInfoPath(makeMockMountInfo(c, content)) defer restore() - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) + rest, err := snap.Parser().ParseArgs([]string{"auto-import"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Check(s.Stdout(), Equals, "") @@ -204,7 +204,7 @@ restore = snap.MockMountInfoPath(makeMockMountInfo(c, content)) defer restore() - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) + rest, err := snap.Parser().ParseArgs([]string{"auto-import"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Check(s.Stdout(), Equals, "") @@ -261,7 +261,7 @@ logbuf, restore := logger.MockLogger() defer restore() - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) + rest, err := snap.Parser().ParseArgs([]string{"auto-import"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Check(s.Stdout(), Equals, `created user "foo"`+"\n") @@ -297,6 +297,6 @@ restore = snap.MockMountInfoPath(makeMockMountInfo(c, content)) defer restore() - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"}) + _, err = snap.Parser().ParseArgs([]string{"auto-import"}) c.Assert(err, ErrorMatches, "cannot queue .*, file size too big: 656384") } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_blame_generated.go snapd-2.34.2+18.04/cmd/snap/cmd_blame_generated.go --- snapd-2.37.1+18.04/cmd/snap/cmd_blame_generated.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_blame_generated.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -package main - -// generated by mkauthors.sh; do not edit - -func init() { - authors = []string{"Mark Shuttleworth", "Gustavo Niemeyer", "Sergio Schvezov", "Simon Fels", "Kyle Fazzari", "Leo Arias", "Sergio Cazzolato", "Gustavo Niemeyer", "Federico Gimenez", "Maciej Borzecki", "Jamie Strandboge", "Pawel Stolowski", "John R. Lenton", "Samuele Pedroni", "Zygmunt Krynicki", "Michael Vogt"} -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_blame.go snapd-2.34.2+18.04/cmd/snap/cmd_blame.go --- snapd-2.37.1+18.04/cmd/snap/cmd_blame.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_blame.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main - -//go:generate mkauthors.sh - -import ( - "fmt" - "math/rand" - - "github.com/jessevdk/go-flags" -) - -type cmdBlame struct{} - -var authors []string - -func init() { - cmd := addCommand("blame", - "", - "", - func() flags.Commander { - return &cmdBlame{} - }, nil, nil) - cmd.hidden = true -} - -func (x *cmdBlame) Execute(args []string) error { - if len(args) > 0 { - return ErrExtraArgs - } - if len(authors) == 0 { - return nil - } - - fmt.Fprintf(Stdout, "It's all %s's fault.\n", authors[rand.Intn(len(authors))]) - return nil -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_buy.go snapd-2.34.2+18.04/cmd/snap/cmd_buy.go --- snapd-2.37.1+18.04/cmd/snap/cmd_buy.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_buy.go 2018-07-19 10:05:50.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/store" "github.com/jessevdk/go-flags" ) @@ -35,21 +36,19 @@ `) type cmdBuy struct { - clientMixin Positional struct { SnapName remoteSnapName } `positional-args:"yes" required:"yes"` } func init() { - cmd := addCommand("buy", shortBuyHelp, longBuyHelp, func() flags.Commander { + addCommand("buy", shortBuyHelp, longBuyHelp, func() flags.Commander { return &cmdBuy{} }, map[string]string{}, []argDesc{{ name: "", - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Snap name"), }}) - cmd.hidden = true } func (x *cmdBuy) Execute(args []string) error { @@ -57,10 +56,12 @@ return ErrExtraArgs } - return buySnap(x.client, string(x.Positional.SnapName)) + return buySnap(string(x.Positional.SnapName)) } -func buySnap(cli *client.Client, snapName string) error { +func buySnap(snapName string) error { + cli := Client() + user := cli.LoggedInUser() if user == nil { return fmt.Errorf(i18n.G("You need to be logged in to purchase software. Please run 'snap login' and try again.")) @@ -75,7 +76,7 @@ return err } - opts := &client.BuyOptions{ + opts := &store.BuyOptions{ SnapID: snap.ID, Currency: resultInfo.SuggestedCurrency, } @@ -111,7 +112,7 @@ for %s. Press ctrl-c to cancel.`), snap.Name, snap.Publisher.Username, formatPrice(opts.Price, opts.Currency)) fmt.Fprint(Stdout, "\n") - err = requestLogin(cli, user.Email) + err = requestLogin(user.Email) if err != nil { return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_buy_test.go snapd-2.34.2+18.04/cmd/snap/cmd_buy_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_buy_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_buy_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -89,7 +89,7 @@ } func (s *BuySnapSuite) TestBuyHelp(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"buy"}) + _, err := snap.Parser().ParseArgs([]string{"buy"}) c.Assert(err, check.NotNil) c.Check(err.Error(), check.Equals, "the required argument `` was not provided") c.Check(s.Stdout(), check.Equals, "") @@ -97,13 +97,13 @@ } func (s *BuySnapSuite) TestBuyInvalidCharacters(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"buy", "a:b"}) + _, err := snap.Parser().ParseArgs([]string{"buy", "a:b"}) c.Assert(err, check.NotNil) c.Check(err.Error(), check.Equals, "cannot buy snap: invalid characters in name") c.Check(s.Stdout(), check.Equals, "") c.Check(s.Stderr(), check.Equals, "") - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"buy", "c*d"}) + _, err = snap.Parser().ParseArgs([]string{"buy", "c*d"}) c.Assert(err, check.NotNil) c.Check(err.Error(), check.Equals, "cannot buy snap: invalid characters in name") c.Check(s.Stdout(), check.Equals, "") @@ -161,7 +161,7 @@ defer mockServer.checkCounts() s.RedirectClientToTestServer(mockServer.serveHttp) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"buy", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"buy", "hello"}) c.Assert(err, check.NotNil) c.Check(err.Error(), check.Equals, "cannot buy snap: snap is free") c.Assert(rest, check.DeepEquals, []string{"hello"}) @@ -306,7 +306,7 @@ // Confirm the purchase. s.password = "the password" - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"buy", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"buy", "hello"}) c.Check(err, check.IsNil) c.Check(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, `Please re-enter your Ubuntu One password to purchase "hello" from "canonical" @@ -367,7 +367,7 @@ // Confirm the purchase. s.password = "the password" - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"buy", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"buy", "hello"}) c.Assert(err, check.NotNil) c.Check(err.Error(), check.Equals, `Sorry, your payment method has been declined by the issuer. Please review your payment details at https://my.ubuntu.com/payment/edit and try again.`) @@ -404,7 +404,7 @@ defer mockServer.checkCounts() s.RedirectClientToTestServer(mockServer.serveHttp) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"buy", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"buy", "hello"}) c.Assert(err, check.NotNil) c.Check(err.Error(), check.Equals, `You need to have a payment method associated with your account in order to buy a snap, please visit https://my.ubuntu.com/payment/edit to add one. @@ -439,7 +439,7 @@ defer mockServer.checkCounts() s.RedirectClientToTestServer(mockServer.serveHttp) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"buy", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"buy", "hello"}) c.Assert(err, check.NotNil) c.Check(err.Error(), check.Equals, `In order to buy "hello", you need to agree to the latest terms and conditions. Please visit https://my.ubuntu.com/payment/edit to do this. @@ -453,7 +453,7 @@ // We don't login here s.Logout(c) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"buy", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"buy", "hello"}) c.Check(err, check.NotNil) c.Check(err.Error(), check.Equals, "You need to be logged in to purchase software. Please run 'snap login' and try again.") c.Check(rest, check.DeepEquals, []string{"hello"}) diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_can_manage_refreshes.go snapd-2.34.2+18.04/cmd/snap/cmd_can_manage_refreshes.go --- snapd-2.37.1+18.04/cmd/snap/cmd_can_manage_refreshes.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_can_manage_refreshes.go 2018-07-19 10:05:50.000000000 +0000 @@ -25,9 +25,7 @@ "github.com/jessevdk/go-flags" ) -type cmdCanManageRefreshes struct { - clientMixin -} +type cmdCanManageRefreshes struct{} func init() { cmd := addDebugCommand("can-manage-refreshes", @@ -45,7 +43,7 @@ } var resp bool - if err := x.client.Debug("can-manage-refreshes", nil, &resp); err != nil { + if err := Client().Debug("can-manage-refreshes", nil, &resp); err != nil { return err } fmt.Fprintf(Stdout, "%v\n", resp) diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_changes.go snapd-2.34.2+18.04/cmd/snap/cmd_changes.go --- snapd-2.37.1+18.04/cmd/snap/cmd_changes.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_changes.go 2018-07-19 10:05:50.000000000 +0000 @@ -41,7 +41,6 @@ `) type cmdChanges struct { - clientMixin timeMixin Positional struct { Snap string `positional-arg-name:""` @@ -101,7 +100,8 @@ Selector: client.ChangesAll, } - changes, err := queryChanges(c.client, &opts) + cli := Client() + changes, err := queryChanges(cli, &opts) if err != nil { return err } @@ -131,15 +131,13 @@ } func (c *cmdTasks) Execute([]string) error { - chid, err := c.GetChangeID() + cli := Client() + chid, err := c.GetChangeID(cli) if err != nil { - if err == noChangeFoundOK { - return nil - } return err } - return c.showChange(chid) + return c.showChange(cli, chid) } func queryChange(cli *client.Client, chid string) (*client.Change, error) { @@ -153,8 +151,8 @@ return chg, nil } -func (c *cmdTasks) showChange(chid string) error { - chg, err := queryChange(c.client, chid) +func (c *cmdTasks) showChange(cli *client.Client, chid string) error { + chg, err := queryChange(cli, chid) if err != nil { return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_changes_test.go snapd-2.34.2+18.04/cmd/snap/cmd_changes_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_changes_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_changes_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -56,13 +56,13 @@ expectedChange := `(?ms)Status +Spawn +Ready +Summary Do +2016-04-21T01:02:03Z +2016-04-21T01:02:04Z +some summary ` - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"change", "--abs-time", "42"}) + rest, err := snap.Parser().ParseArgs([]string{"change", "--abs-time", "42"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, expectedChange) c.Check(s.Stderr(), check.Equals, "") - rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"tasks", "--abs-time", "42"}) + rest, err = snap.Parser().ParseArgs([]string{"tasks", "--abs-time", "42"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, expectedChange) @@ -83,7 +83,7 @@ n++ }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"change", "42"}) + _, err := snap.Parser().ParseArgs([]string{"change", "42"}) c.Assert(err, check.IsNil) c.Check(s.Stderr(), check.Equals, "WARNING: snapd is about to reboot the system\n") } @@ -144,56 +144,23 @@ expectedChange := `(?ms)Status +Spawn +Ready +Summary Do +2016-04-21T01:02:03Z +2016-04-21T01:02:04Z +some summary ` - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"tasks", "--abs-time", "--last=install"}) + rest, err := snap.Parser().ParseArgs([]string{"tasks", "--abs-time", "--last=install"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, expectedChange) c.Check(s.Stderr(), check.Equals, "") - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"tasks", "--abs-time", "--last=foobar"}) + _, err = snap.Parser().ParseArgs([]string{"tasks", "--abs-time", "--last=foobar"}) c.Assert(err, check.NotNil) c.Assert(err, check.ErrorMatches, `no changes of type "foobar" found`) } -func (s *SnapSuite) TestTasksLastQuestionmark(c *check.C) { - n := 0 - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - n++ - c.Check(r.Method, check.Equals, "GET") - c.Assert(r.URL.Path, check.Equals, "/v2/changes") - switch n { - case 1, 2: - fmt.Fprintln(w, `{"type": "sync", "result": []}`) - case 3, 4: - fmt.Fprintln(w, mockChangesJSON) - default: - c.Errorf("expected 4 calls, now on %d", n) - } - }) - for i := 0; i < 2; i++ { - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"tasks", "--last=foobar?"}) - c.Assert(err, check.IsNil) - c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, "") - c.Check(s.Stderr(), check.Equals, "") - - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"tasks", "--last=foobar"}) - if i == 0 { - c.Assert(err, check.ErrorMatches, `no changes found`) - } else { - c.Assert(err, check.ErrorMatches, `no changes of type "foobar" found`) - } - } - - c.Check(n, check.Equals, 4) -} - func (s *SnapSuite) TestTasksSyntaxError(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"tasks", "--abs-time", "--last=install", "42"}) + _, err := snap.Parser().ParseArgs([]string{"tasks", "--abs-time", "--last=install", "42"}) c.Assert(err, check.NotNil) c.Assert(err, check.ErrorMatches, `cannot use change ID and type together`) - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"tasks"}) + _, err = snap.Parser().ParseArgs([]string{"tasks"}) c.Assert(err, check.NotNil) c.Assert(err, check.ErrorMatches, `please provide change ID or type with --last=`) } @@ -223,7 +190,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"change", "--abs-time", "42"}) + rest, err := snap.Parser().ParseArgs([]string{"change", "--abs-time", "42"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `(?ms)Status +Spawn +Ready +Summary diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_confinement.go snapd-2.34.2+18.04/cmd/snap/cmd_confinement.go --- snapd-2.37.1+18.04/cmd/snap/cmd_confinement.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_confinement.go 2018-07-19 10:05:50.000000000 +0000 @@ -33,9 +33,7 @@ partial or none) the system operates in. `) -type cmdConfinement struct { - clientMixin -} +type cmdConfinement struct{} func init() { addDebugCommand("confinement", shortConfinementHelp, longConfinementHelp, func() flags.Commander { @@ -48,7 +46,8 @@ return ErrExtraArgs } - sysInfo, err := cmd.client.SysInfo() + cli := Client() + sysInfo, err := cli.SysInfo() if err != nil { return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_confinement_test.go snapd-2.34.2+18.04/cmd/snap/cmd_confinement_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_confinement_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_confinement_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -32,7 +32,7 @@ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `{"type": "sync", "result": {"confinement": "strict"}}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "confinement"}) + _, err := snap.Parser().ParseArgs([]string{"debug", "confinement"}) c.Assert(err, IsNil) c.Assert(s.Stdout(), Equals, "strict\n") c.Assert(s.Stderr(), Equals, "") diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_connect.go snapd-2.34.2+18.04/cmd/snap/cmd_connect.go --- snapd-2.37.1+18.04/cmd/snap/cmd_connect.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_connect.go 2018-07-19 10:05:50.000000000 +0000 @@ -58,9 +58,9 @@ addCommand("connect", shortConnectHelp, longConnectHelp, func() flags.Commander { return &cmdConnect{} }, waitDescs, []argDesc{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. {name: i18n.G(":")}, - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. {name: i18n.G(":")}, }) } @@ -77,12 +77,13 @@ x.Positionals.PlugSpec.Snap = "" } - id, err := x.client.Connect(x.Positionals.PlugSpec.Snap, x.Positionals.PlugSpec.Name, x.Positionals.SlotSpec.Snap, x.Positionals.SlotSpec.Name) + cli := Client() + id, err := cli.Connect(x.Positionals.PlugSpec.Snap, x.Positionals.PlugSpec.Name, x.Positionals.SlotSpec.Snap, x.Positionals.SlotSpec.Name) if err != nil { return err } - if _, err := x.wait(id); err != nil { + if _, err := x.wait(cli, id); err != nil { if err == noWait { return nil } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_connectivity_check.go snapd-2.34.2+18.04/cmd/snap/cmd_connectivity_check.go --- snapd-2.37.1+18.04/cmd/snap/cmd_connectivity_check.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_connectivity_check.go 2018-07-19 10:05:50.000000000 +0000 @@ -25,9 +25,7 @@ "github.com/jessevdk/go-flags" ) -type cmdConnectivityCheck struct { - clientMixin -} +type cmdConnectivityCheck struct{} func init() { addDebugCommand("connectivity", @@ -43,11 +41,13 @@ return ErrExtraArgs } + cli := Client() + var status struct { Connectivity bool Unreachable []string } - if err := x.client.Debug("connectivity", nil, &status); err != nil { + if err := cli.Debug("connectivity", nil, &status); err != nil { return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_connectivity_check_test.go snapd-2.34.2+18.04/cmd/snap/cmd_connectivity_check_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_connectivity_check_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_connectivity_check_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -47,7 +47,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "connectivity"}) + rest, err := snap.Parser().ParseArgs([]string{"debug", "connectivity"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, `Connectivity status: @@ -74,7 +74,7 @@ n++ }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "connectivity"}) + _, err := snap.Parser().ParseArgs([]string{"debug", "connectivity"}) c.Assert(err, check.ErrorMatches, "1 servers unreachable") // note that only the unreachable hosts are displayed c.Check(s.Stdout(), check.Equals, `Connectivity status: diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_connect_test.go snapd-2.34.2+18.04/cmd/snap/cmd_connect_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_connect_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_connect_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -54,10 +54,12 @@ the plug name. [connect command options] - --no-wait Do not wait for the operation to finish but just print - the change id. + --no-wait Do not wait for the operation to finish but just + print the change id. ` - s.testSubCommandHelp(c, "connect", msg) + rest, err := Parser().ParseArgs([]string{"connect", "--help"}) + c.Assert(err.Error(), Equals, msg) + c.Assert(rest, DeepEquals, []string{}) } func (s *SnapSuite) TestConnectExplicitEverything(c *C) { @@ -88,7 +90,7 @@ c.Fatalf("unexpected path %q", r.URL.Path) } }) - rest, err := Parser(Client()).ParseArgs([]string{"connect", "producer:plug", "consumer:slot"}) + rest, err := Parser().ParseArgs([]string{"connect", "producer:plug", "consumer:slot"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) } @@ -121,7 +123,7 @@ c.Fatalf("unexpected path %q", r.URL.Path) } }) - rest, err := Parser(Client()).ParseArgs([]string{"connect", "producer:plug", "consumer"}) + rest, err := Parser().ParseArgs([]string{"connect", "producer:plug", "consumer"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) } @@ -154,7 +156,7 @@ c.Fatalf("unexpected path %q", r.URL.Path) } }) - rest, err := Parser(Client()).ParseArgs([]string{"connect", "plug", "consumer:slot"}) + rest, err := Parser().ParseArgs([]string{"connect", "plug", "consumer:slot"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) } @@ -187,7 +189,7 @@ c.Fatalf("unexpected path %q", r.URL.Path) } }) - rest, err := Parser(Client()).ParseArgs([]string{"connect", "plug", "consumer"}) + rest, err := Parser().ParseArgs([]string{"connect", "plug", "consumer"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) } @@ -290,7 +292,7 @@ defer os.Unsetenv("GO_FLAGS_COMPLETION") expected := []flags.Completion{} - parser := Parser(Client()) + parser := Parser() parser.CompletionHandler = func(obtained []flags.Completion) { c.Check(obtained, DeepEquals, expected) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_create_key.go snapd-2.34.2+18.04/cmd/snap/cmd_create_key.go --- snapd-2.37.1+18.04/cmd/snap/cmd_create_key.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_create_key.go 2018-07-19 10:05:50.000000000 +0000 @@ -46,9 +46,9 @@ func() flags.Commander { return &cmdCreateKey{} }, nil, []argDesc{{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Name of key to create; defaults to 'default'"), }}) cmd.hidden = true diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_create_key_test.go snapd-2.34.2+18.04/cmd/snap/cmd_create_key_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_create_key_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_create_key_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -26,7 +26,7 @@ ) func (s *SnapSuite) TestCreateKeyInvalidCharacters(c *C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"create-key", "a b"}) + _, err := snap.Parser().ParseArgs([]string{"create-key", "a b"}) c.Assert(err, NotNil) c.Check(err.Error(), Equals, "key name \"a b\" is not valid; only ASCII letters, digits, and hyphens are allowed") c.Check(s.Stdout(), Equals, "") diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_create_user.go snapd-2.34.2+18.04/cmd/snap/cmd_create_user.go --- snapd-2.37.1+18.04/cmd/snap/cmd_create_user.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_create_user.go 2018-07-19 10:05:50.000000000 +0000 @@ -38,7 +38,6 @@ `) type cmdCreateUser struct { - clientMixin Positional struct { Email string } `positional-args:"yes"` @@ -52,18 +51,14 @@ func init() { cmd := addCommand("create-user", shortCreateUserHelp, longCreateUserHelp, func() flags.Commander { return &cmdCreateUser{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "json": i18n.G("Output results in JSON format"), - // TRANSLATORS: This should not start with a lowercase letter. - "sudoer": i18n.G("Grant sudo access to the created user"), - // TRANSLATORS: This should not start with a lowercase letter. - "known": i18n.G("Use known assertions for user creation"), - // TRANSLATORS: This should not start with a lowercase letter. + "json": i18n.G("Output results in JSON format"), + "sudoer": i18n.G("Grant sudo access to the created user"), + "known": i18n.G("Use known assertions for user creation"), "force-managed": i18n.G("Force adding the user, even if the device is already managed"), }, []argDesc{{ - // TRANSLATORS: This is a noun and it needs to begin with < and end with > + // TRANSLATORS: This is a noun, and it needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter (unless it's "login.ubuntu.com"). Also, note users on login.ubuntu.com can have multiple email addresses. + // TRANSLATORS: This should probably not start with a lowercase letter. Also, note users on login.ubuntu.com can have multiple email addresses. desc: i18n.G("An email of a user on login.ubuntu.com"), }}) cmd.hidden = true @@ -74,6 +69,8 @@ return ErrExtraArgs } + cli := Client() + options := client.CreateUserOptions{ Email: x.Positional.Email, Sudoer: x.Sudoer, @@ -86,9 +83,9 @@ var err error if options.Email == "" && options.Known { - results, err = x.client.CreateUsers([]*client.CreateUserOptions{&options}) + results, err = cli.CreateUsers([]*client.CreateUserOptions{&options}) } else { - result, err = x.client.CreateUser(&options) + result, err = cli.CreateUser(&options) if err == nil { results = append(results, result) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_create_user_test.go snapd-2.34.2+18.04/cmd/snap/cmd_create_user_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_create_user_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_create_user_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -71,7 +71,7 @@ n := 0 s.RedirectClientToTestServer(makeCreateUserChecker(c, &n, "one@email.com", false, false)) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"create-user", "one@email.com"}) + rest, err := snap.Parser().ParseArgs([]string{"create-user", "one@email.com"}) c.Assert(err, check.IsNil) c.Check(rest, check.DeepEquals, []string{}) c.Check(n, check.Equals, 1) @@ -83,7 +83,7 @@ n := 0 s.RedirectClientToTestServer(makeCreateUserChecker(c, &n, "one@email.com", true, false)) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"create-user", "--sudoer", "one@email.com"}) + rest, err := snap.Parser().ParseArgs([]string{"create-user", "--sudoer", "one@email.com"}) c.Assert(err, check.IsNil) c.Check(rest, check.DeepEquals, []string{}) c.Check(n, check.Equals, 1) @@ -101,7 +101,7 @@ } actualResponse := &client.CreateUserResult{} - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"create-user", "--json", "one@email.com"}) + rest, err := snap.Parser().ParseArgs([]string{"create-user", "--json", "one@email.com"}) c.Assert(err, check.IsNil) c.Check(rest, check.DeepEquals, []string{}) c.Check(n, check.Equals, 1) @@ -120,7 +120,7 @@ }} var actualResponse []*client.CreateUserResult - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"create-user", "--json", "--known"}) + rest, err := snap.Parser().ParseArgs([]string{"create-user", "--json", "--known"}) c.Assert(err, check.IsNil) c.Check(rest, check.DeepEquals, []string{}) c.Check(n, check.Equals, 1) @@ -133,7 +133,7 @@ n := 0 s.RedirectClientToTestServer(makeCreateUserChecker(c, &n, "one@email.com", false, true)) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"create-user", "--known", "one@email.com"}) + rest, err := snap.Parser().ParseArgs([]string{"create-user", "--known", "one@email.com"}) c.Assert(err, check.IsNil) c.Check(rest, check.DeepEquals, []string{}) c.Check(n, check.Equals, 1) @@ -143,7 +143,7 @@ n := 0 s.RedirectClientToTestServer(makeCreateUserChecker(c, &n, "", false, true)) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"create-user", "--known"}) + rest, err := snap.Parser().ParseArgs([]string{"create-user", "--known"}) c.Assert(err, check.IsNil) c.Check(rest, check.DeepEquals, []string{}) c.Check(n, check.Equals, 1) diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_delete_key.go snapd-2.34.2+18.04/cmd/snap/cmd_delete_key.go --- snapd-2.37.1+18.04/cmd/snap/cmd_delete_key.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_delete_key.go 2018-07-19 10:05:50.000000000 +0000 @@ -42,9 +42,9 @@ func() flags.Commander { return &cmdDeleteKey{} }, nil, []argDesc{{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Name of key to delete"), }}) cmd.hidden = true diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_delete_key_test.go snapd-2.34.2+18.04/cmd/snap/cmd_delete_key_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_delete_key_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_delete_key_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -28,7 +28,7 @@ ) func (s *SnapKeysSuite) TestDeleteKeyRequiresName(c *C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"delete-key"}) + _, err := snap.Parser().ParseArgs([]string{"delete-key"}) c.Assert(err, NotNil) c.Check(err.Error(), Equals, "the required argument `` was not provided") c.Check(s.Stdout(), Equals, "") @@ -36,7 +36,7 @@ } func (s *SnapKeysSuite) TestDeleteKeyNonexistent(c *C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"delete-key", "nonexistent"}) + _, err := snap.Parser().ParseArgs([]string{"delete-key", "nonexistent"}) c.Assert(err, NotNil) c.Check(err.Error(), Equals, "cannot find key named \"nonexistent\" in GPG keyring") c.Check(s.Stdout(), Equals, "") @@ -44,12 +44,12 @@ } func (s *SnapKeysSuite) TestDeleteKey(c *C) { - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"delete-key", "another"}) + rest, err := snap.Parser().ParseArgs([]string{"delete-key", "another"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Check(s.Stdout(), Equals, "") c.Check(s.Stderr(), Equals, "") - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"keys", "--json"}) + _, err = snap.Parser().ParseArgs([]string{"keys", "--json"}) c.Assert(err, IsNil) expectedResponse := []snap.Key{ { diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_disconnect.go snapd-2.34.2+18.04/cmd/snap/cmd_disconnect.go --- snapd-2.37.1+18.04/cmd/snap/cmd_disconnect.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_disconnect.go 2018-07-19 10:05:50.000000000 +0000 @@ -55,9 +55,9 @@ addCommand("disconnect", shortDisconnectHelp, longDisconnectHelp, func() flags.Commander { return &cmdDisconnect{} }, waitDescs, []argDesc{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. {name: i18n.G(":")}, - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. {name: i18n.G(":")}, }) } @@ -80,7 +80,8 @@ return fmt.Errorf("please provide the plug or slot name to disconnect from snap %q", use.Snap) } - id, err := x.client.Disconnect(offer.Snap, offer.Name, use.Snap, use.Name) + cli := Client() + id, err := cli.Disconnect(offer.Snap, offer.Name, use.Snap, use.Name) if err != nil { if client.IsInterfacesUnchangedError(err) { fmt.Fprintf(Stdout, i18n.G("No connections to disconnect")) @@ -90,7 +91,7 @@ return err } - if _, err := x.wait(id); err != nil { + if _, err := x.wait(cli, id); err != nil { if err == noWait { return nil } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_disconnect_test.go snapd-2.34.2+18.04/cmd/snap/cmd_disconnect_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_disconnect_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_disconnect_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -47,10 +47,12 @@ The snap name may be omitted for the core snap. [disconnect command options] - --no-wait Do not wait for the operation to finish but just print - the change id. + --no-wait Do not wait for the operation to finish but just + print the change id. ` - s.testSubCommandHelp(c, "disconnect", msg) + rest, err := Parser().ParseArgs([]string{"disconnect", "--help"}) + c.Assert(err.Error(), Equals, msg) + c.Assert(rest, DeepEquals, []string{}) } func (s *SnapSuite) TestDisconnectExplicitEverything(c *C) { @@ -81,7 +83,7 @@ c.Fatalf("unexpected path %q", r.URL.Path) } }) - rest, err := Parser(Client()).ParseArgs([]string{"disconnect", "producer:plug", "consumer:slot"}) + rest, err := Parser().ParseArgs([]string{"disconnect", "producer:plug", "consumer:slot"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Assert(s.Stdout(), Equals, "") @@ -116,7 +118,7 @@ c.Fatalf("unexpected path %q", r.URL.Path) } }) - rest, err := Parser(Client()).ParseArgs([]string{"disconnect", "consumer:slot"}) + rest, err := Parser().ParseArgs([]string{"disconnect", "consumer:slot"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Assert(s.Stdout(), Equals, "") @@ -151,7 +153,7 @@ c.Fatalf("unexpected path %q", r.URL.Path) } }) - rest, err := Parser(Client()).ParseArgs([]string{"disconnect", "consumer:plug-or-slot"}) + rest, err := Parser().ParseArgs([]string{"disconnect", "consumer:plug-or-slot"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Assert(s.Stdout(), Equals, "") @@ -162,7 +164,7 @@ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Fatalf("expected nothing to reach the server") }) - rest, err := Parser(Client()).ParseArgs([]string{"disconnect", "consumer"}) + rest, err := Parser().ParseArgs([]string{"disconnect", "consumer"}) c.Assert(err, ErrorMatches, `please provide the plug or slot name to disconnect from snap "consumer"`) c.Assert(rest, DeepEquals, []string{"consumer"}) c.Assert(s.Stdout(), Equals, "") @@ -186,7 +188,7 @@ defer os.Unsetenv("GO_FLAGS_COMPLETION") expected := []flags.Completion{} - parser := Parser(Client()) + parser := Parser() parser.CompletionHandler = func(obtained []flags.Completion) { c.Check(obtained, DeepEquals, expected) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_download.go snapd-2.34.2+18.04/cmd/snap/cmd_download.go --- snapd-2.37.1+18.04/cmd/snap/cmd_download.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_download.go 2018-07-19 10:05:50.000000000 +0000 @@ -56,7 +56,7 @@ "revision": i18n.G("Download the given revision of a snap, to which you must have developer access"), }), []argDesc{{ name: "", - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Snap name"), }}) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_ensure_state_soon.go snapd-2.34.2+18.04/cmd/snap/cmd_ensure_state_soon.go --- snapd-2.37.1+18.04/cmd/snap/cmd_ensure_state_soon.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_ensure_state_soon.go 2018-07-19 10:05:50.000000000 +0000 @@ -23,9 +23,7 @@ "github.com/jessevdk/go-flags" ) -type cmdEnsureStateSoon struct { - clientMixin -} +type cmdEnsureStateSoon struct{} func init() { cmd := addDebugCommand("ensure-state-soon", @@ -42,5 +40,5 @@ return ErrExtraArgs } - return x.client.Debug("ensure-state-soon", nil, nil) + return Client().Debug("ensure-state-soon", nil, nil) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_ensure_state_soon_test.go snapd-2.34.2+18.04/cmd/snap/cmd_ensure_state_soon_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_ensure_state_soon_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_ensure_state_soon_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -47,7 +47,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "ensure-state-soon"}) + rest, err := snap.Parser().ParseArgs([]string{"debug", "ensure-state-soon"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, "") diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_export_key.go snapd-2.34.2+18.04/cmd/snap/cmd_export_key.go --- snapd-2.37.1+18.04/cmd/snap/cmd_export_key.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_export_key.go 2018-07-19 10:05:50.000000000 +0000 @@ -48,9 +48,9 @@ }, map[string]string{ "account": i18n.G("Format public key material as a request for an account-key for this account-id"), }, []argDesc{{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Name of key to export"), }}) cmd.hidden = true diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_export_key_test.go snapd-2.34.2+18.04/cmd/snap/cmd_export_key_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_export_key_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_export_key_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -30,7 +30,7 @@ ) func (s *SnapKeysSuite) TestExportKeyNonexistent(c *C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"export-key", "nonexistent"}) + _, err := snap.Parser().ParseArgs([]string{"export-key", "nonexistent"}) c.Assert(err, NotNil) c.Check(err.Error(), Equals, "cannot find key named \"nonexistent\" in GPG keyring") c.Check(s.Stdout(), Equals, "") @@ -38,7 +38,7 @@ } func (s *SnapKeysSuite) TestExportKeyDefault(c *C) { - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"export-key"}) + rest, err := snap.Parser().ParseArgs([]string{"export-key"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) pubKey, err := asserts.DecodePublicKey(s.stdout.Bytes()) @@ -48,7 +48,7 @@ } func (s *SnapKeysSuite) TestExportKeyNonDefault(c *C) { - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"export-key", "another"}) + rest, err := snap.Parser().ParseArgs([]string{"export-key", "another"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) pubKey, err := asserts.DecodePublicKey(s.stdout.Bytes()) @@ -61,7 +61,7 @@ storeSigning := assertstest.NewStoreStack("canonical", nil) manager := asserts.NewGPGKeypairManager() assertstest.NewAccount(storeSigning, "developer1", nil, "") - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"export-key", "another", "--account=developer1"}) + rest, err := snap.Parser().ParseArgs([]string{"export-key", "another", "--account=developer1"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) assertion, err := asserts.Decode(s.stdout.Bytes()) diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_find.go snapd-2.34.2+18.04/cmd/snap/cmd_find.go --- snapd-2.37.1+18.04/cmd/snap/cmd_find.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_find.go 2018-07-19 10:05:50.000000000 +0000 @@ -44,9 +44,6 @@ (see 'snap help login'), it instead searches for private snaps that the user has developer access to, either directly or through the store's collaboration feature. - -A green check mark (given color and unicode support) after a publisher name -indicates that the publisher has been verified. `) func getPrice(prices map[string]float64, currency string) (float64, string, error) { @@ -86,7 +83,7 @@ return ret } - cli := mkClient() + cli := Client() sections, err := cli.Sections() if err != nil { return nil @@ -121,7 +118,7 @@ return sections, nil } -func getSections(cli *client.Client) (sections []string, err error) { +func getSections() (sections []string, err error) { // try loading from cached sections file sections, err = cachedSections() if err != nil { @@ -131,11 +128,12 @@ return sections, nil } // fallback to listing from the daemon + cli := Client() return cli.Sections() } -func showSections(cli *client.Client) error { - sections, err := getSections(cli) +func showSections() error { + sections, err := getSections() if err != nil { return err } @@ -150,31 +148,25 @@ } type cmdFind struct { - clientMixin Private bool `long:"private"` Narrow bool `long:"narrow"` Section SectionName `long:"section" optional:"true" optional-value:"show-all-sections-please" default:"no-section-specified"` Positional struct { Query string } `positional-args:"yes"` - colorMixin } func init() { addCommand("find", shortFindHelp, longFindHelp, func() flags.Commander { return &cmdFind{} - }, colorDescs.also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. + }, map[string]string{ "private": i18n.G("Search private snaps"), - // TRANSLATORS: This should not start with a lowercase letter. - "narrow": i18n.G("Only search for snaps in “stable”"), - // TRANSLATORS: This should not start with a lowercase letter. + "narrow": i18n.G("Only search for snaps in “stable”"), "section": i18n.G("Restrict the search to a given section"), - }), []argDesc{{ - // TRANSLATORS: This needs to begin with < and end with > + }, []argDesc{{ + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), }}).alias = "search" - } func (x *cmdFind) Execute(args []string) error { @@ -194,7 +186,7 @@ // the commandline at all switch x.Section { case "show-all-sections-please": - return showSections(x.client) + return showSections() case "no-section-specified": x.Section = "" } @@ -205,6 +197,8 @@ x.Section = "featured" } + cli := Client() + if x.Section != "" && x.Section != "featured" { sections, err := cachedSections() if err != nil { @@ -212,7 +206,7 @@ } if !strutil.ListContains(sections, string(x.Section)) { // try the store just in case it was added in the last 24 hours - sections, err = x.client.Sections() + sections, err = cli.Sections() if err != nil { return err } @@ -233,8 +227,8 @@ opts.Scope = "wide" } - snaps, resInfo, err := x.client.Find(opts) - if e, ok := err.(*client.Error); ok && (e.Kind == client.ErrorKindNetworkTimeout || e.Kind == client.ErrorKindDNSFailure) { + snaps, resInfo, err := cli.Find(opts) + if e, ok := err.(*client.Error); ok && e.Kind == client.ErrorKindNetworkTimeout { logger.Debugf("cannot list snaps: %v", e) return fmt.Errorf("unable to contact snap store") } @@ -256,19 +250,18 @@ // show featured header *after* we checked for errors from the find if showFeatured { - fmt.Fprint(Stdout, i18n.G("No search term specified. Here are some interesting snaps:\n\n")) + fmt.Fprintf(Stdout, i18n.G("No search term specified. Here are some interesting snaps:\n\n")) } - esc := x.getEscapes() w := tabWriter() - // TRANSLATORS: the %s is to insert a filler escape sequence (please keep it flush to the column header, with no extra spaces) - fmt.Fprintf(w, i18n.G("Name\tVersion\tPublisher%s\tNotes\tSummary\n"), fillerPublisher(esc)) + fmt.Fprintln(w, i18n.G("Name\tVersion\tPublisher\tNotes\tSummary")) for _, snap := range snaps { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", snap.Name, snap.Version, shortPublisher(esc, snap.Publisher), NotesFromRemote(snap, resInfo), snap.Summary) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", snap.Name, snap.Version, snap.Publisher.Username, NotesFromRemote(snap, resInfo), snap.Summary) } w.Flush() + if showFeatured { - fmt.Fprint(Stdout, i18n.G("\nProvide a search term for more specific results.\n")) + fmt.Fprintf(Stdout, i18n.G("\nProvide a search term for more specific results.\n")) } return nil } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_find_test.go snapd-2.34.2+18.04/cmd/snap/cmd_find_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_find_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_find_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -137,14 +137,14 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"find", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary -hello +2.10 +canonical\* +- +GNU Hello, the "hello world" snap -hello-world +6.1 +canonical\* +- +Hello world example +hello +2.10 +canonical +- +GNU Hello, the "hello world" snap +hello-world +6.1 +canonical +- +Hello world example hello-huge +1.0 +noise +- +a really big snap `) c.Check(s.Stderr(), check.Equals, "") @@ -230,11 +230,11 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"find", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary -hello +2.10 +canonical\* +- +GNU Hello, the "hello world" snap +hello +2.10 +canonical +- +GNU Hello, the "hello world" snap hello-huge +1.0 +noise +- +a really big snap `) c.Check(s.Stderr(), check.Equals, "") @@ -257,11 +257,11 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--narrow", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"find", "--narrow", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary -hello +2.10 +canonical\* +- +GNU Hello, the "hello world" snap +hello +2.10 +canonical +- +GNU Hello, the "hello world" snap hello-huge +1.0 +noise +- +a really big snap `) c.Check(s.Stderr(), check.Equals, "") @@ -320,11 +320,11 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"find", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary -hello +2.10 +canonical\* +1.99GBP +GNU Hello, the "hello world" snap +hello +2.10 +canonical +1.99GBP +GNU Hello, the "hello world" snap `) c.Check(s.Stderr(), check.Equals, "") } @@ -381,11 +381,11 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"find", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary -hello +2.10 +canonical\* +bought +GNU Hello, the "hello world" snap +hello +2.10 +canonical +bought +GNU Hello, the "hello world" snap `) c.Check(s.Stderr(), check.Equals, "") } @@ -405,7 +405,7 @@ n++ }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"find"}) + _, err := snap.Parser().ParseArgs([]string{"find"}) c.Assert(err, check.IsNil) c.Check(s.Stderr(), check.Equals, "") c.Check(n, check.Equals, 1) @@ -463,7 +463,7 @@ n++ }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"}) + _, err := snap.Parser().ParseArgs([]string{"find", "hello"}) c.Assert(err, check.ErrorMatches, `unable to contact snap store`) c.Check(s.Stdout(), check.Equals, "") } @@ -485,7 +485,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section"}) + rest, err := snap.Parser().ParseArgs([]string{"find", "--section"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) @@ -517,7 +517,7 @@ n++ }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section=foobar", "hello"}) + _, err := snap.Parser().ParseArgs([]string{"find", "--section=foobar", "hello"}) c.Assert(err, check.ErrorMatches, `No matching section "foobar", use --section to list existing sections`) } @@ -548,7 +548,7 @@ n++ }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section=foobar", "hello"}) + _, err := snap.Parser().ParseArgs([]string{"find", "--section=foobar", "hello"}) c.Assert(err, check.IsNil) c.Check(s.Stderr(), check.Equals, "No matching snaps for \"hello\" in section \"foobar\"\n") c.Check(s.Stdout(), check.Equals, "") @@ -571,13 +571,13 @@ os.MkdirAll(path.Dir(dirs.SnapSectionsFile), 0755) ioutil.WriteFile(dirs.SnapSectionsFile, []byte("sec1\nsec2\nsec3"), 0644) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section=foobar", "hello"}) + _, err := snap.Parser().ParseArgs([]string{"find", "--section=foobar", "hello"}) c.Logf("stdout: %s", s.Stdout()) c.Assert(err, check.ErrorMatches, `No matching section "foobar", use --section to list existing sections`) s.ResetStdStreams() - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section"}) + rest, err := snap.Parser().ParseArgs([]string{"find", "--section"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_get_base_declaration.go snapd-2.34.2+18.04/cmd/snap/cmd_get_base_declaration.go --- snapd-2.37.1+18.04/cmd/snap/cmd_get_base_declaration.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_get_base_declaration.go 2018-07-19 10:05:50.000000000 +0000 @@ -25,9 +25,7 @@ "github.com/jessevdk/go-flags" ) -type cmdGetBaseDeclaration struct { - clientMixin -} +type cmdGetBaseDeclaration struct{} func init() { cmd := addDebugCommand("get-base-declaration", @@ -46,7 +44,7 @@ var resp struct { BaseDeclaration string `json:"base-declaration"` } - if err := x.client.Debug("get-base-declaration", nil, &resp); err != nil { + if err := Client().Debug("get-base-declaration", nil, &resp); err != nil { return err } fmt.Fprintf(Stdout, "%s\n", resp.BaseDeclaration) diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_get_base_declaration_test.go snapd-2.34.2+18.04/cmd/snap/cmd_get_base_declaration_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_get_base_declaration_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_get_base_declaration_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -47,7 +47,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "get-base-declaration"}) + rest, err := snap.Parser().ParseArgs([]string{"debug", "get-base-declaration"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, "hello\n") diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_get.go snapd-2.34.2+18.04/cmd/snap/cmd_get.go --- snapd-2.37.1+18.04/cmd/snap/cmd_get.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_get.go 2018-07-19 10:05:50.000000000 +0000 @@ -22,12 +22,14 @@ import ( "encoding/json" "fmt" + "os" "sort" "strings" "github.com/jessevdk/go-flags" "github.com/snapcore/snapd/i18n" + "golang.org/x/crypto/ssh/terminal" ) var shortGetHelp = i18n.G("Print configuration options") @@ -52,7 +54,6 @@ `) type cmdGet struct { - clientMixin Positional struct { Snap installedSnapName `required:"yes"` Keys []string @@ -66,22 +67,19 @@ func init() { addCommand("get", shortGetHelp, longGetHelp, func() flags.Commander { return &cmdGet{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. "d": i18n.G("Always return document, even with single key"), - // TRANSLATORS: This should not start with a lowercase letter. "l": i18n.G("Always return list, even with single key"), - // TRANSLATORS: This should not start with a lowercase letter. "t": i18n.G("Strict typing with nulls and quoted strings"), }, []argDesc{ { name: "", - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("The snap whose conf is being requested"), }, { - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Key of interest within the configuration"), }, }) @@ -178,6 +176,10 @@ return nil } +var isTerminal = func() bool { + return terminal.IsTerminal(int(os.Stdin.Fd())) +} + // outputDefault will be used when no commandline switch to override the // output where used. The output follows the following rules: // - a single key with a string value is printed directly @@ -202,7 +204,7 @@ // conf looks like a map if cfg, ok := confToPrint.(map[string]interface{}); ok { - if isStdinTTY { + if isTerminal() { return x.outputList(cfg) } @@ -243,7 +245,8 @@ snapName := string(x.Positional.Snap) confKeys := x.Positional.Keys - conf, err := x.client.Conf(snapName, confKeys) + cli := Client() + conf, err := cli.Conf(snapName, confKeys) if err != nil { return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_get_test.go snapd-2.34.2+18.04/cmd/snap/cmd_get_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_get_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_get_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -109,10 +109,10 @@ c.Logf("Test: %s", test.args) - restore := snapset.MockIsStdinTTY(test.isTerminal) + restore := snapset.MockIsTerminal(test.isTerminal) defer restore() - _, err := snapset.Parser(snapset.Client()).ParseArgs(strings.Fields(test.args)) + _, err := snapset.Parser().ParseArgs(strings.Fields(test.args)) if test.error != "" { c.Check(err, ErrorMatches, test.error) } else { diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_handle_link.go snapd-2.34.2+18.04/cmd/snap/cmd_handle_link.go --- snapd-2.37.1+18.04/cmd/snap/cmd_handle_link.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_handle_link.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,91 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main - -import ( - "fmt" - "os" - "syscall" - "time" - - "github.com/jessevdk/go-flags" - - "github.com/snapcore/snapd/i18n" - "github.com/snapcore/snapd/userd/ui" -) - -type cmdHandleLink struct { - waitMixin - - Positional struct { - Uri string `positional-arg-name:""` - } `positional-args:"yes" required:"yes"` -} - -func init() { - cmd := addCommand("handle-link", - i18n.G("Handle a snap:// URI"), - i18n.G("The handle-link command installs the snap-store snap and then invokes it."), - func() flags.Commander { - return &cmdHandleLink{} - }, nil, nil) - cmd.hidden = true -} - -func (x *cmdHandleLink) ensureSnapStoreInstalled() error { - // If the snap-store snap is installed, our work is done - if _, _, err := x.client.Snap("snap-store"); err == nil { - return nil - } - - dialog, err := ui.New() - if err != nil { - return err - } - answeredYes := dialog.YesNo( - i18n.G("Install snap-aware Snap Store snap?"), - i18n.G("The Snap Store is required to open snaps from a web browser."), - &ui.DialogOptions{ - Timeout: 5 * time.Minute, - Footer: i18n.G("This dialog will close automatically after 5 minutes of inactivity."), - }) - if !answeredYes { - return fmt.Errorf(i18n.G("Snap Store required")) - } - - changeID, err := x.client.Install("snap-store", nil) - if err != nil { - return err - } - _, err = x.wait(changeID) - if err != nil && err != noWait { - return err - } - return nil -} - -func (x *cmdHandleLink) Execute([]string) error { - if err := x.ensureSnapStoreInstalled(); err != nil { - return err - } - - argv := []string{"snap", "run", "snap-store", x.Positional.Uri} - return syscall.Exec("/proc/self/exe", argv, os.Environ()) -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_help.go snapd-2.34.2+18.04/cmd/snap/cmd_help.go --- snapd-2.37.1+18.04/cmd/snap/cmd_help.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_help.go 2018-07-19 10:05:50.000000000 +0000 @@ -22,12 +22,11 @@ import ( "bytes" "fmt" - "strings" - "unicode/utf8" - - "github.com/jessevdk/go-flags" + "os" "github.com/snapcore/snapd/i18n" + + "github.com/jessevdk/go-flags" ) var shortHelpHelp = i18n.G("Show help about a command") @@ -41,16 +40,12 @@ ShowHelp func() error `short:"h" long:"help"` } help.ShowHelp = func() error { - // this function is called via --help (or -h). In that - // case, parser.Command.Active should be the command - // on which help is being requested (like "snap foo - // --help", active is foo), or nil in the toplevel. - if parser.Command.Active == nil { - // toplevel --help will get handled via ErrCommandRequired - return nil + var buf bytes.Buffer + parser.WriteHelp(&buf) + return &flags.Error{ + Type: flags.ErrHelp, + Message: buf.String(), } - // not toplevel, so ask for regular help - return &flags.Error{Type: flags.ErrHelp} } hlpgrp, err := parser.AddGroup("Help Options", "", &help) if err != nil { @@ -65,7 +60,6 @@ } type cmdHelp struct { - All bool `long:"all"` Manpage bool `long:"man" hidden:"true"` Positional struct { // TODO: find a way to make Command tab-complete @@ -76,201 +70,30 @@ func init() { addCommand("help", shortHelpHelp, longHelpHelp, func() flags.Commander { return &cmdHelp{} }, - map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "all": i18n.G("Show a short summary of all commands"), - // TRANSLATORS: This should not start with a lowercase letter. - "man": i18n.G("Generate the manpage"), - }, nil) + map[string]string{"man": i18n.G("Generate the manpage")}, nil) } func (cmd *cmdHelp) setParser(parser *flags.Parser) { cmd.parser = parser } -// manfixer is a hackish way to get the generated manpage into section 8 -// (go-flags doesn't have an option for this; I'll be proposing something -// there soon, but still waiting on some other PRs to make it through) -type manfixer struct { - done bool -} - -func (w *manfixer) Write(buf []byte) (int, error) { - if !w.done { - w.done = true - if bytes.HasPrefix(buf, []byte(".TH snap 1 ")) { - // io.Writer.Write must not modify the buffer, even temporarily - n, _ := Stdout.Write(buf[:9]) - Stdout.Write([]byte{'8'}) - m, err := Stdout.Write(buf[10:]) - return n + m + 1, err - } - } - return Stdout.Write(buf) -} - func (cmd cmdHelp) Execute(args []string) error { if len(args) > 0 { return ErrExtraArgs } - if cmd.Manpage { - // you shouldn't try to to combine --man with --all nor a - // subcommand, but --man is hidden so no real need to check. - cmd.parser.WriteManPage(&manfixer{}) - return nil - } - if cmd.All { - if cmd.Positional.Sub != "" { - return fmt.Errorf(i18n.G("help accepts a command, or '--all', but not both.")) - } - printLongHelp(cmd.parser) - return nil - } - if cmd.Positional.Sub != "" { subcmd := cmd.parser.Find(cmd.Positional.Sub) if subcmd == nil { return fmt.Errorf(i18n.G("Unknown command %q. Try 'snap help'."), cmd.Positional.Sub) } - // this makes "snap help foo" work the same as "snap foo --help" cmd.parser.Command.Active = subcmd - return &flags.Error{Type: flags.ErrHelp} } - - return &flags.Error{Type: flags.ErrCommandRequired} -} - -type helpCategory struct { - Label string - Description string - Commands []string -} - -// helpCategories helps us by grouping commands -var helpCategories = []helpCategory{ - { - Label: i18n.G("Basics"), - Description: i18n.G("basic snap management"), - Commands: []string{"find", "info", "install", "list", "remove"}, - }, { - Label: i18n.G("...more"), - Description: i18n.G("slightly more advanced snap management"), - Commands: []string{"refresh", "revert", "switch", "disable", "enable"}, - }, { - Label: i18n.G("History"), - Description: i18n.G("manage system change transactions"), - Commands: []string{"changes", "tasks", "abort", "watch"}, - }, { - Label: i18n.G("Daemons"), - Description: i18n.G("manage services"), - Commands: []string{"services", "start", "stop", "restart", "logs"}, - }, { - Label: i18n.G("Commands"), - Description: i18n.G("manage aliases"), - Commands: []string{"alias", "aliases", "unalias", "prefer"}, - }, { - Label: i18n.G("Configuration"), - Description: i18n.G("system administration and configuration"), - Commands: []string{"get", "set", "wait"}, - }, { - Label: i18n.G("Account"), - Description: i18n.G("authentication to snapd and the snap store"), - Commands: []string{"login", "logout", "whoami"}, - }, { - Label: i18n.G("Permissions"), - Description: i18n.G("manage permissions"), - Commands: []string{"interfaces", "interface", "connect", "disconnect"}, - }, { - Label: i18n.G("Snapshots"), - Description: i18n.G("archives of snap data"), - Commands: []string{"saved", "save", "check-snapshot", "restore", "forget"}, - }, { - Label: i18n.G("Other"), - Description: i18n.G("miscellanea"), - Commands: []string{"version", "warnings", "okay"}, - }, { - Label: i18n.G("Development"), - Description: i18n.G("developer-oriented features"), - Commands: []string{"run", "pack", "try", "ack", "known", "download"}, - }, -} - -var ( - longSnapDescription = strings.TrimSpace(i18n.G(` -The snap command lets you install, configure, refresh and remove snaps. -Snaps are packages that work across many different Linux distributions, -enabling secure delivery and operation of the latest apps and utilities. -`)) - snapUsage = i18n.G("Usage: snap [...]") - snapHelpCategoriesIntro = i18n.G("Commands can be classified as follows:") - snapHelpAllFooter = i18n.G("For more information about a command, run 'snap help '.") - snapHelpFooter = i18n.G("For a short summary of all commands, run 'snap help --all'.") -) - -func printHelpHeader() { - fmt.Fprintln(Stdout, longSnapDescription) - fmt.Fprintln(Stdout) - fmt.Fprintln(Stdout, snapUsage) - fmt.Fprintln(Stdout) - fmt.Fprintln(Stdout, snapHelpCategoriesIntro) - fmt.Fprintln(Stdout) -} - -func printHelpAllFooter() { - fmt.Fprintln(Stdout) - fmt.Fprintln(Stdout, snapHelpAllFooter) -} - -func printHelpFooter() { - printHelpAllFooter() - fmt.Fprintln(Stdout, snapHelpFooter) -} - -// this is called when the Execute returns a flags.Error with ErrCommandRequired -func printShortHelp() { - printHelpHeader() - maxLen := 0 - for _, categ := range helpCategories { - if l := utf8.RuneCountInString(categ.Label); l > maxLen { - maxLen = l - } - } - for _, categ := range helpCategories { - fmt.Fprintf(Stdout, "%*s: %s\n", maxLen+2, categ.Label, strings.Join(categ.Commands, ", ")) - } - printHelpFooter() -} - -// this is "snap help --all" -func printLongHelp(parser *flags.Parser) { - printHelpHeader() - maxLen := 0 - for _, categ := range helpCategories { - for _, command := range categ.Commands { - if l := len(command); l > maxLen { - maxLen = l - } - } + if cmd.Manpage { + cmd.parser.WriteManPage(Stdout) + os.Exit(0) } - // flags doesn't have a LookupCommand? - commands := parser.Commands() - cmdLookup := make(map[string]*flags.Command, len(commands)) - for _, cmd := range commands { - cmdLookup[cmd.Name] = cmd - } - - for _, categ := range helpCategories { - fmt.Fprintln(Stdout) - fmt.Fprintf(Stdout, " %s (%s):\n", categ.Label, categ.Description) - for _, name := range categ.Commands { - cmd := cmdLookup[name] - if cmd == nil { - fmt.Fprintf(Stderr, "??? Cannot find command %q mentioned in help categories, please report!\n", name) - } else { - fmt.Fprintf(Stdout, " %*s %s\n", -maxLen, name, cmd.ShortDescription) - } - } + return &flags.Error{ + Type: flags.ErrHelp, } - printHelpAllFooter() } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_help_test.go snapd-2.34.2+18.04/cmd/snap/cmd_help_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_help_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_help_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -20,13 +20,8 @@ package main_test import ( - "bytes" - "fmt" "os" - "regexp" - "strings" - "github.com/jessevdk/go-flags" "gopkg.in/check.v1" snap "github.com/snapcore/snapd/cmd/snap" @@ -37,140 +32,43 @@ defer func() { os.Args = origArgs }() for _, cmdLine := range [][]string{ - {"snap"}, {"snap", "help"}, {"snap", "--help"}, {"snap", "-h"}, } { - s.ResetStdStreams() - os.Args = cmdLine - comment := check.Commentf("%q", cmdLine) err := snap.RunMain() - c.Assert(err, check.IsNil, comment) - c.Check(s.Stdout(), check.Matches, "(?s)"+strings.Join([]string{ - snap.LongSnapDescription, - "", - regexp.QuoteMeta(snap.SnapUsage), - "", ".*", "", - snap.SnapHelpAllFooter, - snap.SnapHelpFooter, - }, "\n")+`\s*`, comment) - c.Check(s.Stderr(), check.Equals, "", comment) + c.Assert(err, check.IsNil) + c.Check(s.Stdout(), check.Matches, `(?smU)Usage: + +snap + +Install, configure, refresh and remove snap packages. Snaps are +'universal' packages that work across many different Linux systems, +enabling secure distribution of the latest apps and utilities for +cloud, servers, desktops and the internet of things. + +This is the CLI for snapd, a background service that takes care of +snaps on the system. Start with 'snap list' to see installed snaps. + +Available commands: + +abort.* +`) + c.Check(s.Stderr(), check.Equals, "") } } -func (s *SnapSuite) TestHelpAllPrintsLongHelp(c *check.C) { - origArgs := os.Args - defer func() { os.Args = origArgs }() - - os.Args = []string{"snap", "help", "--all"} - - err := snap.RunMain() - c.Assert(err, check.IsNil) - c.Check(s.Stdout(), check.Matches, "(?sm)"+strings.Join([]string{ - snap.LongSnapDescription, - "", - regexp.QuoteMeta(snap.SnapUsage), - "", - snap.SnapHelpCategoriesIntro, - "", ".*", "", - snap.SnapHelpAllFooter, - }, "\n")+`\s*`) - c.Check(s.Stderr(), check.Equals, "") -} - -func nonHiddenCommands() map[string]bool { - parser := snap.Parser(snap.Client()) - commands := parser.Commands() - names := make(map[string]bool, len(commands)) - for _, cmd := range commands { - if cmd.Hidden { - continue - } - names[cmd.Name] = true - } - return names -} - -func (s *SnapSuite) testSubCommandHelp(c *check.C, sub, expected string) { - parser := snap.Parser(snap.Client()) - rest, err := parser.ParseArgs([]string{sub, "--help"}) - c.Assert(err, check.DeepEquals, &flags.Error{Type: flags.ErrHelp}) - c.Assert(rest, check.HasLen, 0) - var buf bytes.Buffer - parser.WriteHelp(&buf) - c.Check(buf.String(), check.Equals, expected) -} - func (s *SnapSuite) TestSubCommandHelpPrintsHelp(c *check.C) { origArgs := os.Args defer func() { os.Args = origArgs }() - for cmd := range nonHiddenCommands() { - s.ResetStdStreams() - os.Args = []string{"snap", cmd, "--help"} - - err := snap.RunMain() - comment := check.Commentf("%q", cmd) - c.Assert(err, check.IsNil, comment) - // regexp matches "Usage: snap " plus an arbitrary - // number of [] plus an arbitrary number of - // <> optionally ending in ellipsis - c.Check(s.Stdout(), check.Matches, fmt.Sprintf(`(?sm)Usage:\s+snap %s(?: \[[^][]+\])*(?:(?: <[^<>]+>)+(?:\.\.\.)?)?$.*`, cmd), comment) - c.Check(s.Stderr(), check.Equals, "", comment) - } -} - -func (s *SnapSuite) TestHelpCategories(c *check.C) { - // non-hidden commands that are not expected to appear in the help summary - excluded := []string{ - "help", - } - all := nonHiddenCommands() - categorised := make(map[string]bool, len(all)+len(excluded)) - for _, cmd := range excluded { - categorised[cmd] = true - } - seen := make(map[string]string, len(all)) - for _, categ := range snap.HelpCategories { - for _, cmd := range categ.Commands { - categorised[cmd] = true - if seen[cmd] != "" { - c.Errorf("duplicated: %q in %q and %q", cmd, seen[cmd], categ.Label) - } - seen[cmd] = categ.Label - } - } - for cmd := range all { - if !categorised[cmd] { - c.Errorf("uncategorised: %q", cmd) - } - } - for cmd := range categorised { - if !all[cmd] { - c.Errorf("unknown (hidden?): %q", cmd) - } - } -} - -func (s *SnapSuite) TestHelpCommandAllFails(c *check.C) { - origArgs := os.Args - defer func() { os.Args = origArgs }() - os.Args = []string{"snap", "help", "interfaces", "--all"} - - err := snap.RunMain() - c.Assert(err, check.ErrorMatches, "help accepts a command, or '--all', but not both.") -} - -func (s *SnapSuite) TestManpageInSection8(c *check.C) { - origArgs := os.Args - defer func() { os.Args = origArgs }() - os.Args = []string{"snap", "help", "--man"} + os.Args = []string{"snap", "install", "--help"} err := snap.RunMain() c.Assert(err, check.IsNil) - - c.Check(s.Stdout(), check.Matches, `\.TH snap 8 (?s).*`) + c.Check(s.Stdout(), check.Matches, `(?smU)Usage: + +snap install \[install-OPTIONS\] ... +.* +`) + c.Check(s.Stderr(), check.Equals, "") } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_info.go snapd-2.34.2+18.04/cmd/snap/cmd_info.go --- snapd-2.37.1+18.04/cmd/snap/cmd_info.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_info.go 2018-07-19 10:05:50.000000000 +0000 @@ -25,7 +25,6 @@ "path/filepath" "strings" "text/tabwriter" - "time" "unicode" "unicode/utf8" @@ -41,8 +40,6 @@ ) type infoCmd struct { - clientMixin - colorMixin timeMixin Verbose bool `long:"verbose"` @@ -67,8 +64,7 @@ longInfoHelp, func() flags.Commander { return &infoCmd{} - }, colorDescs.also(timeDescs).also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. + }, timeDescs.also(map[string]string{ "verbose": i18n.G("Include more details on the snap (expanded notes, base, etc.)"), }), nil) } @@ -322,19 +318,8 @@ var channelRisks = []string{"stable", "candidate", "beta", "edge"} // displayChannels displays channels and tracks in the right order -func (x *infoCmd) displayChannels(w io.Writer, chantpl string, esc *escapes, remote *client.Snap, revLen, sizeLen int) (maxRevLen, maxSizeLen int) { - fmt.Fprintln(w, "channels:") - - releasedfmt := "2006-01-02" - if x.AbsTime { - releasedfmt = time.RFC3339 - } - - type chInfoT struct { - name, version, released, revision, size, notes string - } - var chInfos []*chInfoT - maxRevLen, maxSizeLen = revLen, sizeLen +func displayChannels(w io.Writer, chantpl string, remote *client.Snap) { + fmt.Fprintf(w, "channels:"+strings.Repeat("\t", strings.Count(chantpl, "\t"))+"\n") // order by tracks for _, tr := range remote.Tracks { @@ -345,36 +330,23 @@ if tr == "latest" { chName = risk } - chInfo := chInfoT{name: chName} + var version, revision, size, notes string if ok { - chInfo.version = ch.Version - chInfo.revision = fmt.Sprintf("(%s)", ch.Revision) - if len(chInfo.revision) > maxRevLen { - maxRevLen = len(chInfo.revision) - } - chInfo.released = ch.ReleasedAt.Format(releasedfmt) - chInfo.size = strutil.SizeToStr(ch.Size) - if len(chInfo.size) > maxSizeLen { - maxSizeLen = len(chInfo.size) - } - chInfo.notes = NotesFromChannelSnapInfo(ch).String() + version = ch.Version + revision = fmt.Sprintf("(%s)", ch.Revision) + size = strutil.SizeToStr(ch.Size) + notes = NotesFromChannelSnapInfo(ch).String() trackHasOpenChannel = true } else { if trackHasOpenChannel { - chInfo.version = esc.uparrow + version = "↑" } else { - chInfo.version = esc.dash + version = "–" // that's an en dash (so yaml is happy) } } - chInfos = append(chInfos, &chInfo) + fmt.Fprintf(w, " "+chantpl, chName, version, revision, size, notes) } } - - for _, chInfo := range chInfos { - fmt.Fprintf(w, " "+chantpl, chInfo.name, chInfo.version, chInfo.released, maxRevLen, chInfo.revision, maxSizeLen, chInfo.size, chInfo.notes) - } - - return maxRevLen, maxSizeLen } func formatSummary(raw string) string { @@ -386,6 +358,8 @@ } func (x *infoCmd) Execute([]string) error { + cli := Client() + termWidth, _ := termSize() termWidth -= 3 if termWidth > 100 { @@ -393,7 +367,6 @@ termWidth = 100 } - esc := x.getEscapes() w := tabwriter.NewWriter(Stdout, 2, 2, 1, ' ', 0) noneOK := true @@ -411,8 +384,8 @@ noneOK = false continue } - remote, resInfo, _ := x.client.FindOne(snapName) - local, _, _ := x.client.Snap(snapName) + remote, resInfo, _ := cli.FindOne(snapName) + local, _, _ := cli.Snap(snapName) both := coalesce(local, remote) @@ -428,13 +401,17 @@ fmt.Fprintf(w, "name:\t%s\n", both.Name) fmt.Fprintf(w, "summary:\t%s\n", formatSummary(both.Summary)) - fmt.Fprintf(w, "publisher:\t%s\n", longPublisher(esc, both.Publisher)) + publisher := "–" // that's an en dash (so yaml is happy) + if both.Publisher != nil { + publisher = both.Publisher.Username + } + fmt.Fprintf(w, "publisher:\t%s\n", publisher) if both.Contact != "" { fmt.Fprintf(w, "contact:\t%s\n", strings.TrimPrefix(both.Contact, "mailto:")) } license := both.License if license == "" { - license = "unset" + license = "unknown" } fmt.Fprintf(w, "license:\t%s\n", license) maybePrintPrice(w, remote, resInfo) @@ -473,8 +450,6 @@ maybePrintType(w, both.Type) maybePrintBase(w, both.Base, x.Verbose) maybePrintID(w, both) - var localRev, localSize string - var revLen, sizeLen int if local != nil { if local.TrackingChannel != "" { fmt.Fprintf(w, "tracking:\t%s\n", local.TrackingChannel) @@ -482,22 +457,19 @@ if !local.InstallDate.IsZero() { fmt.Fprintf(w, "refresh-date:\t%s\n", x.fmtTime(local.InstallDate)) } - localRev = fmt.Sprintf("(%s)", local.Revision) - revLen = len(localRev) - localSize = strutil.SizeToStr(local.InstalledSize) - sizeLen = len(localSize) } - chantpl := "%s:\t%s %s%*s %*s %s\n" + chantpl := "%s:\t%s %s %s %s\n" if remote != nil && remote.Channels != nil && remote.Tracks != nil { - chantpl = "%s:\t%s\t%s\t%*s\t%*s\t%s\n" + chantpl = "%s:\t%s\t%s\t%s\t%s\n" w.Flush() - revLen, sizeLen = x.displayChannels(w, chantpl, esc, remote, revLen, sizeLen) + displayChannels(w, chantpl, remote) } if local != nil { + revstr := fmt.Sprintf("(%s)", local.Revision) fmt.Fprintf(w, chantpl, - "installed", local.Version, "", revLen, localRev, sizeLen, localSize, notes) + "installed", local.Version, revstr, strutil.SizeToStr(local.InstalledSize), notes) } } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_info_test.go snapd-2.34.2+18.04/cmd/snap/cmd_info_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_info_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_info_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -115,12 +115,12 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"info", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, `name: hello summary: GNU Hello, the "hello world" snap -publisher: Canonical* +publisher: canonical license: Proprietary price: 1.99GBP description: | @@ -203,8 +203,7 @@ "revision": "1", "version": "2.10", "channel": "1/stable", - "size": 65536, - "released-at": "2018-12-18T15:16:56.723501Z" + "size": 65536 } }, "tracks": ["1"] @@ -235,12 +234,12 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"info", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, `name: hello summary: The GNU Hello snap -publisher: Canonical* +publisher: canonical license: MIT description: | GNU hello prints a friendly greeting. This is part of the snapcraft tour at @@ -304,7 +303,7 @@ "name": "hello", "private": false, "resource": "/v2/snaps/hello", - "revision": "100", + "revision": "1", "status": "available", "summary": "The GNU Hello snap", "type": "app", @@ -333,12 +332,12 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"info", "--abs-time", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, `name: hello summary: The GNU Hello snap -publisher: Canonical* +publisher: canonical license: BSD-3 description: | GNU hello prints a friendly greeting. This is part of the snapcraft tour at @@ -369,20 +368,20 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"info", "--abs-time", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, `name: hello summary: The GNU Hello snap -publisher: Canonical* -license: unset +publisher: canonical +license: unknown description: | GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/ snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 tracking: beta refresh-date: 2006-01-02T22:04:07Z -installed: 2.10 (100) 1kB disabled +installed: 2.10 (1) 1kB disabled `) c.Check(s.Stderr(), check.Equals, "") } @@ -391,92 +390,41 @@ n := 0 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { switch n { - case 0, 2, 4: + case 0: c.Check(r.Method, check.Equals, "GET") c.Check(r.URL.Path, check.Equals, "/v2/find") fmt.Fprintln(w, mockInfoJSONWithChannels) - case 1, 3, 5: + case 1: c.Check(r.Method, check.Equals, "GET") c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") fmt.Fprintln(w, mockInfoJSONNoLicense) default: - c.Fatalf("expected to get 6 requests, now on %d (%v)", n+1, r) + c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r) } n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"info", "--abs-time", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, `name: hello summary: The GNU Hello snap -publisher: Canonical* -license: unset +publisher: canonical +license: unknown description: | GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/ snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 tracking: beta refresh-date: 2006-01-02T22:04:07Z -channels: - 1/stable: 2.10 2018-12-18T15:16:56Z (1) 65kB - - 1/candidate: ^ - 1/beta: ^ - 1/edge: ^ -installed: 2.10 (100) 1kB disabled -`) - c.Check(s.Stderr(), check.Equals, "") - c.Check(n, check.Equals, 2) - - // now the same but without abs-time - s.ResetStdStreams() - rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) - c.Assert(err, check.IsNil) - c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Equals, `name: hello -summary: The GNU Hello snap -publisher: Canonical* -license: unset -description: | - GNU hello prints a friendly greeting. This is part of the snapcraft tour at - https://snapcraft.io/ -snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 -tracking: beta -refresh-date: 2006-01-02 -channels: - 1/stable: 2.10 2018-12-18 (1) 65kB - - 1/candidate: ^ - 1/beta: ^ - 1/edge: ^ -installed: 2.10 (100) 1kB disabled +channels: + 1/stable: 2.10 (1) 65kB - + 1/candidate: ↑ + 1/beta: ↑ + 1/edge: ↑ +installed: 2.10 (1) 1kB disabled `) c.Check(s.Stderr(), check.Equals, "") - c.Check(n, check.Equals, 4) - - // now the same but with unicode on - s.ResetStdStreams() - rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"info", "--unicode=always", "hello"}) - c.Assert(err, check.IsNil) - c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Equals, `name: hello -summary: The GNU Hello snap -publisher: Canonical✓ -license: unset -description: | - GNU hello prints a friendly greeting. This is part of the snapcraft tour at - https://snapcraft.io/ -snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 -tracking: beta -refresh-date: 2006-01-02 -channels: - 1/stable: 2.10 2018-12-18 (1) 65kB - - 1/candidate: ↑ - 1/beta: ↑ - 1/edge: ↑ -installed: 2.10 (100) 1kB disabled -`) - c.Check(s.Stderr(), check.Equals, "") - c.Check(n, check.Equals, 6) } func (s *infoSuite) TestInfoHumanTimes(c *check.C) { @@ -501,20 +449,20 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) + rest, err := snap.Parser().ParseArgs([]string{"info", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, `name: hello summary: The GNU Hello snap -publisher: Canonical* -license: unset +publisher: canonical +license: unknown description: | GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/ snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 tracking: beta refresh-date: TOTALLY NOT A ROBOT -installed: 2.10 (100) 1kB disabled +installed: 2.10 (1) 1kB disabled `) c.Check(s.Stderr(), check.Equals, "") } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_interface.go snapd-2.34.2+18.04/cmd/snap/cmd_interface.go --- snapd-2.37.1+18.04/cmd/snap/cmd_interface.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_interface.go 2018-07-19 10:05:50.000000000 +0000 @@ -28,12 +28,12 @@ "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/snap" "github.com/jessevdk/go-flags" ) type cmdInterface struct { - clientMixin ShowAttrs bool `long:"attrs"` ShowAll bool `long:"all"` Positionals struct { @@ -41,7 +41,7 @@ } `positional-args:"true"` } -var shortInterfaceHelp = i18n.G("Show details of snap interfaces") +var shortInterfaceHelp = i18n.G("List snap interfaces") var longInterfaceHelp = i18n.G(` The interface command shows details of snap interfaces. @@ -53,14 +53,12 @@ addCommand("interface", shortInterfaceHelp, longInterfaceHelp, func() flags.Commander { return &cmdInterface{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. "attrs": i18n.G("Show interface attributes"), - // TRANSLATORS: This should not start with a lowercase letter. - "all": i18n.G("Include unused interfaces"), + "all": i18n.G("Include unused interfaces"), }, []argDesc{{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Show details of a specific interface"), }}) } @@ -73,7 +71,7 @@ if x.Positionals.Interface != "" { // Show one interface in detail. name := string(x.Positionals.Interface) - ifaces, err := x.client.Interfaces(&client.InterfaceOptions{ + ifaces, err := Client().Interfaces(&client.InterfaceOptions{ Names: []string{name}, Doc: true, Plugs: true, @@ -88,7 +86,7 @@ x.showOneInterface(ifaces[0]) } else { // Show an overview of available interfaces. - ifaces, err := x.client.Interfaces(&client.InterfaceOptions{ + ifaces, err := Client().Interfaces(&client.InterfaceOptions{ Connected: !x.ShowAll, }) if err != nil { @@ -124,9 +122,9 @@ labelPart = fmt.Sprintf(" (%s)", plug.Label) } if plug.Name == iface.Name { - fmt.Fprintf(w, " - %s%s", plug.Snap, labelPart) + fmt.Fprintf(w, " - %s%s", snap.UseNick(plug.Snap), labelPart) } else { - fmt.Fprintf(w, ` - %s:%s%s`, plug.Snap, plug.Name, labelPart) + fmt.Fprintf(w, ` - %s:%s%s`, snap.UseNick(plug.Snap), plug.Name, labelPart) } // Print a colon which will make the snap:plug element a key-value // yaml object so that we can write the attributes. @@ -146,9 +144,9 @@ labelPart = fmt.Sprintf(" (%s)", slot.Label) } if slot.Name == iface.Name { - fmt.Fprintf(w, " - %s%s", slot.Snap, labelPart) + fmt.Fprintf(w, " - %s%s", snap.UseNick(slot.Snap), labelPart) } else { - fmt.Fprintf(w, ` - %s:%s%s`, slot.Snap, slot.Name, labelPart) + fmt.Fprintf(w, ` - %s:%s%s`, snap.UseNick(slot.Snap), slot.Name, labelPart) } // Print a colon which will make the snap:slot element a key-value // yaml object so that we can write the attributes. diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_interfaces.go snapd-2.34.2+18.04/cmd/snap/cmd_interfaces.go --- snapd-2.37.1+18.04/cmd/snap/cmd_interfaces.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_interfaces.go 2018-07-19 10:05:50.000000000 +0000 @@ -23,19 +23,19 @@ "fmt" "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/snap" "github.com/jessevdk/go-flags" ) type cmdInterfaces struct { - clientMixin Interface string `short:"i"` Positionals struct { Query interfacesSlotOrPlugSpec `skip-help:"true"` } `positional-args:"true"` } -var shortInterfacesHelp = i18n.G("List interfaces' slots and plugs") +var shortInterfacesHelp = i18n.G("List interfaces in the system") var longInterfacesHelp = i18n.G(` The interfaces command lists interfaces available in the system. @@ -59,12 +59,11 @@ addCommand("interfaces", shortInterfacesHelp, longInterfacesHelp, func() flags.Commander { return &cmdInterfaces{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. "i": i18n.G("Constrain listing to specific interfaces"), }, []argDesc{{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(":"), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Constrain listing to a specific snap or snap:name"), }}) } @@ -74,7 +73,7 @@ return ErrExtraArgs } - ifaces, err := x.client.Connections() + ifaces, err := Client().Connections() if err != nil { return err } @@ -90,24 +89,12 @@ for _, slot := range ifaces.Slots { if wantedSnap != "" { var ok bool - if wantedSnap == slot.Snap { - ok = true - } - // Normally snap nicknames are handled internally in the snapd - // layer. This specific command is an exception as it does - // client-side filtering. As a special case, when the user asked - // for the snap "core" but we see the "system" nickname or the - // "snapd" snap, treat that as a match. - // - // The system nickname was returned in 2.35. - // The snapd snap is returned by 2.36+ if snapd snap is installed - // and is the host for implicit interfaces. - if (wantedSnap == "core" || wantedSnap == "snapd" || wantedSnap == "system") && (slot.Snap == "core" || slot.Snap == "snapd" || slot.Snap == "system") { + if wantedSnap == slot.Snap || wantedSnap == snap.UseNick(slot.Snap) { ok = true } for i := 0; i < len(slot.Connections) && !ok; i++ { - if wantedSnap == slot.Connections[i].Snap { + if wantedSnap == slot.Connections[i].Snap || wantedSnap == snap.UseNick(slot.Connections[i].Snap) { ok = true } } @@ -121,10 +108,9 @@ if x.Interface != "" && slot.Interface != x.Interface { continue } - // There are two special snaps, the "core" and "snapd" snaps are - // abbreviated to an empty snap name. The "system" snap name is still - // here in case we talk to older snapd for some reason. - if slot.Snap == "core" || slot.Snap == "snapd" || slot.Snap == "system" { + // The OS snap is special and enable abbreviated + // display syntax on the slot-side of the connection. + if slot.Snap == "core" || slot.Snap == "ubuntu-core" || slot.Snap == snap.UseNick("core") { fmt.Fprintf(w, ":%s\t", slot.Name) } else { fmt.Fprintf(w, "%s:%s\t", slot.Snap, slot.Name) @@ -149,7 +135,7 @@ // plug, the loop below focuses on printing just the disconnected plugs. for _, plug := range ifaces.Plugs { if wantedSnap != "" { - if wantedSnap != plug.Snap { + if wantedSnap != plug.Snap && wantedSnap != snap.UseNick(plug.Snap) { continue } } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_interfaces_test.go snapd-2.34.2+18.04/cmd/snap/cmd_interfaces_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_interfaces_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_interfaces_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -50,7 +50,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces"}) + rest, err := Parser().ParseArgs([]string{"interfaces"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -81,7 +81,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces"}) + rest, err := Parser().ParseArgs([]string{"interfaces"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -132,7 +132,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces"}) + rest, err := Parser().ParseArgs([]string{"interfaces"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -143,7 +143,7 @@ s.SetUpTest(c) // should be the same - rest, err = Parser(Client()).ParseArgs([]string{"interfaces", "canonical-pi2"}) + rest, err = Parser().ParseArgs([]string{"interfaces", "canonical-pi2"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Assert(s.Stdout(), Equals, expectedStdout) @@ -151,7 +151,7 @@ s.SetUpTest(c) // and the same again - rest, err = Parser(Client()).ParseArgs([]string{"interfaces", "keyboard-lights"}) + rest, err = Parser().ParseArgs([]string{"interfaces", "keyboard-lights"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Assert(s.Stdout(), Equals, expectedStdout) @@ -189,7 +189,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces"}) + rest, err := Parser().ParseArgs([]string{"interfaces"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -256,7 +256,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces"}) + rest, err := Parser().ParseArgs([]string{"interfaces"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -278,7 +278,7 @@ "result": client.Connections{ Slots: []client.Slot{ { - Snap: "system", + Snap: "core", Name: "network-listening", Interface: "network-listening", Label: "Ability to be a network service", @@ -302,7 +302,7 @@ Label: "Ability to be a network service", Connections: []client.SlotRef{ { - Snap: "system", + Snap: "core", Name: "network-listening", }, }, @@ -314,7 +314,7 @@ Label: "Ability to be a network service", Connections: []client.SlotRef{ { - Snap: "system", + Snap: "core", Name: "network-listening", }, }, @@ -323,7 +323,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces"}) + rest, err := Parser().ParseArgs([]string{"interfaces"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -372,7 +372,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces", "-i=serial-port"}) + rest, err := Parser().ParseArgs([]string{"interfaces", "-i=serial-port"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -415,7 +415,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces", "wake-up-alarm"}) + rest, err := Parser().ParseArgs([]string{"interfaces", "wake-up-alarm"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -438,7 +438,7 @@ "result": client.Connections{ Slots: []client.Slot{ { - Snap: "system", + Snap: "core", Name: "core-support", Interface: "some-iface", Connections: []client.PlugRef{{Snap: "core", Name: "core-support-plug"}}, @@ -453,13 +453,13 @@ Snap: "core", Name: "core-support-plug", Interface: "some-iface", - Connections: []client.SlotRef{{Snap: "system", Name: "core-support"}}, + Connections: []client.SlotRef{{Snap: "core", Name: "core-support"}}, }, }, }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces", "system"}) + rest, err := Parser().ParseArgs([]string{"interfaces", "core"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -471,7 +471,7 @@ s.ResetStdStreams() // when called with system nickname we get the same output - rest, err = Parser(Client()).ParseArgs([]string{"interfaces", "system"}) + rest, err = Parser().ParseArgs([]string{"interfaces", "system"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdoutSystem := "" + @@ -514,7 +514,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces", "wake-up-alarm:snooze"}) + rest, err := Parser().ParseArgs([]string{"interfaces", "wake-up-alarm:snooze"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -536,7 +536,7 @@ "result": client.Connections{}, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces"}) + rest, err := Parser().ParseArgs([]string{"interfaces"}) c.Assert(err, ErrorMatches, "no interfaces found") // XXX: not sure why this is returned, I guess that's what happens when a // command Execute returns an error. @@ -578,7 +578,7 @@ }, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces", "-i", "bool-file"}) + rest, err := Parser().ParseArgs([]string{"interfaces", "-i", "bool-file"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -607,7 +607,7 @@ defer os.Unsetenv("GO_FLAGS_COMPLETION") expected := []flags.Completion{} - parser := Parser(Client()) + parser := Parser() parser.CompletionHandler = func(obtained []flags.Completion) { c.Check(obtained, DeepEquals, expected) } @@ -627,48 +627,3 @@ c.Assert(s.Stdout(), Equals, "") c.Assert(s.Stderr(), Equals, "") } - -func (s *SnapSuite) TestConnectionsCoreNicknamedSystem(c *C) { - s.checkConnectionsSystemCoreRemapping(c, "core", "system") -} - -func (s *SnapSuite) TestConnectionsSnapdNicknamedSystem(c *C) { - s.checkConnectionsSystemCoreRemapping(c, "snapd", "system") -} - -func (s *SnapSuite) TestConnectionsSnapdNicknamedCore(c *C) { - s.checkConnectionsSystemCoreRemapping(c, "snapd", "core") -} - -func (s *SnapSuite) TestConnectionsCoreSnap(c *C) { - s.checkConnectionsSystemCoreRemapping(c, "core", "core") -} - -func (s *SnapSuite) checkConnectionsSystemCoreRemapping(c *C, apiSnapName, cliSnapName string) { - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.Method, Equals, "GET") - c.Check(r.URL.Path, Equals, "/v2/interfaces") - body, err := ioutil.ReadAll(r.Body) - c.Check(err, IsNil) - c.Check(body, DeepEquals, []byte{}) - EncodeResponseBody(c, w, map[string]interface{}{ - "type": "sync", - "result": client.Connections{ - Slots: []client.Slot{ - { - Snap: apiSnapName, - Name: "network", - }, - }, - }, - }) - }) - rest, err := Parser(Client()).ParseArgs([]string{"interfaces", cliSnapName}) - c.Assert(err, IsNil) - c.Assert(rest, DeepEquals, []string{}) - expectedStdout := "" + - "Slot Plug\n" + - ":network -\n" - c.Assert(s.Stdout(), Equals, expectedStdout) - c.Assert(s.Stderr(), Equals, "") -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_interface_test.go snapd-2.34.2+18.04/cmd/snap/cmd_interface_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_interface_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_interface_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -41,13 +41,15 @@ one connection is shown, or a list of all interfaces if --all is provided. [interface command options] - --attrs Show interface attributes - --all Include unused interfaces + --attrs Show interface attributes + --all Include unused interfaces [interface command arguments] - : Show details of a specific interface + : Show details of a specific interface ` - s.testSubCommandHelp(c, "interface", msg) + rest, err := Parser().ParseArgs([]string{"interface", "--help"}) + c.Assert(err.Error(), Equals, msg) + c.Assert(rest, DeepEquals, []string{}) } func (s *SnapSuite) TestInterfaceListEmpty(c *C) { @@ -63,7 +65,7 @@ "result": []*client.Interface{}, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interface"}) + rest, err := Parser().ParseArgs([]string{"interface"}) c.Assert(err, ErrorMatches, "no interfaces currently connected") c.Assert(rest, DeepEquals, []string{"interface"}) c.Assert(s.Stdout(), Equals, "") @@ -83,7 +85,7 @@ "result": []*client.Interface{}, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interface", "--all"}) + rest, err := Parser().ParseArgs([]string{"interface", "--all"}) c.Assert(err, ErrorMatches, "no interfaces found") c.Assert(rest, DeepEquals, []string{"--all"}) // XXX: feels like a bug in go-flags. c.Assert(s.Stdout(), Equals, "") @@ -109,7 +111,7 @@ }}, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interface"}) + rest, err := Parser().ParseArgs([]string{"interface"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -142,7 +144,7 @@ }}, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interface", "--all"}) + rest, err := Parser().ParseArgs([]string{"interface", "--all"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -172,11 +174,11 @@ {Snap: "deepin-music", Name: "network"}, {Snap: "http", Name: "network"}, }, - Slots: []client.Slot{{Snap: "system", Name: "network"}}, + Slots: []client.Slot{{Snap: "core", Name: "network"}}, }}, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interface", "network"}) + rest, err := Parser().ParseArgs([]string{"interface", "network"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -222,7 +224,7 @@ }}, }) }) - rest, err := Parser(Client()).ParseArgs([]string{"interface", "--attrs", "serial-port"}) + rest, err := Parser().ParseArgs([]string{"interface", "--attrs", "serial-port"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedStdout := "" + @@ -260,7 +262,7 @@ defer os.Unsetenv("GO_FLAGS_COMPLETION") expected := []flags.Completion{} - parser := Parser(Client()) + parser := Parser() parser.CompletionHandler = func(obtained []flags.Completion) { c.Check(obtained, DeepEquals, expected) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_keys.go snapd-2.34.2+18.04/cmd/snap/cmd_keys.go --- snapd-2.37.1+18.04/cmd/snap/cmd_keys.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_keys.go 2018-07-19 10:05:50.000000000 +0000 @@ -42,10 +42,7 @@ `), func() flags.Commander { return &cmdKeys{} - }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "json": i18n.G("Output results in JSON format"), - }, nil) + }, map[string]string{"json": i18n.G("Output results in JSON format")}, nil) cmd.hidden = true } @@ -66,7 +63,7 @@ func outputText(keys []Key) error { if len(keys) == 0 { - fmt.Fprintf(Stderr, "No keys registered, see `snapcraft create-key`\n") + fmt.Fprintf(Stdout, "No keys registered, see `snapcraft create-key`") return nil } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_keys_test.go snapd-2.34.2+18.04/cmd/snap/cmd_keys_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_keys_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_keys_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -25,7 +25,6 @@ "io/ioutil" "os" "path/filepath" - "testing" . "gopkg.in/check.v1" @@ -61,9 +60,6 @@ `) func (s *SnapKeysSuite) SetUpTest(c *C) { - if testing.Short() && s.GnupgCmd == "/usr/bin/gpg2" { - c.Skip("gpg2 does not do short tests") - } s.BaseSnapSuite.SetUpTest(c) s.tempdir = c.MkDir() @@ -91,7 +87,7 @@ } func (s *SnapKeysSuite) TestKeys(c *C) { - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"keys"}) + rest, err := snap.Parser().ParseArgs([]string{"keys"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Check(s.Stdout(), Matches, `Name +SHA3-384 @@ -106,15 +102,15 @@ err := os.RemoveAll(s.tempdir) c.Assert(err, IsNil) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"keys"}) + rest, err := snap.Parser().ParseArgs([]string{"keys"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) - c.Check(s.Stdout(), Equals, "") - c.Check(s.Stderr(), Equals, "No keys registered, see `snapcraft create-key`\n") + c.Check(s.Stdout(), Equals, "No keys registered, see `snapcraft create-key`") + c.Check(s.Stderr(), Equals, "") } func (s *SnapKeysSuite) TestKeysJSON(c *C) { - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"keys", "--json"}) + rest, err := snap.Parser().ParseArgs([]string{"keys", "--json"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) expectedResponse := []snap.Key{ @@ -136,7 +132,7 @@ func (s *SnapKeysSuite) TestKeysJSONEmpty(c *C) { err := os.RemoveAll(os.Getenv("SNAP_GNUPG_HOME")) c.Assert(err, IsNil) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"keys", "--json"}) + rest, err := snap.Parser().ParseArgs([]string{"keys", "--json"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Check(s.Stdout(), Equals, "[]\n") diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_known.go snapd-2.34.2+18.04/cmd/snap/cmd_known.go --- snapd-2.37.1+18.04/cmd/snap/cmd_known.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_known.go 2018-07-19 10:05:50.000000000 +0000 @@ -32,7 +32,6 @@ ) type cmdKnown struct { - clientMixin KnownOptions struct { // XXX: how to get a list of assert types for completion? AssertTypeName assertTypeName `required:"true"` @@ -54,14 +53,14 @@ return &cmdKnown{} }, nil, []argDesc{ { - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Assertion type name"), }, { - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G("
"), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Constrain listing to those matching header=value"), }, }) @@ -113,7 +112,7 @@ if x.Remote { assertions, err = downloadAssertion(string(x.KnownOptions.AssertTypeName), headers) } else { - assertions, err = x.client.Known(string(x.KnownOptions.AssertTypeName), headers) + assertions, err = Client().Known(string(x.KnownOptions.AssertTypeName), headers) } if err != nil { return err diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_known_test.go snapd-2.34.2+18.04/cmd/snap/cmd_known_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_known_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_known_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -78,7 +78,7 @@ n++ })) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"known", "--remote", "model", "series=16", "brand-id=canonical", "model=pi99"}) + rest, err := snap.Parser().ParseArgs([]string{"known", "--remote", "model", "series=16", "brand-id=canonical", "model=pi99"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, mockModelAssertion) @@ -86,7 +86,7 @@ } func (s *SnapSuite) TestKnownRemoteMissingPrimaryKey(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"known", "--remote", "model", "series=16", "brand-id=canonical"}) + _, err := snap.Parser().ParseArgs([]string{"known", "--remote", "model", "series=16", "brand-id=canonical"}) c.Assert(err, check.ErrorMatches, `cannot query remote assertion: must provide primary key: model`) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_list.go snapd-2.34.2+18.04/cmd/snap/cmd_list.go --- snapd-2.37.1+18.04/cmd/snap/cmd_list.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_list.go 2018-07-19 10:05:50.000000000 +0000 @@ -36,27 +36,19 @@ var shortListHelp = i18n.G("List installed snaps") var longListHelp = i18n.G(` The list command displays a summary of snaps installed in the current system. - -A green check mark (given color and unicode support) after a publisher name -indicates that the publisher has been verified. `) type cmdList struct { - clientMixin Positional struct { Snaps []installedSnapName `positional-arg-name:""` } `positional-args:"yes"` All bool `long:"all"` - colorMixin } func init() { addCommand("list", shortListHelp, longListHelp, func() flags.Commander { return &cmdList{} }, - colorDescs.also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "all": i18n.G("Show all revisions"), - }), nil) + map[string]string{"all": i18n.G("Show all revisions")}, nil) } type snapsByName []*client.Snap @@ -65,6 +57,14 @@ func (s snapsByName) Less(i, j int) bool { return s[i].Name < s[j].Name } func (s snapsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (x *cmdList) Execute(args []string) error { + if len(args) > 0 { + return ErrExtraArgs + } + + return listSnaps(installedSnapNames(x.Positional.Snaps), x.All) +} + var ErrNoMatchingSnaps = errors.New(i18n.G("no matching snaps installed")) // snapd will give us and we want @@ -103,13 +103,9 @@ return ch } -func (x *cmdList) Execute(args []string) error { - if len(args) > 0 { - return ErrExtraArgs - } - - names := installedSnapNames(x.Positional.Snaps) - snaps, err := x.client.List(names, &client.ListOptions{All: x.All}) +func listSnaps(names []string, all bool) error { + cli := Client() + snaps, err := cli.List(names, &client.ListOptions{All: all}) if err != nil { if err == client.ErrNoSnapsInstalled { if len(names) == 0 { @@ -125,25 +121,28 @@ } sort.Sort(snapsByName(snaps)) - esc := x.getEscapes() w := tabWriter() + defer w.Flush() - // TRANSLATORS: the %s is to insert a filler escape sequence (please keep it flush to the column header, with no extra spaces) - fmt.Fprintf(w, i18n.G("Name\tVersion\tRev\tTracking\tPublisher%s\tNotes\n"), fillerPublisher(esc)) + fmt.Fprintln(w, i18n.G("Name\tVersion\tRev\tTracking\tPublisher\tNotes")) for _, snap := range snaps { + // Aid parsing of the output by not leaving the field empty. + publisher := "-" + if snap.Publisher != nil { + publisher = snap.Publisher.Username + } // doing it this way because otherwise it's a sea of %s\t%s\t%s line := []string{ snap.Name, snap.Version, snap.Revision.String(), fmtChannel(snap.TrackingChannel), - shortPublisher(esc, snap.Publisher), + publisher, NotesFromLocal(snap).String(), } fmt.Fprintln(w, strings.Join(line, "\t")) } - w.Flush() return nil } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_list_test.go snapd-2.34.2+18.04/cmd/snap/cmd_list_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_list_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_list_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -34,17 +34,12 @@ The list command displays a summary of snaps installed in the current system. -A green check mark (given color and unicode support) after a publisher name -indicates that the publisher has been verified. - [list command options] - --all Show all revisions - --color=[auto|never|always] Use a little bit of color to highlight - some things. (default: auto) - --unicode=[auto|never|always] Use a little bit of Unicode to improve - legibility. (default: auto) + --all Show all revisions ` - s.testSubCommandHelp(c, "list", msg) + rest, err := snap.Parser().ParseArgs([]string{"list", "--help"}) + c.Assert(err.Error(), check.Equals, msg) + c.Assert(rest, check.DeepEquals, []string{}) } func (s *SnapSuite) TestList(c *check.C) { @@ -62,7 +57,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"list"}) + rest, err := snap.Parser().ParseArgs([]string{"list"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `Name +Version +Rev +Tracking +Publisher +Notes @@ -86,7 +81,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"list", "--all"}) + rest, err := snap.Parser().ParseArgs([]string{"list", "--all"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `Name +Version +Rev +Tracking +Publisher +Notes @@ -109,7 +104,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"list"}) + rest, err := snap.Parser().ParseArgs([]string{"list"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, "") @@ -130,7 +125,7 @@ n++ }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"list", "quux"}) + _, err := snap.Parser().ParseArgs([]string{"list", "quux"}) c.Assert(err, check.ErrorMatches, `no matching snaps installed`) } @@ -149,7 +144,7 @@ n++ }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"list", "quux"}) + _, err := snap.Parser().ParseArgs([]string{"list", "quux"}) c.Assert(err, check.ErrorMatches, "no matching snaps installed") } @@ -168,7 +163,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"list", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"list", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `Name +Version +Rev +Tracking +Publisher +Notes @@ -198,7 +193,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"list"}) + rest, err := snap.Parser().ParseArgs([]string{"list"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `(?ms)^Name +Version +Rev +Tracking +Publisher +Notes$`) diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_login.go snapd-2.34.2+18.04/cmd/snap/cmd_login.go --- snapd-2.37.1+18.04/cmd/snap/cmd_login.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_login.go 2018-07-19 10:05:50.000000000 +0000 @@ -31,7 +31,6 @@ ) type cmdLogin struct { - clientMixin Positional struct { Email string } `positional-args:"yes"` @@ -58,14 +57,14 @@ func() flags.Commander { return &cmdLogin{} }, nil, []argDesc{{ - // TRANSLATORS: This is a noun, and it needs to begin with < and end with > + // TRANSLATORS: This is a noun, and it needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter (unless it's "login.ubuntu.com") + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("The login.ubuntu.com email to login as"), }}) } -func requestLoginWith2faRetry(cli *client.Client, email, password string) error { +func requestLoginWith2faRetry(email, password string) error { var otp []byte var err error @@ -75,6 +74,7 @@ i18n.G("Wrong again. Once more: "), } + cli := Client() reader := bufio.NewReader(nil) for i := 0; ; i++ { @@ -94,7 +94,7 @@ } } -func requestLogin(cli *client.Client, email string) error { +func requestLogin(email string) error { fmt.Fprint(Stdout, fmt.Sprintf(i18n.G("Password of %q: "), email)) password, err := ReadPassword(0) fmt.Fprint(Stdout, "\n") @@ -103,7 +103,7 @@ } // strings.TrimSpace needed because we get \r from the pty in the tests - return requestLoginWith2faRetry(cli, email, strings.TrimSpace(string(password))) + return requestLoginWith2faRetry(email, strings.TrimSpace(string(password))) } func (x *cmdLogin) Execute(args []string) error { @@ -125,7 +125,7 @@ email = string(in) } - err := requestLogin(x.client, email) + err := requestLogin(email) if err != nil { return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_login_test.go snapd-2.34.2+18.04/cmd/snap/cmd_login_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_login_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_login_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -54,7 +54,7 @@ // send the password s.password = "some-password\n" - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"login", "foo@example.com"}) + rest, err := snap.Parser().ParseArgs([]string{"login", "foo@example.com"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Check(s.Stdout(), Equals, `Personal information is handled as per our privacy notice at @@ -76,7 +76,7 @@ // send the password s.password = "some-password" - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"login"}) + rest, err := snap.Parser().ParseArgs([]string{"login"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) // test slightly ugly, on a real system STDOUT will be: diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_logout.go snapd-2.34.2+18.04/cmd/snap/cmd_logout.go --- snapd-2.37.1+18.04/cmd/snap/cmd_logout.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_logout.go 2018-07-19 10:05:50.000000000 +0000 @@ -25,9 +25,7 @@ "github.com/snapcore/snapd/i18n" ) -type cmdLogout struct { - clientMixin -} +type cmdLogout struct{} var shortLogoutHelp = i18n.G("Log out of snapd and the store") @@ -49,5 +47,5 @@ return ErrExtraArgs } - return cmd.client.Logout() + return Client().Logout() } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_managed.go snapd-2.34.2+18.04/cmd/snap/cmd_managed.go --- snapd-2.37.1+18.04/cmd/snap/cmd_managed.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_managed.go 2018-07-19 10:05:50.000000000 +0000 @@ -33,9 +33,7 @@ snapd has registered users. `) -type cmdIsManaged struct { - clientMixin -} +type cmdIsManaged struct{} func init() { cmd := addCommand("managed", shortIsManagedHelp, longIsManagedHelp, func() flags.Commander { return &cmdIsManaged{} }, nil, nil) @@ -47,7 +45,7 @@ return ErrExtraArgs } - sysinfo, err := cmd.client.SysInfo() + sysinfo, err := Client().SysInfo() if err != nil { return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_managed_test.go snapd-2.34.2+18.04/cmd/snap/cmd_managed_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_managed_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_managed_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -39,7 +39,7 @@ fmt.Fprintf(w, `{"type":"sync", "status-code": 200, "result": {"managed":%v}}`, managed) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"managed"}) + _, err := snap.Parser().ParseArgs([]string{"managed"}) c.Assert(err, IsNil) c.Check(s.Stdout(), Equals, fmt.Sprintf("%v\n", managed)) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_pack.go snapd-2.34.2+18.04/cmd/snap/cmd_pack.go --- snapd-2.37.1+18.04/cmd/snap/cmd_pack.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_pack.go 2018-07-19 10:05:50.000000000 +0000 @@ -21,7 +21,6 @@ import ( "fmt" - "path/filepath" "github.com/jessevdk/go-flags" @@ -31,8 +30,7 @@ ) type packCmd struct { - CheckSkeleton bool `long:"check-skeleton"` - Filename string `long:"filename"` + CheckSkeleton bool `long:"check-skeleton"` Positional struct { SnapDir string `positional-arg-name:""` TargetDir string `positional-arg-name:""` @@ -46,11 +44,6 @@ directory. If both source-dir and target-dir are omitted, the pack command packs the current directory. -The default file name for a snap can be derived entirely from its snap.yaml, but -in some situations it's simpler for a script to feed the filename in. In those -cases, --filename can be given to override the default. If this filename is -not absolute it will be taken as relative to target-dir. - When used with --check-skeleton, pack only checks whether snap-dir contains valid snap metadata and raises an error otherwise. Application commands listed in snap metadata file, but appearing with incorrect permission bits result in an @@ -59,28 +52,17 @@ `) func init() { - cmd := addCommand("pack", + addCommand("pack", shortPackHelp, longPackHelp, func() flags.Commander { return &packCmd{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. "check-skeleton": i18n.G("Validate snap-dir metadata only"), - // TRANSLATORS: This should not start with a lowercase letter. - "filename": i18n.G("Output to this filename"), }, nil) - cmd.extra = func(cmd *flags.Command) { - // TRANSLATORS: this describes the default filename for a snap, e.g. core_16-2.35.2_amd64.snap - cmd.FindOptionByLongName("filename").DefaultMask = i18n.G("__.snap") - } } func (x *packCmd) Execute([]string) error { - if x.Positional.TargetDir != "" && x.Filename != "" && filepath.IsAbs(x.Filename) { - return fmt.Errorf(i18n.G("you can't specify an absolute filename while also specifying target dir.")) - } - if x.Positional.SnapDir == "" { x.Positional.SnapDir = "." } @@ -96,14 +78,11 @@ return err } - snapPath, err := pack.Snap(x.Positional.SnapDir, x.Positional.TargetDir, x.Filename) + snapPath, err := pack.Snap(x.Positional.SnapDir, x.Positional.TargetDir) if err != nil { - // TRANSLATORS: the %q is the snap-dir (the first positional - // argument to the command); the %v is an error - return fmt.Errorf(i18n.G("cannot pack %q: %v"), x.Positional.SnapDir, err) + return fmt.Errorf("cannot pack %q: %v", x.Positional.SnapDir, err) } - // TRANSLATORS: %s is the path to the built snap file - fmt.Fprintf(Stdout, i18n.G("built: %s\n"), snapPath) + fmt.Fprintf(Stdout, "built: %s\n", snapPath) return nil } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_pack_test.go snapd-2.34.2+18.04/cmd/snap/cmd_pack_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_pack_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_pack_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -39,7 +39,7 @@ snapDir := makeSnapDirForPack(c, packSnapYaml) // check-skeleton does not fail due to missing files - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"pack", "--check-skeleton", snapDir}) + _, err := snaprun.Parser().ParseArgs([]string{"pack", "--check-skeleton", snapDir}) c.Assert(err, check.IsNil) } @@ -51,8 +51,8 @@ ` snapDir := makeSnapDirForPack(c, snapYaml) - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"pack", "--check-skeleton", snapDir}) - c.Assert(err, check.ErrorMatches, `cannot validate snap "": snap name cannot be empty`) + _, err := snaprun.Parser().ParseArgs([]string{"pack", "--check-skeleton", snapDir}) + c.Assert(err, check.ErrorMatches, "snap name cannot be empty") } func (s *SnapSuite) TestPackCheckSkeletonConflictingCommonID(c *check.C) { @@ -67,8 +67,8 @@ ` snapDir := makeSnapDirForPack(c, snapYaml) - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"pack", "--check-skeleton", snapDir}) - c.Assert(err, check.ErrorMatches, `cannot validate snap "foo": application ("bar" common-id "org.foo.foo" must be unique, already used by application "foo"|"foo" common-id "org.foo.foo" must be unique, already used by application "bar")`) + _, err := snaprun.Parser().ParseArgs([]string{"pack", "--check-skeleton", snapDir}) + c.Assert(err, check.ErrorMatches, `application ("bar" common-id "org.foo.foo" must be unique, already used by application "foo"|"foo" common-id "org.foo.foo" must be unique, already used by application "bar")`) } func (s *SnapSuite) TestPackPacksFailsForMissingPaths(c *check.C) { @@ -77,7 +77,7 @@ snapDir := makeSnapDirForPack(c, packSnapYaml) - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"pack", snapDir, snapDir}) + _, err := snaprun.Parser().ParseArgs([]string{"pack", snapDir, snapDir}) c.Assert(err, check.ErrorMatches, ".* snap is unusable due to missing files") } @@ -94,7 +94,7 @@ err = ioutil.WriteFile(filepath.Join(binDir, "hello"), []byte(helloBinContent), 0755) c.Assert(err, check.IsNil) - _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"pack", snapDir, snapDir}) + _, err = snaprun.Parser().ParseArgs([]string{"pack", snapDir, snapDir}) c.Assert(err, check.IsNil) matches, err := filepath.Glob(snapDir + "/hello*.snap") diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_paths.go snapd-2.34.2+18.04/cmd/snap/cmd_paths.go --- snapd-2.37.1+18.04/cmd/snap/cmd_paths.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_paths.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,62 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main - -import ( - "fmt" - - "github.com/jessevdk/go-flags" - - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/i18n" -) - -var pathsHelp = i18n.G("Print system paths") -var longPathsHelp = i18n.G(` -The paths command prints the list of paths detected and used by snapd. -`) - -type cmdPaths struct{} - -func init() { - addDebugCommand("paths", pathsHelp, longPathsHelp, func() flags.Commander { - return &cmdPaths{} - }, nil, nil) -} - -func (cmd cmdPaths) Execute(args []string) error { - if len(args) > 0 { - return ErrExtraArgs - } - - // TODO: include paths reported by snap-confine - for _, p := range []struct { - name string - path string - }{ - {"SNAPD_MOUNT", dirs.SnapMountDir}, - {"SNAPD_BIN", dirs.SnapBinariesDir}, - {"SNAPD_LIBEXEC", dirs.DistroLibExecDir}, - } { - fmt.Fprintf(Stdout, "%s=%s\n", p.name, p.path) - } - - return nil -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_paths_test.go snapd-2.34.2+18.04/cmd/snap/cmd_paths_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_paths_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_paths_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,90 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main_test - -import ( - . "gopkg.in/check.v1" - - snap "github.com/snapcore/snapd/cmd/snap" - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/release" -) - -func (s *SnapSuite) TestPathsUbuntu(c *C) { - restore := release.MockReleaseInfo(&release.OS{ID: "ubuntu"}) - defer restore() - defer dirs.SetRootDir("/") - - dirs.SetRootDir("/") - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "paths"}) - c.Assert(err, IsNil) - c.Assert(s.Stdout(), Equals, ""+ - "SNAPD_MOUNT=/snap\n"+ - "SNAPD_BIN=/snap/bin\n"+ - "SNAPD_LIBEXEC=/usr/lib/snapd\n") - c.Assert(s.Stderr(), Equals, "") -} - -func (s *SnapSuite) TestPathsFedora(c *C) { - restore := release.MockReleaseInfo(&release.OS{ID: "fedora"}) - defer restore() - defer dirs.SetRootDir("/") - - dirs.SetRootDir("/") - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "paths"}) - c.Assert(err, IsNil) - c.Assert(s.Stdout(), Equals, ""+ - "SNAPD_MOUNT=/var/lib/snapd/snap\n"+ - "SNAPD_BIN=/var/lib/snapd/snap/bin\n"+ - "SNAPD_LIBEXEC=/usr/libexec/snapd\n") - c.Assert(s.Stderr(), Equals, "") -} - -func (s *SnapSuite) TestPathsArch(c *C) { - defer dirs.SetRootDir("/") - - // old /etc/os-release contents - restore := release.MockReleaseInfo(&release.OS{ID: "arch", IDLike: []string{"archlinux"}}) - defer restore() - - dirs.SetRootDir("/") - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "paths"}) - c.Assert(err, IsNil) - c.Assert(s.Stdout(), Equals, ""+ - "SNAPD_MOUNT=/var/lib/snapd/snap\n"+ - "SNAPD_BIN=/var/lib/snapd/snap/bin\n"+ - "SNAPD_LIBEXEC=/usr/lib/snapd\n") - c.Assert(s.Stderr(), Equals, "") - - s.ResetStdStreams() - - // new contents, as set by filesystem-2018.12-1 - restore = release.MockReleaseInfo(&release.OS{ID: "archlinux"}) - defer restore() - - dirs.SetRootDir("/") - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"debug", "paths"}) - c.Assert(err, IsNil) - c.Assert(s.Stdout(), Equals, ""+ - "SNAPD_MOUNT=/var/lib/snapd/snap\n"+ - "SNAPD_BIN=/var/lib/snapd/snap/bin\n"+ - "SNAPD_LIBEXEC=/usr/lib/snapd\n") - c.Assert(s.Stderr(), Equals, "") -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_prefer.go snapd-2.34.2+18.04/cmd/snap/cmd_prefer.go --- snapd-2.37.1+18.04/cmd/snap/cmd_prefer.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_prefer.go 2018-07-19 10:05:50.000000000 +0000 @@ -32,7 +32,7 @@ } `positional-args:"true"` } -var shortPreferHelp = i18n.G("Enable aliases from a snap, disabling any conflicting aliases") +var shortPreferHelp = i18n.G("Prefer aliases from a snap and disable conflicts") var longPreferHelp = i18n.G(` The prefer command enables all aliases of the given snap in preference to conflicting aliases of other snaps whose aliases will be disabled @@ -52,11 +52,12 @@ return ErrExtraArgs } - id, err := x.client.Prefer(string(x.Positionals.Snap)) + cli := Client() + id, err := cli.Prefer(string(x.Positionals.Snap)) if err != nil { return err } - chg, err := x.wait(id) + chg, err := x.wait(cli, id) if err != nil { if err == noWait { return nil diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_prefer_test.go snapd-2.34.2+18.04/cmd/snap/cmd_prefer_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_prefer_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_prefer_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -37,10 +37,12 @@ (or removed, for manual ones). [prefer command options] - --no-wait Do not wait for the operation to finish but just print the - change id. + --no-wait Do not wait for the operation to finish but just print the + change id. ` - s.testSubCommandHelp(c, "prefer", msg) + rest, err := Parser().ParseArgs([]string{"prefer", "--help"}) + c.Assert(err.Error(), Equals, msg) + c.Assert(rest, DeepEquals, []string{}) } func (s *SnapSuite) TestPrefer(c *C) { @@ -60,7 +62,7 @@ c.Fatalf("unexpected path %q", r.URL.Path) } }) - rest, err := Parser(Client()).ParseArgs([]string{"prefer", "some-snap"}) + rest, err := Parser().ParseArgs([]string{"prefer", "some-snap"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Assert(s.Stdout(), Equals, ""+ diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_prepare_image.go snapd-2.34.2+18.04/cmd/snap/cmd_prepare_image.go --- snapd-2.37.1+18.04/cmd/snap/cmd_prepare_image.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_prepare_image.go 2018-07-19 10:05:50.000000000 +0000 @@ -48,20 +48,18 @@ func() flags.Commander { return &cmdPrepareImage{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "extra-snaps": i18n.G("Extra snaps to be installed"), - // TRANSLATORS: This should not start with a lowercase letter. - "channel": i18n.G("The channel to use"), + "extra-snaps": "Extra snaps to be installed", + "channel": "The channel to use", }, []argDesc{ { - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("The model assertion name"), }, { - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("The output directory"), }, }) diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_repair_repairs_test.go snapd-2.34.2+18.04/cmd/snap/cmd_repair_repairs_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_repair_repairs_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_repair_repairs_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -45,7 +45,7 @@ mockSnapRepair := mockSnapRepair(c) defer mockSnapRepair.Restore() - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"repair", "canonical-1"}) + _, err := snap.Parser().ParseArgs([]string{"repair", "canonical-1"}) c.Assert(err, IsNil) c.Check(mockSnapRepair.Calls(), DeepEquals, [][]string{ {"snap-repair", "show", "canonical-1"}, @@ -59,7 +59,7 @@ mockSnapRepair := mockSnapRepair(c) defer mockSnapRepair.Restore() - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"repairs"}) + _, err := snap.Parser().ParseArgs([]string{"repairs"}) c.Assert(err, IsNil) c.Check(mockSnapRepair.Calls(), DeepEquals, [][]string{ {"snap-repair", "list"}, diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_run.go snapd-2.34.2+18.04/cmd/snap/cmd_run.go --- snapd-2.37.1+18.04/cmd/snap/cmd_run.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_run.go 2018-07-19 10:05:50.000000000 +0000 @@ -23,7 +23,6 @@ "bufio" "fmt" "io" - "io/ioutil" "os" "os/exec" "os/user" @@ -34,17 +33,13 @@ "syscall" "time" - "github.com/godbus/dbus" "github.com/jessevdk/go-flags" - "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/osutil/strace" - "github.com/snapcore/snapd/selinux" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snapenv" "github.com/snapcore/snapd/strutil/shlex" @@ -53,28 +48,22 @@ ) var ( - syscallExec = syscall.Exec - userCurrent = user.Current - osGetenv = os.Getenv - timeNow = time.Now - selinuxIsEnabled = selinux.IsEnabled - selinuxVerifyPathContext = selinux.VerifyPathContext - selinuxRestoreContext = selinux.RestoreContext + syscallExec = syscall.Exec + userCurrent = user.Current + osGetenv = os.Getenv + timeNow = time.Now ) type cmdRun struct { - clientMixin Command string `long:"command" hidden:"yes"` HookName string `long:"hook" hidden:"yes"` Revision string `short:"r" default:"unset" hidden:"yes"` Shell bool `long:"shell" ` - // This options is both a selector (use or don't use strace) and it // can also carry extra options for strace. This is why there is // "default" and "optional-value" to distinguish this. - Strace string `long:"strace" optional:"true" optional-value:"with-strace" default:"no-strace" default-mask:"-"` - Gdb bool `long:"gdb"` - TraceExec bool `long:"trace-exec"` + Strace string `long:"strace" optional:"true" optional-value:"with-strace" default:"no-strace" default-mask:"-"` + Gdb bool `long:"gdb"` // not a real option, used to check if cmdRun is initialized by // the parser @@ -92,27 +81,18 @@ func() flags.Commander { return &cmdRun{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "command": i18n.G("Alternative command to run"), - // TRANSLATORS: This should not start with a lowercase letter. - "hook": i18n.G("Hook to run"), - // TRANSLATORS: This should not start with a lowercase letter. - "r": i18n.G("Use a specific snap revision when running hook"), - // TRANSLATORS: This should not start with a lowercase letter. - "shell": i18n.G("Run a shell instead of the command (useful for debugging)"), - // TRANSLATORS: This should not start with a lowercase letter. - "strace": i18n.G("Run the command under strace (useful for debugging). Extra strace options can be specified as well here. Pass --raw to strace early snap helpers."), - // TRANSLATORS: This should not start with a lowercase letter. - "gdb": i18n.G("Run the command with gdb"), - // TRANSLATORS: This should not start with a lowercase letter. - "timer": i18n.G("Run as a timer service with given schedule"), - // TRANSLATORS: This should not start with a lowercase letter. - "trace-exec": i18n.G("Display exec calls timing data"), + "command": i18n.G("Alternative command to run"), + "hook": i18n.G("Hook to run"), + "r": i18n.G("Use a specific snap revision when running hook"), + "shell": i18n.G("Run a shell instead of the command (useful for debugging)"), + "strace": i18n.G("Run the command under strace (useful for debugging). Extra strace options can be specified as well here. Pass --raw to strace early snap helpers."), + "gdb": i18n.G("Run the command with gdb"), + "timer": i18n.G("Run as a timer service with given schedule"), "parser-ran": "", }, nil) } -func maybeWaitForSecurityProfileRegeneration(cli *client.Client) error { +func maybeWaitForSecurityProfileRegeneration() error { // check if the security profiles key has changed, if so, we need // to wait for snapd to re-generate all profiles mismatch, err := interfaces.SystemKeyMismatch() @@ -145,6 +125,7 @@ } } + cli := Client() for i := 0; i < timeout; i++ { if _, err := cli.SysInfo(); err == nil { return nil @@ -182,7 +163,7 @@ return fmt.Errorf(i18n.G("too many arguments for hook %q: %s"), x.HookName, strings.Join(args, " ")) } - if err := maybeWaitForSecurityProfileRegeneration(x.client); err != nil { + if err := maybeWaitForSecurityProfileRegeneration(); err != nil { return err } @@ -319,57 +300,16 @@ } // see snapenv.User - instanceUserData := info.UserDataDir(usr.HomeDir) - instanceCommonUserData := info.UserCommonDataDir(usr.HomeDir) - createDirs := []string{instanceUserData, instanceCommonUserData} - if info.InstanceKey != "" { - // parallel instance snaps get additional mapping in their mount - // namespace, namely /home/joe/snap/foo_bar -> - // /home/joe/snap/foo, make sure that the mount point exists and - // is owned by the user - snapUserDir := snap.UserSnapDir(usr.HomeDir, info.SnapName()) - createDirs = append(createDirs, snapUserDir) - } - for _, d := range createDirs { + userData := info.UserDataDir(usr.HomeDir) + commonUserData := info.UserCommonDataDir(usr.HomeDir) + for _, d := range []string{userData, commonUserData} { if err := os.MkdirAll(d, 0755); err != nil { // TRANSLATORS: %q is the directory whose creation failed, %v the error message return fmt.Errorf(i18n.G("cannot create %q: %v"), d, err) } } - if err := createOrUpdateUserDataSymlink(info, usr); err != nil { - return err - } - - return maybeRestoreSecurityContext(usr) -} - -// maybeRestoreSecurityContext attempts to restore security context of ~/snap on -// systems where it's applicable -func maybeRestoreSecurityContext(usr *user.User) error { - snapUserHome := filepath.Join(usr.HomeDir, dirs.UserHomeSnapDir) - enabled, err := selinuxIsEnabled() - if err != nil { - return fmt.Errorf("cannot determine SELinux status: %v", err) - } - if !enabled { - logger.Debugf("SELinux not enabled") - return nil - } - - match, err := selinuxVerifyPathContext(snapUserHome) - if err != nil { - return fmt.Errorf("failed to verify SELinux context of %v: %v", snapUserHome, err) - } - if match { - return nil - } - logger.Noticef("restoring default SELinux context of %v", snapUserHome) - - if err := selinuxRestoreContext(snapUserHome, selinux.RestoreMode{Recursive: true}); err != nil { - return fmt.Errorf("cannot restore SELinux context of %v: %v", snapUserHome, err) - } - return nil + return createOrUpdateUserDataSymlink(info, usr) } func (x *cmdRun) useStrace() bool { @@ -590,99 +530,47 @@ return targetPath, nil } -func activateXdgDocumentPortal(info *snap.Info, snapApp, hook string) error { - // Don't do anything for apps or hooks that don't plug the - // desktop interface - // - // NOTE: This check is imperfect because we don't really know - // if the interface is connected or not but this is an - // acceptable compromise for not having to communicate with - // snapd in snap run. In a typical desktop session the - // document portal can be in use by many applications, not - // just by snaps, so this is at most, pre-emptively using some - // extra memory. - var plugs map[string]*snap.PlugInfo - if hook != "" { - plugs = info.Hooks[hook].Plugs - } else { - _, appName := snap.SplitSnapApp(snapApp) - plugs = info.Apps[appName].Plugs - } - plugsDesktop := false - for _, plug := range plugs { - if plug.Interface == "desktop" { - plugsDesktop = true - break - } - } - if !plugsDesktop { - return nil - } - - u, err := userCurrent() +func straceCmd() ([]string, error) { + current, err := user.Current() if err != nil { - return fmt.Errorf(i18n.G("cannot get the current user: %s"), err) - } - xdgRuntimeDir := filepath.Join(dirs.XdgRuntimeDirBase, u.Uid) - - // If $XDG_RUNTIME_DIR/doc appears to be a mount point, assume - // that the document portal is up and running. - expectedMountPoint := filepath.Join(xdgRuntimeDir, "doc") - if mounted, err := osutil.IsMounted(expectedMountPoint); err != nil { - logger.Noticef("Could not check document portal mount state: %s", err) - } else if mounted { - return nil + return nil, err } - - // If there is no session bus, our job is done. We check this - // manually to avoid dbus.SessionBus() auto-launching a new - // bus. - busAddress := osGetenv("DBUS_SESSION_BUS_ADDRESS") - if len(busAddress) == 0 { - return nil + sudoPath, err := exec.LookPath("sudo") + if err != nil { + return nil, fmt.Errorf("cannot use strace without sudo: %s", err) } - // We've previously tried to start the document portal and - // were told the service is unknown: don't bother connecting - // to the session bus again. + // Try strace from the snap first, we use new syscalls like + // "_newselect" that are known to not work with the strace of e.g. + // ubuntu 14.04. // - // As the file is in $XDG_RUNTIME_DIR, it will be cleared over - // full logout/login or reboot cycles. - portalsUnavailableFile := filepath.Join(xdgRuntimeDir, ".portals-unavailable") - if osutil.FileExists(portalsUnavailableFile) { - return nil + // TODO: some architectures do not have some syscalls (e.g. + // s390x does not have _newselect). In + // https://github.com/strace/strace/issues/57 options are + // discussed. We could use "-e trace=?syscall" but that is + // only available since strace 4.17 which is not even in + // ubutnu 17.10. + var stracePath string + cand := filepath.Join(dirs.SnapMountDir, "strace-static", "current", "bin", "strace") + if osutil.FileExists(cand) { + stracePath = cand } - - conn, err := dbus.SessionBus() - if err != nil { - return err - } - - portal := conn.Object("org.freedesktop.portal.Documents", - "/org/freedesktop/portal/documents") - var mountPoint []byte - if err := portal.Call("org.freedesktop.portal.Documents.GetMountPoint", 0).Store(&mountPoint); err != nil { - // It is not considered an error if - // xdg-document-portal is not available on the system. - if dbusErr, ok := err.(dbus.Error); ok && dbusErr.Name == "org.freedesktop.DBus.Error.ServiceUnknown" { - // We ignore errors here: if writing the file - // fails, we'll just try connecting to D-Bus - // again next time. - if err = ioutil.WriteFile(portalsUnavailableFile, []byte(""), 0644); err != nil { - logger.Noticef("WARNING: cannot write file at %s: %s", portalsUnavailableFile, err) - } - return nil + if stracePath == "" { + stracePath, err = exec.LookPath("strace") + if err != nil { + return nil, fmt.Errorf("cannot find an installed strace, please try 'snap install strace-static'") } - return err } - // Sanity check to make sure the document portal is exposed - // where we think it is. - actualMountPoint := strings.TrimRight(string(mountPoint), "\x00") - if actualMountPoint != expectedMountPoint { - return fmt.Errorf(i18n.G("Expected portal at %#v, got %#v"), expectedMountPoint, actualMountPoint) - } - return nil + return []string{ + sudoPath, "-E", + stracePath, + "-u", current.Username, + "-f", + // these syscalls are excluded because they make strace hang + // on all or some architectures (gettimeofday on arm64) + "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid,gettimeofday", + }, nil } func (x *cmdRun) runCmdUnderGdb(origCmd, env []string) error { @@ -699,76 +587,25 @@ return gcmd.Run() } -func (x *cmdRun) runCmdWithTraceExec(origCmd, env []string) error { - // setup private tmp dir with strace fifo - straceTmp, err := ioutil.TempDir("", "exec-trace") - if err != nil { - return err - } - defer os.RemoveAll(straceTmp) - straceLog := filepath.Join(straceTmp, "strace.fifo") - if err := syscall.Mkfifo(straceLog, 0640); err != nil { - return err - } - // ensure we have one writer on the fifo so that if strace fails - // nothing blocks - fw, err := os.OpenFile(straceLog, os.O_RDWR, 0640) - if err != nil { - return err - } - defer fw.Close() - - // read strace data from fifo async - var slg *strace.ExecveTiming - var straceErr error - doneCh := make(chan bool, 1) - go func() { - // FIXME: make this configurable? - nSlowest := 10 - slg, straceErr = strace.TraceExecveTimings(straceLog, nSlowest) - close(doneCh) - }() - - cmd, err := strace.TraceExecCommand(straceLog, origCmd...) - if err != nil { - return err - } - // run - cmd.Env = env - cmd.Stdin = Stdin - cmd.Stdout = Stdout - cmd.Stderr = Stderr - err = cmd.Run() - // ensure we close the fifo here so that the strace.TraceExecCommand() - // helper gets a EOF from the fifo (i.e. all writers must be closed - // for this) - fw.Close() - - // wait for strace reader - <-doneCh - if straceErr == nil { - slg.Display(Stderr) - } else { - logger.Noticef("cannot extract runtime data: %v", straceErr) - } - return err -} - func (x *cmdRun) runCmdUnderStrace(origCmd, env []string) error { - extraStraceOpts, raw, err := x.straceOpts() + // prepend strace magic + cmd, err := straceCmd() if err != nil { return err } - cmd, err := strace.Command(extraStraceOpts, origCmd...) + straceOpts, raw, err := x.straceOpts() if err != nil { return err } + cmd = append(cmd, straceOpts...) + cmd = append(cmd, origCmd...) // run with filter - cmd.Env = env - cmd.Stdin = Stdin - cmd.Stdout = Stdout - stderr, err := cmd.StderrPipe() + gcmd := exec.Command(cmd[0], cmd[1:]...) + gcmd.Env = env + gcmd.Stdin = Stdin + gcmd.Stdout = Stdout + stderr, err := gcmd.StderrPipe() if err != nil { return err } @@ -832,11 +669,11 @@ } io.Copy(Stderr, r) }() - if err := cmd.Start(); err != nil { + if err := gcmd.Start(); err != nil { return err } <-filterDone - err = cmd.Wait() + err = gcmd.Wait() return err } @@ -876,10 +713,6 @@ logger.Noticef("WARNING: cannot copy user Xauthority file: %s", err) } - if err := activateXdgDocumentPortal(info, snapApp, hook); err != nil { - logger.Noticef("WARNING: cannot start document portal: %s", err) - } - cmd := []string{snapConfine} if info.NeedsClassic() { cmd = append(cmd, "--classic") @@ -931,9 +764,7 @@ } env := snapenv.ExecEnv(info, extraEnv) - if x.TraceExec { - return x.runCmdWithTraceExec(cmd, env) - } else if x.Gdb { + if x.Gdb { return x.runCmdUnderGdb(cmd, env) } else if x.useStrace() { return x.runCmdUnderStrace(cmd, env) diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_run_test.go snapd-2.34.2+18.04/cmd/snap/cmd_run_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_run_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_run_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -20,7 +20,6 @@ package main_test import ( - "errors" "fmt" "os" "os/user" @@ -34,7 +33,6 @@ "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/selinux" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" @@ -51,28 +49,28 @@ `) func (s *SnapSuite) TestInvalidParameters(c *check.C) { - invalidParameters := []string{"run", "--hook=configure", "--command=command-name", "--", "snap-name"} - _, err := snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) + invalidParameters := []string{"run", "--hook=configure", "--command=command-name", "snap-name"} + _, err := snaprun.Parser().ParseArgs(invalidParameters) c.Check(err, check.ErrorMatches, ".*you can only use one of --hook, --command, and --timer.*") - invalidParameters = []string{"run", "--hook=configure", "--timer=10:00-12:00", "--", "snap-name"} - _, err = snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) + invalidParameters = []string{"run", "--hook=configure", "--timer=10:00-12:00", "snap-name"} + _, err = snaprun.Parser().ParseArgs(invalidParameters) c.Check(err, check.ErrorMatches, ".*you can only use one of --hook, --command, and --timer.*") - invalidParameters = []string{"run", "--command=command-name", "--timer=10:00-12:00", "--", "snap-name"} - _, err = snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) + invalidParameters = []string{"run", "--command=command-name", "--timer=10:00-12:00", "snap-name"} + _, err = snaprun.Parser().ParseArgs(invalidParameters) c.Check(err, check.ErrorMatches, ".*you can only use one of --hook, --command, and --timer.*") - invalidParameters = []string{"run", "-r=1", "--command=command-name", "--", "snap-name"} - _, err = snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) + invalidParameters = []string{"run", "-r=1", "--command=command-name", "snap-name"} + _, err = snaprun.Parser().ParseArgs(invalidParameters) c.Check(err, check.ErrorMatches, ".*-r can only be used with --hook.*") - invalidParameters = []string{"run", "-r=1", "--", "snap-name"} - _, err = snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) + invalidParameters = []string{"run", "-r=1", "snap-name"} + _, err = snaprun.Parser().ParseArgs(invalidParameters) c.Check(err, check.ErrorMatches, ".*-r can only be used with --hook.*") - invalidParameters = []string{"run", "--hook=configure", "--", "foo", "bar", "snap-name"} - _, err = snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) + invalidParameters = []string{"run", "--hook=configure", "foo", "bar", "snap-name"} + _, err = snaprun.Parser().ParseArgs(invalidParameters) c.Check(err, check.ErrorMatches, ".*too many arguments for hook \"configure\": bar.*") } @@ -95,10 +93,10 @@ // and run it! // a regular run will fail - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) + _, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.ErrorMatches, `.* your core/snapd package`) // a hook run will not fail - _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "--", "snapname"}) + _, err = snaprun.Parser().ParseArgs([]string{"run", "--hook=configure", "snapname"}) c.Assert(err, check.IsNil) // but nothing is run ever @@ -126,7 +124,7 @@ defer restorer() // and run it! - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) + rest, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) @@ -159,7 +157,7 @@ defer restorer() // and run it! - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) + rest, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) @@ -195,7 +193,7 @@ return nil }) defer restorer() - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) + rest, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) c.Check(execArgs, check.DeepEquals, []string{ @@ -226,7 +224,7 @@ defer restorer() // and run it! - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--command=my-command", "--", "snapname.app", "arg1", "arg2"}) + _, err := snaprun.Parser().ParseArgs([]string{"run", "--command=my-command", "snapname.app", "arg1", "arg2"}) c.Assert(err, check.IsNil) c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) c.Check(execArgs, check.DeepEquals, []string{ @@ -254,30 +252,6 @@ c.Check(osutil.FileExists(filepath.Join(fakeHome, "/snap/snapname/common")), check.Equals, true) } -func (s *SnapSuite) TestParallelInstanceSnapRunCreateDataDirs(c *check.C) { - info, err := snap.InfoFromSnapYaml(mockYaml) - c.Assert(err, check.IsNil) - info.SideInfo.Revision = snap.R(42) - info.InstanceKey = "foo" - - fakeHome := c.MkDir() - restorer := snaprun.MockUserCurrent(func() (*user.User, error) { - return &user.User{HomeDir: fakeHome}, nil - }) - defer restorer() - - err = snaprun.CreateUserDataDirs(info) - c.Assert(err, check.IsNil) - c.Check(osutil.FileExists(filepath.Join(fakeHome, "/snap/snapname_foo/42")), check.Equals, true) - c.Check(osutil.FileExists(filepath.Join(fakeHome, "/snap/snapname_foo/common")), check.Equals, true) - // mount point for snap instance mapping has been created - c.Check(osutil.FileExists(filepath.Join(fakeHome, "/snap/snapname")), check.Equals, true) - // and it's empty inside - m, err := filepath.Glob(filepath.Join(fakeHome, "/snap/snapname/*")) - c.Assert(err, check.IsNil) - c.Assert(m, check.HasLen, 0) -} - func (s *SnapSuite) TestSnapRunHookIntegration(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() @@ -299,7 +273,7 @@ defer restorer() // Run a hook from the active revision - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "--", "snapname"}) + _, err := snaprun.Parser().ParseArgs([]string{"run", "--hook=configure", "snapname"}) c.Assert(err, check.IsNil) c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) c.Check(execArgs, check.DeepEquals, []string{ @@ -331,7 +305,7 @@ defer restorer() // Specifically pass "unset" which would use the active version. - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "-r=unset", "--", "snapname"}) + _, err := snaprun.Parser().ParseArgs([]string{"run", "--hook=configure", "-r=unset", "snapname"}) c.Assert(err, check.IsNil) c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) c.Check(execArgs, check.DeepEquals, []string{ @@ -367,7 +341,7 @@ defer restorer() // Run a hook on revision 41 - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "-r=41", "--", "snapname"}) + _, err := snaprun.Parser().ParseArgs([]string{"run", "--hook=configure", "-r=41", "snapname"}) c.Assert(err, check.IsNil) c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) c.Check(execArgs, check.DeepEquals, []string{ @@ -391,13 +365,13 @@ defer restorer() // Attempt to run a hook on revision 41, which doesn't exist - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "-r=41", "--", "snapname"}) + _, err := snaprun.Parser().ParseArgs([]string{"run", "--hook=configure", "-r=41", "snapname"}) c.Assert(err, check.NotNil) c.Check(err, check.ErrorMatches, "cannot find .*") } func (s *SnapSuite) TestSnapRunHookInvalidRevisionIntegration(c *check.C) { - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "-r=invalid", "--", "snapname"}) + _, err := snaprun.Parser().ParseArgs([]string{"run", "--hook=configure", "-r=invalid", "snapname"}) c.Assert(err, check.NotNil) c.Check(err, check.ErrorMatches, "invalid snap revision: \"invalid\"") } @@ -416,23 +390,23 @@ }) defer restorer() - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=missing-hook", "--", "snapname"}) + _, err := snaprun.Parser().ParseArgs([]string{"run", "--hook=missing-hook", "snapname"}) c.Assert(err, check.ErrorMatches, `cannot find hook "missing-hook" in "snapname"`) c.Check(called, check.Equals, false) } func (s *SnapSuite) TestSnapRunErorsForUnknownRunArg(c *check.C) { - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--unknown", "--", "snapname.app", "--arg1", "arg2"}) + _, err := snaprun.Parser().ParseArgs([]string{"run", "--unknown", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.ErrorMatches, "unknown flag `unknown'") } func (s *SnapSuite) TestSnapRunErorsForMissingApp(c *check.C) { - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--command=shell"}) + _, err := snaprun.Parser().ParseArgs([]string{"run", "--command=shell"}) c.Assert(err, check.ErrorMatches, "need the application to run as argument") } func (s *SnapSuite) TestSnapRunErorrForUnavailableApp(c *check.C) { - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "not-there"}) + _, err := snaprun.Parser().ParseArgs([]string{"run", "not-there"}) c.Assert(err, check.ErrorMatches, fmt.Sprintf("cannot find current revision for snap not-there: readlink %s/not-there/current: no such file or directory", dirs.SnapMountDir)) } @@ -462,7 +436,7 @@ defer os.Unsetenv("SNAP_THE_WORLD") // and ensure those SNAP_ vars get overridden - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) + rest, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42") @@ -517,7 +491,7 @@ defer restorer() // and run it! - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) + rest, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) c.Check(execArg0, check.Equals, filepath.Join(dirs.SnapMountDir, "/core/111", dirs.CoreLibExecDir, "snap-confine")) @@ -556,7 +530,7 @@ defer restorer() // and run it! - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) + rest, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) c.Check(execArg0, check.Equals, filepath.Join(dirs.SnapMountDir, "/snapd/222", dirs.CoreLibExecDir, "snap-confine")) @@ -608,7 +582,7 @@ })() // and run it! - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) + rest, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app"}) c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) @@ -731,7 +705,7 @@ c.Assert(err, check.IsNil) // and run it under strace - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--strace", "--", "snapname.app", "--arg1", "arg2"}) + rest, err := snaprun.Parser().ParseArgs([]string{"run", "--strace", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) c.Check(sudoCmd.Calls(), check.DeepEquals, [][]string{ @@ -740,7 +714,7 @@ filepath.Join(straceCmd.BinDir(), "strace"), "-u", user.Username, "-f", - "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid,gettimeofday,nanosleep", + "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid,gettimeofday", filepath.Join(dirs.DistroLibExecDir, "snap-confine"), "snap.snapname.app", filepath.Join(dirs.CoreLibExecDir, "snap-exec"), @@ -754,7 +728,7 @@ sudoCmd.ForgetCalls() // try again without filtering - rest, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--strace=--raw", "--", "snapname.app", "--arg1", "arg2"}) + rest, err = snaprun.Parser().ParseArgs([]string{"run", "--strace=--raw", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) c.Check(sudoCmd.Calls(), check.DeepEquals, [][]string{ @@ -763,7 +737,7 @@ filepath.Join(straceCmd.BinDir(), "strace"), "-u", user.Username, "-f", - "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid,gettimeofday,nanosleep", + "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid,gettimeofday", filepath.Join(dirs.DistroLibExecDir, "snap-confine"), "snap.snapname.app", filepath.Join(dirs.CoreLibExecDir, "snap-exec"), @@ -801,7 +775,7 @@ c.Assert(err, check.IsNil) // and run it under strace - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", `--strace=-tt --raw -o "file with spaces"`, "--", "snapname.app", "--arg1", "arg2"}) + rest, err := snaprun.Parser().ParseArgs([]string{"run", `--strace=-tt --raw -o "file with spaces"`, "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) c.Check(sudoCmd.Calls(), check.DeepEquals, [][]string{ @@ -810,7 +784,7 @@ filepath.Join(straceCmd.BinDir(), "strace"), "-u", user.Username, "-f", - "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid,gettimeofday,nanosleep", + "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid,gettimeofday", "-tt", "-o", "file with spaces", @@ -843,7 +817,7 @@ defer restorer() // and run it! - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--shell", "--", "snapname.app", "--arg1", "arg2"}) + rest, err := snaprun.Parser().ParseArgs([]string{"run", "--shell", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) @@ -883,7 +857,7 @@ defer restorer() // pretend we are outside of timer range - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", `--timer="mon,10:00~12:00,,fri,13:00"`, "--", "snapname.app", "--arg1", "arg2"}) + rest, err := snaprun.Parser().ParseArgs([]string{"run", `--timer="mon,10:00~12:00,,fri,13:00"`, "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) c.Assert(execCalled, check.Equals, false) @@ -899,7 +873,7 @@ defer restorer() // and run it under strace - _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", `--timer="mon,10:00~12:00,,fri,13:00"`, "--", "snapname.app", "--arg1", "arg2"}) + _, err = snaprun.Parser().ParseArgs([]string{"run", `--timer="mon,10:00~12:00,,fri,13:00"`, "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.IsNil) c.Assert(execCalled, check.Equals, true) c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) @@ -909,203 +883,3 @@ filepath.Join(dirs.CoreLibExecDir, "snap-exec"), "snapname.app", "--arg1", "arg2"}) } - -func (s *SnapSuite) TestRunCmdWithTraceExecUnhappy(c *check.C) { - defer mockSnapConfine(dirs.DistroLibExecDir)() - - // mock installed snap - snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ - Revision: snap.R("1"), - }) - - // pretend we have sudo - sudoCmd := testutil.MockCommand(c, "sudo", "echo unhappy; exit 12") - defer sudoCmd.Restore() - - // pretend we have strace - straceCmd := testutil.MockCommand(c, "strace", "") - defer straceCmd.Restore() - - rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--trace-exec", "--", "snapname.app", "--arg1", "arg2"}) - c.Assert(err, check.ErrorMatches, "exit status 12") - c.Assert(rest, check.DeepEquals, []string{"--", "snapname.app", "--arg1", "arg2"}) - c.Check(s.Stdout(), check.Equals, "unhappy\n") - c.Check(s.Stderr(), check.Equals, "") -} - -func (s *SnapSuite) TestSnapRunRestoreSecurityContextHappy(c *check.C) { - logbuf, restorer := logger.MockLogger() - defer restorer() - - defer mockSnapConfine(dirs.DistroLibExecDir)() - - // mock installed snap - snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ - Revision: snap.R("x2"), - }) - - fakeHome := c.MkDir() - restorer = snaprun.MockUserCurrent(func() (*user.User, error) { - return &user.User{HomeDir: fakeHome}, nil - }) - defer restorer() - - // redirect exec - execCalled := 0 - restorer = snaprun.MockSyscallExec(func(_ string, args []string, envv []string) error { - execCalled++ - return nil - }) - defer restorer() - - verifyCalls := 0 - restoreCalls := 0 - isEnabledCalls := 0 - enabled := false - verify := true - - snapUserDir := filepath.Join(fakeHome, dirs.UserHomeSnapDir) - - restorer = snaprun.MockSELinuxVerifyPathContext(func(what string) (bool, error) { - c.Check(what, check.Equals, snapUserDir) - verifyCalls++ - return verify, nil - }) - defer restorer() - - restorer = snaprun.MockSELinuxRestoreContext(func(what string, mode selinux.RestoreMode) error { - c.Check(mode, check.Equals, selinux.RestoreMode{Recursive: true}) - c.Check(what, check.Equals, snapUserDir) - restoreCalls++ - return nil - }) - defer restorer() - - restorer = snaprun.MockSELinuxIsEnabled(func() (bool, error) { - isEnabledCalls++ - return enabled, nil - }) - defer restorer() - - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) - c.Assert(err, check.IsNil) - c.Check(execCalled, check.Equals, 1) - c.Check(isEnabledCalls, check.Equals, 1) - c.Check(verifyCalls, check.Equals, 0) - c.Check(restoreCalls, check.Equals, 0) - - // pretend SELinux is on - enabled = true - - _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) - c.Assert(err, check.IsNil) - c.Check(execCalled, check.Equals, 2) - c.Check(isEnabledCalls, check.Equals, 2) - c.Check(verifyCalls, check.Equals, 1) - c.Check(restoreCalls, check.Equals, 0) - - // pretend the context does not match - verify = false - - logbuf.Reset() - - _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) - c.Assert(err, check.IsNil) - c.Check(execCalled, check.Equals, 3) - c.Check(isEnabledCalls, check.Equals, 3) - c.Check(verifyCalls, check.Equals, 2) - c.Check(restoreCalls, check.Equals, 1) - - // and we let the user know what we're doing - c.Check(logbuf.String(), testutil.Contains, fmt.Sprintf("restoring default SELinux context of %s", snapUserDir)) -} - -func (s *SnapSuite) TestSnapRunRestoreSecurityContextFail(c *check.C) { - logbuf, restorer := logger.MockLogger() - defer restorer() - - defer mockSnapConfine(dirs.DistroLibExecDir)() - - // mock installed snap - snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ - Revision: snap.R("x2"), - }) - - fakeHome := c.MkDir() - restorer = snaprun.MockUserCurrent(func() (*user.User, error) { - return &user.User{HomeDir: fakeHome}, nil - }) - defer restorer() - - // redirect exec - execCalled := 0 - restorer = snaprun.MockSyscallExec(func(_ string, args []string, envv []string) error { - execCalled++ - return nil - }) - defer restorer() - - verifyCalls := 0 - restoreCalls := 0 - isEnabledCalls := 0 - enabledErr := errors.New("enabled failed") - verifyErr := errors.New("verify failed") - restoreErr := errors.New("restore failed") - - snapUserDir := filepath.Join(fakeHome, dirs.UserHomeSnapDir) - - restorer = snaprun.MockSELinuxVerifyPathContext(func(what string) (bool, error) { - c.Check(what, check.Equals, snapUserDir) - verifyCalls++ - return false, verifyErr - }) - defer restorer() - - restorer = snaprun.MockSELinuxRestoreContext(func(what string, mode selinux.RestoreMode) error { - c.Check(mode, check.Equals, selinux.RestoreMode{Recursive: true}) - c.Check(what, check.Equals, snapUserDir) - restoreCalls++ - return restoreErr - }) - defer restorer() - - restorer = snaprun.MockSELinuxIsEnabled(func() (bool, error) { - isEnabledCalls++ - return enabledErr == nil, enabledErr - }) - defer restorer() - - _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) - // these errors are only logged, but we still run the snap - c.Assert(err, check.IsNil) - c.Check(execCalled, check.Equals, 1) - c.Check(logbuf.String(), testutil.Contains, "cannot determine SELinux status: enabled failed") - c.Check(isEnabledCalls, check.Equals, 1) - c.Check(verifyCalls, check.Equals, 0) - c.Check(restoreCalls, check.Equals, 0) - // pretend selinux is on - enabledErr = nil - - logbuf.Reset() - - _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) - c.Assert(err, check.IsNil) - c.Check(execCalled, check.Equals, 2) - c.Check(logbuf.String(), testutil.Contains, fmt.Sprintf("failed to verify SELinux context of %s: verify failed", snapUserDir)) - c.Check(isEnabledCalls, check.Equals, 2) - c.Check(verifyCalls, check.Equals, 1) - c.Check(restoreCalls, check.Equals, 0) - - // pretend the context does not match - verifyErr = nil - - logbuf.Reset() - - _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) - c.Assert(err, check.IsNil) - c.Check(execCalled, check.Equals, 3) - c.Check(logbuf.String(), testutil.Contains, fmt.Sprintf("cannot restore SELinux context of %s: restore failed", snapUserDir)) - c.Check(isEnabledCalls, check.Equals, 3) - c.Check(verifyCalls, check.Equals, 2) - c.Check(restoreCalls, check.Equals, 1) -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_sandbox_features.go snapd-2.34.2+18.04/cmd/snap/cmd_sandbox_features.go --- snapd-2.37.1+18.04/cmd/snap/cmd_sandbox_features.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_sandbox_features.go 2018-07-19 10:05:50.000000000 +0000 @@ -36,7 +36,6 @@ `) type cmdSandboxFeatures struct { - clientMixin Required []string `long:"required" arg-name:""` } @@ -53,7 +52,8 @@ return ErrExtraArgs } - sysInfo, err := cmd.client.SysInfo() + cli := Client() + sysInfo, err := cli.SysInfo() if err != nil { return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_sandbox_features_test.go snapd-2.34.2+18.04/cmd/snap/cmd_sandbox_features_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_sandbox_features_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_sandbox_features_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -32,7 +32,7 @@ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `{"type": "sync", "result": {"sandbox-features": {"apparmor": ["a", "b", "c"], "selinux": ["1", "2", "3"]}}}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "sandbox-features"}) + _, err := snap.Parser().ParseArgs([]string{"debug", "sandbox-features"}) c.Assert(err, IsNil) c.Assert(s.Stdout(), Equals, ""+ "apparmor: a b c\n"+ @@ -44,7 +44,7 @@ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `{"type": "sync", "result": {"sandbox-features": {"apparmor": ["a", "b", "c"], "selinux": ["1", "2", "3"]}}}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "sandbox-features", "--required=apparmor:a", "--required=selinux:2"}) + _, err := snap.Parser().ParseArgs([]string{"debug", "sandbox-features", "--required=apparmor:a", "--required=selinux:2"}) c.Assert(err, IsNil) c.Assert(s.Stdout(), Equals, "") c.Assert(s.Stderr(), Equals, "") @@ -54,7 +54,7 @@ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `{"type": "sync", "result": {"sandbox-features": {"apparmor": ["a", "b", "c"], "selinux": ["1", "2", "3"]}}}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "sandbox-features", "--required=magic:thing"}) + _, err := snap.Parser().ParseArgs([]string{"debug", "sandbox-features", "--required=magic:thing"}) c.Assert(err, ErrorMatches, `sandbox feature not available: "magic:thing"`) c.Assert(s.Stdout(), Equals, "") c.Assert(s.Stderr(), Equals, "") diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_services.go snapd-2.34.2+18.04/cmd/snap/cmd_services.go --- snapd-2.37.1+18.04/cmd/snap/cmd_services.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_services.go 2018-07-19 10:05:50.000000000 +0000 @@ -26,19 +26,16 @@ "github.com/jessevdk/go-flags" "github.com/snapcore/snapd/client" - "github.com/snapcore/snapd/cmd" "github.com/snapcore/snapd/i18n" ) type svcStatus struct { - clientMixin Positional struct { ServiceNames []serviceName } `positional-args:"yes"` } type svcLogs struct { - clientMixin N string `short:"n" default:"10"` Follow bool `short:"f"` Positional struct { @@ -52,7 +49,7 @@ The services command lists information about the services specified, or about the services in all currently installed snaps. `) - shortLogsHelp = i18n.G("Retrieve logs for services") + shortLogsHelp = i18n.G("Retrieve logs of services") longLogsHelp = i18n.G(` The logs command fetches logs of the given services and displays them in chronological order. @@ -76,33 +73,27 @@ func init() { argdescs := []argDesc{{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. desc: i18n.G("A service specification, which can be just a snap name (for all services in the snap), or . for a single service."), }} addCommand("services", shortServicesHelp, longServicesHelp, func() flags.Commander { return &svcStatus{} }, nil, argdescs) addCommand("logs", shortLogsHelp, longLogsHelp, func() flags.Commander { return &svcLogs{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. "n": i18n.G("Show only the given number of lines, or 'all'."), - // TRANSLATORS: This should not start with a lowercase letter. "f": i18n.G("Wait for new lines and print them as they come in."), }, argdescs) addCommand("start", shortStartHelp, longStartHelp, func() flags.Commander { return &svcStart{} }, waitDescs.also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. "enable": i18n.G("As well as starting the service now, arrange for it to be started on boot."), }), argdescs) addCommand("stop", shortStopHelp, longStopHelp, func() flags.Commander { return &svcStop{} }, waitDescs.also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. "disable": i18n.G("As well as stopping the service now, arrange for it to no longer be started on boot."), }), argdescs) addCommand("restart", shortRestartHelp, longRestartHelp, func() flags.Commander { return &svcRestart{} }, waitDescs.also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. "reload": i18n.G("If the service has a reload command, use it instead of restarting."), }), argdescs) } @@ -120,20 +111,15 @@ return ErrExtraArgs } - services, err := s.client.Apps(svcNames(s.Positional.ServiceNames), client.AppOptions{Service: true}) + services, err := Client().Apps(svcNames(s.Positional.ServiceNames), client.AppOptions{Service: true}) if err != nil { return err } - if len(services) == 0 { - fmt.Fprintln(Stderr, i18n.G("There are no services provided by installed snaps.")) - return nil - } - w := tabWriter() defer w.Flush() - fmt.Fprintln(w, i18n.G("Service\tStartup\tCurrent\tNotes")) + fmt.Fprintln(w, i18n.G("Service\tStartup\tCurrent")) for _, svc := range services { startup := i18n.G("disabled") @@ -144,7 +130,7 @@ if svc.Active { current = i18n.G("active") } - fmt.Fprintf(w, "%s.%s\t%s\t%s\t%s\n", svc.Snap, svc.Name, startup, current, cmd.ClientAppInfoNotes(svc)) + fmt.Fprintf(w, "%s.%s\t%s\t%s\n", svc.Snap, svc.Name, startup, current) } return nil @@ -164,7 +150,7 @@ sN = int(n) } - logs, err := s.client.Logs(svcNames(s.Positional.ServiceNames), client.LogOptions{N: sN, Follow: s.Follow}) + logs, err := Client().Logs(svcNames(s.Positional.ServiceNames), client.LogOptions{N: sN, Follow: s.Follow}) if err != nil { return err } @@ -188,12 +174,13 @@ if len(args) > 0 { return ErrExtraArgs } + cli := Client() names := svcNames(s.Positional.ServiceNames) - changeID, err := s.client.Start(names, client.StartOptions{Enable: s.Enable}) + changeID, err := cli.Start(names, client.StartOptions{Enable: s.Enable}) if err != nil { return err } - if _, err := s.wait(changeID); err != nil { + if _, err := s.wait(cli, changeID); err != nil { if err == noWait { return nil } @@ -217,12 +204,13 @@ if len(args) > 0 { return ErrExtraArgs } + cli := Client() names := svcNames(s.Positional.ServiceNames) - changeID, err := s.client.Stop(names, client.StopOptions{Disable: s.Disable}) + changeID, err := cli.Stop(names, client.StopOptions{Disable: s.Disable}) if err != nil { return err } - if _, err := s.wait(changeID); err != nil { + if _, err := s.wait(cli, changeID); err != nil { if err == noWait { return nil } @@ -246,12 +234,13 @@ if len(args) > 0 { return ErrExtraArgs } + cli := Client() names := svcNames(s.Positional.ServiceNames) - changeID, err := s.client.Restart(names, client.RestartOptions{Reload: s.Reload}) + changeID, err := cli.Restart(names, client.RestartOptions{Reload: s.Reload}) if err != nil { return err } - if _, err := s.wait(changeID); err != nil { + if _, err := s.wait(cli, changeID); err != nil { if err == noWait { return nil } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_services_test.go snapd-2.34.2+18.04/cmd/snap/cmd_services_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_services_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_services_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -20,7 +20,6 @@ package main_test import ( - "encoding/json" "fmt" "net/http" "time" @@ -84,7 +83,7 @@ func (s *appOpSuite) testOpNoArgs(c *check.C, op string) { s.RedirectClientToTestServer(nil) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{op}) + _, err := snap.Parser().ParseArgs([]string{op}) c.Assert(err, check.ErrorMatches, `.* required argument .* not provided`) } @@ -106,7 +105,7 @@ n++ }) - _, err := snap.Parser(snap.Client()).ParseArgs(s.args(op, names, extra, noWait)) + _, err := snap.Parser().ParseArgs(s.args(op, names, extra, noWait)) c.Assert(err, check.ErrorMatches, "error") c.Check(n, check.Equals, 1) } @@ -136,7 +135,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs(s.args(op, names, extra, noWait)) + rest, err := snap.Parser().ParseArgs(s.args(op, names, extra, noWait)) c.Assert(err, check.IsNil) c.Assert(rest, check.HasLen, 0) c.Check(s.Stderr(), check.Equals, "") @@ -170,85 +169,3 @@ } } } - -func (s *appOpSuite) TestAppStatus(c *check.C) { - n := 0 - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - switch n { - case 0: - c.Check(r.URL.Path, check.Equals, "/v2/apps") - c.Check(r.URL.Query(), check.HasLen, 1) - c.Check(r.URL.Query().Get("select"), check.Equals, "service") - c.Check(r.Method, check.Equals, "GET") - w.WriteHeader(200) - enc := json.NewEncoder(w) - enc.Encode(map[string]interface{}{ - "type": "sync", - "result": []map[string]interface{}{ - {"snap": "foo", "name": "bar", "daemon": "oneshot", - "active": false, "enabled": true, - "activators": []map[string]interface{}{ - {"name": "bar", "type": "timer", "active": true, "enabled": true}, - }, - }, {"snap": "foo", "name": "baz", "daemon": "oneshot", - "active": false, "enabled": true, - "activators": []map[string]interface{}{ - {"name": "baz-sock1", "type": "socket", "active": true, "enabled": true}, - {"name": "baz-sock2", "type": "socket", "active": false, "enabled": true}, - }, - }, {"snap": "foo", "name": "zed", - "active": true, "enabled": true, - }, - }, - "status": "OK", - "status-code": 200, - }) - default: - c.Fatalf("expected to get 1 requests, now on %d", n+1) - } - - n++ - }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"services"}) - c.Assert(err, check.IsNil) - c.Assert(rest, check.HasLen, 0) - c.Check(s.Stderr(), check.Equals, "") - c.Check(s.Stdout(), check.Equals, `Service Startup Current Notes -foo.bar enabled inactive timer-activated -foo.baz enabled inactive socket-activated -foo.zed enabled active - -`) - // ensure that the fake server api was actually hit - c.Check(n, check.Equals, 1) -} - -func (s *appOpSuite) TestAppStatusNoServices(c *check.C) { - n := 0 - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - switch n { - case 0: - c.Check(r.URL.Path, check.Equals, "/v2/apps") - c.Check(r.URL.Query(), check.HasLen, 1) - c.Check(r.URL.Query().Get("select"), check.Equals, "service") - c.Check(r.Method, check.Equals, "GET") - w.WriteHeader(200) - enc := json.NewEncoder(w) - enc.Encode(map[string]interface{}{ - "type": "sync", - "result": []map[string]interface{}{}, - "status": "OK", - "status-code": 200, - }) - default: - c.Fatalf("expected to get 1 requests, now on %d", n+1) - } - n++ - }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"services"}) - c.Assert(err, check.IsNil) - c.Assert(rest, check.HasLen, 0) - c.Check(s.Stdout(), check.Equals, "") - c.Check(s.Stderr(), check.Equals, "There are no services provided by installed snaps.\n") - // ensure that the fake server api was actually hit - c.Check(n, check.Equals, 1) -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_set.go snapd-2.34.2+18.04/cmd/snap/cmd_set.go --- snapd-2.37.1+18.04/cmd/snap/cmd_set.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_set.go 2018-07-19 10:05:50.000000000 +0000 @@ -55,12 +55,12 @@ addCommand("set", shortSetHelp, longSetHelp, func() flags.Commander { return &cmdSet{} }, waitDescs, []argDesc{ { name: "", - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("The snap to configure (e.g. hello-world)"), }, { - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Configuration value (key=value)"), }, }) @@ -83,12 +83,13 @@ } snapName := string(x.Positional.Snap) - id, err := x.client.SetConf(snapName, patchValues) + cli := Client() + id, err := cli.SetConf(snapName, patchValues) if err != nil { return err } - if _, err := x.wait(id); err != nil { + if _, err := x.wait(cli, id); err != nil { if err == noWait { return nil } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_set_test.go snapd-2.34.2+18.04/cmd/snap/cmd_set_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_set_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_set_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -39,7 +39,7 @@ func (s *SnapSuite) TestInvalidSetParameters(c *check.C) { invalidParameters := []string{"set", "snap-name", "key", "value"} - _, err := snapset.Parser(snapset.Client()).ParseArgs(invalidParameters) + _, err := snapset.Parser().ParseArgs(invalidParameters) c.Check(err, check.ErrorMatches, ".*invalid configuration:.*(want key=value).*") } @@ -53,7 +53,7 @@ s.mockSetConfigServer(c, "value") // Set a config value for the active snap - _, err := snapset.Parser(snapset.Client()).ParseArgs([]string{"set", "snapname", "key=value"}) + _, err := snapset.Parser().ParseArgs([]string{"set", "snapname", "key=value"}) c.Assert(err, check.IsNil) } @@ -67,7 +67,7 @@ s.mockSetConfigServer(c, json.Number("1.2")) // Set a config value for the active snap - _, err := snapset.Parser(snapset.Client()).ParseArgs([]string{"set", "snapname", "key=1.2"}) + _, err := snapset.Parser().ParseArgs([]string{"set", "snapname", "key=1.2"}) c.Assert(err, check.IsNil) } @@ -80,7 +80,7 @@ s.mockSetConfigServer(c, json.Number("1234567890")) // Set a config value for the active snap - _, err := snapset.Parser(snapset.Client()).ParseArgs([]string{"set", "snapname", "key=1234567890"}) + _, err := snapset.Parser().ParseArgs([]string{"set", "snapname", "key=1234567890"}) c.Assert(err, check.IsNil) } @@ -94,7 +94,7 @@ s.mockSetConfigServer(c, map[string]interface{}{"subkey": "value"}) // Set a config value for the active snap - _, err := snapset.Parser(snapset.Client()).ParseArgs([]string{"set", "snapname", `key={"subkey":"value"}`}) + _, err := snapset.Parser().ParseArgs([]string{"set", "snapname", `key={"subkey":"value"}`}) c.Assert(err, check.IsNil) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_sign_build.go snapd-2.34.2+18.04/cmd/snap/cmd_sign_build.go --- snapd-2.37.1+18.04/cmd/snap/cmd_sign_build.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_sign_build.go 2018-07-19 10:05:50.000000000 +0000 @@ -56,18 +56,14 @@ func() flags.Commander { return &cmdSignBuild{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. "developer-id": i18n.G("Identifier of the signer"), - // TRANSLATORS: This should not start with a lowercase letter. - "snap-id": i18n.G("Identifier of the snap package associated with the build"), - // TRANSLATORS: This should not start with a lowercase letter. - "k": i18n.G("Name of the GnuPG key to use (defaults to 'default' as key name)"), - // TRANSLATORS: This should not start with a lowercase letter. - "grade": i18n.G("Grade states the build quality of the snap (defaults to 'stable')"), + "snap-id": i18n.G("Identifier of the snap package associated with the build"), + "k": i18n.G("Name of the GnuPG key to use (defaults to 'default' as key name)"), + "grade": i18n.G("Grade states the build quality of the snap (defaults to 'stable')"), }, []argDesc{{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Filename of the snap you want to assert a build for"), }}) cmd.hidden = true diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_sign_build_test.go snapd-2.34.2+18.04/cmd/snap/cmd_sign_build_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_sign_build_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_sign_build_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -38,7 +38,7 @@ var _ = Suite(&SnapSignBuildSuite{}) func (s *SnapSignBuildSuite) TestSignBuildMandatoryFlags(c *C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"sign-build", "foo_1_amd64.snap"}) + _, err := snap.Parser().ParseArgs([]string{"sign-build", "foo_1_amd64.snap"}) c.Assert(err, NotNil) c.Check(err.Error(), Equals, "the required flags `--developer-id' and `--snap-id' were not specified") c.Check(s.Stdout(), Equals, "") @@ -46,7 +46,7 @@ } func (s *SnapSignBuildSuite) TestSignBuildMissingSnap(c *C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"sign-build", "foo_1_amd64.snap", "--developer-id", "dev-id1", "--snap-id", "snap-id-1"}) + _, err := snap.Parser().ParseArgs([]string{"sign-build", "foo_1_amd64.snap", "--developer-id", "dev-id1", "--snap-id", "snap-id-1"}) c.Assert(err, NotNil) c.Check(err.Error(), Equals, "cannot compute snap \"foo_1_amd64.snap\" digest: open foo_1_amd64.snap: no such file or directory") c.Check(s.Stdout(), Equals, "") @@ -63,7 +63,7 @@ os.Setenv("SNAP_GNUPG_HOME", tempdir) defer os.Unsetenv("SNAP_GNUPG_HOME") - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"sign-build", snapFilename, "--developer-id", "dev-id1", "--snap-id", "snap-id-1"}) + _, err := snap.Parser().ParseArgs([]string{"sign-build", snapFilename, "--developer-id", "dev-id1", "--snap-id", "snap-id-1"}) c.Assert(err, NotNil) c.Check(err.Error(), Equals, "cannot use \"default\" key: cannot find key named \"default\" in GPG keyring") c.Check(s.Stdout(), Equals, "") @@ -87,7 +87,7 @@ os.Setenv("SNAP_GNUPG_HOME", tempdir) defer os.Unsetenv("SNAP_GNUPG_HOME") - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"sign-build", snapFilename, "--developer-id", "dev-id1", "--snap-id", "snap-id-1"}) + _, err := snap.Parser().ParseArgs([]string{"sign-build", snapFilename, "--developer-id", "dev-id1", "--snap-id", "snap-id-1"}) c.Assert(err, IsNil) assertion, err := asserts.Decode([]byte(s.Stdout())) @@ -122,7 +122,7 @@ os.Setenv("SNAP_GNUPG_HOME", tempdir) defer os.Unsetenv("SNAP_GNUPG_HOME") - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"sign-build", snapFilename, "--developer-id", "dev-id1", "--snap-id", "snap-id-1", "--grade", "devel"}) + _, err := snap.Parser().ParseArgs([]string{"sign-build", snapFilename, "--developer-id", "dev-id1", "--snap-id", "snap-id-1", "--grade", "devel"}) c.Assert(err, IsNil) assertion, err := asserts.Decode([]byte(s.Stdout())) c.Assert(err, IsNil) diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_sign.go snapd-2.34.2+18.04/cmd/snap/cmd_sign.go --- snapd-2.37.1+18.04/cmd/snap/cmd_sign.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_sign.go 2018-07-19 10:05:50.000000000 +0000 @@ -44,10 +44,7 @@ func init() { cmd := addCommand("sign", shortSignHelp, longSignHelp, func() flags.Commander { return &cmdSign{} - }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "k": i18n.G("Name of the key to use, otherwise use the default key"), - }, nil) + }, map[string]string{"k": i18n.G("Name of the key to use, otherwise use the default key")}, nil) cmd.hidden = true } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_sign_test.go snapd-2.34.2+18.04/cmd/snap/cmd_sign_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_sign_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_sign_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -43,7 +43,7 @@ func (s *SnapKeysSuite) TestHappyDefaultKey(c *C) { s.stdin.Write(statement) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"sign"}) + rest, err := snap.Parser().ParseArgs([]string{"sign"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) @@ -55,7 +55,7 @@ func (s *SnapKeysSuite) TestHappyNonDefaultKey(c *C) { s.stdin.Write(statement) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"sign", "-k", "another"}) + rest, err := snap.Parser().ParseArgs([]string{"sign", "-k", "another"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_snap_op.go snapd-2.34.2+18.04/cmd/snap/cmd_snap_op.go --- snapd-2.37.1+18.04/cmd/snap/cmd_snap_op.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_snap_op.go 2018-07-19 10:05:50.000000000 +0000 @@ -36,19 +36,16 @@ ) var ( - shortInstallHelp = i18n.G("Install snaps on the system") - shortRemoveHelp = i18n.G("Remove snaps from the system") - shortRefreshHelp = i18n.G("Refresh snaps in the system") - shortTryHelp = i18n.G("Test an unpacked snap in the system") + shortInstallHelp = i18n.G("Install a snap to the system") + shortRemoveHelp = i18n.G("Remove a snap from the system") + shortRefreshHelp = i18n.G("Refresh a snap in the system") + shortTryHelp = i18n.G("Test a snap in the system") shortEnableHelp = i18n.G("Enable a snap in the system") shortDisableHelp = i18n.G("Disable a snap in the system") ) var longInstallHelp = i18n.G(` -The install command installs the named snaps on the system. - -To install multiple instances of the same snap, append an underscore and a -unique identifier (for each instance) to a snap's name. +The install command installs the named snaps in the system. With no further options, the snaps are installed tracking the stable channel, with strict security confinement. @@ -59,12 +56,10 @@ Note a later refresh will typically undo a revision override, taking the snap back to the current revision of the channel it's tracking. - -Use --name to set the instance name when installing from snap file. `) var longRemoveHelp = i18n.G(` -The remove command removes the named snap instance from the system. +The remove command removes the named snap from the system. By default all the snap revisions are removed, including their data and the common data directory. When a --revision option is passed only the specified @@ -118,7 +113,8 @@ func (x *cmdRemove) removeOne(opts *client.SnapOptions) error { name := string(x.Positional.Snaps[0]) - changeID, err := x.client.Remove(name, opts) + cli := Client() + changeID, err := cli.Remove(name, opts) if err != nil { msg, err := errorToCmdMessage(name, err, opts) if err != nil { @@ -128,29 +124,26 @@ return nil } - if _, err := x.wait(changeID); err != nil { + if _, err := x.wait(cli, changeID); err != nil { if err == noWait { return nil } return err } - if opts.Revision != "" { - fmt.Fprintf(Stdout, i18n.G("%s (revision %s) removed\n"), name, opts.Revision) - } else { - fmt.Fprintf(Stdout, i18n.G("%s removed\n"), name) - } + fmt.Fprintf(Stdout, i18n.G("%s removed\n"), name) return nil } func (x *cmdRemove) removeMany(opts *client.SnapOptions) error { names := installedSnapNames(x.Positional.Snaps) - changeID, err := x.client.RemoveMany(names, opts) + cli := Client() + changeID, err := cli.RemoveMany(names, opts) if err != nil { return err } - chg, err := x.wait(changeID) + chg, err := x.wait(cli, changeID) if err != nil { if err == noWait { return nil @@ -216,16 +209,11 @@ } var channelDescs = mixinDescs{ - // TRANSLATORS: This should not start with a lowercase letter. - "channel": i18n.G("Use this channel instead of stable"), - // TRANSLATORS: This should not start with a lowercase letter. - "beta": i18n.G("Install from the beta channel"), - // TRANSLATORS: This should not start with a lowercase letter. - "edge": i18n.G("Install from the edge channel"), - // TRANSLATORS: This should not start with a lowercase letter. + "channel": i18n.G("Use this channel instead of stable"), + "beta": i18n.G("Install from the beta channel"), + "edge": i18n.G("Install from the edge channel"), "candidate": i18n.G("Install from the candidate channel"), - // TRANSLATORS: This should not start with a lowercase letter. - "stable": i18n.G("Install from the stable channel"), + "stable": i18n.G("Install from the stable channel"), } func (mx *channelMixin) setChannelFromCommandline() error { @@ -257,7 +245,8 @@ } // show what has been done -func showDone(cli *client.Client, names []string, op string, esc *escapes) error { +func showDone(names []string, op string) error { + cli := Client() snaps, err := cli.List(names, nil) if err != nil { return err @@ -271,16 +260,16 @@ switch op { case "install": if snap.Publisher != nil { - // TRANSLATORS: the args are a snap name optionally followed by a channel, then a version, then the developer name (e.g. "some-snap (beta) 1.3 from Alice installed") - fmt.Fprintf(Stdout, i18n.G("%s%s %s from %s installed\n"), snap.Name, channelStr, snap.Version, longPublisher(esc, snap.Publisher)) + // TRANSLATORS: the args are a snap name optionally followed by a channel, then a version, then the developer name (e.g. "some-snap (beta) 1.3 from 'alice' installed") + fmt.Fprintf(Stdout, i18n.G("%s%s %s from '%s' installed\n"), snap.Name, channelStr, snap.Version, snap.Publisher.Username) } else { // TRANSLATORS: the args are a snap name optionally followed by a channel, then a version (e.g. "some-snap (beta) 1.3 installed") fmt.Fprintf(Stdout, i18n.G("%s%s %s installed\n"), snap.Name, channelStr, snap.Version) } case "refresh": if snap.Publisher != nil { - // TRANSLATORS: the args are a snap name optionally followed by a channel, then a version, then the developer name (e.g. "some-snap (beta) 1.3 from Alice refreshed") - fmt.Fprintf(Stdout, i18n.G("%s%s %s from %s refreshed\n"), snap.Name, channelStr, snap.Version, longPublisher(esc, snap.Publisher)) + // TRANSLATORS: the args are a snap name optionally followed by a channel, then a version, then the developer name (e.g. "some-snap (beta) 1.3 from 'alice' refreshed") + fmt.Fprintf(Stdout, i18n.G("%s%s %s from '%s' refreshed\n"), snap.Name, channelStr, snap.Version, snap.Publisher.Username) } else { // TRANSLATORS: the args are a snap name optionally followed by a channel, then a version (e.g. "some-snap (beta) 1.3 refreshed") fmt.Fprintf(Stdout, i18n.G("%s%s %s refreshed\n"), snap.Name, channelStr, snap.Version) @@ -311,11 +300,8 @@ } var modeDescs = mixinDescs{ - // TRANSLATORS: This should not start with a lowercase letter. - "classic": i18n.G("Put snap in classic mode and disable security confinement"), - // TRANSLATORS: This should not start with a lowercase letter. - "devmode": i18n.G("Put snap in development mode and disable security confinement"), - // TRANSLATORS: This should not start with a lowercase letter. + "classic": i18n.G("Put snap in classic mode and disable security confinement"), + "devmode": i18n.G("Put snap in development mode and disable security confinement"), "jailmode": i18n.G("Put snap in enforced confinement mode"), } @@ -339,7 +325,6 @@ } type cmdInstall struct { - colorMixin waitMixin channelMixin @@ -353,31 +338,25 @@ Unaliased bool `long:"unaliased"` - Name string `long:"name"` - Positional struct { Snaps []remoteSnapName `positional-arg-name:""` } `positional-args:"yes" required:"yes"` } -func (x *cmdInstall) installOne(nameOrPath, desiredName string, opts *client.SnapOptions) error { +func (x *cmdInstall) installOne(name string, opts *client.SnapOptions) error { var err error + var installFromFile bool var changeID string - var snapName string - var path string - if strings.Contains(nameOrPath, "/") || strings.HasSuffix(nameOrPath, ".snap") || strings.Contains(nameOrPath, ".snap.") { - path = nameOrPath - changeID, err = x.client.InstallPath(path, x.Name, opts) + cli := Client() + if strings.Contains(name, "/") || strings.HasSuffix(name, ".snap") || strings.Contains(name, ".snap.") { + installFromFile = true + changeID, err = cli.InstallPath(name, opts) } else { - snapName = nameOrPath - if desiredName != "" { - return errors.New(i18n.G("cannot use explicit name when installing from store")) - } - changeID, err = x.client.Install(snapName, opts) + changeID, err = cli.Install(name, opts) } if err != nil { - msg, err := errorToCmdMessage(nameOrPath, err, opts) + msg, err := errorToCmdMessage(name, err, opts) if err != nil { return err } @@ -385,7 +364,7 @@ return nil } - chg, err := x.wait(changeID) + chg, err := x.wait(cli, changeID) if err != nil { if err == noWait { return nil @@ -394,13 +373,16 @@ } // extract the snapName from the change, important for sideloaded - if path != "" { + var snapName string + + if installFromFile { if err := chg.Get("snap-name", &snapName); err != nil { - return fmt.Errorf("cannot extract the snap-name from local file %q: %s", nameOrPath, err) + return fmt.Errorf("cannot extract the snap-name from local file %q: %s", name, err) } + name = snapName } - return showDone(x.client, []string{snapName}, "install", x.getEscapes()) + return showDone([]string{name}, "install") } func (x *cmdInstall) installMany(names []string, opts *client.SnapOptions) error { @@ -411,7 +393,8 @@ } } - changeID, err := x.client.InstallMany(names, opts) + cli := Client() + changeID, err := cli.InstallMany(names, opts) if err != nil { var snapName string if err, ok := err.(*client.Error); ok { @@ -425,7 +408,7 @@ return nil } - chg, err := x.wait(changeID) + chg, err := x.wait(cli, changeID) if err != nil { if err == noWait { return nil @@ -439,7 +422,7 @@ } if len(installed) > 0 { - if err := showDone(x.client, installed, "install", x.getEscapes()); err != nil { + if err := showDone(installed, "install"); err != nil { return err } } @@ -478,31 +461,18 @@ x.setModes(opts) names := remoteSnapNames(x.Positional.Snaps) - if len(names) == 0 { - return errors.New(i18n.G("cannot install zero snaps")) - } - for _, name := range names { - if len(name) == 0 { - return errors.New(i18n.G("cannot install snap with empty name")) - } - } - if len(names) == 1 { - return x.installOne(names[0], x.Name, opts) + return x.installOne(names[0], opts) } if x.asksForMode() || x.asksForChannel() { return errors.New(i18n.G("a single snap name is needed to specify mode or channel flags")) } - if x.Name != "" { - return errors.New(i18n.G("cannot use instance name when installing multiple snaps")) - } return x.installMany(names, nil) } type cmdRefresh struct { - colorMixin timeMixin waitMixin channelMixin @@ -519,12 +489,13 @@ } func (x *cmdRefresh) refreshMany(snaps []string, opts *client.SnapOptions) error { - changeID, err := x.client.RefreshMany(snaps, opts) + cli := Client() + changeID, err := cli.RefreshMany(snaps, opts) if err != nil { return err } - chg, err := x.wait(changeID) + chg, err := x.wait(cli, changeID) if err != nil { if err == noWait { return nil @@ -538,7 +509,7 @@ } if len(refreshed) > 0 { - return showDone(x.client, refreshed, "refresh", x.getEscapes()) + return showDone(refreshed, "refresh") } fmt.Fprintln(Stderr, i18n.G("All snaps up to date.")) @@ -547,7 +518,8 @@ } func (x *cmdRefresh) refreshOne(name string, opts *client.SnapOptions) error { - changeID, err := x.client.Refresh(name, opts) + cli := Client() + changeID, err := cli.Refresh(name, opts) if err != nil { msg, err := errorToCmdMessage(name, err, opts) if err != nil { @@ -557,26 +529,19 @@ return nil } - if _, err := x.wait(changeID); err != nil { + if _, err := x.wait(cli, changeID); err != nil { if err == noWait { return nil } return err } - return showDone(x.client, []string{name}, "refresh", x.getEscapes()) -} - -func parseSysinfoTime(s string) time.Time { - t, err := time.Parse(time.RFC3339, s) - if err != nil { - return time.Time{} - } - return t + return showDone([]string{name}, "refresh") } func (x *cmdRefresh) showRefreshTimes() error { - sysinfo, err := x.client.SysInfo() + cli := Client() + sysinfo, err := cli.SysInfo() if err != nil { return err } @@ -588,27 +553,17 @@ } else { return errors.New("internal error: both refresh.timer and refresh.schedule are empty") } - last := parseSysinfoTime(sysinfo.Refresh.Last) - hold := parseSysinfoTime(sysinfo.Refresh.Hold) - next := parseSysinfoTime(sysinfo.Refresh.Next) - - if !last.IsZero() { - fmt.Fprintf(Stdout, "last: %s\n", x.fmtTime(last)) + if t, err := time.Parse(time.RFC3339, sysinfo.Refresh.Last); err == nil { + fmt.Fprintf(Stdout, "last: %s\n", x.fmtTime(t)) } else { fmt.Fprintf(Stdout, "last: n/a\n") } - if !hold.IsZero() { - fmt.Fprintf(Stdout, "hold: %s\n", x.fmtTime(hold)) + + if t, err := time.Parse(time.RFC3339, sysinfo.Refresh.Hold); err == nil { + fmt.Fprintf(Stdout, "hold: %s\n", x.fmtTime(t)) } - // only show "next" if its after "hold" to not confuse users - if !next.IsZero() { - // Snapstate checks for holdTime.After(limitTime) so we need - // to check for before or equal here to be fully correct. - if next.Before(hold) || next.Equal(hold) { - fmt.Fprintf(Stdout, "next: %s (but held)\n", x.fmtTime(next)) - } else { - fmt.Fprintf(Stdout, "next: %s\n", x.fmtTime(next)) - } + if t, err := time.Parse(time.RFC3339, sysinfo.Refresh.Next); err == nil { + fmt.Fprintf(Stdout, "next: %s\n", x.fmtTime(t)) } else { fmt.Fprintf(Stdout, "next: n/a\n") } @@ -616,7 +571,8 @@ } func (x *cmdRefresh) listRefresh() error { - snaps, _, err := x.client.Find(&client.FindOptions{ + cli := Client() + snaps, _, err := cli.Find(&client.FindOptions{ Refresh: true, }) if err != nil { @@ -629,14 +585,12 @@ sort.Sort(snapsByName(snaps)) - esc := x.getEscapes() w := tabWriter() defer w.Flush() - // TRANSLATORS: the %s is to insert a filler escape sequence (please keep it flush to the column header, with no extra spaces) - fmt.Fprintf(w, i18n.G("Name\tVersion\tRev\tPublisher%s\tNotes\n"), fillerPublisher(esc)) + fmt.Fprintln(w, i18n.G("Name\tVersion\tRev\tPublisher\tNotes")) for _, snap := range snaps { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", snap.Name, snap.Version, snap.Revision, shortPublisher(esc, snap.Publisher), NotesFromRemote(snap, nil)) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", snap.Name, snap.Version, snap.Revision, snap.Publisher.Username, NotesFromRemote(snap, nil)) } return nil @@ -652,14 +606,14 @@ if x.Time { if x.asksForMode() || x.asksForChannel() { - return errors.New(i18n.G("--time does not take mode or channel flags")) + return errors.New(i18n.G("--time does not take mode nor channel flags")) } return x.showRefreshTimes() } if x.List { - if len(x.Positional.Snaps) > 0 || x.asksForMode() || x.asksForChannel() { - return errors.New(i18n.G("--list does not accept additional arguments")) + if x.asksForMode() || x.asksForChannel() { + return errors.New(i18n.G("--list does not take mode nor channel flags")) } return x.listRefresh() @@ -720,6 +674,7 @@ if err := x.validateMode(); err != nil { return err } + cli := Client() name := x.Positional.SnapDir opts := &client.SnapOptions{} x.setModes(opts) @@ -743,17 +698,17 @@ return fmt.Errorf(i18n.G("cannot get full path for %q: %v"), name, err) } - changeID, err := x.client.Try(path, opts) + changeID, err := cli.Try(path, opts) + if e, ok := err.(*client.Error); ok && e.Kind == client.ErrorKindNotSnap { + return fmt.Errorf(i18n.G(`%q does not contain an unpacked snap. + +Try 'snapcraft prime' in your project directory, then 'snap try' again.`), path) + } if err != nil { - msg, err := errorToCmdMessage(name, err, opts) - if err != nil { - return err - } - fmt.Fprintln(Stderr, msg) - return nil + return err } - chg, err := x.wait(changeID) + chg, err := x.wait(cli, changeID) if err != nil { if err == noWait { return nil @@ -770,7 +725,7 @@ name = snapName // show output as speced - snaps, err := x.client.List([]string{name}, nil) + snaps, err := cli.List([]string{name}, nil) if err != nil { return err } @@ -793,14 +748,15 @@ } func (x *cmdEnable) Execute([]string) error { + cli := Client() name := string(x.Positional.Snap) opts := &client.SnapOptions{} - changeID, err := x.client.Enable(name, opts) + changeID, err := cli.Enable(name, opts) if err != nil { return err } - if _, err := x.wait(changeID); err != nil { + if _, err := x.wait(cli, changeID); err != nil { if err == noWait { return nil } @@ -820,14 +776,15 @@ } func (x *cmdDisable) Execute([]string) error { + cli := Client() name := string(x.Positional.Snap) opts := &client.SnapOptions{} - changeID, err := x.client.Disable(name, opts) + changeID, err := cli.Disable(name, opts) if err != nil { return err } - if _, err := x.wait(changeID); err != nil { + if _, err := x.wait(cli, changeID); err != nil { if err == noWait { return nil } @@ -867,22 +824,23 @@ return err } + cli := Client() name := string(x.Positional.Snap) opts := &client.SnapOptions{Revision: x.Revision} x.setModes(opts) - changeID, err := x.client.Revert(name, opts) + changeID, err := cli.Revert(name, opts) if err != nil { return err } - if _, err := x.wait(changeID); err != nil { + if _, err := x.wait(cli, changeID); err != nil { if err == noWait { return nil } return err } - return showDone(x.client, []string{name}, "revert", nil) + return showDone([]string{name}, "revert") } var shortSwitchHelp = i18n.G("Switches snap to a different channel") @@ -908,17 +866,18 @@ return fmt.Errorf("missing --channel= parameter") } + cli := Client() name := string(x.Positional.Snap) channel := string(x.Channel) opts := &client.SnapOptions{ Channel: channel, } - changeID, err := x.client.Switch(name, opts) + changeID, err := cli.Switch(name, opts) if err != nil { return err } - if _, err := x.wait(changeID); err != nil { + if _, err := x.wait(cli, changeID); err != nil { if err == noWait { return nil } @@ -931,42 +890,27 @@ func init() { addCommand("remove", shortRemoveHelp, longRemoveHelp, func() flags.Commander { return &cmdRemove{} }, - waitDescs.also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "revision": i18n.G("Remove only the given revision"), - }), nil) + waitDescs.also(map[string]string{"revision": i18n.G("Remove only the given revision")}), nil) addCommand("install", shortInstallHelp, longInstallHelp, func() flags.Commander { return &cmdInstall{} }, - colorDescs.also(waitDescs).also(channelDescs).also(modeDescs).also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "revision": i18n.G("Install the given revision of a snap, to which you must have developer access"), - // TRANSLATORS: This should not start with a lowercase letter. - "dangerous": i18n.G("Install the given snap file even if there are no pre-acknowledged signatures for it, meaning it was not verified and could be dangerous (--devmode implies this)"), - // TRANSLATORS: This should not start with a lowercase letter. + waitDescs.also(channelDescs).also(modeDescs).also(map[string]string{ + "revision": i18n.G("Install the given revision of a snap, to which you must have developer access"), + "dangerous": i18n.G("Install the given snap file even if there are no pre-acknowledged signatures for it, meaning it was not verified and could be dangerous (--devmode implies this)"), "force-dangerous": i18n.G("Alias for --dangerous (DEPRECATED)"), - // TRANSLATORS: This should not start with a lowercase letter. - "unaliased": i18n.G("Install the given snap without enabling its automatic aliases"), - // TRANSLATORS: This should not start with a lowercase letter. - "name": i18n.G("Install the snap file under the given instance name"), + "unaliased": i18n.G("Install the given snap without enabling its automatic aliases"), }), nil) addCommand("refresh", shortRefreshHelp, longRefreshHelp, func() flags.Commander { return &cmdRefresh{} }, - colorDescs.also(waitDescs).also(channelDescs).also(modeDescs).also(timeDescs).also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "amend": i18n.G("Allow refresh attempt on snap unknown to the store"), - // TRANSLATORS: This should not start with a lowercase letter. - "revision": i18n.G("Refresh to the given revision, to which you must have developer access"), - // TRANSLATORS: This should not start with a lowercase letter. - "list": i18n.G("Show the new versions of snaps that would be updated with the next refresh"), - // TRANSLATORS: This should not start with a lowercase letter. - "time": i18n.G("Show auto refresh information but do not perform a refresh"), - // TRANSLATORS: This should not start with a lowercase letter. + waitDescs.also(channelDescs).also(modeDescs).also(timeDescs).also(map[string]string{ + "amend": i18n.G("Allow refresh attempt on snap unknown to the store"), + "revision": i18n.G("Refresh to the given revision, to which you must have developer access"), + "list": i18n.G("Show available snaps for refresh but do not perform a refresh"), + "time": i18n.G("Show auto refresh information but do not perform a refresh"), "ignore-validation": i18n.G("Ignore validation by other snaps blocking the refresh"), }), nil) addCommand("try", shortTryHelp, longTryHelp, func() flags.Commander { return &cmdTry{} }, waitDescs.also(modeDescs), nil) addCommand("enable", shortEnableHelp, longEnableHelp, func() flags.Commander { return &cmdEnable{} }, waitDescs, nil) addCommand("disable", shortDisableHelp, longDisableHelp, func() flags.Commander { return &cmdDisable{} }, waitDescs, nil) addCommand("revert", shortRevertHelp, longRevertHelp, func() flags.Commander { return &cmdRevert{} }, waitDescs.also(modeDescs).also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "revision": i18n.G("Revert to the given revision"), + "revision": "Revert to the given revision", }), nil) addCommand("switch", shortSwitchHelp, longSwitchHelp, func() flags.Commander { return &cmdSwitch{} }, waitDescs.also(channelDescs), nil) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_snap_op_test.go snapd-2.34.2+18.04/cmd/snap/cmd_snap_op_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_snap_op_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_snap_op_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -38,7 +38,6 @@ "github.com/snapcore/snapd/progress" "github.com/snapcore/snapd/progress/progresstest" "github.com/snapcore/snapd/testutil" - "os" ) type snapOpTestServer struct { @@ -49,7 +48,6 @@ total int channel string rebooting bool - snap string } var _ = check.Suite(&SnapOpSuite{}) @@ -76,11 +74,11 @@ case 2: t.c.Check(r.Method, check.Equals, "GET") t.c.Check(r.URL.Path, check.Equals, "/v2/changes/42") - fmt.Fprintf(w, `{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-name": "%s"}}}\n`, t.snap) + fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-name": "foo"}}}`) case 3: t.c.Check(r.Method, check.Equals, "GET") t.c.Check(r.URL.Path, check.Equals, "/v2/snaps") - fmt.Fprintf(w, `{"type": "sync", "result": [{"name": "%s", "status": "active", "version": "1.0", "developer": "bar", "publisher": {"id": "bar-id", "username": "bar", "display-name": "Bar", "validation": "unproven"}, "revision":42, "channel":"%s"}]}\n`, t.snap, t.channel) + fmt.Fprintf(w, `{"type": "sync", "result": [{"name": "foo", "status": "active", "version": "1.0", "developer": "bar", "publisher": {"id": "bar-id", "username": "bar", "display-name": "Bar", "validation": "unproven"}, "revision":42, "channel":"%s"}]}\n`, t.channel) default: t.c.Fatalf("expected to get %d requests, now on %d", t.total, t.n+1) } @@ -108,7 +106,6 @@ s.srv = snapOpTestServer{ c: c, total: 4, - snap: "foo", } } @@ -196,10 +193,10 @@ } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "candidate", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "candidate", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(candidate\) 1.0 from Bar installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(candidate\) 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) @@ -217,10 +214,10 @@ s.RedirectClientToTestServer(s.srv.handle) // snap install --channel=3.4 means 3.4/stable, this is what we test here - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "3.4", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "3.4", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/stable\) 1.0 from Bar installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/stable\) 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) @@ -237,10 +234,10 @@ } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "3.4/hotfix-1", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "3.4/hotfix-1", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/hotfix-1\) 1.0 from Bar installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/hotfix-1\) 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) @@ -258,10 +255,10 @@ } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "beta", "--devmode", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "beta", "--devmode", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(beta\) 1.0 from Bar installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(beta\) 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) @@ -277,10 +274,10 @@ } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--classic", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) @@ -296,10 +293,10 @@ } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--unaliased", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--unaliased", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) @@ -310,7 +307,7 @@ fmt.Fprintln(w, `{"type": "error", "result": {"message": "snap not found", "value": "foo", "kind": "snap-not-found"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("error: %v\n", err), check.Equals, `error: snap "foo" not found `) @@ -324,7 +321,7 @@ fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision available as specified", "value": "foo", "kind": "snap-revision-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: snap "foo" not available as specified (see 'snap info foo') @@ -339,7 +336,7 @@ fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision available as specified", "value": "foo", "kind": "snap-revision-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=mytrack", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "--channel=mytrack", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: snap "foo" not available on channel "mytrack/stable" (see 'snap info @@ -355,7 +352,7 @@ fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision available as specified", "value": "foo", "kind": "snap-revision-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--revision=2", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "--revision=2", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: snap "foo" revision 2 not available (see 'snap info foo') @@ -377,7 +374,7 @@ }, "kind": "snap-channel-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: snap "foo" is not available on stable but is available to install on the @@ -407,7 +404,7 @@ }, "kind": "snap-channel-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--candidate", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "--candidate", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: snap "foo" is not available on candidate but is available to install on @@ -435,7 +432,7 @@ }, "kind": "snap-channel-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: snap "foo" is not available on latest/stable but is available to install @@ -463,7 +460,7 @@ }, "kind": "snap-channel-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=2.0/stable", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "--channel=2.0/stable", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: snap "foo" is not available on 2.0/stable but is available to install on @@ -490,7 +487,7 @@ }, "kind": "snap-channel-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=2.0/stable", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "--channel=2.0/stable", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: snap "foo" is not available on 2.0/stable but other tracks exist. @@ -515,7 +512,7 @@ }, "kind": "snap-architecture-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: snap "foo" is not available on stable for this architecture (arm64) but @@ -538,7 +535,7 @@ }, "kind": "snap-architecture-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=1.0/stable", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "--channel=1.0/stable", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: snap "foo" is not available on this architecture (arm64) but exists on @@ -560,7 +557,7 @@ }, "kind": "snap-channel-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=a/b/c/d", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "--channel=a/b/c/d", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: requested channel "a/b/c/d" is not valid (see 'snap info foo' for valid @@ -582,7 +579,7 @@ }, "kind": "snap-channel-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=stable/baz", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "--channel=stable/baz", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: requested a non-existing branch on latest/stable for snap "foo": baz @@ -603,7 +600,7 @@ }, "kind": "snap-channel-not-available"}, "status-code": 404}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=stable/baz", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"install", "--channel=stable/baz", "foo"}) c.Assert(err, check.NotNil) c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, ` error: requested a non-existing branch for snap "foo": latest/stable/baz @@ -667,10 +664,10 @@ err := ioutil.WriteFile(snapPath, snapBody, 0644) c.Assert(err, check.IsNil) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", snapPath}) + rest, err := snap.Parser().ParseArgs([]string{"install", snapPath}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) @@ -698,10 +695,10 @@ err := ioutil.WriteFile(snapPath, snapBody, 0644) c.Assert(err, check.IsNil) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--devmode", snapPath}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--devmode", snapPath}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) @@ -729,10 +726,10 @@ err := ioutil.WriteFile(snapPath, snapBody, 0644) c.Assert(err, check.IsNil) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", snapPath}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--classic", snapPath}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) @@ -760,60 +757,15 @@ err := ioutil.WriteFile(snapPath, snapBody, 0644) c.Assert(err, check.IsNil) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--dangerous", snapPath}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--dangerous", snapPath}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) } -func (s *SnapOpSuite) TestInstallPathInstance(c *check.C) { - s.srv.checker = func(r *http.Request) { - c.Check(r.URL.Path, check.Equals, "/v2/snaps") - - form := testForm(r, c) - defer form.RemoveAll() - - c.Check(form.Value["action"], check.DeepEquals, []string{"install"}) - c.Check(form.Value["name"], check.DeepEquals, []string{"foo_bar"}) - c.Check(form.Value["devmode"], check.IsNil) - c.Check(form.Value["snap-path"], check.NotNil) - c.Check(form.Value, check.HasLen, 3) - - name, _, body := formFile(form, c) - c.Check(name, check.Equals, "snap") - c.Check(string(body), check.Equals, "snap-data") - } - - snapBody := []byte("snap-data") - s.RedirectClientToTestServer(s.srv.handle) - // instance is named foo_bar - s.srv.snap = "foo_bar" - snapPath := filepath.Join(c.MkDir(), "foo.snap") - err := ioutil.WriteFile(snapPath, snapBody, 0644) - c.Assert(err, check.IsNil) - - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", snapPath, "--name", "foo_bar"}) - c.Assert(rest, check.DeepEquals, []string{}) - c.Assert(err, check.IsNil) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo_bar 1.0 from Bar installed`) - c.Check(s.Stderr(), check.Equals, "") - // ensure that the fake server api was actually hit - c.Check(s.srv.n, check.Equals, s.srv.total) -} - -func (s *SnapSuite) TestInstallWithInstanceNoPath(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--name", "foo_bar", "some-snap"}) - c.Assert(err, check.ErrorMatches, "cannot use explicit name when installing from store") -} - -func (s *SnapSuite) TestInstallManyWithInstance(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--name", "foo_bar", "some-snap-1", "some-snap-2"}) - c.Assert(err, check.ErrorMatches, "cannot use instance name when installing multiple snaps") -} - func (s *SnapOpSuite) TestRevertRunthrough(c *check.C) { s.srv.total = 4 s.srv.channel = "potato" @@ -825,7 +777,7 @@ } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"revert", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"revert", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) // tracking channel is "" in the test server @@ -875,7 +827,7 @@ } } - rest, err := snap.Parser(snap.Client()).ParseArgs(cmd) + rest, err := snap.Parser().ParseArgs(cmd) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, "foo reverted to 1.0\n") @@ -901,25 +853,11 @@ } func (s *SnapOpSuite) TestRevertMissingName(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"revert"}) + _, err := snap.Parser().ParseArgs([]string{"revert"}) c.Assert(err, check.NotNil) c.Assert(err, check.ErrorMatches, "the required argument `` was not provided") } -func (s *SnapSuite) TestRefreshListLessOptions(c *check.C) { - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - c.Fatal("expected to get 0 requests") - }) - - for _, flag := range []string{"--beta", "--channel=potato", "--classic"} { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--list", flag}) - c.Assert(err, check.ErrorMatches, "--list does not accept additional arguments") - - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--list", flag, "some-snap"}) - c.Assert(err, check.ErrorMatches, "--list does not accept additional arguments") - } -} - func (s *SnapSuite) TestRefreshList(c *check.C) { n := 0 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { @@ -935,7 +873,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--list"}) + rest, err := snap.Parser().ParseArgs([]string{"refresh", "--list"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `Name +Version +Rev +Publisher +Notes @@ -960,7 +898,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time", "--abs-time"}) + rest, err := snap.Parser().ParseArgs([]string{"refresh", "--time", "--abs-time"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, `schedule: 00:00-04:59/5:00-10:59/11:00-16:59/17:00-23:59 @@ -986,7 +924,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time", "--abs-time"}) + rest, err := snap.Parser().ParseArgs([]string{"refresh", "--time", "--abs-time"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, `timer: 0:00-24:00/4 @@ -1012,13 +950,13 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time", "--abs-time"}) + rest, err := snap.Parser().ParseArgs([]string{"refresh", "--time", "--abs-time"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Equals, `timer: 0:00-24:00/4 last: 2017-04-25T17:35:00+02:00 hold: 2017-04-28T00:00:00+02:00 -next: 2017-04-26T00:58:00+02:00 (but held) +next: 2017-04-26T00:58:00+02:00 `) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit @@ -1031,10 +969,16 @@ c.Check(r.URL.Path, check.Equals, "/v2/system-info") fmt.Fprintln(w, `{"type": "sync", "status-code": 200, "result": {"refresh": {"last": "2017-04-25T17:35:00+0200", "next": "2017-04-26T00:58:00+0200"}}}`) }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--time"}) c.Assert(err, check.ErrorMatches, `internal error: both refresh.timer and refresh.schedule are empty`) } +func (s *SnapSuite) TestRefreshListErr(c *check.C) { + s.RedirectClientToTestServer(nil) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--list", "--beta"}) + c.Check(err, check.ErrorMatches, "--list does not take .* flags") +} + func (s *SnapOpSuite) TestRefreshOne(c *check.C) { s.RedirectClientToTestServer(s.srv.handle) s.srv.checker = func(r *http.Request) { @@ -1044,9 +988,9 @@ "action": "refresh", }) } - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "foo"}) c.Assert(err, check.IsNil) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar refreshed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from 'bar' refreshed`) } @@ -1061,9 +1005,9 @@ }) s.srv.channel = "beta" } - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--beta", "foo"}) c.Assert(err, check.IsNil) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(beta\) 1.0 from Bar refreshed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(beta\) 1.0 from 'bar' refreshed`) } func (s *SnapOpSuite) TestRefreshOneClassic(c *check.C) { @@ -1076,7 +1020,7 @@ "classic": true, }) } - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--classic", "one"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--classic", "one"}) c.Assert(err, check.IsNil) } @@ -1090,7 +1034,7 @@ "devmode": true, }) } - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--devmode", "one"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--devmode", "one"}) c.Assert(err, check.IsNil) } @@ -1104,7 +1048,7 @@ "jailmode": true, }) } - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--jailmode", "one"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--jailmode", "one"}) c.Assert(err, check.IsNil) } @@ -1118,7 +1062,7 @@ "ignore-validation": true, }) } - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--ignore-validation", "one"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--ignore-validation", "one"}) c.Assert(err, check.IsNil) } @@ -1144,37 +1088,37 @@ func (s *SnapOpSuite) TestRefreshOneModeErr(c *check.C) { s.RedirectClientToTestServer(nil) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--jailmode", "--devmode", "one"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--jailmode", "--devmode", "one"}) c.Assert(err, check.ErrorMatches, `cannot use devmode and jailmode flags together`) } func (s *SnapOpSuite) TestRefreshOneChanErr(c *check.C) { s.RedirectClientToTestServer(nil) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta", "--channel=foo", "one"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--beta", "--channel=foo", "one"}) c.Assert(err, check.ErrorMatches, `Please specify a single channel`) } func (s *SnapOpSuite) TestRefreshAllChannel(c *check.C) { s.RedirectClientToTestServer(nil) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--beta"}) c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`) } func (s *SnapOpSuite) TestRefreshManyChannel(c *check.C) { s.RedirectClientToTestServer(nil) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta", "one", "two"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--beta", "one", "two"}) c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`) } func (s *SnapOpSuite) TestRefreshManyIgnoreValidation(c *check.C) { s.RedirectClientToTestServer(nil) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--ignore-validation", "one", "two"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--ignore-validation", "one", "two"}) c.Assert(err, check.ErrorMatches, `a single snap name must be specified when ignoring validation`) } func (s *SnapOpSuite) TestRefreshAllModeFlags(c *check.C) { s.RedirectClientToTestServer(nil) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--devmode"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--devmode"}) c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`) } @@ -1188,7 +1132,7 @@ "amend": true, }) } - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--amend", "one"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--amend", "one"}) c.Assert(err, check.IsNil) } @@ -1239,7 +1183,7 @@ } } - rest, err := snap.Parser(snap.Client()).ParseArgs(cmd) + rest, err := snap.Parser().ParseArgs(cmd) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, fmt.Sprintf(`(?sm).*foo 1.0 mounted from .*%s`, tryDir)) @@ -1280,77 +1224,19 @@ }) cmd := []string{"try", "/"} - _, err := snap.Parser(snap.Client()).ParseArgs(cmd) - c.Assert(err, testutil.EqualsWrapped, ` -"/" does not contain an unpacked snap. + _, err := snap.Parser().ParseArgs(cmd) + c.Assert(err, check.ErrorMatches, `"/" does not contain an unpacked snap. Try 'snapcraft prime' in your project directory, then 'snap try' again.`) } -func (s *SnapOpSuite) TestTryMissingOpt(c *check.C) { - oldArgs := os.Args - defer func() { - os.Args = oldArgs - }() - os.Args = []string{"snap", "try", "./"} - var kind string - - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.Method, check.Equals, "POST", check.Commentf("%q", kind)) - w.WriteHeader(400) - fmt.Fprintf(w, ` -{ - "type": "error", - "result": { - "message":"error from server", - "value": "some-snap", - "kind": %q - }, - "status-code": 400 -}`, kind) - }) - - type table struct { - kind, expected string - } - - tests := []table{ - {"snap-needs-classic", "published using classic confinement"}, - {"snap-needs-devmode", "only meant for development"}, - } - - for _, test := range tests { - kind = test.kind - c.Check(snap.RunMain(), testutil.ContainsWrapped, test.expected, check.Commentf("%q", kind)) - } -} - -func (s *SnapOpSuite) TestInstallConfinedAsClassic(c *check.C) { - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - c.Check(r.Method, check.Equals, "POST") - w.WriteHeader(400) - fmt.Fprintf(w, `{ - "type": "error", - "result": { - "message":"error from server", - "value": "some-snap", - "kind": "snap-not-classic" - }, - "status-code": 400 -}`) - }) - - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", "some-snap"}) - c.Assert(err, check.ErrorMatches, `snap "some-snap" is not compatible with --classic`) -} - func (s *SnapSuite) TestInstallChannelDuplicationError(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--edge", "--beta", "some-snap"}) + _, err := snap.Parser().ParseArgs([]string{"install", "--edge", "--beta", "some-snap"}) c.Assert(err, check.ErrorMatches, "Please specify a single channel") } func (s *SnapSuite) TestRefreshChannelDuplicationError(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--edge", "--beta", "some-snap"}) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--edge", "--beta", "some-snap"}) c.Assert(err, check.ErrorMatches, "Please specify a single channel") } @@ -1365,10 +1251,10 @@ } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--edge", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--edge", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(edge\) 1.0 from Bar installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(edge\) 1.0 from 'bar' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) @@ -1384,7 +1270,7 @@ } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"enable", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"enable", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `(?sm).*foo enabled`) @@ -1403,7 +1289,7 @@ } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"disable", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"disable", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `(?sm).*foo disabled`) @@ -1422,7 +1308,7 @@ } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"remove", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `(?sm).*foo removed`) @@ -1431,29 +1317,9 @@ c.Check(s.srv.n, check.Equals, s.srv.total) } -func (s *SnapOpSuite) TestRemoveRevision(c *check.C) { - s.srv.total = 3 - s.srv.checker = func(r *http.Request) { - c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") - c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ - "action": "remove", - "revision": "17", - }) - } - - s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--revision=17", "foo"}) - c.Assert(err, check.IsNil) - c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(revision 17\) removed`) - c.Check(s.Stderr(), check.Equals, "") - // ensure that the fake server api was actually hit - c.Check(s.srv.n, check.Equals, s.srv.total) -} - func (s *SnapOpSuite) TestRemoveManyRevision(c *check.C) { s.RedirectClientToTestServer(nil) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--revision=17", "one", "two"}) + _, err := snap.Parser().ParseArgs([]string{"remove", "--revision=17", "one", "two"}) c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify the revision`) } @@ -1487,7 +1353,7 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "one", "two"}) + rest, err := snap.Parser().ParseArgs([]string{"remove", "one", "two"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `(?sm).*one removed`) @@ -1499,13 +1365,13 @@ func (s *SnapOpSuite) TestInstallManyChannel(c *check.C) { s.RedirectClientToTestServer(nil) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--beta", "one", "two"}) + _, err := snap.Parser().ParseArgs([]string{"install", "--beta", "one", "two"}) c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`) } func (s *SnapOpSuite) TestInstallManyMixFileAndStore(c *check.C) { s.RedirectClientToTestServer(nil) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "store-snap", "./local.snap"}) + _, err := snap.Parser().ParseArgs([]string{"install", "store-snap", "./local.snap"}) c.Assert(err, check.ErrorMatches, `only one snap file can be installed at a time`) } @@ -1544,26 +1410,17 @@ n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "one", "two"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "one", "two"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) // note that (stable) is omitted - c.Check(s.Stdout(), check.Matches, `(?sm).*one 1.0 from Bar installed`) - c.Check(s.Stdout(), check.Matches, `(?sm).*two \(edge\) 2.0 from Baz installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*one 1.0 from 'bar' installed`) + c.Check(s.Stdout(), check.Matches, `(?sm).*two \(edge\) 2.0 from 'baz' installed`) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(n, check.Equals, total) } -func (s *SnapOpSuite) TestInstallZeroEmpty(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install"}) - c.Assert(err, check.ErrorMatches, "cannot install zero snaps") - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"install", ""}) - c.Assert(err, check.ErrorMatches, "cannot install snap with empty name") - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"install", "", "bar"}) - c.Assert(err, check.ErrorMatches, "cannot install snap with empty name") -} - func (s *SnapOpSuite) TestNoWait(c *check.C) { s.srv.checker = func(r *http.Request) {} @@ -1594,7 +1451,7 @@ s.RedirectClientToTestServer(s.srv.handle) for _, cmd := range cmds { - rest, err := snap.Parser(snap.Client()).ParseArgs(cmd) + rest, err := snap.Parser().ParseArgs(cmd) c.Assert(err, check.IsNil, check.Commentf("%v", cmd)) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, "(?sm)42\n") @@ -1638,7 +1495,7 @@ }) for _, cmd := range cmds { - _, err := snap.Parser(snap.Client()).ParseArgs(cmd) + _, err := snap.Parser().ParseArgs(cmd) c.Assert(err, check.ErrorMatches, "failure", check.Commentf("%v", cmd)) } } @@ -1688,7 +1545,7 @@ }) for _, cmd := range cmds { - _, err := snap.Parser(snap.Client()).ParseArgs(cmd) + _, err := snap.Parser().ParseArgs(cmd) c.Assert(err, check.ErrorMatches, "server error", check.Commentf("%v", cmd)) // reset n = 0 @@ -1706,7 +1563,7 @@ } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--beta", "foo"}) + rest, err := snap.Parser().ParseArgs([]string{"switch", "--beta", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" switched to the "beta" channel`) @@ -1716,12 +1573,12 @@ } func (s *SnapOpSuite) TestSwitchUnhappy(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch"}) + _, err := snap.Parser().ParseArgs([]string{"switch"}) c.Assert(err, check.ErrorMatches, "the required argument `` was not provided") } func (s *SnapOpSuite) TestSwitchAlsoUnhappy(c *check.C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "foo"}) + _, err := snap.Parser().ParseArgs([]string{"switch", "foo"}) c.Assert(err, check.ErrorMatches, `missing --channel= parameter`) } @@ -1743,6 +1600,6 @@ }) cmd := []string{"install", "hello"} - _, err := snap.Parser(snap.Client()).ParseArgs(cmd) + _, err := snap.Parser().ParseArgs(cmd) c.Assert(err, check.ErrorMatches, `unable to contact snap store`) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_snapshot.go snapd-2.34.2+18.04/cmd/snap/cmd_snapshot.go --- snapd-2.37.1+18.04/cmd/snap/cmd_snapshot.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_snapshot.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,331 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main - -import ( - "fmt" - - "github.com/jessevdk/go-flags" - - "github.com/snapcore/snapd/i18n" - "github.com/snapcore/snapd/strutil" - "github.com/snapcore/snapd/strutil/quantity" -) - -func fmtSize(size int64) string { - return quantity.FormatAmount(uint64(size), -1) -} - -var ( - shortSavedHelp = i18n.G("List currently stored snapshots") - shortSaveHelp = i18n.G("Save a snapshot of the current data") - shortForgetHelp = i18n.G("Delete a snapshot") - shortCheckHelp = i18n.G("Check a snapshot") - shortRestoreHelp = i18n.G("Restore a snapshot") -) - -var longSavedHelp = i18n.G(` -The saved command displays a list of snapshots that have been created -previously with the 'save' command. -`) -var longSaveHelp = i18n.G(` -The save command creates a snapshot of the current user, system and -configuration data for the given snaps. - -By default, this command saves the data of all snaps for all users. -Alternatively, you can specify the data of which snaps to save, or -for which users, or a combination of these. - -If a snap is included in a save operation, excluding its system and -configuration data from the snapshot is not currently possible. This -restriction may be lifted in the future. -`) -var longForgetHelp = i18n.G(` -The forget command deletes a snapshot. This operation can not be -undone. - -A snapshot contains archives for the user, system and configuration -data of each snap included in the snapshot. - -By default, this command forgets all the data in a snapshot. -Alternatively, you can specify the data of which snaps to forget. -`) -var longCheckHelp = i18n.G(` -The check-snapshot command verifies the user, system and configuration -data of the snaps included in the specified snapshot. - -The check operation runs the same data integrity verification that is -performed when a snapshot is restored. - -By default, this command checks all the data in a snapshot. -Alternatively, you can specify the data of which snaps to check, or -for which users, or a combination of these. - -If a snap is included in a check-snapshot operation, excluding its -system and configuration data from the check is not currently -possible. This restriction may be lifted in the future. -`) -var longRestoreHelp = i18n.G(` -The restore command replaces the current user, system and -configuration data of included snaps, with the corresponding data from -the specified snapshot. - -By default, this command restores all the data in a snapshot. -Alternatively, you can specify the data of which snaps to restore, or -for which users, or a combination of these. - -If a snap is included in a restore operation, excluding its system and -configuration data from the restore is not currently possible. This -restriction may be lifted in the future. -`) - -type savedCmd struct { - clientMixin - durationMixin - ID snapshotID `long:"id"` - Positional struct { - Snaps []installedSnapName `positional-arg-name:""` - } `positional-args:"yes"` -} - -func (x *savedCmd) Execute([]string) error { - setID := uint64(x.ID) - snaps := installedSnapNames(x.Positional.Snaps) - list, err := x.client.SnapshotSets(setID, snaps) - if err != nil { - return err - } - if len(list) == 0 { - fmt.Fprintln(Stdout, i18n.G("No snapshots found.")) - return nil - } - w := tabWriter() - defer w.Flush() - - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", - // TRANSLATORS: 'Set' as in group or bag of things - i18n.G("Set"), - "Snap", - // TRANSLATORS: 'Age' as in how old something is - i18n.G("Age"), - i18n.G("Version"), - // TRANSLATORS: 'Rev' is an abbreviation of 'Revision' - i18n.G("Rev"), - i18n.G("Size"), - // TRANSLATORS: 'Notes' as in 'Comments' - i18n.G("Notes")) - for _, sg := range list { - for _, sh := range sg.Snapshots { - note := "-" - if sh.Broken != "" { - note = "broken: " + sh.Broken - } - size := quantity.FormatAmount(uint64(sh.Size), -1) + "B" - fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%s\t%s\n", sg.ID, sh.Snap, x.fmtDuration(sh.Time), sh.Version, sh.Revision, size, note) - } - } - return nil -} - -type saveCmd struct { - waitMixin - durationMixin - Users string `long:"users"` - Positional struct { - Snaps []installedSnapName `positional-arg-name:""` - } `positional-args:"yes"` -} - -func (x *saveCmd) Execute([]string) error { - snaps := installedSnapNames(x.Positional.Snaps) - users := strutil.CommaSeparatedList(x.Users) - setID, changeID, err := x.client.SnapshotMany(snaps, users) - if err != nil { - return err - } - if _, err := x.wait(changeID); err != nil { - if err == noWait { - return nil - } - return err - } - - y := &savedCmd{ - clientMixin: x.clientMixin, - durationMixin: x.durationMixin, - ID: snapshotID(setID), - } - return y.Execute(nil) -} - -type forgetCmd struct { - waitMixin - Positional struct { - ID snapshotID `positional-arg-name:""` - Snaps []installedSnapName `positional-arg-name:""` - } `positional-args:"yes" required:"yes"` -} - -func (x *forgetCmd) Execute([]string) error { - setID := uint64(x.Positional.ID) - snaps := installedSnapNames(x.Positional.Snaps) - changeID, err := x.client.ForgetSnapshots(setID, snaps) - if err != nil { - return err - } - _, err = x.wait(changeID) - if err == noWait { - return nil - } - if err != nil { - return err - } - - if len(snaps) > 0 { - // TRANSLATORS: the %s is a comma-separated list of quoted snap names - fmt.Fprintf(Stdout, i18n.NG("Snapshot #%d of snap %s forgotten.\n", "Snapshot #%d of snaps %s forgotten.\n", len(snaps)), x.Positional.ID, strutil.Quoted(snaps)) - } else { - fmt.Fprintf(Stdout, i18n.G("Snapshot #%d forgotten.\n"), x.Positional.ID) - } - return nil -} - -type checkSnapshotCmd struct { - waitMixin - Users string `long:"users"` - Positional struct { - ID snapshotID `positional-arg-name:""` - Snaps []installedSnapName `positional-arg-name:""` - } `positional-args:"yes" required:"yes"` -} - -func (x *checkSnapshotCmd) Execute([]string) error { - setID := uint64(x.Positional.ID) - snaps := installedSnapNames(x.Positional.Snaps) - users := strutil.CommaSeparatedList(x.Users) - changeID, err := x.client.CheckSnapshots(setID, snaps, users) - if err != nil { - return err - } - _, err = x.wait(changeID) - if err == noWait { - return nil - } - if err != nil { - return err - } - - // TODO: also mention the home archives that were actually checked - if len(snaps) > 0 { - // TRANSLATORS: the %s is a comma-separated list of quoted snap names - fmt.Fprintf(Stdout, i18n.G("Snapshot #%d of snaps %s verified successfully.\n"), - x.Positional.ID, strutil.Quoted(snaps)) - } else { - fmt.Fprintf(Stdout, i18n.G("Snapshot #%d verified successfully.\n"), x.Positional.ID) - } - return nil -} - -type restoreCmd struct { - waitMixin - Users string `long:"users"` - Positional struct { - ID snapshotID `positional-arg-name:""` - Snaps []installedSnapName `positional-arg-name:""` - } `positional-args:"yes" required:"yes"` -} - -func (x *restoreCmd) Execute([]string) error { - setID := uint64(x.Positional.ID) - snaps := installedSnapNames(x.Positional.Snaps) - users := strutil.CommaSeparatedList(x.Users) - changeID, err := x.client.RestoreSnapshots(setID, snaps, users) - if err != nil { - return err - } - _, err = x.wait(changeID) - if err == noWait { - return nil - } - if err != nil { - return err - } - - // TODO: also mention the home archives that were actually restored - if len(snaps) > 0 { - // TRANSLATORS: the %s is a comma-separated list of quoted snap names - fmt.Fprintf(Stdout, i18n.G("Restored snapshot #%d of snaps %s.\n"), - x.Positional.ID, strutil.Quoted(snaps)) - } else { - fmt.Fprintf(Stdout, i18n.G("Restored snapshot #%d.\n"), x.Positional.ID) - } - return nil -} - -func init() { - addCommand("saved", - shortSavedHelp, - longSavedHelp, - func() flags.Commander { - return &savedCmd{} - }, - durationDescs.also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "id": i18n.G("Show only a specific snapshot."), - }), - nil) - - addCommand("save", - shortSaveHelp, - longSaveHelp, - func() flags.Commander { - return &saveCmd{} - }, durationDescs.also(waitDescs).also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "users": i18n.G("Snapshot data of only specific users (comma-separated) (default: all users)"), - }), nil) - - addCommand("restore", - shortRestoreHelp, - longRestoreHelp, - func() flags.Commander { - return &restoreCmd{} - }, waitDescs.also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "users": i18n.G("Restore data of only specific users (comma-separated) (default: all users)"), - }), nil) - - addCommand("forget", - shortForgetHelp, - longForgetHelp, - func() flags.Commander { - return &forgetCmd{} - }, waitDescs, nil) - - addCommand("check-snapshot", - shortCheckHelp, - longCheckHelp, - func() flags.Commander { - return &checkSnapshotCmd{} - }, waitDescs.also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "users": i18n.G("Check data of only specific users (comma-separated) (default: all users)"), - }), nil) -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_unalias.go snapd-2.34.2+18.04/cmd/snap/cmd_unalias.go --- snapd-2.37.1+18.04/cmd/snap/cmd_unalias.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_unalias.go 2018-07-19 10:05:50.000000000 +0000 @@ -32,7 +32,7 @@ } `positional-args:"true"` } -var shortUnaliasHelp = i18n.G("Remove a manual alias, or the aliases for an entire snap") +var shortUnaliasHelp = i18n.G("Unalias a manual alias or an entire snap") var longUnaliasHelp = i18n.G(` The unalias command removes a single alias if the provided argument is a manual alias, or disables all aliases of a snap, including manual ones, if the @@ -43,7 +43,7 @@ addCommand("unalias", shortUnaliasHelp, longUnaliasHelp, func() flags.Commander { return &cmdUnalias{} }, waitDescs.also(nil), []argDesc{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. {name: i18n.G("")}, }) } @@ -53,11 +53,12 @@ return ErrExtraArgs } - id, err := x.client.Unalias(string(x.Positionals.AliasOrSnap)) + cli := Client() + id, err := cli.Unalias(string(x.Positionals.AliasOrSnap)) if err != nil { return err } - chg, err := x.wait(id) + chg, err := x.wait(cli, id) if err != nil { if err == noWait { return nil diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_unalias_test.go snapd-2.34.2+18.04/cmd/snap/cmd_unalias_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_unalias_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_unalias_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -37,10 +37,12 @@ argument is a snap name. [unalias command options] - --no-wait Do not wait for the operation to finish but just - print the change id. + --no-wait Do not wait for the operation to finish but just + print the change id. ` - s.testSubCommandHelp(c, "unalias", msg) + rest, err := Parser().ParseArgs([]string{"unalias", "--help"}) + c.Assert(err.Error(), Equals, msg) + c.Assert(rest, DeepEquals, []string{}) } func (s *SnapSuite) TestUnalias(c *C) { @@ -61,7 +63,7 @@ c.Fatalf("unexpected path %q", r.URL.Path) } }) - rest, err := Parser(Client()).ParseArgs([]string{"unalias", "alias1"}) + rest, err := Parser().ParseArgs([]string{"unalias", "alias1"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Assert(s.Stdout(), Equals, ""+ diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_userd.go snapd-2.34.2+18.04/cmd/snap/cmd_userd.go --- snapd-2.37.1+18.04/cmd/snap/cmd_userd.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_userd.go 2018-07-19 10:05:50.000000000 +0000 @@ -49,7 +49,6 @@ func() flags.Commander { return &cmdUserd{} }, map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. "autostart": i18n.G("Autostart user applications"), }, nil) cmd.hidden = true @@ -69,7 +68,7 @@ } x.userd.Start() - ch := make(chan os.Signal, 3) + ch := make(chan os.Signal) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1) select { case sig := <-ch: diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_userd_test.go snapd-2.34.2+18.04/cmd/snap/cmd_userd_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_userd_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_userd_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -21,7 +21,6 @@ import ( "os" - "strings" "syscall" "time" @@ -56,47 +55,33 @@ } func (s *userdSuite) TestUserdBadCommandline(c *C) { - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"userd", "extra-arg"}) + _, err := snap.Parser().ParseArgs([]string{"userd", "extra-arg"}) c.Assert(err, ErrorMatches, "too many arguments for command") } -func (s *userdSuite) TestUserdDBus(c *C) { +func (s *userdSuite) TestUserd(c *C) { go func() { - myPid := os.Getpid() defer func() { - me, err := os.FindProcess(myPid) + me, err := os.FindProcess(os.Getpid()) c.Assert(err, IsNil) me.Signal(syscall.SIGUSR1) }() - names := map[string]bool{ - "io.snapcraft.Launcher": false, - "io.snapcraft.Settings": false, - } - for i := 0; i < 1000; i++ { - seenCount := 0 - for name, seen := range names { - if seen { - seenCount++ - continue - } - pid, err := testutil.DBusGetConnectionUnixProcessID(s.SessionBus, name) - c.Logf("name: %v pid: %v err: %v", name, pid, err) - if pid == myPid { - names[name] = true - seenCount++ + needle := "io.snapcraft.Launcher" + for i := 0; i < 10; i++ { + for _, objName := range s.SessionBus.Names() { + if objName == needle { + return } + time.Sleep(1 * time.Second) } - if seenCount == len(names) { - return - } - time.Sleep(10 * time.Millisecond) + } - c.Fatalf("not all names have appeared on the bus: %v", names) + c.Fatalf("%s does not appeared on the bus", needle) }() - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"userd"}) + rest, err := snap.Parser().ParseArgs([]string{"userd"}) c.Assert(err, IsNil) c.Check(rest, DeepEquals, []string{}) - c.Check(strings.ToLower(s.Stdout()), Equals, "exiting on user defined signal 1.\n") + c.Check(s.Stdout(), Equals, "Exiting on user defined signal 1.\n") } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_version.go snapd-2.34.2+18.04/cmd/snap/cmd_version.go --- snapd-2.37.1+18.04/cmd/snap/cmd_version.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_version.go 2018-07-19 10:05:50.000000000 +0000 @@ -22,11 +22,11 @@ import ( "fmt" - "github.com/jessevdk/go-flags" - "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/cmd" "github.com/snapcore/snapd/i18n" + + "github.com/jessevdk/go-flags" ) var shortVersionHelp = i18n.G("Show version details") @@ -35,9 +35,7 @@ and operating system. `) -type cmdVersion struct { - clientMixin -} +type cmdVersion struct{} func init() { addCommand("version", shortVersionHelp, longVersionHelp, func() flags.Commander { return &cmdVersion{} }, nil, nil) @@ -48,12 +46,21 @@ return ErrExtraArgs } - printVersions(cmd.client) + printVersions() return nil } -func printVersions(cli *client.Client) error { - sv := serverVersion(cli) +func printVersions() error { + sv, err := Client().ServerVersion() + if err != nil { + sv = &client.ServerVersion{ + Version: i18n.G("unavailable"), + Series: "-", + OSID: "-", + OSVersionID: "-", + } + } + w := tabWriter() fmt.Fprintf(w, "snap\t%s\n", cmd.Version) @@ -70,5 +77,5 @@ } w.Flush() - return nil + return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_version_linux.go snapd-2.34.2+18.04/cmd/snap/cmd_version_linux.go --- snapd-2.37.1+18.04/cmd/snap/cmd_version_linux.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_version_linux.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,53 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2016 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main - -import ( - "fmt" - "runtime" - - "github.com/snapcore/snapd/client" - "github.com/snapcore/snapd/i18n" - "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/release" -) - -func serverVersion(cli *client.Client) *client.ServerVersion { - if release.OnWSL { - return &client.ServerVersion{ - Version: i18n.G("unavailable"), - Series: release.Series, - OSID: "Windows Subsystem for Linux", - OnClassic: true, - KernelVersion: fmt.Sprintf("%s (%s)", osutil.KernelVersion(), runtime.GOARCH), - } - } - sv, err := cli.ServerVersion() - - if err != nil { - sv = &client.ServerVersion{ - Version: i18n.G("unavailable"), - Series: "-", - OSID: "-", - OSVersionID: "-", - } - } - return sv -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_version_other.go snapd-2.34.2+18.04/cmd/snap/cmd_version_other.go --- snapd-2.37.1+18.04/cmd/snap/cmd_version_other.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_version_other.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- -// +build !linux - -/* - * Copyright (C) 2016 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main - -import ( - "fmt" - "runtime" - - "github.com/snapcore/snapd/client" - "github.com/snapcore/snapd/i18n" - "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/release" -) - -func serverVersion(*client.Client) *client.ServerVersion { - return &client.ServerVersion{ - Version: i18n.G("unavailable"), - Series: release.Series, - OSID: runtime.GOOS, - OnClassic: true, - KernelVersion: fmt.Sprintf("%s (%s)", osutil.KernelVersion(), runtime.GOARCH), - } -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_version_test.go snapd-2.34.2+18.04/cmd/snap/cmd_version_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_version_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_version_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -37,7 +37,7 @@ restore = mockVersion("4.56") defer restore() - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"version"}) + _, err := snap.Parser().ParseArgs([]string{"version"}) c.Assert(err, IsNil) c.Assert(s.Stdout(), Equals, "snap 4.56\nsnapd 7.89\nseries 56\nubuntu 12.34\n") c.Assert(s.Stderr(), Equals, "") @@ -52,7 +52,7 @@ restore = mockVersion("4.56") defer restore() - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"version"}) + _, err := snap.Parser().ParseArgs([]string{"version"}) c.Assert(err, IsNil) c.Assert(s.Stdout(), Equals, "snap 4.56\nsnapd 7.89\nseries 56\n") c.Assert(s.Stderr(), Equals, "") @@ -67,7 +67,7 @@ restore = mockVersion("4.56") defer restore() - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"version"}) + _, err := snap.Parser().ParseArgs([]string{"version"}) c.Assert(err, IsNil) c.Assert(s.Stdout(), Equals, "snap 4.56\nsnapd 7.89\nseries 56\narch -\n") c.Assert(s.Stderr(), Equals, "") diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_wait.go snapd-2.34.2+18.04/cmd/snap/cmd_wait.go --- snapd-2.37.1+18.04/cmd/snap/cmd_wait.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_wait.go 2018-07-19 10:05:50.000000000 +0000 @@ -22,7 +22,6 @@ import ( "encoding/json" "fmt" - "math/rand" "reflect" "time" @@ -33,10 +32,9 @@ ) type cmdWait struct { - clientMixin Positional struct { Snap installedSnapName `required:"yes"` - Key string + Key string `required:"yes"` } `positional-args:"yes"` } @@ -49,12 +47,12 @@ }, nil, []argDesc{ { name: "", - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("The snap for which configuration will be checked"), }, { - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Key of interest within the configuration"), }, }) @@ -117,27 +115,9 @@ snapName := string(x.Positional.Snap) confKey := x.Positional.Key - // This is fine because not providing a confKey is unsupported so this - // won't interfere with supported uses of `snap wait`. - if snapName == "godot" && confKey == "" { - switch rand.Intn(10) { - case 0: - fmt.Fprintln(Stdout, `The tears of the world are a constant quantity. -For each one who begins to weep somewhere else another stops. -The same is true of the laugh.`) - case 1: - fmt.Fprintln(Stdout, "Nothing happens. Nobody comes, nobody goes. It's awful.") - default: - fmt.Fprintln(Stdout, `"Let's go." "We can't." "Why not?" "We're waiting for Godot."`) - } - return nil - } - if confKey == "" { - return fmt.Errorf("the required argument `` was not provided") - } - + cli := Client() for { - conf, err := x.client.Conf(snapName, []string{confKey}) + conf, err := cli.Conf(snapName, []string{confKey}) if err != nil && !isNoOption(err) { return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_wait_test.go snapd-2.34.2+18.04/cmd/snap/cmd_wait_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_wait_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_wait_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -31,19 +31,25 @@ ) func (s *SnapSuite) TestCmdWaitHappy(c *C) { + var seeded bool + restore := snap.MockWaitConfTimeout(10 * time.Millisecond) defer restore() + go func() { + time.Sleep(50 * time.Millisecond) + seeded = true + }() n := 0 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/snaps/system/conf") - fmt.Fprintln(w, fmt.Sprintf(`{"type":"sync", "status-code": 200, "result": {"seed.loaded":%v}}`, n > 1)) + fmt.Fprintln(w, fmt.Sprintf(`{"type":"sync", "status-code": 200, "result": {"seed.loaded":%v}}`, seeded)) n++ }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"wait", "system", "seed.loaded"}) + _, err := snap.Parser().ParseArgs([]string{"wait", "system", "seed.loaded"}) c.Assert(err, IsNil) // ensure we retried a bit but make the check not overly precise @@ -59,7 +65,7 @@ n++ }) - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"wait", "snapName"}) + _, err := snap.Parser().ParseArgs([]string{"wait", "snapName"}) c.Assert(err, ErrorMatches, "the required argument `` was not provided") c.Check(n, Equals, 0) @@ -161,7 +167,7 @@ testValueCh <- "42" } - _, err := snap.Parser(snap.Client()).ParseArgs([]string{"wait", "system", "test.value"}) + _, err := snap.Parser().ParseArgs([]string{"wait", "system", "test.value"}) c.Assert(err, IsNil) if t.willWait { // we waited once, then got a non-wait value diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_warnings.go snapd-2.34.2+18.04/cmd/snap/cmd_warnings.go --- snapd-2.37.1+18.04/cmd/snap/cmd_warnings.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_warnings.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,224 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/jessevdk/go-flags" - - "github.com/snapcore/snapd/client" - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/i18n" - "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/strutil/quantity" -) - -type cmdWarnings struct { - clientMixin - timeMixin - All bool `long:"all"` - Verbose bool `long:"verbose"` -} - -type cmdOkay struct{ clientMixin } - -var shortWarningsHelp = i18n.G("List warnings") -var longWarningsHelp = i18n.G(` -The warnings command lists the warnings that have been reported to the system. - -Once warnings have been listed with 'snap warnings', 'snap okay' may be used to -silence them. A warning that's been silenced in this way will not be listed -again unless it happens again, _and_ a cooldown time has passed. - -Warnings expire automatically, and once expired they are forgotten. -`) - -var shortOkayHelp = i18n.G("Acknowledge warnings") -var longOkayHelp = i18n.G(` -The okay command acknowledges the warnings listed with 'snap warnings'. - -Once acknowledged a warning won't appear again unless it re-occurrs and -sufficient time has passed. -`) - -func init() { - addCommand("warnings", shortWarningsHelp, longWarningsHelp, func() flags.Commander { return &cmdWarnings{} }, timeDescs.also(map[string]string{ - // TRANSLATORS: This should not start with a lowercase letter. - "all": i18n.G("Show all warnings"), - // TRANSLATORS: This should not start with a lowercase letter. - "verbose": i18n.G("Show more information"), - }), nil) - addCommand("okay", shortOkayHelp, longOkayHelp, func() flags.Commander { return &cmdOkay{} }, nil, nil) -} - -func (cmd *cmdWarnings) Execute(args []string) error { - if len(args) > 0 { - return ErrExtraArgs - } - now := time.Now() - - warnings, err := cmd.client.Warnings(client.WarningsOptions{All: cmd.All}) - if err != nil { - return err - } - if len(warnings) == 0 { - if t, _ := lastWarningTimestamp(); t.IsZero() { - fmt.Fprintln(Stdout, i18n.G("No warnings.")) - } else { - fmt.Fprintln(Stdout, i18n.G("No further warnings.")) - } - return nil - } - - if err := writeWarningTimestamp(now); err != nil { - return err - } - - w := tabWriter() - if cmd.Verbose { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", - i18n.G("First occurrence"), - i18n.G("Last occurrence"), - i18n.G("Expires after"), - i18n.G("Acknowledged"), - i18n.G("Repeats after"), - i18n.G("Warning")) - for _, warning := range warnings { - lastShown := "-" - if !warning.LastShown.IsZero() { - lastShown = cmd.fmtTime(warning.LastShown) - } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", - cmd.fmtTime(warning.FirstAdded), - cmd.fmtTime(warning.LastAdded), - quantity.FormatDuration(warning.ExpireAfter.Seconds()), - lastShown, - quantity.FormatDuration(warning.RepeatAfter.Seconds()), - warning.Message) - } - } else { - fmt.Fprintf(w, "%s\t%s\n", i18n.G("Last occurrence"), i18n.G("Warning")) - for _, warning := range warnings { - fmt.Fprintf(w, "%s\t%s\n", cmd.fmtTime(warning.LastAdded), warning.Message) - } - } - w.Flush() - - return nil -} - -func (cmd *cmdOkay) Execute(args []string) error { - if len(args) > 0 { - return ErrExtraArgs - } - - last, err := lastWarningTimestamp() - if err != nil { - return fmt.Errorf("no client-side warning timestamp found: %v", err) - } - - return cmd.client.Okay(last) -} - -const warnFileEnvKey = "SNAPD_LAST_WARNING_TIMESTAMP_FILENAME" - -func warnFilename(homedir string) string { - if fn := os.Getenv(warnFileEnvKey); fn != "" { - return fn - } - - return filepath.Join(dirs.GlobalRootDir, homedir, ".snap", "warnings.json") -} - -type clientWarningData struct { - Timestamp time.Time `json:"timestamp"` -} - -func writeWarningTimestamp(t time.Time) error { - user, err := osutil.RealUser() - if err != nil { - return err - } - uid, gid, err := osutil.UidGid(user) - if err != nil { - return err - } - - filename := warnFilename(user.HomeDir) - if err := osutil.MkdirAllChown(filepath.Dir(filename), 0700, uid, gid); err != nil { - return err - } - - aw, err := osutil.NewAtomicFile(filename, 0600, 0, uid, gid) - if err != nil { - return err - } - // Cancel once Committed is a NOP :-) - defer aw.Cancel() - - enc := json.NewEncoder(aw) - if err := enc.Encode(clientWarningData{Timestamp: t}); err != nil { - return err - } - - return aw.Commit() -} - -func lastWarningTimestamp() (time.Time, error) { - user, err := osutil.RealUser() - if err != nil { - return time.Time{}, fmt.Errorf("cannot determine real user: %v", err) - } - f, err := os.Open(warnFilename(user.HomeDir)) - if err != nil { - return time.Time{}, fmt.Errorf("cannot open timestamp file: %v", err) - - } - dec := json.NewDecoder(f) - var d clientWarningData - if err := dec.Decode(&d); err != nil { - return time.Time{}, fmt.Errorf("cannot decode timestamp file: %v", err) - } - if dec.More() { - return time.Time{}, fmt.Errorf("spurious extra data in timestamp file") - } - return d.Timestamp, nil -} - -func maybePresentWarnings(count int, timestamp time.Time) { - if count == 0 { - return - } - - if last, _ := lastWarningTimestamp(); !timestamp.After(last) { - return - } - - fmt.Fprintf(Stderr, - i18n.NG("WARNING: There is %d new warning. See 'snap warnings'.\n", - "WARNING: There are %d new warnings. See 'snap warnings'.\n", - count), - count) -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_warnings_test.go snapd-2.34.2+18.04/cmd/snap/cmd_warnings_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_warnings_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_warnings_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,206 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main_test - -import ( - "fmt" - "io/ioutil" - "net/http" - "time" - - "gopkg.in/check.v1" - - snap "github.com/snapcore/snapd/cmd/snap" -) - -type warningSuite struct { - BaseSnapSuite -} - -var _ = check.Suite(&warningSuite{}) - -const twoWarnings = `{ - "result": [ - { - "expire-after": "672h0m0s", - "first-added": "2018-09-19T12:41:18.505007495Z", - "last-added": "2018-09-19T12:41:18.505007495Z", - "message": "hello world number one", - "repeat-after": "24h0m0s" - }, - { - "expire-after": "672h0m0s", - "first-added": "2018-09-19T12:44:19.680362867Z", - "last-added": "2018-09-19T12:44:19.680362867Z", - "message": "hello world number two", - "repeat-after": "24h0m0s" - } - ], - "status": "OK", - "status-code": 200, - "type": "sync" - }` - -func mkWarningsFakeHandler(c *check.C, body string) func(w http.ResponseWriter, r *http.Request) { - var called bool - return func(w http.ResponseWriter, r *http.Request) { - if called { - c.Fatalf("expected a single request") - } - called = true - c.Check(r.URL.Path, check.Equals, "/v2/warnings") - c.Check(r.URL.Query(), check.HasLen, 0) - - buf, err := ioutil.ReadAll(r.Body) - c.Assert(err, check.IsNil) - c.Check(string(buf), check.Equals, "") - c.Check(r.Method, check.Equals, "GET") - w.WriteHeader(200) - fmt.Fprintln(w, body) - } -} - -func (s *warningSuite) TestNoWarningsEver(c *check.C) { - s.RedirectClientToTestServer(mkWarningsFakeHandler(c, `{"type": "sync", "status-code": 200, "result": []}`)) - - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"warnings", "--abs-time"}) - c.Assert(err, check.IsNil) - c.Check(rest, check.HasLen, 0) - c.Check(s.Stderr(), check.Equals, "") - c.Check(s.Stdout(), check.Equals, "No warnings.\n") -} - -func (s *warningSuite) TestNoFurtherWarnings(c *check.C) { - snap.WriteWarningTimestamp(time.Now()) - - s.RedirectClientToTestServer(mkWarningsFakeHandler(c, `{"type": "sync", "status-code": 200, "result": []}`)) - - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"warnings", "--abs-time"}) - c.Assert(err, check.IsNil) - c.Check(rest, check.HasLen, 0) - c.Check(s.Stderr(), check.Equals, "") - c.Check(s.Stdout(), check.Equals, "No further warnings.\n") -} - -func (s *warningSuite) TestWarnings(c *check.C) { - s.RedirectClientToTestServer(mkWarningsFakeHandler(c, twoWarnings)) - - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"warnings", "--abs-time"}) - c.Assert(err, check.IsNil) - c.Check(rest, check.HasLen, 0) - c.Check(s.Stderr(), check.Equals, "") - c.Check(s.Stdout(), check.Equals, ` -Last occurrence Warning -2018-09-19T12:41:18Z hello world number one -2018-09-19T12:44:19Z hello world number two -`[1:]) -} - -func (s *warningSuite) TestVerboseWarnings(c *check.C) { - s.RedirectClientToTestServer(mkWarningsFakeHandler(c, twoWarnings)) - - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"warnings", "--abs-time", "--verbose"}) - c.Assert(err, check.IsNil) - c.Check(rest, check.HasLen, 0) - c.Check(s.Stderr(), check.Equals, "") - c.Check(s.Stdout(), check.Equals, ` -First occurrence Last occurrence Expires after Acknowledged Repeats after Warning -2018-09-19T12:41:18Z 2018-09-19T12:41:18Z 28d0h - 1d00h hello world number one -2018-09-19T12:44:19Z 2018-09-19T12:44:19Z 28d0h - 1d00h hello world number two -`[1:]) -} - -func (s *warningSuite) TestOkay(c *check.C) { - t0 := time.Now() - snap.WriteWarningTimestamp(t0) - - var n int - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - n++ - if n != 1 { - c.Fatalf("expected 1 request, now on %d", n) - } - c.Check(r.URL.Path, check.Equals, "/v2/warnings") - c.Check(r.URL.Query(), check.HasLen, 0) - c.Assert(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{"action": "okay", "timestamp": t0.Format(time.RFC3339Nano)}) - c.Check(r.Method, check.Equals, "POST") - w.WriteHeader(200) - fmt.Fprintln(w, `{ - "status": "OK", - "status-code": 200, - "type": "sync" - }`) - }) - - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"okay"}) - c.Assert(err, check.IsNil) - c.Check(rest, check.HasLen, 0) - c.Check(s.Stderr(), check.Equals, "") - c.Check(s.Stdout(), check.Equals, "") -} - -func (s *warningSuite) TestListWithWarnings(c *check.C) { - var called bool - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - if called { - c.Fatalf("expected a single request") - } - called = true - c.Check(r.URL.Path, check.Equals, "/v2/snaps") - c.Check(r.URL.Query(), check.HasLen, 0) - - buf, err := ioutil.ReadAll(r.Body) - c.Assert(err, check.IsNil) - c.Check(string(buf), check.Equals, "") - c.Check(r.Method, check.Equals, "GET") - w.WriteHeader(200) - fmt.Fprintln(w, `{ - "result": [{}], - "status": "OK", - "status-code": 200, - "type": "sync", - "warning-count": 2, - "warning-timestamp": "2018-09-19T12:44:19.680362867Z" - }`) - }) - cli := snap.Client() - rest, err := snap.Parser(cli).ParseArgs([]string{"list"}) - c.Assert(err, check.IsNil) - - { - // TODO: I hope to get to refactor run() so we can - // call it from tests and not have to do this (whole - // block) by hand - - count, stamp := cli.WarningsSummary() - c.Check(count, check.Equals, 2) - c.Check(stamp, check.Equals, time.Date(2018, 9, 19, 12, 44, 19, 680362867, time.UTC)) - - snap.MaybePresentWarnings(count, stamp) - } - - c.Check(rest, check.HasLen, 0) - c.Check(s.Stdout(), check.Equals, ` -Name Version Rev Tracking Publisher Notes - unset - - disabled -`[1:]) - c.Check(s.Stderr(), check.Equals, "WARNING: There are 2 new warnings. See 'snap warnings'.\n") - -} diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_watch.go snapd-2.34.2+18.04/cmd/snap/cmd_watch.go --- snapd-2.37.1+18.04/cmd/snap/cmd_watch.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_watch.go 2018-07-19 10:05:50.000000000 +0000 @@ -43,19 +43,15 @@ if len(args) > 0 { return ErrExtraArgs } - id, err := x.GetChangeID() + cli := Client() + id, err := x.GetChangeID(cli) if err != nil { - if err == noChangeFoundOK { - return nil - } return err } // this is the only valid use of wait without a waitMixin (ie // without --no-wait), so we fake it here. - wmx := &waitMixin{skipAbort: true} - wmx.client = x.client - _, err = wmx.wait(id) + _, err = waitMixin{skipAbort: true}.wait(cli, id) return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_watch_test.go snapd-2.34.2+18.04/cmd/snap/cmd_watch_test.go --- snapd-2.37.1+18.04/cmd/snap/cmd_watch_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_watch_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -32,7 +32,7 @@ ) var fmtWatchChangeJSON = `{"type": "sync", "result": { - "id": "two", + "id": "42", "kind": "some-kind", "summary": "some summary...", "status": "Doing", @@ -43,112 +43,31 @@ func (s *SnapSuite) TestCmdWatch(c *C) { meter := &progresstest.Meter{} defer progress.MockMeter(meter)() - defer snap.MockMaxGoneTime(time.Millisecond)() - defer snap.MockPollTime(time.Millisecond)() + restore := snap.MockMaxGoneTime(time.Millisecond) + defer restore() n := 0 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - n++ switch n { - case 1: + case 0: c.Check(r.Method, Equals, "GET") - c.Check(r.URL.Path, Equals, "/v2/changes/two") + c.Check(r.URL.Path, Equals, "/v2/changes/42") fmt.Fprintf(w, fmtWatchChangeJSON, 0, 100*1024) - case 2: + case 1: c.Check(r.Method, Equals, "GET") - c.Check(r.URL.Path, Equals, "/v2/changes/two") + c.Check(r.URL.Path, Equals, "/v2/changes/42") fmt.Fprintf(w, fmtWatchChangeJSON, 50*1024, 100*1024) - case 3: + case 2: c.Check(r.Method, Equals, "GET") - c.Check(r.URL.Path, Equals, "/v2/changes/two") - fmt.Fprintln(w, `{"type": "sync", "result": {"id": "two", "ready": true, "status": "Done"}}`) - default: - c.Errorf("expected 3 queries, currently on %d", n) + c.Check(r.URL.Path, Equals, "/v2/changes/42") + fmt.Fprintln(w, `{"type": "sync", "result": {"id": "42", "ready": true, "status": "Done"}}`) } + n++ }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"watch", "two"}) + _, err := snap.Parser().ParseArgs([]string{"watch", "42"}) c.Assert(err, IsNil) - c.Assert(rest, HasLen, 0) c.Check(n, Equals, 3) - c.Check(meter.Values, DeepEquals, []float64{51200}) - c.Check(s.Stdout(), Equals, "") - c.Check(s.Stderr(), Equals, "") -} - -func (s *SnapSuite) TestWatchLast(c *C) { - meter := &progresstest.Meter{} - defer progress.MockMeter(meter)() - defer snap.MockMaxGoneTime(time.Millisecond)() - defer snap.MockPollTime(time.Millisecond)() - n := 0 - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - n++ - switch n { - case 1: - c.Check(r.Method, Equals, "GET") - c.Check(r.URL.Path, Equals, "/v2/changes") - fmt.Fprintln(w, mockChangesJSON) - case 2: - c.Check(r.Method, Equals, "GET") - c.Check(r.URL.Path, Equals, "/v2/changes/two") - fmt.Fprintf(w, fmtWatchChangeJSON, 0, 100*1024) - case 3: - c.Check(r.Method, Equals, "GET") - c.Check(r.URL.Path, Equals, "/v2/changes/two") - fmt.Fprintf(w, fmtWatchChangeJSON, 50*1024, 100*1024) - case 4: - c.Check(r.Method, Equals, "GET") - c.Check(r.URL.Path, Equals, "/v2/changes/two") - fmt.Fprintln(w, `{"type": "sync", "result": {"id": "two", "ready": true, "status": "Done"}}`) - default: - c.Errorf("expected 4 queries, currently on %d", n) - } - }) - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"watch", "--last=install"}) - c.Assert(err, IsNil) - c.Assert(rest, HasLen, 0) - c.Check(n, Equals, 4) c.Check(meter.Values, DeepEquals, []float64{51200}) - c.Check(s.Stdout(), Equals, "") - c.Check(s.Stderr(), Equals, "") -} - -func (s *SnapSuite) TestWatchLastQuestionmark(c *C) { - meter := &progresstest.Meter{} - defer progress.MockMeter(meter)() - restore := snap.MockMaxGoneTime(time.Millisecond) - defer restore() - - n := 0 - s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { - n++ - c.Check(r.Method, Equals, "GET") - c.Assert(r.URL.Path, Equals, "/v2/changes") - switch n { - case 1, 2: - fmt.Fprintln(w, `{"type": "sync", "result": []}`) - case 3, 4: - fmt.Fprintln(w, mockChangesJSON) - default: - c.Errorf("expected 4 calls, now on %d", n) - } - }) - for i := 0; i < 2; i++ { - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"watch", "--last=foobar?"}) - c.Assert(err, IsNil) - c.Assert(rest, DeepEquals, []string{}) - c.Check(s.Stdout(), Matches, "") - c.Check(s.Stderr(), Equals, "") - - _, err = snap.Parser(snap.Client()).ParseArgs([]string{"watch", "--last=foobar"}) - if i == 0 { - c.Assert(err, ErrorMatches, `no changes found`) - } else { - c.Assert(err, ErrorMatches, `no changes of type "foobar" found`) - } - } - - c.Check(n, Equals, 4) } diff -Nru snapd-2.37.1+18.04/cmd/snap/cmd_whoami.go snapd-2.34.2+18.04/cmd/snap/cmd_whoami.go --- snapd-2.37.1+18.04/cmd/snap/cmd_whoami.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/cmd_whoami.go 2018-07-19 10:05:50.000000000 +0000 @@ -27,14 +27,12 @@ "github.com/jessevdk/go-flags" ) -var shortWhoAmIHelp = i18n.G("Show the email the user is logged in with") +var shortWhoAmIHelp = i18n.G("Print the email the user is logged in with") var longWhoAmIHelp = i18n.G(` -The whoami command shows the email the user is logged in with. +The whoami command prints the email the user is logged in with. `) -type cmdWhoAmI struct { - clientMixin -} +type cmdWhoAmI struct{} func init() { addCommand("whoami", shortWhoAmIHelp, longWhoAmIHelp, func() flags.Commander { return &cmdWhoAmI{} }, nil, nil) @@ -45,7 +43,7 @@ return ErrExtraArgs } - email, err := cmd.client.WhoAmI() + email, err := Client().WhoAmI() if err != nil { return err } diff -Nru snapd-2.37.1+18.04/cmd/snap/color.go snapd-2.34.2+18.04/cmd/snap/color.go --- snapd-2.37.1+18.04/cmd/snap/color.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/color.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,181 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main - -import ( - "fmt" - "os" - "strings" - - "golang.org/x/crypto/ssh/terminal" - - "github.com/snapcore/snapd/i18n" - "github.com/snapcore/snapd/snap" -) - -type colorMixin struct { - Color string `long:"color" default:"auto" choice:"auto" choice:"never" choice:"always"` - Unicode string `long:"unicode" default:"auto" choice:"auto" choice:"never" choice:"always"` // do we want this hidden? -} - -func (mx colorMixin) getEscapes() *escapes { - esc := colorTable(mx.Color) - if canUnicode(mx.Unicode) { - esc.dash = "–" // that's an en dash (so yaml is happy) - esc.uparrow = "↑" - esc.tick = "✓" - } else { - esc.dash = "--" // two dashes keeps yaml happy also - esc.uparrow = "^" - esc.tick = "*" - } - - return &esc -} - -func canUnicode(mode string) bool { - switch mode { - case "always": - return true - case "never": - return false - } - if !isStdoutTTY { - return false - } - var lang string - for _, k := range []string{"LC_MESSAGES", "LC_ALL", "LANG"} { - lang = os.Getenv(k) - if lang != "" { - break - } - } - if lang == "" { - return false - } - lang = strings.ToUpper(lang) - return strings.Contains(lang, "UTF-8") || strings.Contains(lang, "UTF8") -} - -var isStdoutTTY = terminal.IsTerminal(1) - -func colorTable(mode string) escapes { - switch mode { - case "always": - return color - case "never": - return noesc - } - if !isStdoutTTY { - return noesc - } - if _, ok := os.LookupEnv("NO_COLOR"); ok { - // from http://no-color.org/: - // command-line software which outputs text with ANSI color added should - // check for the presence of a NO_COLOR environment variable that, when - // present (regardless of its value), prevents the addition of ANSI color. - return mono // bold & dim is still ok - } - if term := os.Getenv("TERM"); term == "xterm-mono" || term == "linux-m" { - // these are often used to flag "I don't want to see color" more than "I can't do color" - // (if you can't *do* color, `color` and `mono` should produce the same results) - return mono - } - return color -} - -var colorDescs = mixinDescs{ - // TRANSLATORS: This should not start with a lowercase letter. - "color": i18n.G("Use a little bit of color to highlight some things."), - // TRANSLATORS: This should not start with a lowercase letter. - "unicode": i18n.G("Use a little bit of Unicode to improve legibility."), -} - -type escapes struct { - green string - end string - - tick, dash, uparrow string -} - -var ( - color = escapes{ - green: "\033[32m", - end: "\033[0m", - } - - mono = escapes{ - green: "\033[1m", - end: "\033[0m", - } - - noesc = escapes{} -) - -// fillerPublisher is used to add an no-op escape sequence to a header in a -// tabwriter table, so that things line up. -func fillerPublisher(esc *escapes) string { - return esc.green + esc.end -} - -// longPublisher returns a string that'll present the publisher of a snap to the -// terminal user: -// -// * if the publisher's username and display name match, it's just the display -// name; otherwise, it'll include the username in parentheses -// -// * if the publisher is verified, it'll include a green check mark; otherwise, -// it'll include a no-op escape sequence of the same length as the escape -// sequence used to make it green (this so that tabwriter gets things right). -func longPublisher(esc *escapes, storeAccount *snap.StoreAccount) string { - if storeAccount == nil { - return esc.dash + esc.green + esc.end - } - badge := "" - if storeAccount.Validation == "verified" { - badge = esc.tick - } - // NOTE this makes e.g. 'Potato' == 'potato', and 'Potato Team' == 'potato-team', - // but 'Potato Team' != 'potatoteam', 'Potato Inc.' != 'potato' (in fact 'Potato Inc.' != 'potato-inc') - if strings.EqualFold(strings.Replace(storeAccount.Username, "-", " ", -1), storeAccount.DisplayName) { - return storeAccount.DisplayName + esc.green + badge + esc.end - } - return fmt.Sprintf("%s (%s%s%s%s)", storeAccount.DisplayName, storeAccount.Username, esc.green, badge, esc.end) -} - -// shortPublisher returns a string that'll present the publisher of a snap to the -// terminal user: -// -// * it'll always be just the username -// -// * if the publisher is verified, it'll include a green check mark; otherwise, -// it'll include a no-op escape sequence of the same length as the escape -// sequence used to make it green (this so that tabwriter gets things right). -func shortPublisher(esc *escapes, storeAccount *snap.StoreAccount) string { - if storeAccount == nil { - return "-" + esc.green + esc.end - } - badge := "" - if storeAccount.Validation == "verified" { - badge = esc.tick - } - return storeAccount.Username + esc.green + badge + esc.end - -} diff -Nru snapd-2.37.1+18.04/cmd/snap/color_test.go snapd-2.34.2+18.04/cmd/snap/color_test.go --- snapd-2.37.1+18.04/cmd/snap/color_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/color_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,196 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2018 Canonical Ltd - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package main_test - -import ( - "os" - "runtime" - // "fmt" - // "net/http" - - "gopkg.in/check.v1" - - cmdsnap "github.com/snapcore/snapd/cmd/snap" - "github.com/snapcore/snapd/snap" -) - -func setEnviron(env map[string]string) func() { - old := make(map[string]string, len(env)) - ok := make(map[string]bool, len(env)) - - for k, v := range env { - old[k], ok[k] = os.LookupEnv(k) - if v != "" { - os.Setenv(k, v) - } else { - os.Unsetenv(k) - } - } - - return func() { - for k := range ok { - if ok[k] { - os.Setenv(k, old[k]) - } else { - os.Unsetenv(k) - } - } - } -} - -func (s *SnapSuite) TestCanUnicode(c *check.C) { - // setenv is per thread - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - type T struct { - lang, lcAll, lcMsg string - expected bool - } - - for _, t := range []T{ - {expected: false}, // all locale unset - {lang: "C", expected: false}, - {lang: "C", lcAll: "C", expected: false}, - {lang: "C", lcAll: "C", lcMsg: "C", expected: false}, - {lang: "C.UTF-8", lcAll: "C", lcMsg: "C", expected: false}, // LC_MESSAGES wins - {lang: "C.UTF-8", lcAll: "C.UTF-8", lcMsg: "C", expected: false}, - {lang: "C.UTF-8", lcAll: "C.UTF-8", lcMsg: "C.UTF-8", expected: true}, - {lang: "C.UTF-8", lcAll: "C", lcMsg: "C.UTF-8", expected: true}, - {lang: "C", lcAll: "C", lcMsg: "C.UTF-8", expected: true}, - {lang: "C", lcAll: "C.UTF-8", expected: true}, - {lang: "C.UTF-8", expected: true}, - {lang: "C.utf8", expected: true}, // deals with a bit of rando weirdness - } { - restore := setEnviron(map[string]string{"LANG": t.lang, "LC_ALL": t.lcAll, "LC_MESSAGES": t.lcMsg}) - c.Check(cmdsnap.CanUnicode("never"), check.Equals, false) - c.Check(cmdsnap.CanUnicode("always"), check.Equals, true) - restoreIsTTY := cmdsnap.MockIsStdoutTTY(true) - c.Check(cmdsnap.CanUnicode("auto"), check.Equals, t.expected) - cmdsnap.MockIsStdoutTTY(false) - c.Check(cmdsnap.CanUnicode("auto"), check.Equals, false) - restoreIsTTY() - restore() - } -} - -func (s *SnapSuite) TestColorTable(c *check.C) { - // setenv is per thread - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - type T struct { - isTTY bool - noColor, term string - expected interface{} - desc string - } - - for _, t := range []T{ - {isTTY: false, expected: cmdsnap.NoEscColorTable, desc: "not a tty"}, - {isTTY: false, noColor: "1", expected: cmdsnap.NoEscColorTable, desc: "no tty *and* NO_COLOR set"}, - {isTTY: false, term: "linux-m", expected: cmdsnap.NoEscColorTable, desc: "no tty *and* mono term set"}, - {isTTY: true, expected: cmdsnap.ColorColorTable, desc: "is a tty"}, - {isTTY: true, noColor: "1", expected: cmdsnap.MonoColorTable, desc: "is a tty, but NO_COLOR set"}, - {isTTY: true, term: "linux-m", expected: cmdsnap.MonoColorTable, desc: "is a tty, but TERM=linux-m"}, - {isTTY: true, term: "xterm-mono", expected: cmdsnap.MonoColorTable, desc: "is a tty, but TERM=xterm-mono"}, - } { - restoreIsTTY := cmdsnap.MockIsStdoutTTY(t.isTTY) - restoreEnv := setEnviron(map[string]string{"NO_COLOR": t.noColor, "TERM": t.term}) - c.Check(cmdsnap.ColorTable("never"), check.DeepEquals, cmdsnap.NoEscColorTable, check.Commentf(t.desc)) - c.Check(cmdsnap.ColorTable("always"), check.DeepEquals, cmdsnap.ColorColorTable, check.Commentf(t.desc)) - c.Check(cmdsnap.ColorTable("auto"), check.DeepEquals, t.expected, check.Commentf(t.desc)) - restoreEnv() - restoreIsTTY() - } -} - -func (s *SnapSuite) TestPublisherEscapes(c *check.C) { - // just check never/always; for auto checks look above - type T struct { - color, unicode bool - username, display string - verified bool - short, long, fill string - } - for _, t := range []T{ - // non-verified equal under fold: - {color: false, unicode: false, username: "potato", display: "Potato", - short: "potato", long: "Potato", fill: ""}, - {color: false, unicode: true, username: "potato", display: "Potato", - short: "potato", long: "Potato", fill: ""}, - {color: true, unicode: false, username: "potato", display: "Potato", - short: "potato\x1b[32m\x1b[0m", long: "Potato\x1b[32m\x1b[0m", fill: "\x1b[32m\x1b[0m"}, - {color: true, unicode: true, username: "potato", display: "Potato", - short: "potato\x1b[32m\x1b[0m", long: "Potato\x1b[32m\x1b[0m", fill: "\x1b[32m\x1b[0m"}, - // verified equal under fold: - {color: false, unicode: false, username: "potato", display: "Potato", verified: true, - short: "potato*", long: "Potato*", fill: ""}, - {color: false, unicode: true, username: "potato", display: "Potato", verified: true, - short: "potato✓", long: "Potato✓", fill: ""}, - {color: true, unicode: false, username: "potato", display: "Potato", verified: true, - short: "potato\x1b[32m*\x1b[0m", long: "Potato\x1b[32m*\x1b[0m", fill: "\x1b[32m\x1b[0m"}, - {color: true, unicode: true, username: "potato", display: "Potato", verified: true, - short: "potato\x1b[32m✓\x1b[0m", long: "Potato\x1b[32m✓\x1b[0m", fill: "\x1b[32m\x1b[0m"}, - // non-verified, different - {color: false, unicode: false, username: "potato", display: "Carrot", - short: "potato", long: "Carrot (potato)", fill: ""}, - {color: false, unicode: true, username: "potato", display: "Carrot", - short: "potato", long: "Carrot (potato)", fill: ""}, - {color: true, unicode: false, username: "potato", display: "Carrot", - short: "potato\x1b[32m\x1b[0m", long: "Carrot (potato\x1b[32m\x1b[0m)", fill: "\x1b[32m\x1b[0m"}, - {color: true, unicode: true, username: "potato", display: "Carrot", - short: "potato\x1b[32m\x1b[0m", long: "Carrot (potato\x1b[32m\x1b[0m)", fill: "\x1b[32m\x1b[0m"}, - // verified, different - {color: false, unicode: false, username: "potato", display: "Carrot", verified: true, - short: "potato*", long: "Carrot (potato*)", fill: ""}, - {color: false, unicode: true, username: "potato", display: "Carrot", verified: true, - short: "potato✓", long: "Carrot (potato✓)", fill: ""}, - {color: true, unicode: false, username: "potato", display: "Carrot", verified: true, - short: "potato\x1b[32m*\x1b[0m", long: "Carrot (potato\x1b[32m*\x1b[0m)", fill: "\x1b[32m\x1b[0m"}, - {color: true, unicode: true, username: "potato", display: "Carrot", verified: true, - short: "potato\x1b[32m✓\x1b[0m", long: "Carrot (potato\x1b[32m✓\x1b[0m)", fill: "\x1b[32m\x1b[0m"}, - // some interesting equal-under-folds: - {color: false, unicode: false, username: "potato", display: "PoTaTo", - short: "potato", long: "PoTaTo", fill: ""}, - {color: false, unicode: false, username: "potato-team", display: "Potato Team", - short: "potato-team", long: "Potato Team", fill: ""}, - } { - pub := &snap.StoreAccount{Username: t.username, DisplayName: t.display} - if t.verified { - pub.Validation = "verified" - } - color := "never" - if t.color { - color = "always" - } - unicode := "never" - if t.unicode { - unicode = "always" - } - - mx := cmdsnap.ColorMixin(color, unicode) - esc := cmdsnap.ColorMixinGetEscapes(mx) - - c.Check(cmdsnap.ShortPublisher(esc, pub), check.Equals, t.short) - c.Check(cmdsnap.LongPublisher(esc, pub), check.Equals, t.long) - c.Check(cmdsnap.FillerPublisher(esc), check.Equals, t.fill) - } -} diff -Nru snapd-2.37.1+18.04/cmd/snap/complete.go snapd-2.34.2+18.04/cmd/snap/complete.go --- snapd-2.37.1+18.04/cmd/snap/complete.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/complete.go 2018-07-19 10:05:50.000000000 +0000 @@ -23,7 +23,6 @@ "bufio" "fmt" "os" - "strconv" "strings" "github.com/jessevdk/go-flags" @@ -37,7 +36,7 @@ type installedSnapName string func (s installedSnapName) Complete(match string) []flags.Completion { - snaps, err := mkClient().List(nil, nil) + snaps, err := Client().List(nil, nil) if err != nil { return nil } @@ -104,7 +103,7 @@ if len(match) < 3 { return nil } - snaps, _, err := mkClient().Find(&client.FindOptions{ + snaps, _, err := Client().Find(&client.FindOptions{ Prefix: true, Query: match, }) @@ -148,7 +147,7 @@ type changeID string func (s changeID) Complete(match string) []flags.Completion { - changes, err := mkClient().Changes(&client.ChangesOptions{Selector: client.ChangesAll}) + changes, err := Client().Changes(&client.ChangesOptions{Selector: client.ChangesAll}) if err != nil { return nil } @@ -166,7 +165,7 @@ type assertTypeName string func (n assertTypeName) Complete(match string) []flags.Completion { - cli := mkClient() + cli := Client() names, err := cli.AssertionTypes() if err != nil { return nil @@ -296,7 +295,7 @@ parts := strings.SplitN(match, ":", 2) // Ask snapd about available interfaces. - ifaces, err := mkClient().Connections() + ifaces, err := Client().Connections() if err != nil { return nil } @@ -387,7 +386,7 @@ type interfaceName string func (s interfaceName) Complete(match string) []flags.Completion { - ifaces, err := mkClient().Interfaces(nil) + ifaces, err := Client().Interfaces(nil) if err != nil { return nil } @@ -405,7 +404,7 @@ type appName string func (s appName) Complete(match string) []flags.Completion { - cli := mkClient() + cli := Client() apps, err := cli.Apps(nil, client.AppOptions{}) if err != nil { return nil @@ -429,7 +428,7 @@ type serviceName string func (s serviceName) Complete(match string) []flags.Completion { - cli := mkClient() + cli := Client() apps, err := cli.Apps(nil, client.AppOptions{Service: true}) if err != nil { return nil @@ -454,7 +453,7 @@ type aliasOrSnap string func (s aliasOrSnap) Complete(match string) []flags.Completion { - aliases, err := mkClient().Aliases() + aliases, err := Client().Aliases() if err != nil { return nil } @@ -474,21 +473,3 @@ } return ret } - -type snapshotID uint64 - -func (snapshotID) Complete(match string) []flags.Completion { - shots, err := mkClient().SnapshotSets(0, nil) - if err != nil { - return nil - } - var ret []flags.Completion - for _, sg := range shots { - sid := strconv.FormatUint(sg.ID, 10) - if strings.HasPrefix(sid, match) { - ret = append(ret, flags.Completion{Item: sid}) - } - } - - return ret -} diff -Nru snapd-2.37.1+18.04/cmd/snap/error.go snapd-2.34.2+18.04/cmd/snap/error.go --- snapd-2.37.1+18.04/cmd/snap/error.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/error.go 2018-07-19 10:05:50.000000000 +0000 @@ -91,10 +91,6 @@ if !ok { return "", e } - // retryable errors are just passed through - if client.IsRetryable(err) { - return "", err - } // ensure the "real" error is available if we ask for it logger.Debugf("error: %s", err) @@ -105,16 +101,6 @@ usesSnapName := true var msg string switch err.Kind { - case client.ErrorKindNotSnap: - msg = i18n.G(`%q does not contain an unpacked snap. - -Try 'snapcraft prime' in your project directory, then 'snap try' again.`) - if snapName == "" || snapName == "./" { - errValStr, ok := err.Value.(string) - if ok && errValStr != "" { - snapName = errValStr - } - } case client.ErrorKindSnapNotFound: msg = i18n.G("snap %q not found") if snapName == "" { @@ -169,10 +155,6 @@ isError = false msg = i18n.G(`snap %q is already installed, see 'snap help refresh'`) case client.ErrorKindSnapNeedsDevMode: - if opts != nil && opts.Dangerous { - msg = i18n.G("snap %q requires devmode or confinement override") - break - } msg = i18n.G(` The publisher of snap %q has indicated that they do not consider this revision to be of production quality and that it is only meant for development or testing @@ -191,8 +173,6 @@ If you understand and want to proceed repeat the command including --classic. `) - case client.ErrorKindSnapNotClassic: - msg = i18n.G(`snap %q is not compatible with --classic`) case client.ErrorKindLoginRequired: usesSnapName = false u, _ := user.Current() diff -Nru snapd-2.37.1+18.04/cmd/snap/export_test.go snapd-2.34.2+18.04/cmd/snap/export_test.go --- snapd-2.37.1+18.04/cmd/snap/export_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/export_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -27,17 +27,12 @@ "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/overlord/auth" - "github.com/snapcore/snapd/selinux" "github.com/snapcore/snapd/store" ) var RunMain = run var ( - Client = mkClient - - FirstNonOptionIsRun = firstNonOptionIsRun - CreateUserDataDirs = createUserDataDirs ResolveApp = resolveApp IsReexeced = isReexeced @@ -49,33 +44,6 @@ FormatChannel = fmtChannel PrintDescr = printDescr TrueishJSON = trueishJSON - - CanUnicode = canUnicode - ColorTable = colorTable - MonoColorTable = mono - ColorColorTable = color - NoEscColorTable = noesc - ColorMixinGetEscapes = (colorMixin).getEscapes - FillerPublisher = fillerPublisher - LongPublisher = longPublisher - ShortPublisher = shortPublisher - - ReadRpc = readRpc - - WriteWarningTimestamp = writeWarningTimestamp - MaybePresentWarnings = maybePresentWarnings - - LongSnapDescription = longSnapDescription - SnapUsage = snapUsage - SnapHelpCategoriesIntro = snapHelpCategoriesIntro - SnapHelpAllFooter = snapHelpAllFooter - SnapHelpFooter = snapHelpFooter - HelpCategories = helpCategories - - LintArg = lintArg - LintDesc = lintDesc - - FixupArg = fixupArg ) func MockPollTime(d time.Duration) (restore func()) { @@ -164,19 +132,11 @@ return assertTypeName("").Complete(match) } -func MockIsStdoutTTY(t bool) (restore func()) { - oldIsStdoutTTY := isStdoutTTY - isStdoutTTY = t +func MockIsTerminal(t bool) (restore func()) { + oldIsTerminal := isTerminal + isTerminal = func() bool { return t } return func() { - isStdoutTTY = oldIsStdoutTTY - } -} - -func MockIsStdinTTY(t bool) (restore func()) { - oldIsStdinTTY := isStdinTTY - isStdinTTY = t - return func() { - isStdinTTY = oldIsStdinTTY + isTerminal = oldIsTerminal } } @@ -205,39 +165,5 @@ } func Wait(cli *client.Client, id string) (*client.Change, error) { - wmx := waitMixin{} - wmx.client = cli - return wmx.wait(id) -} - -func ColorMixin(cmode, umode string) colorMixin { - return colorMixin{Color: cmode, Unicode: umode} -} - -func CmdAdviseSnap() *cmdAdviseSnap { - return &cmdAdviseSnap{} -} - -func MockSELinuxIsEnabled(isEnabled func() (bool, error)) (restore func()) { - old := selinuxIsEnabled - selinuxIsEnabled = isEnabled - return func() { - selinuxIsEnabled = old - } -} - -func MockSELinuxVerifyPathContext(verifypathcon func(string) (bool, error)) (restore func()) { - old := selinuxVerifyPathContext - selinuxVerifyPathContext = verifypathcon - return func() { - selinuxVerifyPathContext = old - } -} - -func MockSELinuxRestoreContext(restorecon func(string, selinux.RestoreMode) error) (restore func()) { - old := selinuxRestoreContext - selinuxRestoreContext = restorecon - return func() { - selinuxRestoreContext = old - } + return waitMixin{}.wait(cli, id) } diff -Nru snapd-2.37.1+18.04/cmd/snap/interfaces_common.go snapd-2.34.2+18.04/cmd/snap/interfaces_common.go --- snapd-2.37.1+18.04/cmd/snap/interfaces_common.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/interfaces_common.go 2018-07-19 10:05:50.000000000 +0000 @@ -26,6 +26,36 @@ "github.com/snapcore/snapd/i18n" ) +// AttributePair contains a pair of key-value strings +type AttributePair struct { + // The key + Key string + // The value + Value string +} + +// UnmarshalFlag parses a string into an AttributePair +func (ap *AttributePair) UnmarshalFlag(value string) error { + parts := strings.SplitN(value, "=", 2) + if len(parts) < 2 || parts[0] == "" { + ap.Key = "" + ap.Value = "" + return fmt.Errorf(i18n.G("invalid attribute: %q (want key=value)"), value) + } + ap.Key = parts[0] + ap.Value = parts[1] + return nil +} + +// AttributePairSliceToMap converts a slice of AttributePair into a map +func AttributePairSliceToMap(attrs []AttributePair) map[string]string { + result := make(map[string]string) + for _, attr := range attrs { + result[attr.Key] = attr.Value + } + return result +} + // SnapAndName holds a snap name and a plug or slot name. type SnapAndName struct { Snap string diff -Nru snapd-2.37.1+18.04/cmd/snap/interfaces_common_test.go snapd-2.34.2+18.04/cmd/snap/interfaces_common_test.go --- snapd-2.37.1+18.04/cmd/snap/interfaces_common_test.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/interfaces_common_test.go 2018-07-19 10:05:50.000000000 +0000 @@ -25,6 +25,56 @@ . "github.com/snapcore/snapd/cmd/snap" ) +type AttributePairSuite struct{} + +var _ = Suite(&AttributePairSuite{}) + +func (s *AttributePairSuite) TestUnmarshalFlagAttributePair(c *C) { + var ap AttributePair + // Typical + err := ap.UnmarshalFlag("key=value") + c.Assert(err, IsNil) + c.Check(ap.Key, Equals, "key") + c.Check(ap.Value, Equals, "value") + // Empty key + err = ap.UnmarshalFlag("=value") + c.Assert(err, ErrorMatches, `invalid attribute: "=value" \(want key=value\)`) + c.Check(ap.Key, Equals, "") + c.Check(ap.Value, Equals, "") + // Empty value + err = ap.UnmarshalFlag("key=") + c.Assert(err, IsNil) + c.Check(ap.Key, Equals, "key") + c.Check(ap.Value, Equals, "") + // Both key and value empty + err = ap.UnmarshalFlag("=") + c.Assert(err, ErrorMatches, `invalid attribute: "=" \(want key=value\)`) + c.Check(ap.Key, Equals, "") + c.Check(ap.Value, Equals, "") + // Value containing = + err = ap.UnmarshalFlag("key=value=more") + c.Assert(err, IsNil) + c.Check(ap.Key, Equals, "key") + c.Check(ap.Value, Equals, "value=more") + // Malformed format + err = ap.UnmarshalFlag("malformed") + c.Assert(err, ErrorMatches, `invalid attribute: "malformed" \(want key=value\)`) + c.Check(ap.Key, Equals, "") + c.Check(ap.Value, Equals, "") +} + +func (s *AttributePairSuite) TestAttributePairSliceToMap(c *C) { + attrs := []AttributePair{ + {Key: "key1", Value: "value1"}, + {Key: "key2", Value: "value2"}, + } + m := AttributePairSliceToMap(attrs) + c.Check(m, DeepEquals, map[string]string{ + "key1": "value1", + "key2": "value2", + }) +} + type SnapAndNameSuite struct{} var _ = Suite(&SnapAndNameSuite{}) diff -Nru snapd-2.37.1+18.04/cmd/snap/last.go snapd-2.34.2+18.04/cmd/snap/last.go --- snapd-2.37.1+18.04/cmd/snap/last.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/last.go 2018-07-19 10:05:50.000000000 +0000 @@ -20,7 +20,6 @@ package main import ( - "errors" "fmt" "github.com/snapcore/snapd/client" @@ -28,7 +27,6 @@ ) type changeIDMixin struct { - clientMixin LastChangeType string `long:"last"` Positional struct { ID changeID `positional-arg-name:""` @@ -36,21 +34,17 @@ } var changeIDMixinOptDesc = mixinDescs{ - // TRANSLATORS: This should not start with a lowercase letter. - "last": i18n.G("Select last change of given type (install, refresh, remove, try, auto-refresh, etc.). A question mark at the end of the type means to do nothing (instead of returning an error) if no change of the given type is found. Note the question mark could need protecting from the shell."), + "last": i18n.G("Select last change of given type (install, refresh, remove, try, auto-refresh etc.)"), } var changeIDMixinArgDesc = []argDesc{{ - // TRANSLATORS: This needs to begin with < and end with > + // TRANSLATORS: This needs to be wrapped in <>s. name: i18n.G(""), - // TRANSLATORS: This should not start with a lowercase letter. + // TRANSLATORS: This should probably not start with a lowercase letter. desc: i18n.G("Change ID"), }} -// should not be user-visible, but keep it clear and polite because mistakes happen -var noChangeFoundOK = errors.New("no change found but that's ok") - -func (l *changeIDMixin) GetChangeID() (string, error) { +func (l *changeIDMixin) GetChangeID(cli *client.Client) (string, error) { if l.Positional.ID == "" && l.LastChangeType == "" { return "", fmt.Errorf(i18n.G("please provide change ID or type with --last=")) } @@ -63,14 +57,7 @@ return string(l.Positional.ID), nil } - cli := l.client - // note that at this point we know l.LastChangeType != "" kind := l.LastChangeType - optional := false - if l := len(kind) - 1; kind[l] == '?' { - optional = true - kind = kind[:l] - } // our internal change types use "-snap" postfix but let user skip it and use short form. if kind == "refresh" || kind == "install" || kind == "remove" || kind == "connect" || kind == "disconnect" || kind == "configure" || kind == "try" { kind += "-snap" @@ -80,16 +67,10 @@ return "", err } if len(changes) == 0 { - if optional { - return "", noChangeFoundOK - } return "", fmt.Errorf(i18n.G("no changes found")) } chg := findLatestChangeByKind(changes, kind) if chg == nil { - if optional { - return "", noChangeFoundOK - } return "", fmt.Errorf(i18n.G("no changes of type %q found"), l.LastChangeType) } diff -Nru snapd-2.37.1+18.04/cmd/snap/main.go snapd-2.34.2+18.04/cmd/snap/main.go --- snapd-2.37.1+18.04/cmd/snap/main.go 2019-01-29 17:35:36.000000000 +0000 +++ snapd-2.34.2+18.04/cmd/snap/main.go 2018-07-19 10:05:50.000000000 +0000 @@ -22,10 +22,8 @@ import ( "fmt" "io" - "net/http" "os" "path/filepath" - "runtime" "strings" "unicode" "unicode/utf8" @@ -41,7 +39,6 @@ "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" ) @@ -91,7 +88,6 @@ optDescs map[string]string argDescs []argDesc alias string - extra func(*flags.Command) } // commands holds information about all non-debug commands. @@ -158,69 +154,29 @@ func lintArg(cmdName, optName, desc, origDesc string) { lintDesc(cmdName, optName, desc, origDesc) - if len(optName) > 0 && optName[0] == '<' && optName[len(optName)-1] == '>' { - return - } - if len(optName) > 0 && optName[0] == '<' && strings.HasSuffix(optName, ">s") { - // see comment in fixupArg about the >s case - return - } - noticef("argument %q's %q should begin with < and end with >", cmdName, optName) -} - -func fixupArg(optName string) string { - // Due to misunderstanding some localized versions of option name are - // literally "