diff -Nru snapd-2.0.5/asserts/account.go snapd-2.0.8/asserts/account.go --- snapd-2.0.5/asserts/account.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/asserts/account.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,83 @@ +// -*- 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 asserts + +import "time" + +var ( + accountValidationCertified = "certified" +) + +// Account holds an account assertion, which ties a name for an account +// to its identifier and provides the authority's confidence in the name's validity. +type Account struct { + assertionBase + certified bool + timestamp time.Time +} + +// AccountID returns the account-id of the account. +func (acc *Account) AccountID() string { + return acc.Header("account-id") +} + +// Username returns the user name for the account. +func (acc *Account) Username() string { + return acc.Header("username") +} + +// DisplayName returns the human-friendly name for the account. +func (acc *Account) DisplayName() string { + return acc.Header("display-name") +} + +// IsCertified returns true if the authority has confidence in the account's name. +func (acc *Account) IsCertified() bool { + return acc.certified +} + +// Timestamp returns the time when the account was issued. +func (acc *Account) Timestamp() time.Time { + return acc.timestamp +} + +func assembleAccount(assert assertionBase) (Assertion, error) { + _, err := checkNotEmpty(assert.headers, "display-name") + if err != nil { + return nil, err + } + + _, err = checkNotEmpty(assert.headers, "validation") + if err != nil { + return nil, err + } + certified := assert.headers["validation"] == accountValidationCertified + + timestamp, err := checkRFC3339Date(assert.headers, "timestamp") + if err != nil { + return nil, err + } + + return &Account{ + assertionBase: assert, + certified: certified, + timestamp: timestamp, + }, nil +} diff -Nru snapd-2.0.5/asserts/account_key.go snapd-2.0.8/asserts/account_key.go --- snapd-2.0.5/asserts/account_key.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/account_key.go 2016-06-08 05:58:01.000000000 +0000 @@ -73,14 +73,14 @@ if err != nil { return nil, err } - fp, err := checkMandatory(ab.headers, fingerprintName) + fp, err := checkNotEmpty(ab.headers, fingerprintName) if err != nil { return nil, err } if fp != pubKey.Fingerprint() { return nil, fmt.Errorf("public key does not match provided fingerprint") } - keyID, err := checkMandatory(ab.headers, keyIDName) + keyID, err := checkNotEmpty(ab.headers, keyIDName) if err != nil { return nil, err } diff -Nru snapd-2.0.5/asserts/account_key_test.go snapd-2.0.8/asserts/account_key_test.go --- snapd-2.0.5/asserts/account_key_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/account_key_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,7 +27,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" + "github.com/snapcore/snapd/asserts" ) type accountKeySuite struct { @@ -45,7 +45,7 @@ } accDb, err := asserts.OpenDatabase(cfg1) c.Assert(err, IsNil) - pk := asserts.OpenPGPPrivateKey(testPrivKey1) + pk := testPrivKey1 err = accDb.ImportKey("acc-id1", pk) c.Assert(err, IsNil) aks.fp = pk.PublicKey().Fingerprint() @@ -104,12 +104,19 @@ invalidHeaderTests := []struct{ original, invalid, expectedErr string }{ {"account-id: acc-id1\n", "", `"account-id" header is mandatory`}, + {"account-id: acc-id1\n", "account-id: \n", `"account-id" header should not be empty`}, + {"public-key-id: " + aks.keyid + "\n", "", `"public-key-id" header is mandatory`}, + {"public-key-id: " + aks.keyid + "\n", "public-key-id: \n", `"public-key-id" header should not be empty`}, + {"public-key-fingerprint: " + aks.fp + "\n", "", `"public-key-fingerprint" header is mandatory`}, + {"public-key-fingerprint: " + aks.fp + "\n", "public-key-fingerprint: \n", `"public-key-fingerprint" header should not be empty`}, {aks.sinceLine, "", `"since" header is mandatory`}, - {aks.untilLine, "", `"until" header is mandatory`}, + {aks.sinceLine, "since: \n", `"since" header should not be empty`}, {aks.sinceLine, "since: 12:30\n", `"since" header is not a RFC3339 date: .*`}, + {aks.sinceLine, "since: \n", `"since" header should not be empty`}, + {aks.untilLine, "", `"until" header is mandatory`}, + {aks.untilLine, "until: \n", `"until" header should not be empty`}, {aks.untilLine, "until: " + aks.since.Format(time.RFC3339) + "\n", `invalid 'since' and 'until' times \(no gap after 'since' till 'until'\)`}, - {"public-key-id: " + aks.keyid + "\n", "", `"public-key-id" header is mandatory`}, - {"public-key-fingerprint: " + aks.fp + "\n", "", `"public-key-fingerprint" header is mandatory`}, + {aks.untilLine, "until: \n", `"until" header should not be empty`}, } for _, test := range invalidHeaderTests { @@ -188,7 +195,7 @@ cfg := &asserts.DatabaseConfig{ Backstore: bs, KeypairManager: asserts.NewMemoryKeypairManager(), - TrustedKeys: []*asserts.AccountKey{asserts.BootstrapAccountKeyForTest("canonical", &trustedKey.PublicKey)}, + TrustedKeys: []*asserts.AccountKey{asserts.BootstrapAccountKeyForTest("canonical", trustedKey.PublicKey())}, } db, err := asserts.OpenDatabase(cfg) c.Assert(err, IsNil) @@ -206,7 +213,7 @@ "since": aks.since.Format(time.RFC3339), "until": aks.until.Format(time.RFC3339), } - accKey, err := asserts.AssembleAndSignInTest(asserts.AccountKeyType, headers, []byte(aks.pubKeyBody), asserts.OpenPGPPrivateKey(trustedKey)) + accKey, err := asserts.AssembleAndSignInTest(asserts.AccountKeyType, headers, []byte(aks.pubKeyBody), trustedKey) c.Assert(err, IsNil) db := aks.openDB(c) @@ -226,7 +233,7 @@ "since": aks.since.Format(time.RFC3339), "until": aks.until.Format(time.RFC3339), } - accKey, err := asserts.AssembleAndSignInTest(asserts.AccountKeyType, headers, []byte(aks.pubKeyBody), asserts.OpenPGPPrivateKey(trustedKey)) + accKey, err := asserts.AssembleAndSignInTest(asserts.AccountKeyType, headers, []byte(aks.pubKeyBody), trustedKey) c.Assert(err, IsNil) db := aks.openDB(c) diff -Nru snapd-2.0.5/asserts/account_test.go snapd-2.0.8/asserts/account_test.go --- snapd-2.0.5/asserts/account_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/asserts/account_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,134 @@ +// -*- 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 asserts_test + +import ( + "fmt" + "strings" + "time" + + "github.com/snapcore/snapd/asserts" + . "gopkg.in/check.v1" +) + +var ( + _ = Suite(&accountSuite{}) +) + +type accountSuite struct { + ts time.Time + tsLine string +} + +func (s *accountSuite) SetUpSuite(c *C) { + s.ts = time.Now().Truncate(time.Second).UTC() + s.tsLine = "timestamp: " + s.ts.Format(time.RFC3339) + "\n" +} + +const accountExample = "type: account\n" + + "authority-id: canonical\n" + + "account-id: abc-123\n" + + "display-name: Nice User\n" + + "username: nice\n" + + "validation: certified\n" + + "TSLINE" + + "body-length: 0" + + "\n\n" + + "openpgp c2ln" + +func (s *accountSuite) TestDecodeOK(c *C) { + encoded := strings.Replace(accountExample, "TSLINE", s.tsLine, 1) + a, err := asserts.Decode([]byte(encoded)) + c.Assert(err, IsNil) + c.Check(a.Type(), Equals, asserts.AccountType) + account := a.(*asserts.Account) + c.Check(account.AuthorityID(), Equals, "canonical") + c.Check(account.Timestamp(), Equals, s.ts) + c.Check(account.AccountID(), Equals, "abc-123") + c.Check(account.DisplayName(), Equals, "Nice User") + c.Check(account.Username(), Equals, "nice") + c.Check(account.IsCertified(), Equals, true) +} + +func (s *accountSuite) TestIsCertified(c *C) { + tests := []struct { + value string + isCertified bool + }{ + {"certified", true}, + {"unproven", false}, + {"nonsense", false}, + } + + template := strings.Replace(accountExample, "TSLINE", s.tsLine, 1) + for _, test := range tests { + encoded := strings.Replace( + template, + "validation: certified\n", + fmt.Sprintf("validation: %s\n", test.value), + 1, + ) + assert, err := asserts.Decode([]byte(encoded)) + c.Assert(err, IsNil) + account := assert.(*asserts.Account) + c.Check(account.IsCertified(), Equals, test.isCertified) + } +} + +const ( + accountErrPrefix = "assertion account: " +) + +func (s *accountSuite) TestDecodeInvalid(c *C) { + encoded := strings.Replace(accountExample, "TSLINE", s.tsLine, 1) + + invalidTests := []struct{ original, invalid, expectedErr string }{ + {"account-id: abc-123\n", "", `"account-id" header is mandatory`}, + {"account-id: abc-123\n", "account-id: \n", `"account-id" header should not be empty`}, + {"display-name: Nice User\n", "", `"display-name" header is mandatory`}, + {"display-name: Nice User\n", "display-name: \n", `"display-name" header should not be empty`}, + {"validation: certified\n", "", `"validation" header is mandatory`}, + {"validation: certified\n", "validation: \n", `"validation" header should not be empty`}, + {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: .*`}, + } + + for _, test := range invalidTests { + invalid := strings.Replace(encoded, test.original, test.invalid, 1) + _, err := asserts.Decode([]byte(invalid)) + c.Check(err, ErrorMatches, accountErrPrefix+test.expectedErr) + } +} + +func (s *accountSuite) TestCheckInconsistentTimestamp(c *C) { + ex, err := asserts.Decode([]byte(strings.Replace(accountExample, "TSLINE", s.tsLine, 1))) + c.Assert(err, IsNil) + + signingKeyID, accSignDB, db := makeSignAndCheckDbWithAccountKey(c, "canonical") + + headers := ex.Headers() + headers["timestamp"] = "2011-01-01T14:00:00Z" + account, err := accSignDB.Sign(asserts.AccountType, headers, nil, signingKeyID) + c.Assert(err, IsNil) + + err = db.Check(account) + c.Assert(err, ErrorMatches, "account assertion timestamp outside of signing key validity") +} diff -Nru snapd-2.0.5/asserts/asserts.go snapd-2.0.8/asserts/asserts.go --- snapd-2.0.5/asserts/asserts.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/asserts.go 2016-06-08 05:58:01.000000000 +0000 @@ -44,10 +44,10 @@ // Understood assertion types. var ( + AccountType = &AssertionType{"account", []string{"account-id"}, assembleAccount} AccountKeyType = &AssertionType{"account-key", []string{"account-id", "public-key-id"}, assembleAccountKey} - DeviceSerialType = &AssertionType{"device-serial", []string{"brand-id", "model", "serial"}, assembleDeviceSerial} - IdentityType = &AssertionType{"identity", []string{"account-id"}, assembleIdentity} ModelType = &AssertionType{"model", []string{"series", "brand-id", "model"}, assembleModel} + SerialType = &AssertionType{"serial", []string{"brand-id", "model", "serial"}, assembleSerial} SnapDeclarationType = &AssertionType{"snap-declaration", []string{"series", "snap-id"}, assembleSnapDeclaration} SnapBuildType = &AssertionType{"snap-build", []string{"series", "snap-id", "snap-digest"}, assembleSnapBuild} SnapRevisionType = &AssertionType{"snap-revision", []string{"series", "snap-id", "snap-digest"}, assembleSnapRevision} @@ -56,10 +56,10 @@ ) var typeRegistry = map[string]*AssertionType{ + AccountType.Name: AccountType, AccountKeyType.Name: AccountKeyType, - IdentityType.Name: IdentityType, ModelType.Name: ModelType, - DeviceSerialType.Name: DeviceSerialType, + SerialType.Name: SerialType, SnapDeclarationType.Name: SnapDeclarationType, SnapBuildType.Name: SnapBuildType, SnapRevisionType.Name: SnapRevisionType, @@ -472,11 +472,11 @@ return nil, fmt.Errorf("assertion body length and declared body-length don't match: %v != %v", len(body), length) } - if _, err := checkMandatory(headers, "authority-id"); err != nil { + if _, err := checkNotEmpty(headers, "authority-id"); err != nil { return nil, fmt.Errorf("assertion: %v", err) } - typ, err := checkMandatory(headers, "type") + typ, err := checkNotEmpty(headers, "type") if err != nil { return nil, fmt.Errorf("assertion: %v", err) } @@ -486,7 +486,7 @@ } for _, primKey := range assertType.PrimaryKey { - if _, err := checkMandatory(headers, primKey); err != nil { + if _, err := checkNotEmpty(headers, primKey); err != nil { return nil, fmt.Errorf("assertion %s: %v", assertType.Name, err) } } @@ -543,7 +543,7 @@ finalHeaders["type"] = assertType.Name finalHeaders["body-length"] = strconv.Itoa(bodyLength) - if _, err := checkMandatory(finalHeaders, "authority-id"); err != nil { + if _, err := checkNotEmpty(finalHeaders, "authority-id"); err != nil { return nil, err } @@ -568,7 +568,7 @@ "body-length": true, } for _, primKey := range assertType.PrimaryKey { - if _, err := checkMandatory(finalHeaders, primKey); err != nil { + if _, err := checkNotEmpty(finalHeaders, primKey); err != nil { return nil, err } writeHeader(buf, finalHeaders, primKey) @@ -604,7 +604,7 @@ signature, err := signContent(content, privKey) if err != nil { - return nil, fmt.Errorf("failed to sign assertion: %v", err) + return nil, fmt.Errorf("cannot sign assertion: %v", err) } // be 'cat' friendly, add a ignored newline to the signature which is the last part of the encoded assertion signature = append(signature, '\n') diff -Nru snapd-2.0.5/asserts/asserts_test.go snapd-2.0.8/asserts/asserts_test.go --- snapd-2.0.5/asserts/asserts_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/asserts_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" + "github.com/snapcore/snapd/asserts" ) type assertsSuite struct{} @@ -394,7 +394,7 @@ "authority-id": "auth-id1", "primary-key": "0", } - a, err := asserts.AssembleAndSignInTest(asserts.TestOnlyType, headers, nil, asserts.OpenPGPPrivateKey(testPrivKey1)) + a, err := asserts.AssembleAndSignInTest(asserts.TestOnlyType, headers, nil, testPrivKey1) c.Assert(err, IsNil) _, err = asserts.Decode(asserts.Encode(a)) @@ -407,7 +407,7 @@ "primary-key": "0", } body := []byte("THE-BODY") - a, err := asserts.AssembleAndSignInTest(asserts.TestOnlyType, headers, body, asserts.OpenPGPPrivateKey(testPrivKey1)) + a, err := asserts.AssembleAndSignInTest(asserts.TestOnlyType, headers, body, testPrivKey1) c.Assert(err, IsNil) c.Check(a.Body(), DeepEquals, body) @@ -437,7 +437,7 @@ headers["odd"] = "true" } - a, err := asserts.AssembleAndSignInTest(asserts.TestOnlyType, headers, nil, asserts.OpenPGPPrivateKey(testPrivKey1)) + a, err := asserts.AssembleAndSignInTest(asserts.TestOnlyType, headers, nil, testPrivKey1) c.Assert(err, IsNil) decoded, err := asserts.Decode(asserts.Encode(a)) diff -Nru snapd-2.0.5/asserts/crypto.go snapd-2.0.8/asserts/crypto.go --- snapd-2.0.5/asserts/crypto.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/crypto.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,7 +24,8 @@ "crypto" "crypto/rand" "crypto/rsa" - _ "crypto/sha256" // be explicit about needing SHA256 + _ "crypto/sha256" // be explicit about supporting SHA256 + _ "crypto/sha512" // be explicit about needing SHA512 "encoding/base64" "encoding/hex" "fmt" @@ -82,27 +83,17 @@ return encodeFormatAndData(key.keyFormat(), buf.Bytes()), nil } -var openpgpConfig = &packet.Config{ - DefaultHash: crypto.SHA256, +type openpgpSigner interface { + sign(content []byte) (*packet.Signature, error) } func signContent(content []byte, privateKey PrivateKey) ([]byte, error) { - opgPrivKey, ok := privateKey.(openpgpPrivateKey) + signer, ok := privateKey.(openpgpSigner) if !ok { panic(fmt.Errorf("not an internally supported PrivateKey: %T", privateKey)) } - privKey := opgPrivKey.privk - - sig := new(packet.Signature) - sig.PubKeyAlgo = privKey.PubKeyAlgo - sig.Hash = openpgpConfig.Hash() - sig.CreationTime = time.Now() - sig.IssuerKeyId = &privKey.KeyId - h := openpgpConfig.Hash().New() - h.Write(content) - - err := sig.Sign(h, privKey, openpgpConfig) + sig, err := signer.sign(content) if err != nil { return nil, err } @@ -167,7 +158,7 @@ panic(fmt.Errorf("not an internally supported Signature: %T", sig)) } - h := openpgpConfig.Hash().New() + h := opgSig.sig.Hash.New() h.Write(content) return pubKey.VerifySignature(h, opgSig.sig) } @@ -272,6 +263,29 @@ return opgPrivK.privk.Serialize(w) } +var openpgpConfig = &packet.Config{ + DefaultHash: crypto.SHA512, +} + +func (opgPrivK openpgpPrivateKey) sign(content []byte) (*packet.Signature, error) { + privk := opgPrivK.privk + sig := new(packet.Signature) + sig.PubKeyAlgo = privk.PubKeyAlgo + sig.Hash = openpgpConfig.Hash() + sig.CreationTime = time.Now() + sig.IssuerKeyId = &privk.KeyId + + h := openpgpConfig.Hash().New() + h.Write(content) + + err := sig.Sign(h, privk, openpgpConfig) + if err != nil { + return nil, err + } + + return sig, nil +} + func decodePrivateKey(privKey []byte) (PrivateKey, error) { pkt, err := decodeOpenpgp(privKey, "private key") if err != nil { @@ -289,14 +303,123 @@ return openpgpPrivateKey{privk} } -func generatePrivateKey() (*packet.PrivateKey, error) { - priv, err := rsa.GenerateKey(rand.Reader, 2048) +// GenerateKey generates a private/public key pair. +func GenerateKey() (PrivateKey, error) { + priv, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return nil, err } - return packet.NewRSAPrivateKey(time.Now(), priv), nil + return OpenPGPPrivateKey(packet.NewRSAPrivateKey(time.Now(), priv)), nil } func encodePrivateKey(privKey PrivateKey) ([]byte, error) { return encodeKey(privKey, "private key") } + +// externally held key pairs + +type extPGPPrivateKey struct { + pubKey PublicKey + from string + doSign func(fingerprint string, content []byte) ([]byte, error) +} + +func newExtPGPPrivateKey(exportedPubKeyStream io.Reader, from string, sign func(fingerprint string, content []byte) ([]byte, error)) (PrivateKey, error) { + var pubKey *packet.PublicKey + + rd := packet.NewReader(exportedPubKeyStream) + for { + pkt, err := rd.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, fmt.Errorf("cannot read exported public key: %v", err) + } + cand, ok := pkt.(*packet.PublicKey) + if ok { + if cand.IsSubkey { + continue + } + if pubKey != nil { + return nil, fmt.Errorf("cannot select exported public key, found many") + } + pubKey = cand + } + } + + if pubKey == nil { + return nil, fmt.Errorf("cannot read exported public key, found none (broken export)") + + } + + rsaPubKey, ok := pubKey.PublicKey.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("not a RSA key") + } + + bitLen := rsaPubKey.N.BitLen() + if bitLen < 4096 { + return nil, fmt.Errorf("need at least 4096 bits key, got %d", bitLen) + } + + return &extPGPPrivateKey{ + pubKey: OpenPGPPublicKey(pubKey), + from: from, + doSign: sign, + }, nil +} + +func (expk *extPGPPrivateKey) PublicKey() PublicKey { + return expk.pubKey +} + +func (expk *extPGPPrivateKey) keyEncode(w io.Writer) error { + return fmt.Errorf("cannot access external private key to encode it") +} + +func (expk *extPGPPrivateKey) keyFormat() string { + return "" +} + +func (expk *extPGPPrivateKey) sign(content []byte) (*packet.Signature, error) { + out, err := expk.doSign(expk.pubKey.Fingerprint(), content) + if err != nil { + return nil, err + } + + badSig := fmt.Sprintf("bad %s produced signature: ", expk.from) + + sigpkt, err := packet.Read(bytes.NewBuffer(out)) + if err != nil { + return nil, fmt.Errorf(badSig+"%v", err) + } + + sig, ok := sigpkt.(*packet.Signature) + if !ok { + return nil, fmt.Errorf(badSig+"got %T", sigpkt) + } + + opgSig := openpgpSignature{sig} + + if sig.IssuerKeyId == nil { + return nil, fmt.Errorf(badSig + "no key id in the signature") + } + + sigKeyID := opgSig.KeyID() + wantedID := expk.pubKey.ID() + if sigKeyID != wantedID { + return nil, fmt.Errorf(badSig+"wrong key id (expected %q): %s", wantedID, sigKeyID) + } + + if sig.Hash != crypto.SHA512 { + return nil, fmt.Errorf(badSig + "expected SHA512 digest") + } + + err = expk.pubKey.verify(content, opgSig) + if err != nil { + return nil, fmt.Errorf(badSig+"it does not verify: %v", err) + } + + return sig, nil +} diff -Nru snapd-2.0.5/asserts/database.go snapd-2.0.8/asserts/database.go --- snapd-2.0.5/asserts/database.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/database.go 2016-06-08 05:58:01.000000000 +0000 @@ -174,25 +174,6 @@ }, nil } -// GenerateKey generates a private/public key pair for identity and -// stores it returning its key id. -func (db *Database) GenerateKey(authorityID string) (keyID string, err error) { - // TODO: optionally delegate the whole thing to the keypair mgr - - // TODO: support specifying different key types/algorithms - privKey, err := generatePrivateKey() - if err != nil { - return "", fmt.Errorf("failed to generate private key: %v", err) - } - - pk := OpenPGPPrivateKey(privKey) - err = db.ImportKey(authorityID, pk) - if err != nil { - return "", err - } - return pk.PublicKey().ID(), nil -} - // ImportKey stores the given private/public key pair for identity. func (db *Database) ImportKey(authorityID string, privKey PrivateKey) error { return db.keypairMgr.Put(authorityID, privKey) @@ -225,7 +206,7 @@ // Sign assembles an assertion with the provided information and signs it // with the private key from `headers["authority-id"]` that has the provided key id. func (db *Database) Sign(assertType *AssertionType, headers map[string]string, body []byte, keyID string) (Assertion, error) { - authorityID, err := checkMandatory(headers, "authority-id") + authorityID, err := checkNotEmpty(headers, "authority-id") if err != nil { return nil, err } diff -Nru snapd-2.0.5/asserts/database_test.go snapd-2.0.8/asserts/database_test.go --- snapd-2.0.5/asserts/database_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/database_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -34,8 +34,7 @@ "golang.org/x/crypto/openpgp/packet" . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/asserts" ) func Test(t *testing.T) { TestingT(t) } @@ -79,10 +78,10 @@ } func (dbs *databaseSuite) TestImportKey(c *C) { - expectedFingerprint := hex.EncodeToString(testPrivKey1.PublicKey.Fingerprint[:]) - expectedKeyID := hex.EncodeToString(testPrivKey1.PublicKey.Fingerprint[12:]) + expectedFingerprint := hex.EncodeToString(testPrivKey1Pkt.PublicKey.Fingerprint[:]) + expectedKeyID := hex.EncodeToString(testPrivKey1Pkt.PublicKey.Fingerprint[12:]) - err := dbs.db.ImportKey("account0", asserts.OpenPGPPrivateKey(testPrivKey1)) + err := dbs.db.ImportKey("account0", testPrivKey1) c.Assert(err, IsNil) keyPath := filepath.Join(dbs.topDir, "private-keys-v0/account0", expectedKeyID) @@ -100,23 +99,15 @@ } func (dbs *databaseSuite) TestImportKeyAlreadyExists(c *C) { - err := dbs.db.ImportKey("account0", asserts.OpenPGPPrivateKey(testPrivKey1)) + err := dbs.db.ImportKey("account0", testPrivKey1) c.Assert(err, IsNil) - err = dbs.db.ImportKey("account0", asserts.OpenPGPPrivateKey(testPrivKey1)) + err = dbs.db.ImportKey("account0", testPrivKey1) c.Check(err, ErrorMatches, "key pair with given key id already exists") } -func (dbs *databaseSuite) TestGenerateKey(c *C) { - fingerp, err := dbs.db.GenerateKey("account0") - c.Assert(err, IsNil) - c.Check(fingerp, NotNil) - keyPath := filepath.Join(dbs.topDir, "private-keys-v0/account0", fingerp) - c.Check(osutil.FileExists(keyPath), Equals, true) -} - func (dbs *databaseSuite) TestPublicKey(c *C) { - pk := asserts.OpenPGPPrivateKey(testPrivKey1) + pk := testPrivKey1 fingerp := pk.PublicKey().Fingerprint() keyid := pk.PublicKey().ID() err := dbs.db.ImportKey("account0", pk) @@ -136,21 +127,21 @@ c.Assert(err, IsNil) pubKey, ok := pkt.(*packet.PublicKey) c.Assert(ok, Equals, true) - c.Assert(pubKey.Fingerprint, DeepEquals, testPrivKey1.PublicKey.Fingerprint) + c.Assert(pubKey.Fingerprint, DeepEquals, testPrivKey1Pkt.PublicKey.Fingerprint) } func (dbs *databaseSuite) TestPublicKeyNotFound(c *C) { - pk := asserts.OpenPGPPrivateKey(testPrivKey1) + pk := testPrivKey1 keyID := pk.PublicKey().ID() _, err := dbs.db.PublicKey("account0", keyID) - c.Check(err, ErrorMatches, "no matching key pair found") + c.Check(err, ErrorMatches, "cannot find key pair") err = dbs.db.ImportKey("account0", pk) c.Assert(err, IsNil) _, err = dbs.db.PublicKey("account0", "ff"+keyID) - c.Check(err, ErrorMatches, "no matching key pair found") + c.Check(err, ErrorMatches, "cannot find key pair") } type checkSuite struct { @@ -171,7 +162,7 @@ "authority-id": "canonical", "primary-key": "0", } - chks.a, err = asserts.AssembleAndSignInTest(asserts.TestOnlyType, headers, nil, asserts.OpenPGPPrivateKey(testPrivKey0)) + chks.a, err = asserts.AssembleAndSignInTest(asserts.TestOnlyType, headers, nil, testPrivKey0) c.Assert(err, IsNil) } @@ -193,7 +184,7 @@ cfg := &asserts.DatabaseConfig{ Backstore: chks.bs, KeypairManager: asserts.NewMemoryKeypairManager(), - TrustedKeys: []*asserts.AccountKey{asserts.ExpiredAccountKeyForTest("canonical", &trustedKey.PublicKey)}, + TrustedKeys: []*asserts.AccountKey{asserts.ExpiredAccountKeyForTest("canonical", trustedKey.PublicKey())}, } db, err := asserts.OpenDatabase(cfg) c.Assert(err, IsNil) @@ -208,7 +199,7 @@ cfg := &asserts.DatabaseConfig{ Backstore: chks.bs, KeypairManager: asserts.NewMemoryKeypairManager(), - TrustedKeys: []*asserts.AccountKey{asserts.BootstrapAccountKeyForTest("canonical", &trustedKey.PublicKey)}, + TrustedKeys: []*asserts.AccountKey{asserts.BootstrapAccountKeyForTest("canonical", trustedKey.PublicKey())}, } db, err := asserts.OpenDatabase(cfg) c.Assert(err, IsNil) @@ -217,13 +208,13 @@ content, encodedSig := chks.a.Signature() // forgery forgedSig := new(packet.Signature) - forgedSig.PubKeyAlgo = testPrivKey1.PubKeyAlgo + forgedSig.PubKeyAlgo = testPrivKey1Pkt.PubKeyAlgo forgedSig.Hash = crypto.SHA256 forgedSig.CreationTime = time.Now() - forgedSig.IssuerKeyId = &testPrivKey0.KeyId + forgedSig.IssuerKeyId = &asserts.PrivateKeyPacket(testPrivKey0).KeyId h := crypto.SHA256.New() h.Write(content) - err = forgedSig.Sign(h, testPrivKey1, &packet.Config{DefaultHash: crypto.SHA256}) + err = forgedSig.Sign(h, testPrivKey1Pkt, &packet.Config{DefaultHash: crypto.SHA256}) c.Assert(err, IsNil) buf := new(bytes.Buffer) forgedSig.Serialize(buf) @@ -254,7 +245,7 @@ c.Assert(err, IsNil) safs.signingDB = db0 - pk := asserts.OpenPGPPrivateKey(testPrivKey0) + pk := testPrivKey0 err = db0.ImportKey("canonical", pk) c.Assert(err, IsNil) safs.signingKeyID = pk.PublicKey().ID() @@ -267,7 +258,7 @@ cfg := &asserts.DatabaseConfig{ Backstore: bs, KeypairManager: asserts.NewMemoryKeypairManager(), - TrustedKeys: []*asserts.AccountKey{asserts.BootstrapAccountKeyForTest("canonical", &trustedKey.PublicKey)}, + TrustedKeys: []*asserts.AccountKey{asserts.BootstrapAccountKeyForTest("canonical", trustedKey.PublicKey())}, } db, err := asserts.OpenDatabase(cfg) c.Assert(err, IsNil) @@ -320,7 +311,7 @@ "primary-key": "a", } a1, err := safs.signingDB.Sign(asserts.TestOnlyType, headers, nil, "abcd") - c.Assert(err, ErrorMatches, "no matching key pair found") + c.Assert(err, ErrorMatches, "cannot find key pair") c.Check(a1, IsNil) } @@ -499,7 +490,7 @@ } func (safs *signAddFindSuite) TestFindFindsTrustedAccountKeys(c *C) { - pk1 := asserts.OpenPGPPrivateKey(testPrivKey1) + pk1 := testPrivKey1 pubKey1Encoded, err := asserts.EncodePublicKey(pk1.PublicKey()) c.Assert(err, IsNil) @@ -584,3 +575,72 @@ c.Check(test.err, ErrorMatches, test.expected) } } + +type signatureBackwardCompSuite struct{} + +var _ = Suite(&signatureBackwardCompSuite{}) + +func (sbcs *signatureBackwardCompSuite) TestOldSHA256SignaturesWork(c *C) { + // these were using SHA256 + + const ( + testTrustedKey = `type: account-key +authority-id: can0nical +account-id: can0nical +public-key-id: 844efa9730eec4be +public-key-fingerprint: 716ff3cec4b9364a2bd930dc844efa9730eec4be +since: 2016-01-14T15:00:00Z +until: 2023-01-14T15:00:00Z +body-length: 376 + +openpgp xsBNBFaXv40BCADIlqLKFZaPaoe4TNLQv77vh4JWTlt7Z3IN2ducNqfg50q5mnkyUD2D +SckvsMy1440+a0Z83m/A7aPaO1JkLpMGfLr23VLyKCaAe0k6hg69/6aEfXhfy0yYvEOgGcBiX+fN +T6tqdRCsd+08LtisjYez7iJvmVwQ/syeduoTU4EiSVO1zlgc3eeq3TFyvcN0E1EsZ/7l2A33amTo +mtAPVyQsa1B+lTeaUgwuPBWV0oTuYcUSfYsmmsXEKx/PnzkliicnrC9QZ5CcisskVve3QwPAuLUz +2nV7/6vSRF22T4cUPF4QntjZBB6xjopdDH6wQsKyzLTTRak74moWksx8MEmVABEBAAE= + +openpgp wsBcBAABCAAQBQJWl8DiCRCETvqXMO7EvgAAhjkIAEoINWjQkujtx/TFYsKh0yYcQSpT +v8O83mLRP7Ty+mH99uQ0/DbeQ1hM5st8cFgzU8SzlDCh6BUMnAl/bR/hhibFD40CBLd13kDXl1aN +APybmSYoDVRQPAPop44UF0aCrTIw4Xds3E56d2Rsn+CkNML03kRc/i0Q53uYzZwxXVnzW/gVOXDL +u/IZtjeo3KsB645MVEUxJLQmjlgMOwMvCHJgWhSvZOuf7wC0soBCN9Ufa/0M/PZFXzzn8LpjKVrX +iDXhV7cY5PceG8ZV7Duo1JadOCzpkOHmai4DcrN7ZeY8bJnuNjOwvTLkrouw9xci4IxpPDRu0T/i +K9qaJtUo4cA=` + testAccKey = `type: account-key +authority-id: can0nical +account-id: developer1 +public-key-id: adea89b00094c337 +public-key-fingerprint: 5fa7b16ad5e8c8810d5a0686adea89b00094c337 +since: 2016-01-14T15:00:00Z +until: 2023-01-14T15:00:00Z +body-length: 376 + +openpgp xsBNBFaXv5MBCACkK//qNb3UwRtDviGcCSEi8Z6d5OXok3yilQmEh0LuW6DyP9sVpm08 +Vb1LGewOa5dThWGX4XKRBI/jCUnjCJQ6v15lLwHe1N7MJQ58DUxKqWFMV9yn4RcDPk6LqoFpPGdR +rbp9Ivo3PqJRMyD0wuJk9RhbaGZmILcL//BLgomE9NgQdAfZbiEnGxtkqAjeVtBtcJIj5TnCC658 +ZCqwugQeO9iJuIn3GosYvvTB6tReq6GP6b4dqvoi7SqxHVhtt2zD4Y6FUZIVmvZK0qwkV0gua2az +LzPOeoVcU1AEl7HVeBk7G6GiT5jx+CjjoGa0j22LdJB9S3JXHtGYk5p9CAwhABEBAAE= + +openpgp wsBcBAABCAAQBQJWl8HNCRCETvqXMO7EvgAAeuAIABn/1i8qGyaIhxOWE2cHIPYW3hq2 +PWpq7qrPN5Dbp/00xrTvc6tvMQWsXlMrAsYuq3sBCxUp3JRp9XhGiQeJtb8ft10g3+3J7e8OGHjl +CfXJ3A5el8Xxp5qkFywCsLdJgNtF6+uSQ4dO8SrAwzkM7c3JzntxdiFOjDLUSyZ+rXL42jdRagTY +8bcZfb47vd68Hyz3EvSvJuHSDbcNSTd3B832cimpfq5vJ7FoDrchVn3sg+3IwekuPhG3LQn5BVtc +0ontHd+V1GaandhqBaDA01cGZN0gnqv2Haogt0P/h3nZZZJ1nTW5PLC6hs8TZdBdl3Lel8yAHD5L +ZF5jSvRDLgI=` + ) + + tKey, err := asserts.Decode([]byte(testTrustedKey)) + c.Assert(err, IsNil) + + cfg := &asserts.DatabaseConfig{ + KeypairManager: asserts.NewMemoryKeypairManager(), + TrustedKeys: []*asserts.AccountKey{tKey.(*asserts.AccountKey)}, + } + db, err := asserts.OpenDatabase(cfg) + c.Assert(err, IsNil) + + a, err := asserts.Decode([]byte(testAccKey)) + c.Assert(err, IsNil) + + err = db.Check(a) + c.Check(err, IsNil) +} diff -Nru snapd-2.0.5/asserts/device_asserts.go snapd-2.0.8/asserts/device_asserts.go --- snapd-2.0.5/asserts/device_asserts.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/device_asserts.go 2016-06-08 05:58:01.000000000 +0000 @@ -111,7 +111,7 @@ } for _, mandatory := range modelMandatory { - if _, err := checkMandatory(assert.headers, mandatory); err != nil { + if _, err := checkNotEmpty(assert.headers, mandatory); err != nil { return nil, err } } @@ -142,46 +142,46 @@ }, nil } -// DeviceSerial holds a device-serial assertion, which is a statement binding a +// Serial holds a serial assertion, which is a statement binding a // device identity with the device public key. -type DeviceSerial struct { +type Serial struct { assertionBase timestamp time.Time pubKey PublicKey } // BrandID returns the brand identifier of the device. -func (ds *DeviceSerial) BrandID() string { - return ds.Header("brand-id") +func (ser *Serial) BrandID() string { + return ser.Header("brand-id") } // Model returns the model name identifier of the device. -func (ds *DeviceSerial) Model() string { - return ds.Header("model") +func (ser *Serial) Model() string { + return ser.Header("model") } -// Serial returns the serial of the device, together with brand id and model -// they form the unique identifier of the device. -func (ds *DeviceSerial) Serial() string { - return ds.Header("serial") +// Serial returns the serial identifier of the device, together with +// brand id and model they form the unique identifier of the device. +func (ser *Serial) Serial() string { + return ser.Header("serial") } // DeviceKey returns the public key of the device. -func (ds *DeviceSerial) DeviceKey() PublicKey { - return ds.pubKey +func (ser *Serial) DeviceKey() PublicKey { + return ser.pubKey } -// Timestamp returns the time when the device-serial assertion was issued. -func (ds *DeviceSerial) Timestamp() time.Time { - return ds.timestamp +// Timestamp returns the time when the serial assertion was issued. +func (ser *Serial) Timestamp() time.Time { + return ser.timestamp } -// TODO: implement further consistency checks for DeviceSerial but first review approach +// TODO: implement further consistency checks for Serial but first review approach -func assembleDeviceSerial(assert assertionBase) (Assertion, error) { +func assembleSerial(assert assertionBase) (Assertion, error) { // TODO: authority-id can only == canonical or brand-id - encodedKey, err := checkMandatory(assert.headers, "device-key") + encodedKey, err := checkNotEmpty(assert.headers, "device-key") if err != nil { return nil, err } @@ -196,7 +196,7 @@ } // ignore extra headers and non-empty body for future compatibility - return &DeviceSerial{ + return &Serial{ assertionBase: assert, timestamp: timestamp, pubKey: pubKey, diff -Nru snapd-2.0.5/asserts/device_asserts_test.go snapd-2.0.8/asserts/device_asserts_test.go --- snapd-2.0.5/asserts/device_asserts_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/device_asserts_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,13 +20,12 @@ package asserts_test import ( - "fmt" "strings" "time" . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" + "github.com/snapcore/snapd/asserts" ) type modelSuite struct { @@ -35,8 +34,8 @@ } var ( - _ = Suite(&deviceSerialSuite{}) _ = Suite(&modelSuite{}) + _ = Suite(&serialSuite{}) ) func (mods *modelSuite) SetUpSuite(c *C) { @@ -87,25 +86,35 @@ modelErrPrefix = "assertion model: " ) -func (mods *modelSuite) TestDecodeInvalidMandatory(c *C) { - encoded := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) - - mandatoryHeaders := []string{"series", "brand-id", "model", "os", "architecture", "gadget", "kernel", "store", "allowed-modes", "required-snaps", "class", "timestamp"} - - for _, mandatory := range mandatoryHeaders { - invalid := strings.Replace(encoded, mandatory+":", "xyz:", 1) - _, err := asserts.Decode([]byte(invalid)) - c.Check(err, ErrorMatches, fmt.Sprintf("%s%q header is mandatory", modelErrPrefix, mandatory)) - } -} - func (mods *modelSuite) TestDecodeInvalid(c *C) { encoded := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1) invalidTests := []struct{ original, invalid, expectedErr string }{ + {"series: 16\n", "", `"series" header is mandatory`}, + {"series: 16\n", "series: \n", `"series" header should not be empty`}, + {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`}, + {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`}, {"brand-id: brand-id1\n", "brand-id: random\n", `authority-id and brand-id must match, model assertions are expected to be signed by the brand: "brand-id1" != "random"`}, - {"required-snaps: foo, bar\n", "required-snaps: foo,\n", `empty entry in comma separated "required-snaps" header: "foo,"`}, + {"model: baz-3000\n", "", `"model" header is mandatory`}, + {"model: baz-3000\n", "model: \n", `"model" header should not be empty`}, + {"os: core\n", "", `"os" header is mandatory`}, + {"os: core\n", "os: \n", `"os" header should not be empty`}, + {"architecture: amd64\n", "", `"architecture" header is mandatory`}, + {"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`}, + {"kernel: baz-linux\n", "", `"kernel" header is mandatory`}, + {"kernel: baz-linux\n", "kernel: \n", `"kernel" header should not be empty`}, + {"store: brand-store\n", "", `"store" header is mandatory`}, + {"store: brand-store\n", "store: \n", `"store" header should not be empty`}, + {"allowed-modes: \n", "", `"allowed-modes" header is mandatory`}, {"allowed-modes: \n", "allowed-modes: ,\n", `empty entry in comma separated "allowed-modes" header: ","`}, + {"required-snaps: foo, bar\n", "", `"required-snaps" header is mandatory`}, + {"required-snaps: foo, bar\n", "required-snaps: foo,\n", `empty entry in comma separated "required-snaps" header: "foo,"`}, + {"class: fixed\n", "", `"class" header is mandatory`}, + {"class: fixed\n", "class: \n", `"class" header should not be empty`}, + {mods.tsLine, "", `"timestamp" header is mandatory`}, + {mods.tsLine, "timestamp: \n", `"timestamp" header should not be empty`}, {mods.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, } @@ -146,24 +155,24 @@ c.Assert(err, ErrorMatches, "model assertion timestamp outside of signing key validity") } -type deviceSerialSuite struct { +type serialSuite struct { ts time.Time tsLine string deviceKey asserts.PrivateKey encodedDevKey string } -func (dss *deviceSerialSuite) SetUpSuite(c *C) { - dss.ts = time.Now().Truncate(time.Second).UTC() - dss.tsLine = "timestamp: " + dss.ts.Format(time.RFC3339) + "\n" +func (ss *serialSuite) SetUpSuite(c *C) { + ss.ts = time.Now().Truncate(time.Second).UTC() + ss.tsLine = "timestamp: " + ss.ts.Format(time.RFC3339) + "\n" - dss.deviceKey = asserts.OpenPGPPrivateKey(testPrivKey2) - encodedPubKey, err := asserts.EncodePublicKey(dss.deviceKey.PublicKey()) + ss.deviceKey = testPrivKey2 + encodedPubKey, err := asserts.EncodePublicKey(ss.deviceKey.PublicKey()) c.Assert(err, IsNil) - dss.encodedDevKey = string(encodedPubKey) + ss.encodedDevKey = string(encodedPubKey) } -const deviceSerialExample = "type: device-serial\n" + +const serialExample = "type: serial\n" + "authority-id: canonical\n" + "brand-id: brand-id1\n" + "model: baz-3000\n" + @@ -175,40 +184,48 @@ "\n\n" + "openpgp c2ln" -func (dss *deviceSerialSuite) TestDecodeOK(c *C) { - encoded := strings.Replace(deviceSerialExample, "TSLINE", dss.tsLine, 1) - encoded = strings.Replace(encoded, "DEVICEKEY", strings.Replace(dss.encodedDevKey, "\n", "\n ", -1), 1) +func (ss *serialSuite) TestDecodeOK(c *C) { + encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1) + encoded = strings.Replace(encoded, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1) a, err := asserts.Decode([]byte(encoded)) c.Assert(err, IsNil) - c.Check(a.Type(), Equals, asserts.DeviceSerialType) - deviceSerial := a.(*asserts.DeviceSerial) - c.Check(deviceSerial.AuthorityID(), Equals, "canonical") - c.Check(deviceSerial.Timestamp(), Equals, dss.ts) - c.Check(deviceSerial.BrandID(), Equals, "brand-id1") - c.Check(deviceSerial.Model(), Equals, "baz-3000") - c.Check(deviceSerial.Serial(), Equals, "2700") - c.Check(deviceSerial.DeviceKey().Fingerprint(), Equals, dss.deviceKey.PublicKey().Fingerprint()) + c.Check(a.Type(), Equals, asserts.SerialType) + serial := a.(*asserts.Serial) + c.Check(serial.AuthorityID(), Equals, "canonical") + c.Check(serial.Timestamp(), Equals, ss.ts) + c.Check(serial.BrandID(), Equals, "brand-id1") + c.Check(serial.Model(), Equals, "baz-3000") + c.Check(serial.Serial(), Equals, "2700") + c.Check(serial.DeviceKey().Fingerprint(), Equals, ss.deviceKey.PublicKey().Fingerprint()) } const ( - deviceSerialErrPrefix = "assertion device-serial: " + serialErrPrefix = "assertion serial: " ) -func (dss *deviceSerialSuite) TestDecodeInvalid(c *C) { - encoded := strings.Replace(deviceSerialExample, "TSLINE", dss.tsLine, 1) +func (ss *serialSuite) TestDecodeInvalid(c *C) { + encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1) invalidTests := []struct{ original, invalid, expectedErr string }{ + {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`}, + {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`}, + {"model: baz-3000\n", "", `"model" header is mandatory`}, + {"model: baz-3000\n", "model: \n", `"model" header should not be empty`}, {"serial: 2700\n", "", `"serial" header is mandatory`}, + {"serial: 2700\n", "serial: \n", `"serial" header should not be empty`}, + {ss.tsLine, "", `"timestamp" header is mandatory`}, + {ss.tsLine, "timestamp: \n", `"timestamp" header should not be empty`}, + {ss.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, {"device-key:\n DEVICEKEY\n", "", `"device-key" header is mandatory`}, + {"device-key:\n DEVICEKEY\n", "device-key: \n", `"device-key" header should not be empty`}, {"device-key:\n DEVICEKEY\n", "device-key: openpgp ZZZ\n", `public key: could not decode base64 data:.*`}, - {dss.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, } for _, test := range invalidTests { invalid := strings.Replace(encoded, test.original, test.invalid, 1) - invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(dss.encodedDevKey, "\n", "\n ", -1), 1) + invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1) _, err := asserts.Decode([]byte(invalid)) - c.Check(err, ErrorMatches, deviceSerialErrPrefix+test.expectedErr) + c.Check(err, ErrorMatches, serialErrPrefix+test.expectedErr) } } diff -Nru snapd-2.0.5/asserts/digest.go snapd-2.0.8/asserts/digest.go --- snapd-2.0.5/asserts/digest.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/digest.go 2016-06-08 05:58:01.000000000 +0000 @@ -29,13 +29,13 @@ func EncodeDigest(hash crypto.Hash, hashDigest []byte) (string, error) { algo := "" switch hash { - case crypto.SHA256: - algo = "sha256" + case crypto.SHA512: + algo = "sha512" default: return "", fmt.Errorf("unsupported hash") } if len(hashDigest) != hash.Size() { return "", fmt.Errorf("hash digest by %s should be %d bytes", algo, hash.Size()) } - return fmt.Sprintf("%s %s", algo, base64.RawURLEncoding.EncodeToString(hashDigest)), nil + return fmt.Sprintf("%s-%s", algo, base64.RawURLEncoding.EncodeToString(hashDigest)), nil } diff -Nru snapd-2.0.5/asserts/digest_test.go snapd-2.0.8/asserts/digest_test.go --- snapd-2.0.5/asserts/digest_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/digest_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,7 +27,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" + "github.com/snapcore/snapd/asserts" ) type encodeDigestSuite struct{} @@ -35,14 +35,14 @@ var _ = Suite(&encodeDigestSuite{}) func (eds *encodeDigestSuite) TestEncodeDigestOK(c *C) { - h := crypto.SHA256.New() + h := crypto.SHA512.New() h.Write([]byte("some stuff to hash")) digest := h.Sum(nil) - encoded, err := asserts.EncodeDigest(crypto.SHA256, digest) + encoded, err := asserts.EncodeDigest(crypto.SHA512, digest) c.Assert(err, IsNil) - c.Check(strings.HasPrefix(encoded, "sha256 "), Equals, true) - decoded, err := base64.RawURLEncoding.DecodeString(encoded[len("sha256 "):]) + c.Check(strings.HasPrefix(encoded, "sha512-"), Equals, true) + decoded, err := base64.RawURLEncoding.DecodeString(encoded[len("sha512-"):]) c.Assert(err, IsNil) c.Check(decoded, DeepEquals, digest) } @@ -51,6 +51,6 @@ _, err := asserts.EncodeDigest(crypto.SHA1, nil) c.Check(err, ErrorMatches, "unsupported hash") - _, err = asserts.EncodeDigest(crypto.SHA256, []byte{1, 2}) - c.Check(err, ErrorMatches, "hash digest by sha256 should be 32 bytes") + _, err = asserts.EncodeDigest(crypto.SHA512, []byte{1, 2}) + c.Check(err, ErrorMatches, "hash digest by sha512 should be 64 bytes") } diff -Nru snapd-2.0.5/asserts/export_test.go snapd-2.0.8/asserts/export_test.go --- snapd-2.0.5/asserts/export_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/export_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,8 +28,10 @@ // expose test-only things here -// generatePrivateKey exposed for tests -var GeneratePrivateKeyInTest = generatePrivateKey +// access internal openpgp lib packet +func PrivateKeyPacket(pk PrivateKey) *packet.PrivateKey { + return pk.(openpgpPrivateKey).privk +} // assembleAndSign exposed for tests var AssembleAndSignInTest = assembleAndSign @@ -53,8 +55,7 @@ return enc.append(encoded) } -func makeAccountKeyForTest(authorityID string, pubKey *packet.PublicKey, validYears int) *AccountKey { - openPGPPubKey := OpenPGPPublicKey(pubKey) +func makeAccountKeyForTest(authorityID string, openPGPPubKey PublicKey, validYears int) *AccountKey { return &AccountKey{ assertionBase: assertionBase{ headers: map[string]string{ @@ -69,11 +70,11 @@ } } -func BootstrapAccountKeyForTest(authorityID string, pubKey *packet.PublicKey) *AccountKey { +func BootstrapAccountKeyForTest(authorityID string, pubKey PublicKey) *AccountKey { return makeAccountKeyForTest(authorityID, pubKey, 9999) } -func ExpiredAccountKeyForTest(authorityID string, pubKey *packet.PublicKey) *AccountKey { +func ExpiredAccountKeyForTest(authorityID string, pubKey PublicKey) *AccountKey { return makeAccountKeyForTest(authorityID, pubKey, 1) } @@ -112,3 +113,15 @@ func AccountKeyIsKeyValidAt(ak *AccountKey, when time.Time) bool { return ak.isKeyValidAt(when) } + +type GPGRunner func(homedir string, input []byte, args ...string) ([]byte, error) + +func MockRunGPG(mock func(prev GPGRunner, homedir string, input []byte, args ...string) ([]byte, error)) (restore func()) { + prevRunGPG := runGPG + runGPG = func(homedir string, input []byte, args ...string) ([]byte, error) { + return mock(prevRunGPG, homedir, input, args...) + } + return func() { + runGPG = prevRunGPG + } +} diff -Nru snapd-2.0.5/asserts/fsbackstore_test.go snapd-2.0.8/asserts/fsbackstore_test.go --- snapd-2.0.5/asserts/fsbackstore_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/fsbackstore_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" + "github.com/snapcore/snapd/asserts" ) type fsBackstoreSuite struct{} diff -Nru snapd-2.0.5/asserts/fsentryutils.go snapd-2.0.8/asserts/fsentryutils.go --- snapd-2.0.5/asserts/fsentryutils.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/fsentryutils.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,7 +25,7 @@ "os" "path/filepath" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/osutil" ) // utilities to read/write fs entries diff -Nru snapd-2.0.5/asserts/fskeypairmgr.go snapd-2.0.8/asserts/fskeypairmgr.go --- snapd-2.0.5/asserts/fskeypairmgr.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/fskeypairmgr.go 2016-06-08 05:58:01.000000000 +0000 @@ -73,7 +73,7 @@ return nil } -var errKeypairNotFound = errors.New("no matching key pair found") +var errKeypairNotFound = errors.New("cannot find key pair") func (fskm *filesystemKeypairManager) Get(authorityID, keyID string) (PrivateKey, error) { fskm.mu.RLock() diff -Nru snapd-2.0.5/asserts/fskeypairmgr_test.go snapd-2.0.8/asserts/fskeypairmgr_test.go --- snapd-2.0.5/asserts/fskeypairmgr_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/fskeypairmgr_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" + "github.com/snapcore/snapd/asserts" ) type fsKeypairMgrSuite struct{} diff -Nru snapd-2.0.5/asserts/gpgkeypairmgr.go snapd-2.0.8/asserts/gpgkeypairmgr.go --- snapd-2.0.5/asserts/gpgkeypairmgr.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/asserts/gpgkeypairmgr.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,108 @@ +// -*- 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 asserts + +import ( + "bytes" + "fmt" + "os/exec" + "strings" +) + +func runGPGImpl(homedir string, input []byte, args ...string) ([]byte, error) { + general := []string{"-q"} + if homedir != "" { + general = append([]string{"--homedir", homedir}, general...) + } + allArgs := append(general, args...) + + cmd := exec.Command("gpg", allArgs...) + var outBuf bytes.Buffer + var errBuf bytes.Buffer + + if len(input) != 0 { + cmd.Stdin = bytes.NewBuffer(input) + } + + cmd.Stdout = &outBuf + cmd.Stderr = &errBuf + + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("gpg %s failed: %v (%q)", strings.Join(args, " "), err, errBuf.Bytes()) + } + + return outBuf.Bytes(), nil +} + +var runGPG = runGPGImpl + +type gpgKeypairManager struct { + homedir string +} + +func (gkm *gpgKeypairManager) gpg(input []byte, args ...string) ([]byte, error) { + return runGPG(gkm.homedir, input, args...) +} + +// NewGPGKeypairManager creates a new key pair manager backed by a local GnuPG setup +// using the given GPG homedir, and asking GPG to fallback "~/.gnupg" +// to default if empty. +// Importing keys through the keypair manager interface is not +// suppored. +// Main purpose is allowing signing using keys from a GPG setup. +func NewGPGKeypairManager(homedir string) KeypairManager { + return &gpgKeypairManager{ + homedir: homedir, + } +} + +func (gkm *gpgKeypairManager) Put(authorityID string, privKey PrivateKey) error { + // NOTE: we don't need this initially at least and this keypair mgr is not for general arbitrary usage + return fmt.Errorf("cannot import private key into GPG keyring") +} + +func (gkm *gpgKeypairManager) Get(authorityID, keyID string) (PrivateKey, error) { + out, err := gkm.gpg(nil, "--batch", "--export", "--export-options", "export-minimal,export-clean,no-export-attributes", "0x"+keyID) + if err != nil { + return nil, err + } + if len(out) == 0 { + return nil, fmt.Errorf("cannot find key %q in GPG keyring", keyID) + } + + pubKeyBuf := bytes.NewBuffer(out) + privKey, err := newExtPGPPrivateKey(pubKeyBuf, "GPG", gkm.sign) + if err != nil { + return nil, fmt.Errorf("cannot use GPG key %q: %v", keyID, err) + } + gotID := privKey.PublicKey().ID() + if gotID != keyID { + return nil, fmt.Errorf("got wrong key from GPG, expected %q: %s", keyID, gotID) + } + return privKey, nil +} + +func (gkm *gpgKeypairManager) sign(fingerprint string, content []byte) ([]byte, error) { + out, err := gkm.gpg(content, "--personal-digest-preferences", "SHA512", "--default-key", "0x"+fingerprint, "--detach-sign") + if err != nil { + return nil, fmt.Errorf("cannot sign using GPG: %v", err) + } + return out, nil +} diff -Nru snapd-2.0.5/asserts/gpgkeypairmgr_test.go snapd-2.0.8/asserts/gpgkeypairmgr_test.go --- snapd-2.0.5/asserts/gpgkeypairmgr_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/asserts/gpgkeypairmgr_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,410 @@ +// -*- 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 asserts_test + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/rsa" + "fmt" + "os/exec" + "regexp" + "time" + + . "gopkg.in/check.v1" + + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/packet" + + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/osutil" +) + +type gpgKeypairMgrSuite struct { + homedir string + keypairMgr asserts.KeypairManager +} + +var _ = Suite(&gpgKeypairMgrSuite{}) + +func (gkms *gpgKeypairMgrSuite) SetUpSuite(c *C) { + if !osutil.FileExists("/usr/bin/gpg") { + c.Skip("gpg not installed") + } +} + +const ( + testKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1 + +lQcYBFdLWPQBEAC4GPsC/slzwiuJDVVVfEAyTt/Pwn+TEvGuHUr0fVzT+wld66CN +ZHGIx2q9h3ai58M33CpsCsILJaIt5BrQC7DGSiyx7KG7SWkZ0HsXF1qUmiWK4PIk +QgTphXC+Q2WuTWpXXmyaN8/bHcKir3vj5b7JP1rHL95whRj3WCdAqgCxI31mIqp3 +ecdNHSztRCDCVtFBxULY9BzyIYtw26b2COZtuHMuhsJZll72+qQj+zc+L/T61706 +LSC21DI/sEqTk97IC9BEhgKbGxITY9Bt2PezbsjflHtesbCWI5E5T3OpUHCQhZli +9I/xsFtk9SIrxaCAqFlOQbf4MK/W+je7BGsOZfXWzoNWxOWbCjV+4YSt4a8xuULK +oJe/heAQySwn4s5qQaTK8Xr5Z2XTQ20q8GZ/gJ1p5JQXMxON//UbPKwH1cWwfIMj +Y9WZIw2Pcn+c3fWc5aI3Czpzq8T5RmaE1qdGx+MBLlLBXLKPVwSZlZoDeW5vzmIR +R/tNYUzSNeU62NFoAH6myl7wo7u9dgj//VSBPXrZumqQnWsLXdv2E2n6eQ3DQUX0 +NqWUSs0jVoqGfByfc7NliYq1y8Nn+TnuTwcGfyyjfFbGhFeSXYRn+1/pLLLtsetb +v56/cNMwbJrO2xaBFBFObzz3jgF2ntrgM8usAWvLI3mbWEoBPYVppz80QwARAQAB +AA/8CNHq5hFWjc0N2z1AIIjZOy/L3unMGpFBR/IPqcKpzwl2FkGtiaiixzFP/AWV +7vxt6MALvkJjr+IH25f+mty8hZDUhFpjGJR4ocElweJKDNg3wpLADHGnR5gvjHCx +L0EhPk9VTQGDMVXDLJp+CS9Jd93TrRBY4V4sZzPvftRw6mEEvH8eWKavyueCGVn2 +vH4pUgkv3dy62Eo4IoTmLQpvHm7vAcR4t48R51ZJxTmKGNjLrWA8U3cJsV4INu9C +G2uYXvrbPwqGlxocaxZl9s/6yhDINJdUs8w35XGNycefMcubS6lC7dqWsjccodZx +Yn8k5JUW4OjFdhwVs0B9/rVEUFxJtQ6t28bl4qAGZaXgU4z4lj1zUcOFf3jQdfu8 +uePfo9Ts8o2B6HaF4nInxCVZzHACy3Xk8/Kl7Qv8UJcfzaJto0v7p6V9BfHji3kg +pOuxoSzhOl0EOD93XHhM1J0CaYTB2AmErAktHiSS7QolEEfJS4OrX6LBMOF6NwDG +rdX6H/hsO2qeF48s1tpPw0gTa4+awdsQYFwBjFBHWqOROrjr+d3S3iRsKafjXoEG +wGnBZ3VeTqlhylp9v+A7qx1A7H62Fyf8kJn1sXi209hs9y+83icL4iP9j2BbLVoe +a0Fn8bNvBhJBwLBfibUgM2LWnIzU0/sVaX4Yk3ni1fX5GG0IAM7aqKVi5IVN2mlW +2MFWaH8wmML6r/EpXYHhY8lAj9BWBlkQZqoMe2g+CbNol0nLNf/B24qHsSv0JXBI +TWxBt2UtJiHx9plaQLYT1O+FYl2zHU90GTxKqmv6SmR8oKk4bOG7g4hoXMdLAgWt +CUrjTgG3HUk7Wg+ZGvKyx53CMYXao2lWxWYnWNhX3n17ulqWtPEKzPFcYnJbBIDK +9V9swkOIV0yMxnMWtxIGKeG2IfnCTl5z3qROzFRGvYEkT5zJrcWxekSDkAiZoXRT +e4JMQDmI9rdnXZ8HybcAv1noYlxDIRuHPB0jp4X1GROaG2zcruVhPoxa4uT5FV+3 +K+jpurcIAOPWN002QotWWPdtxBgOhReU6CClk/OOzm3UgzYi4Gk0kLSe0Ozn8B8P +kxhkRZ08HVdydl2aKBFu7Y/1RFq+o5WoVukCWIWPG+h/stHkTkk1EdesL/hjsN6H +DoVnT0i7HAIsC9bb7hL+WTPZQoDsuwYs3k+zsEQSDXhkN7W+5CVZdE3Cc/IY8wWO +/+lZoHoDR7lThEJl+G8YiNdb6T3YUNxH3jMzBN1ydQS64CYdqySzK5UxSIxMjXz3 +7Ww0RnFx6kN0g2ae3IlxUbmse2ugLETzX7ABTqbDVpgJLJMkyLk21h9DRfnlAAKY +IjAsrvNCsQDON2w3F7iZlqrj2Kh99tUH/2Z27+sNrOEVUjMf+Ds9RrKOkrUMcNWe +l1dM0UAHMOpBew9qimdXwI7lrH6SW4k4QEDdWBHhPOUVYqj2F+i+8sZwgqhmDwsw +2R3oPLP/pGrQRK2jjLNRztvgy22ASrYYHZd/WkUjBNHRVTXJYArGrvbz3KbhCe3N +b9Z/CJSx1zeiTRrJSzTxTIlsJGEw06WtAy7bSeXeOo3rD0yUPmP/GLKfIUfxUHkV +f+u5vm6XVbDf0kp3ZgDWjFtEJNWNajDOI3xA8dv5yXUnQYRLluo33QEZVYg5S+LK +p9lTBrkp/u8st5Mwzq1ptm45SgmnrT0vsf8kiaB6uE9wuSVE3009+suAuLQHICh0 +ZXN0KYkCOAQTAQgAIgUCV0tY9AIbLwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA +CgkQtSz0OKLQePc2IxAAheBE2JTGZlxgPn88zc3BlDeqh89ZeQ5Kl7qz1dU/DpiQ +Wf1cNaV9bf+3bczWtcRHFjREVEj3MR7d8WulRz2br5zB1thGt1h1ayfT5c+W/AM1 +I/VgC/SFdKN6pk1fJjDc9qrJn86pOexAHNHyPwTAxnlQ3t4Q9OzOuHTDBuLvWkzz +rlWAYFqBiYlnRBa3v0De1dKpYl5mbv/N1co9neyl8EsfL9AyBUv42j8OYBb3mAYF +mrG2KObUib7zYeJ+M1d1VMkBZUSw2ZWStseHxo42S3c5teHQwnYSfHznTL/fibLA +OreGNemvTCbWVuZfLYGt1yRjrDP2uBpdH3bq/uLtdhvXzeTc2Hs86mYa9+gJgkXC +XwnJ41Hw+dRoSsoUOc4WQALBT9BsEuK3MzXj11Wyg3QwLiyF2Tr72mzVKvZO/GRy +jBvmEimoevu0RB9MlDB7c01B34sBQ0R+GhKyQxxC0cstKGoi/8wF1O3HmbrbjiRk +grX5x90vvu5HJHR1Pjpi/9FDj8nm7gKZ+pGYqmYvzLU8hOy6WiQObiRid56uQem6 +ECfMJlWnQtzAUVTBrtckGSzlKhO6laLiR9Bg90uCzKjYehW6PCVpMp2vmEsqo0r0 +n/YBufIZs9/L1Gblpi0SL6ZTjZdQ2Wj8btU+OlWiJM3LNIMJAZRtMRBfmljnijQ= +=r01O +-----END PGP PRIVATE KEY BLOCK----- +` + + testKeyID = "b52cf438a2d078f7" + + testKeyFingerprint = "42a3050d365c10d5c093abeeb52cf438a2d078f7" +) + +func (gkms *gpgKeypairMgrSuite) importKey(c *C, key string) { + gpg := exec.Command("gpg", "--homedir", gkms.homedir, "-q", "--batch", "--import", "--armor") + gpg.Stdin = bytes.NewBufferString(key) + out, err := gpg.CombinedOutput() + c.Assert(err, IsNil, Commentf("test key import failed: %v (%q)", err, out)) +} + +func (gkms *gpgKeypairMgrSuite) SetUpTest(c *C) { + gkms.homedir = c.MkDir() + gkms.keypairMgr = asserts.NewGPGKeypairManager(gkms.homedir) + // import test key + gkms.importKey(c, testKey) +} + +func (gkms *gpgKeypairMgrSuite) TestGetPublicKeyLooksGood(c *C) { + got, err := gkms.keypairMgr.Get("auth-id1", testKeyID) + c.Assert(err, IsNil) + fp := got.PublicKey().Fingerprint() + c.Check(fp, Equals, testKeyFingerprint) +} + +func (gkms *gpgKeypairMgrSuite) TestGetNotFound(c *C) { + got, err := gkms.keypairMgr.Get("auth-id1", "ffffffffffffffff") + c.Check(err, ErrorMatches, `cannot find key "ffffffffffffffff" in GPG keyring`) + c.Check(got, IsNil) +} + +func (gkms *gpgKeypairMgrSuite) TestUseInSigning(c *C) { + trustedKey, err := asserts.GenerateKey() + c.Assert(err, IsNil) + + tmgr := asserts.NewMemoryKeypairManager() + tmgr.Put("trusted", trustedKey) + + authorityDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ + KeypairManager: tmgr, + }) + c.Assert(err, IsNil) + + now := time.Now().UTC() + headers := map[string]string{ + "authority-id": "trusted", + "account-id": "trusted", + "public-key-id": trustedKey.PublicKey().ID(), + "public-key-fingerprint": trustedKey.PublicKey().Fingerprint(), + "since": now.Format(time.RFC3339), + "until": now.AddDate(10, 0, 0).Format(time.RFC3339), + } + pubTrustedKeyEnc, err := asserts.EncodePublicKey(trustedKey.PublicKey()) + c.Assert(err, IsNil) + trustedAccKey, err := authorityDB.Sign(asserts.AccountKeyType, headers, pubTrustedKeyEnc, trustedKey.PublicKey().ID()) + c.Assert(err, IsNil) + + devKey, err := gkms.keypairMgr.Get("dev1", testKeyID) + c.Assert(err, IsNil) + headers = map[string]string{ + "authority-id": "trusted", + "account-id": "dev1-id", + "public-key-id": devKey.PublicKey().ID(), + "public-key-fingerprint": devKey.PublicKey().Fingerprint(), + "since": now.Format(time.RFC3339), + "until": now.AddDate(10, 0, 0).Format(time.RFC3339), + } + pubDevKeyEnc, err := asserts.EncodePublicKey(devKey.PublicKey()) + c.Assert(err, IsNil) + devAccKey, err := authorityDB.Sign(asserts.AccountKeyType, headers, pubDevKeyEnc, trustedKey.PublicKey().ID()) + c.Assert(err, IsNil) + + signDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ + KeypairManager: gkms.keypairMgr, + }) + c.Assert(err, IsNil) + + checkDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ + KeypairManager: asserts.NewMemoryKeypairManager(), + Backstore: asserts.NewMemoryBackstore(), + TrustedKeys: []*asserts.AccountKey{trustedAccKey.(*asserts.AccountKey)}, + }) + c.Assert(err, IsNil) + err = checkDB.Add(devAccKey) + c.Assert(err, IsNil) + + headers = map[string]string{ + "authority-id": "dev1-id", + "series": "16", + "snap-id": "snap-id-1", + "snap-digest": "sha512-...", + "grade": "devel", + "snap-size": "1025", + "timestamp": now.Format(time.RFC3339), + } + snapBuild, err := signDB.Sign(asserts.SnapBuildType, headers, nil, testKeyID) + c.Assert(err, IsNil) + + err = checkDB.Check(snapBuild) + c.Check(err, IsNil) +} + +const ( + dsaKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1 + +lQNTBFdLWt0RCAC3sBvyl2j13gKxvnRF7DpBfN1cxba8n/qvCu2uGvlaekCFCVol +jJt594gL0QRzWPaV+KWQQroZ4u0knYA15QCbFqJ/ziX7zRI+5xcOGJ8ZBJJnDiGM +Eu7v2NGpxJHxgz1n+fjUqDPC/fHMfnQ1bkYNbXDXht2Uw9j8LP3FPueYRH46ZYQs +G91s6x+row7RCIGJcg0gVJhVvqoojk+Z+7pQ2kiNIeBeVztjybZGLlqL6fnKfeXq +TsBjnsqUIxdu286UU/xkn6sHa4APqr5wywNjvWoRyWIXxQVTWQp81PlvPfJFyCJJ +diOb6z2+sfbQ+jdB/MUXYAT2HaOhRMaP/9UPAQCr/nhHyDBb5iq1F/YdftulV9wx +cOGdWxM2AD9LnLLHGQf+Oct7QLco7SK43NzIDNvp1J/ESK6smfsIgMz6ICyj1Z21 +8Rch0do/0fAiKQpAimxvMQnSE4JtT92xPPV0PdHde/Xs8QxoaKnF2XECoIqMFmjP +VLerqhyWOv3CE+MHLbj0b0WMl5DSYAcizgF6768R8To9Oow/YdEy7GFCutPoFlNE +EHW+FA0EZVwGi3BelWMEAjJS+EtJ8knP9d7Im+GHBZ41f0yWU06CWgncfQvxxrOw +9f/uO2eoTpSb4QLqyasnp4e93iul1r1sJuGYFscQUo1gXJWvGJyh+iYj/K+bk53Y +fbbc4efJOLNJ6blBLFRY1cwFWKKEmn+GtsN7TA88lAf+MOnzlSpEDMMNHSPcU/RI +KJe2VDuf3z7nP6Isy9PbPLtuXothU0iLtR76SZuVkUMtRDf+s2B79Lb5c4LQhg8H +DAiuJqUtCUmyAwwHj2cv5rZT3YuOOb80D16rHXM4Ut05oYeGNEulHG2Qsqe6pxUp +gEL7Ar2ZempjeVpN8jNqbOW8WHsYJ49CHA6pF30hGIHk2zMvBKBORa5kGEpgSDex +kZWB66bOXveUpharOwsvnaa/9SLL+DLcdaVUydrGZMPNVTmoXQmJpvNZj+7uU8IU +RYDEoe9lalEwXUv7Z2eAbMbo23AYKN4omxuaW9cp/hldiXoHgh70KGuwlBtSd+ml +bAAA/jFnXDTFL0rDbz9ykVftBS/QooNR2xZLam/0G824RpQKDyO0BiAoZHNhKYh6 +BBMRCAAiBQJXS1rdAhsjBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRBWv9KR +5/7U5VSKAQCSrbnVtHaGN9ZUk/PtnJMhbBTtk2R2Y4huxCdFUYQc0QD+IxU66SP+ +Iri22tgdp1HhmTuG2ZyaxUs0cDgkzRTrsAg= +=Pbfn +-----END PGP PRIVATE KEY BLOCK----- +` + + dsaKeyID = "56bfd291e7fed4e5" +) + +func (gkms *gpgKeypairMgrSuite) TestGetWrongKeyType(c *C) { + gkms.importKey(c, dsaKey) + _, err := gkms.keypairMgr.Get("auth-id1", dsaKeyID) + c.Check(err, ErrorMatches, fmt.Sprintf(`cannot use GPG key %q: not a RSA key`, dsaKeyID)) +} + +func (gkms *gpgKeypairMgrSuite) TestGetNotUnique(c *C) { + mockGPG := func(prev asserts.GPGRunner, homedir string, input []byte, args ...string) ([]byte, error) { + c.Assert(args[1], Equals, "--export") + + pk1, err := rsa.GenerateKey(rand.Reader, 512) + c.Assert(err, IsNil) + pk2, err := rsa.GenerateKey(rand.Reader, 512) + c.Assert(err, IsNil) + + buf := new(bytes.Buffer) + err = packet.NewRSAPublicKey(time.Now(), &pk1.PublicKey).Serialize(buf) + c.Assert(err, IsNil) + err = packet.NewRSAPublicKey(time.Now(), &pk2.PublicKey).Serialize(buf) + c.Assert(err, IsNil) + + return buf.Bytes(), nil + } + restore := asserts.MockRunGPG(mockGPG) + defer restore() + + _, err := gkms.keypairMgr.Get("auth-id1", testKeyID) + c.Check(err, ErrorMatches, fmt.Sprintf("cannot use GPG key %q: cannot select exported public key, found many", testKeyID)) +} + +func (gkms *gpgKeypairMgrSuite) TestGetWrongKeyLength(c *C) { + mockGPG := func(prev asserts.GPGRunner, homedir string, input []byte, args ...string) ([]byte, error) { + c.Assert(args[1], Equals, "--export") + + pk, err := rsa.GenerateKey(rand.Reader, 512) + c.Assert(err, IsNil) + pubPkt := packet.NewRSAPublicKey(time.Now(), &pk.PublicKey) + buf := new(bytes.Buffer) + err = pubPkt.Serialize(buf) + c.Assert(err, IsNil) + return buf.Bytes(), nil + } + restore := asserts.MockRunGPG(mockGPG) + defer restore() + + _, err := gkms.keypairMgr.Get("auth-id1", testKeyID) + c.Check(err, ErrorMatches, fmt.Sprintf("cannot use GPG key %q: need at least 4096 bits key, got 512", testKeyID)) +} + +func (gkms *gpgKeypairMgrSuite) TestUseInSigningBrokenSignature(c *C) { + blk, err := armor.Decode(bytes.NewBuffer([]byte(testKey))) + c.Assert(err, IsNil) + pkPkt, err := packet.Read(blk.Body) + c.Assert(err, IsNil) + privk, ok := pkPkt.(*packet.PrivateKey) + c.Assert(ok, Equals, true) + + var breakSig func(sig *packet.Signature, cont []byte) []byte + + mockGPG := func(prev asserts.GPGRunner, homedir string, input []byte, args ...string) ([]byte, error) { + if args[1] == "--export" { + return prev(homedir, input, args...) + } + n := len(args) + c.Assert(args[n-1], Equals, "--detach-sign") + + sig := new(packet.Signature) + sig.PubKeyAlgo = packet.PubKeyAlgoRSA + sig.Hash = crypto.SHA512 + sig.CreationTime = time.Now() + sig.IssuerKeyId = &privk.KeyId + + // poking to break the signature + cont := breakSig(sig, input) + + h := sig.Hash.New() + h.Write([]byte(cont)) + + err := sig.Sign(h, privk, nil) + c.Assert(err, IsNil) + + buf := new(bytes.Buffer) + sig.Serialize(buf) + return buf.Bytes(), nil + } + restore := asserts.MockRunGPG(mockGPG) + defer restore() + + signDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ + KeypairManager: gkms.keypairMgr, + }) + c.Assert(err, IsNil) + + headers := map[string]string{ + "authority-id": "dev1-id", + "series": "16", + "snap-id": "snap-id-1", + "snap-digest": "sha512-...", + "grade": "devel", + "snap-size": "1025", + "timestamp": time.Now().Format(time.RFC3339), + } + + tests := []struct { + breakSig func(*packet.Signature, []byte) []byte + expectedErr string + }{ + {func(sig *packet.Signature, cont []byte) []byte { + sig.Hash = crypto.SHA1 + return cont + }, "cannot sign assertion: bad GPG produced signature: expected SHA512 digest"}, + {func(sig *packet.Signature, cont []byte) []byte { + sig.IssuerKeyId = nil + return cont + }, "cannot sign assertion: bad GPG produced signature: no key id in the signature"}, + {func(sig *packet.Signature, cont []byte) []byte { + sig.IssuerKeyId = new(uint64) + *sig.IssuerKeyId = 0xffffffffffffffff + return cont + }, regexp.QuoteMeta(fmt.Sprintf("cannot sign assertion: bad GPG produced signature: wrong key id (expected %q): ffffffffffffffff", testKeyID))}, + {func(sig *packet.Signature, cont []byte) []byte { + return cont[:5] + }, "cannot sign assertion: bad GPG produced signature: it does not verify:.*"}, + } + + for _, t := range tests { + breakSig = t.breakSig + + _, err = signDB.Sign(asserts.SnapBuildType, headers, nil, testKeyID) + c.Check(err, ErrorMatches, t.expectedErr) + } + +} + +func (gkms *gpgKeypairMgrSuite) TestUseInSigningFailure(c *C) { + mockGPG := func(prev asserts.GPGRunner, homedir string, input []byte, args ...string) ([]byte, error) { + if args[1] == "--export" { + return prev(homedir, input, args...) + } + n := len(args) + c.Assert(args[n-1], Equals, "--detach-sign") + return nil, fmt.Errorf("boom") + } + restore := asserts.MockRunGPG(mockGPG) + defer restore() + + signDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ + KeypairManager: gkms.keypairMgr, + }) + c.Assert(err, IsNil) + + headers := map[string]string{ + "authority-id": "dev1-id", + "series": "16", + "snap-id": "snap-id-1", + "snap-digest": "sha512-...", + "grade": "devel", + "snap-size": "1025", + "timestamp": time.Now().Format(time.RFC3339), + } + + _, err = signDB.Sign(asserts.SnapBuildType, headers, nil, testKeyID) + c.Check(err, ErrorMatches, "cannot sign assertion: cannot sign using GPG: boom") +} diff -Nru snapd-2.0.5/asserts/header_checks.go snapd-2.0.8/asserts/header_checks.go --- snapd-2.0.5/asserts/header_checks.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/header_checks.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,11 +28,19 @@ // common checks used when decoding/assembling assertions -func checkMandatory(headers map[string]string, name string) (string, error) { +func checkExists(headers map[string]string, name string) (string, error) { value, ok := headers[name] if !ok { return "", fmt.Errorf("%q header is mandatory", name) } + return value, nil +} + +func checkNotEmpty(headers map[string]string, name string) (string, error) { + value, err := checkExists(headers, name) + if err != nil { + return "", err + } if len(value) == 0 { return "", fmt.Errorf("%q header should not be empty", name) } @@ -70,7 +78,7 @@ } func checkRFC3339Date(headers map[string]string, name string) (time.Time, error) { - dateStr, err := checkMandatory(headers, name) + dateStr, err := checkNotEmpty(headers, name) if err != nil { return time.Time{}, err } @@ -82,7 +90,7 @@ } func checkUint(headers map[string]string, name string, bitSize int) (uint64, error) { - valueStr, err := checkMandatory(headers, name) + valueStr, err := checkNotEmpty(headers, name) if err != nil { return 0, err } diff -Nru snapd-2.0.5/asserts/identity.go snapd-2.0.8/asserts/identity.go --- snapd-2.0.5/asserts/identity.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/identity.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,78 +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 asserts - -import "time" - -var ( - identityValidationCertified = "certified" -) - -// Identity holds an identity assertion, which provides a name for an account -// and the authority's confidence in the name's validity. -type Identity struct { - assertionBase - certified bool - timestamp time.Time -} - -// AccountID returns the account-id of the identit. -func (id *Identity) AccountID() string { - return id.Header("account-id") -} - -// DisplayName returns the human-friendly name for the identity. -func (id *Identity) DisplayName() string { - return id.Header("display-name") -} - -// IsCertified returns true if the authority has confidence in the identity's name. -func (id *Identity) IsCertified() bool { - return id.certified -} - -// Timestamp returns the time when the identity was issued. -func (id *Identity) Timestamp() time.Time { - return id.timestamp -} - -func assembleIdentity(assert assertionBase) (Assertion, error) { - _, err := checkMandatory(assert.headers, "display-name") - if err != nil { - return nil, err - } - - _, err = checkMandatory(assert.headers, "validation") - if err != nil { - return nil, err - } - certified := assert.headers["validation"] == identityValidationCertified - - timestamp, err := checkRFC3339Date(assert.headers, "timestamp") - if err != nil { - return nil, err - } - - return &Identity{ - assertionBase: assert, - certified: certified, - timestamp: timestamp, - }, nil -} diff -Nru snapd-2.0.5/asserts/identity_test.go snapd-2.0.8/asserts/identity_test.go --- snapd-2.0.5/asserts/identity_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/identity_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,127 +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 asserts_test - -import ( - "fmt" - "strings" - "time" - - "github.com/ubuntu-core/snappy/asserts" - . "gopkg.in/check.v1" -) - -var ( - _ = Suite(&identitySuite{}) -) - -type identitySuite struct { - ts time.Time - tsLine string -} - -func (ids *identitySuite) SetUpSuite(c *C) { - ids.ts = time.Now().Truncate(time.Second).UTC() - ids.tsLine = "timestamp: " + ids.ts.Format(time.RFC3339) + "\n" -} - -const identityExample = "type: identity\n" + - "authority-id: canonical\n" + - "account-id: abc-123\n" + - "display-name: Display Name\n" + - "validation: certified\n" + - "TSLINE" + - "body-length: 0" + - "\n\n" + - "openpgp c2ln" - -func (ids *identitySuite) TestDecodeOK(c *C) { - encoded := strings.Replace(identityExample, "TSLINE", ids.tsLine, 1) - a, err := asserts.Decode([]byte(encoded)) - c.Assert(err, IsNil) - c.Check(a.Type(), Equals, asserts.IdentityType) - identity := a.(*asserts.Identity) - c.Check(identity.AuthorityID(), Equals, "canonical") - c.Check(identity.Timestamp(), Equals, ids.ts) - c.Check(identity.AccountID(), Equals, "abc-123") - c.Check(identity.DisplayName(), Equals, "Display Name") - c.Check(identity.IsCertified(), Equals, true) -} - -func (ids *identitySuite) TestIsCertified(c *C) { - tests := []struct { - value string - isCertified bool - }{ - {"certified", true}, - {"unproven", false}, - {"nonsense", false}, - } - - template := strings.Replace(identityExample, "TSLINE", ids.tsLine, 1) - for _, test := range tests { - encoded := strings.Replace( - template, - "validation: certified\n", - fmt.Sprintf("validation: %s\n", test.value), - 1, - ) - assert, err := asserts.Decode([]byte(encoded)) - c.Assert(err, IsNil) - identity := assert.(*asserts.Identity) - c.Check(identity.IsCertified(), Equals, test.isCertified) - } -} - -const ( - identityErrPrefix = "assertion identity: " -) - -func (ids *identitySuite) TestDecodeInvalid(c *C) { - encoded := strings.Replace(identityExample, "TSLINE", ids.tsLine, 1) - - invalidTests := []struct{ original, invalid, expectedErr string }{ - {"account-id: abc-123\n", "", `"account-id" header is mandatory`}, - {"display-name: Display Name\n", "", `"display-name" header is mandatory`}, - {"validation: certified\n", "", `"validation" header is mandatory`}, - {ids.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, - } - - for _, test := range invalidTests { - invalid := strings.Replace(encoded, test.original, test.invalid, 1) - _, err := asserts.Decode([]byte(invalid)) - c.Check(err, ErrorMatches, identityErrPrefix+test.expectedErr) - } -} - -func (ids *identitySuite) TestCheckInconsistentTimestamp(c *C) { - ex, err := asserts.Decode([]byte(strings.Replace(identityExample, "TSLINE", ids.tsLine, 1))) - c.Assert(err, IsNil) - - signingKeyID, accSignDB, db := makeSignAndCheckDbWithAccountKey(c, "canonical") - - headers := ex.Headers() - headers["timestamp"] = "2011-01-01T14:00:00Z" - identity, err := accSignDB.Sign(asserts.IdentityType, headers, nil, signingKeyID) - c.Assert(err, IsNil) - - err = db.Check(identity) - c.Assert(err, ErrorMatches, "identity assertion timestamp outside of signing key validity") -} diff -Nru snapd-2.0.5/asserts/membackstore_test.go snapd-2.0.8/asserts/membackstore_test.go --- snapd-2.0.5/asserts/membackstore_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/membackstore_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" + "github.com/snapcore/snapd/asserts" ) type memBackstoreSuite struct { diff -Nru snapd-2.0.5/asserts/memkeypairmgr_test.go snapd-2.0.8/asserts/memkeypairmgr_test.go --- snapd-2.0.5/asserts/memkeypairmgr_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/memkeypairmgr_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" + "github.com/snapcore/snapd/asserts" ) type memKeypairMgtSuite struct { @@ -36,7 +36,7 @@ } func (mkms *memKeypairMgtSuite) TestPutAndGet(c *C) { - pk1 := asserts.OpenPGPPrivateKey(testPrivKey1) + pk1 := testPrivKey1 keyID := pk1.PublicKey().ID() err := mkms.keypairMgr.Put("auth-id1", pk1) c.Assert(err, IsNil) @@ -48,7 +48,7 @@ } func (mkms *memKeypairMgtSuite) TestPutAlreadyExists(c *C) { - pk1 := asserts.OpenPGPPrivateKey(testPrivKey1) + pk1 := testPrivKey1 err := mkms.keypairMgr.Put("auth-id1", pk1) c.Assert(err, IsNil) @@ -57,17 +57,17 @@ } func (mkms *memKeypairMgtSuite) TestGetNotFound(c *C) { - pk1 := asserts.OpenPGPPrivateKey(testPrivKey1) + pk1 := testPrivKey1 keyID := pk1.PublicKey().ID() got, err := mkms.keypairMgr.Get("auth-id1", keyID) c.Check(got, IsNil) - c.Check(err, ErrorMatches, "no matching key pair found") + c.Check(err, ErrorMatches, "cannot find key pair") err = mkms.keypairMgr.Put("auth-id1", pk1) c.Assert(err, IsNil) got, err = mkms.keypairMgr.Get("auth-id1", keyID+"x") c.Check(got, IsNil) - c.Check(err, ErrorMatches, "no matching key pair found") + c.Check(err, ErrorMatches, "cannot find key pair") } diff -Nru snapd-2.0.5/asserts/privkeys_for_test.go snapd-2.0.8/asserts/privkeys_for_test.go --- snapd-2.0.5/asserts/privkeys_for_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/privkeys_for_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,7 @@ import ( "fmt" - "golang.org/x/crypto/openpgp/packet" - - "github.com/ubuntu-core/snappy/asserts" + "github.com/snapcore/snapd/asserts" ) // private keys to use in tests @@ -32,10 +30,12 @@ testPrivKey0 = genTestPrivKey() testPrivKey1 = genTestPrivKey() testPrivKey2 = genTestPrivKey() + + testPrivKey1Pkt = asserts.PrivateKeyPacket(testPrivKey1) ) -func genTestPrivKey() *packet.PrivateKey { - privKey, err := asserts.GeneratePrivateKeyInTest() +func genTestPrivKey() asserts.PrivateKey { + privKey, err := asserts.GenerateKey() if err != nil { panic(fmt.Errorf("failed to create priv key for tests: %v", err)) } diff -Nru snapd-2.0.5/asserts/snap_asserts.go snapd-2.0.8/asserts/snap_asserts.go --- snapd-2.0.5/asserts/snap_asserts.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/snap_asserts.go 2016-06-08 05:58:01.000000000 +0000 @@ -65,12 +65,12 @@ // XXX: consistency check is signed by canonical func assembleSnapDeclaration(assert assertionBase) (Assertion, error) { - _, err := checkMandatory(assert.headers, "snap-name") + _, err := checkExists(assert.headers, "snap-name") if err != nil { return nil, err } - _, err = checkMandatory(assert.headers, "publisher-id") + _, err = checkNotEmpty(assert.headers, "publisher-id") if err != nil { return nil, err } @@ -134,7 +134,7 @@ func assembleSnapBuild(assert assertionBase) (Assertion, error) { // TODO: more parsing/checking of snap-digest - _, err := checkMandatory(assert.headers, "grade") + _, err := checkNotEmpty(assert.headers, "grade") if err != nil { return nil, err } @@ -225,7 +225,7 @@ return nil, err } - _, err = checkMandatory(assert.headers, "developer-id") + _, err = checkNotEmpty(assert.headers, "developer-id") if err != nil { return nil, err } diff -Nru snapd-2.0.5/asserts/snap_asserts_test.go snapd-2.0.8/asserts/snap_asserts_test.go --- snapd-2.0.5/asserts/snap_asserts_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/snap_asserts_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" + "github.com/snapcore/snapd/asserts" ) var ( @@ -70,6 +70,24 @@ c.Check(snapDecl.Gates(), DeepEquals, []string{"snap-id-3", "snap-id-4"}) } +func (sds *snapDeclSuite) TestEmptySnapName(c *C) { + encoded := "type: snap-declaration\n" + + "authority-id: canonical\n" + + "series: 16\n" + + "snap-id: snap-id-1\n" + + "snap-name: \n" + + "publisher-id: dev-id1\n" + + "gates: snap-id-3,snap-id-4\n" + + sds.tsLine + + "body-length: 0" + + "\n\n" + + "openpgp c2ln" + a, err := asserts.Decode([]byte(encoded)) + c.Assert(err, IsNil) + snapDecl := a.(*asserts.SnapDeclaration) + c.Check(snapDecl.SnapName(), Equals, "") +} + const ( snapDeclErrPrefix = "assertion snap-declaration: " ) @@ -89,12 +107,17 @@ invalidTests := []struct{ original, invalid, expectedErr string }{ {"series: 16\n", "", `"series" header is mandatory`}, + {"series: 16\n", "series: \n", `"series" header should not be empty`}, {"snap-id: snap-id-1\n", "", `"snap-id" header is mandatory`}, + {"snap-id: snap-id-1\n", "snap-id: \n", `"snap-id" header should not be empty`}, {"snap-name: first\n", "", `"snap-name" header is mandatory`}, {"publisher-id: dev-id1\n", "", `"publisher-id" header is mandatory`}, + {"publisher-id: dev-id1\n", "publisher-id: \n", `"publisher-id" header should not be empty`}, + {sds.tsLine, "", `"timestamp" header is mandatory`}, + {sds.tsLine, "timestamp: \n", `"timestamp" header should not be empty`}, + {sds.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, {"gates: snap-id-3,snap-id-4\n", "", `\"gates\" header is mandatory`}, {"gates: snap-id-3,snap-id-4\n", "gates: foo,\n", `empty entry in comma separated "gates" header: "foo,"`}, - {sds.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, } for _, test := range invalidTests { @@ -159,12 +182,18 @@ invalidTests := []struct{ original, invalid, expectedErr string }{ {"series: 16\n", "", `"series" header is mandatory`}, + {"series: 16\n", "series: \n", `"series" header should not be empty`}, {"snap-id: snap-id-1\n", "", `"snap-id" header is mandatory`}, + {"snap-id: snap-id-1\n", "snap-id: \n", `"snap-id" header should not be empty`}, {"snap-digest: sha256 ...\n", "", `"snap-digest" header is mandatory`}, - {"grade: stable\n", "", `"grade" header is mandatory`}, + {"snap-digest: sha256 ...\n", "snap-digest: \n", `"snap-digest" header should not be empty`}, {"snap-size: 10000\n", "", `"snap-size" header is mandatory`}, {"snap-size: 10000\n", "snap-size: -1\n", `"snap-size" header is not an unsigned integer: -1`}, {"snap-size: 10000\n", "snap-size: zzz\n", `"snap-size" header is not an unsigned integer: zzz`}, + {"grade: stable\n", "", `"grade" header is mandatory`}, + {"grade: stable\n", "grade: \n", `"grade" header should not be empty`}, + {sbs.tsLine, "", `"timestamp" header is mandatory`}, + {sbs.tsLine, "timestamp: \n", `"timestamp" header should not be empty`}, {sbs.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, } @@ -183,8 +212,8 @@ } accSignDB, err := asserts.OpenDatabase(cfg1) c.Assert(err, IsNil) - pk1 := asserts.OpenPGPPrivateKey(testPrivKey1) - err = accSignDB.ImportKey(accountID, asserts.OpenPGPPrivateKey(testPrivKey1)) + pk1 := testPrivKey1 + err = accSignDB.ImportKey(accountID, testPrivKey1) c.Assert(err, IsNil) accFingerp := pk1.PublicKey().Fingerprint() accKeyID := pk1.PublicKey().ID() @@ -203,7 +232,7 @@ "since": "2015-11-20T15:04:00Z", "until": "2500-11-20T15:04:00Z", } - accKey, err := asserts.AssembleAndSignInTest(asserts.AccountKeyType, headers, []byte(accPubKeyBody), asserts.OpenPGPPrivateKey(trustedKey)) + accKey, err := asserts.AssembleAndSignInTest(asserts.AccountKeyType, headers, []byte(accPubKeyBody), trustedKey) c.Assert(err, IsNil) topDir := filepath.Join(c.MkDir(), "asserts-db") @@ -212,7 +241,7 @@ cfg := &asserts.DatabaseConfig{ Backstore: bs, KeypairManager: asserts.NewMemoryKeypairManager(), - TrustedKeys: []*asserts.AccountKey{asserts.BootstrapAccountKeyForTest("canonical", &trustedKey.PublicKey)}, + TrustedKeys: []*asserts.AccountKey{asserts.BootstrapAccountKeyForTest("canonical", trustedKey.PublicKey())}, } checkDB, err = asserts.OpenDatabase(cfg) c.Assert(err, IsNil) @@ -331,15 +360,23 @@ encoded := srs.makeValidEncoded() invalidTests := []struct{ original, invalid, expectedErr string }{ {"series: 16\n", "", `"series" header is mandatory`}, + {"series: 16\n", "series: \n", `"series" header should not be empty`}, {"snap-id: snap-id-1\n", "", `"snap-id" header is mandatory`}, + {"snap-id: snap-id-1\n", "snap-id: \n", `"snap-id" header should not be empty`}, {"snap-digest: sha256 ...\n", "", `"snap-digest" header is mandatory`}, + {"snap-digest: sha256 ...\n", "snap-digest: \n", `"snap-digest" header should not be empty`}, {"snap-size: 123\n", "", `"snap-size" header is mandatory`}, + {"snap-size: 123\n", "snap-size: \n", `"snap-size" header should not be empty`}, {"snap-size: 123\n", "snap-size: -1\n", `"snap-size" header is not an unsigned integer: -1`}, {"snap-size: 123\n", "snap-size: zzz\n", `"snap-size" header is not an unsigned integer: zzz`}, {"snap-revision: 1\n", "", `"snap-revision" header is mandatory`}, + {"snap-revision: 1\n", "snap-revision: \n", `"snap-revision" header should not be empty`}, {"snap-revision: 1\n", "snap-revision: -1\n", `"snap-revision" header is not an unsigned integer: -1`}, {"snap-revision: 1\n", "snap-revision: zzz\n", `"snap-revision" header is not an unsigned integer: zzz`}, {"developer-id: dev-id1\n", "", `"developer-id" header is mandatory`}, + {"developer-id: dev-id1\n", "developer-id: \n", `"developer-id" header should not be empty`}, + {srs.tsLine, "", `"timestamp" header is mandatory`}, + {srs.tsLine, "timestamp: \n", `"timestamp" header should not be empty`}, {srs.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, } diff -Nru snapd-2.0.5/asserts/sysdb.go snapd-2.0.8/asserts/sysdb.go --- snapd-2.0.5/asserts/sysdb.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/sysdb.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,7 +23,7 @@ "fmt" "io/ioutil" - "github.com/ubuntu-core/snappy/dirs" + "github.com/snapcore/snapd/dirs" ) func openDatabaseAt(path string, cfg *DatabaseConfig) (*Database, error) { diff -Nru snapd-2.0.5/asserts/sysdb_test.go snapd-2.0.8/asserts/sysdb_test.go --- snapd-2.0.5/asserts/sysdb_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/asserts/sysdb_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,8 +27,8 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" - "github.com/ubuntu-core/snappy/dirs" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/dirs" ) type sysDBSuite struct { @@ -40,7 +40,7 @@ func (sdbs *sysDBSuite) SetUpTest(c *C) { tmpdir := c.MkDir() - pk := asserts.OpenPGPPrivateKey(testPrivKey0) + pk := testPrivKey0 trustedPubKey := pk.PublicKey() trustedPubKeyEncoded, err := asserts.EncodePublicKey(trustedPubKey) c.Assert(err, IsNil) diff -Nru snapd-2.0.5/classic/create.go snapd-2.0.8/classic/create.go --- snapd-2.0.5/classic/create.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/classic/create.go 2016-06-08 05:58:01.000000000 +0000 @@ -30,11 +30,11 @@ "path/filepath" "strings" - "github.com/ubuntu-core/snappy/arch" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/release" + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/release" ) var ( @@ -47,13 +47,9 @@ func findDownloadPathFromLxdIndex(r io.Reader) (string, error) { arch := arch.UbuntuArchitecture() - lsb, err := release.ReadLSB() - if err != nil { - return "", err - } - release := lsb.Codename + codename := release.ReleaseInfo.Codename - needle := fmt.Sprintf("ubuntu;%s;%s;default;", release, arch) + needle := fmt.Sprintf("ubuntu;%s;%s;default;", codename, arch) scanner := bufio.NewScanner(r) for scanner.Scan() { if strings.HasPrefix(scanner.Text(), needle) { diff -Nru snapd-2.0.5/classic/create_test.go snapd-2.0.8/classic/create_test.go --- snapd-2.0.5/classic/create_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/classic/create_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -32,12 +32,12 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/arch" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/release" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/testutil" ) type CreateTestSuite struct { @@ -52,6 +52,9 @@ func (t *CreateTestSuite) SetUpTest(c *C) { t.BaseTest.SetUpTest(c) + if release.ReleaseInfo.ID != "ubuntu" { + c.Skip("classic test only work on ubuntu") + } dirs.SetRootDir(c.MkDir()) @@ -78,16 +81,12 @@ } func makeMockLxdIndexSystem() string { - lsb, err := release.ReadLSB() - if err != nil { - panic(err) - } arch := arch.UbuntuArchitecture() s := fmt.Sprintf(` ubuntu;xenial;otherarch;default;20151126_03:49;/images/ubuntu/xenial/armhf/default/20151126_03:49/ ubuntu;%s;%s;default;20151126_03:49;/images/ubuntu/CODENAME/ARCH/default/20151126_03:49/ -`, lsb.Codename, arch) +`, release.ReleaseInfo.Codename, arch) return s } diff -Nru snapd-2.0.5/classic/enabled_test.go snapd-2.0.8/classic/enabled_test.go --- snapd-2.0.5/classic/enabled_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/classic/enabled_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/testutil" ) type EnabledTestSuite struct { diff -Nru snapd-2.0.5/classic/helpers.go snapd-2.0.8/classic/helpers.go --- snapd-2.0.5/classic/helpers.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/classic/helpers.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ "path/filepath" "strings" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" ) // Enabled returns true if the classic mode is already enabled diff -Nru snapd-2.0.5/classic/helpers_test.go snapd-2.0.8/classic/helpers_test.go --- snapd-2.0.5/classic/helpers_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/classic/helpers_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,7 +25,7 @@ "io/ioutil" "path/filepath" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/testutil" ) type HelpersTestSuite struct { diff -Nru snapd-2.0.5/classic/run.go snapd-2.0.8/classic/run.go --- snapd-2.0.5/classic/run.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/classic/run.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ "path/filepath" "time" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/strutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/strutil" ) type bindMount struct { diff -Nru snapd-2.0.5/client/asserts.go snapd-2.0.8/client/asserts.go --- snapd-2.0.5/client/asserts.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/asserts.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,7 +27,7 @@ "net/url" "strconv" - "github.com/ubuntu-core/snappy/asserts" // for parsing + "github.com/snapcore/snapd/asserts" // for parsing ) // Ack tries to add an assertion to the system assertion diff -Nru snapd-2.0.5/client/asserts_test.go snapd-2.0.8/client/asserts_test.go --- snapd-2.0.5/client/asserts_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/asserts_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,7 +27,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" + "github.com/snapcore/snapd/asserts" ) func (cs *clientSuite) TestClientAssert(c *C) { diff -Nru snapd-2.0.5/client/change.go snapd-2.0.8/client/change.go --- snapd-2.0.5/client/change.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/change.go 2016-06-08 05:58:01.000000000 +0000 @@ -130,9 +130,21 @@ ChangesAll = ChangesReady | ChangesInProgress ) -func (client *Client) Changes(which ChangeSelector) ([]*Change, error) { +type ChangesOptions struct { + SnapName string // if empty, no filtering by name is done + Selector ChangeSelector +} + +func (client *Client) Changes(opts *ChangesOptions) ([]*Change, error) { query := url.Values{} - query.Set("select", which.String()) + if opts != nil { + if opts.Selector != 0 { + query.Set("select", opts.Selector.String()) + } + if opts.SnapName != "" { + query.Set("for", opts.SnapName) + } + } var chgds []changeAndData _, err := client.doSync("GET", "/v2/changes", query, nil, nil, &chgds) diff -Nru snapd-2.0.5/client/change_test.go snapd-2.0.8/client/change_test.go --- snapd-2.0.5/client/change_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/change_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/client" + "github.com/snapcore/snapd/client" "io/ioutil" "time" ) @@ -131,7 +131,13 @@ "tasks": [{"kind": "bar", "summary": "...", "status": "Do", "progress": {"done": 0, "total": 1}}] }]}` - for _, i := range []client.ChangeSelector{client.ChangesAll, client.ChangesReady, client.ChangesInProgress} { + for _, i := range []*client.ChangesOptions{ + {Selector: client.ChangesAll}, + {Selector: client.ChangesReady}, + {Selector: client.ChangesInProgress}, + {SnapName: "foo"}, + nil, + } { chg, err := cs.cli.Changes(i) c.Assert(err, check.IsNil) c.Check(chg, check.DeepEquals, []*client.Change{{ @@ -141,8 +147,17 @@ Status: "Do", Tasks: []*client.Task{{Kind: "bar", Summary: "...", Status: "Do", Progress: client.TaskProgress{Done: 0, Total: 1}}}, }}) - c.Check(cs.req.URL.RawQuery, check.Equals, "select="+i.String()) + if i == nil { + c.Check(cs.req.URL.RawQuery, check.Equals, "") + } else { + if i.Selector != 0 { + c.Check(cs.req.URL.RawQuery, check.Equals, "select="+i.Selector.String()) + } else { + c.Check(cs.req.URL.RawQuery, check.Equals, "for="+i.SnapName) + } + } } + } func (cs *clientSuite) TestClientChangesData(c *check.C) { @@ -155,7 +170,7 @@ "data": {"n": 42} }]}` - chgs, err := cs.cli.Changes(client.ChangesAll) + chgs, err := cs.cli.Changes(&client.ChangesOptions{Selector: client.ChangesAll}) c.Assert(err, check.IsNil) chg := chgs[0] diff -Nru snapd-2.0.5/client/client.go snapd-2.0.8/client/client.go --- snapd-2.0.5/client/client.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/client.go 2016-06-08 05:58:01.000000000 +0000 @@ -30,7 +30,7 @@ "os" "path" - "github.com/ubuntu-core/snappy/dirs" + "github.com/snapcore/snapd/dirs" ) func unixDialer(_, _ string) (net.Conn, error) { @@ -189,6 +189,20 @@ return rsp.Change, nil } +func (client *Client) ServerVersion() (string, error) { + sysInfo, err := client.SysInfo() + if err != nil { + return "unknown", err + } + + version := sysInfo.Version + if version == "" { + version = "unknown" + } + + return fmt.Sprintf("%s (series %s)", version, sysInfo.Series), nil +} + // A response produced by the REST API will usually fit in this // (exceptions are the icons/ endpoints obvs) type response struct { @@ -232,11 +246,8 @@ // SysInfo holds system information type SysInfo struct { - Flavor string `json:"flavor"` - Release string `json:"release"` - DefaultChannel string `json:"default-channel"` - APICompatibility string `json:"api-compat"` - Store string `json:"store,omitempty"` + Series string `json:"series,omitempty"` + Version string `json:"version,omitempty"` } func (rsp *response) err() error { diff -Nru snapd-2.0.5/client/client_test.go snapd-2.0.8/client/client_test.go --- snapd-2.0.5/client/client_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/client_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -33,8 +33,8 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/client" - "github.com/ubuntu-core/snappy/dirs" + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/dirs" ) // Hook up check.v1 into the "go test" runner @@ -78,23 +78,6 @@ client.New(&client.Config{BaseURL: ":"}) }, check.PanicMatches, `cannot parse server base URL: ":" \(parse :: missing protocol scheme\)`) } - -func (cs *clientSuite) TestNewCustomURL(c *check.C) { - f := func(w http.ResponseWriter, r *http.Request) { - c.Check(r.URL.Path, check.Equals, "/v2/system-info") - c.Check(r.URL.RawQuery, check.Equals, "") - fmt.Fprintln(w, `{"type":"sync", "result":{"store":"X"}}`) - } - srv := httptest.NewServer(http.HandlerFunc(f)) - defer srv.Close() - - cli := client.New(&client.Config{BaseURL: srv.URL}) - c.Assert(cli, check.Not(check.IsNil)) - si, err := cli.SysInfo() - c.Check(err, check.IsNil) - c.Check(si.Store, check.Equals, "X") -} - func (cs *clientSuite) TestClientDoReportsErrors(c *check.C) { cs.err = errors.New("ouchie") err := cs.cli.Do("GET", "/", nil, nil, nil) @@ -148,19 +131,13 @@ func (cs *clientSuite) TestClientSysInfo(c *check.C) { cs.rsp = `{"type": "sync", "result": - {"flavor": "f", - "release": "r", - "default-channel": "dc", - "api-compat": "42", - "store": "store"}}` + {"series": "16", + "version": "2"}}` sysInfo, err := cs.cli.SysInfo() c.Check(err, check.IsNil) c.Check(sysInfo, check.DeepEquals, &client.SysInfo{ - Flavor: "f", - Release: "r", - DefaultChannel: "dc", - APICompatibility: "42", - Store: "store", + Version: "2", + Series: "16", }) } @@ -175,7 +152,7 @@ c.Check(r.URL.Path, check.Equals, "/v2/system-info") c.Check(r.URL.RawQuery, check.Equals, "") - fmt.Fprintln(w, `{"type":"sync", "result":{"store":"X"}}`) + fmt.Fprintln(w, `{"type":"sync", "result":{"series":"42"}}`) } srv := &httptest.Server{ @@ -188,7 +165,7 @@ cli := client.New(nil) si, err := cli.SysInfo() c.Check(err, check.IsNil) - c.Check(si.Store, check.Equals, "X") + c.Check(si.Series, check.Equals, "42") } func (cs *clientSuite) TestClientReportsOpError(c *check.C) { diff -Nru snapd-2.0.5/client/interfaces_test.go snapd-2.0.8/client/interfaces_test.go --- snapd-2.0.5/client/interfaces_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/interfaces_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,7 +24,7 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/client" + "github.com/snapcore/snapd/client" ) func (cs *clientSuite) TestClientInterfacesCallsEndpoint(c *check.C) { diff -Nru snapd-2.0.5/client/login.go snapd-2.0.8/client/login.go --- snapd-2.0.5/client/login.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/login.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ "os" "path/filepath" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/osutil" ) // User holds logged in user information. diff -Nru snapd-2.0.5/client/login_test.go snapd-2.0.8/client/login_test.go --- snapd-2.0.5/client/login_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/login_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,8 +27,8 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/client" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/osutil" ) func (cs *clientSuite) TestClientLogin(c *check.C) { diff -Nru snapd-2.0.5/client/packages.go snapd-2.0.8/client/packages.go --- snapd-2.0.5/client/packages.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/packages.go 2016-06-08 05:58:01.000000000 +0000 @@ -39,7 +39,12 @@ Status string `json:"status"` Type string `json:"type"` Version string `json:"version"` - Revision int `json:"revision"` + Channel string `json:"channel"` + Revision Revision `json:"revision"` + Confinement string `json:"confinement"` + Private bool `json:"private"` + DevMode bool `json:"devmode"` + TryMode bool `json:"trymode"` Prices map[string]float64 `json:"prices"` } @@ -55,15 +60,26 @@ TypeKernel = "kernel" TypeGadget = "gadget" TypeOS = "os" + + StrictConfinement = "strict" + DevmodeConfinement = "devmode" ) type ResultInfo struct { SuggestedCurrency string `json:"suggested-currency"` } -// ListSnaps returns the list of all snaps installed on the system +// FindOptions supports exactly one of the following options: +// - Refresh: only return snaps that are refreshable +// - Query: only return snaps that match the query string +type FindOptions struct { + Refresh bool + Query string +} + +// List returns the list of all snaps installed on the system // with names in the given list; if the list is empty, all snaps. -func (client *Client) ListSnaps(names []string) ([]*Snap, error) { +func (client *Client) List(names []string) ([]*Snap, error) { snaps, _, err := client.snapsFromPath("/v2/snaps", nil) if err != nil { return nil, err @@ -88,12 +104,18 @@ return result, nil } -// FindSnaps returns a list of snaps available for install from the +// Find returns a list of snaps available for install from the // store for this system and that match the query -func (client *Client) FindSnaps(query string) ([]*Snap, *ResultInfo, error) { - q := url.Values{} +func (client *Client) Find(opts *FindOptions) ([]*Snap, *ResultInfo, error) { + if opts == nil { + opts = &FindOptions{} + } - q.Set("q", query) + q := url.Values{} + q.Set("q", opts.Query) + if opts.Refresh { + q.Set("select", "refresh") + } return client.snapsFromPath("/v2/find", q) } diff -Nru snapd-2.0.5/client/packages_test.go snapd-2.0.8/client/packages_test.go --- snapd-2.0.5/client/packages_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/packages_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -21,17 +21,30 @@ import ( "fmt" + "net/url" "time" "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/client" + "github.com/snapcore/snapd/client" ) func (cs *clientSuite) TestClientSnapsCallsEndpoint(c *check.C) { - _, _ = cs.cli.ListSnaps(nil) + _, _ = cs.cli.List(nil) c.Check(cs.req.Method, check.Equals, "GET") c.Check(cs.req.URL.Path, check.Equals, "/v2/snaps") + c.Check(cs.req.URL.Query(), check.DeepEquals, url.Values{}) +} + +func (cs *clientSuite) TestClientFindRefreshSetsQuery(c *check.C) { + _, _, _ = cs.cli.Find(&client.FindOptions{ + Refresh: true, + }) + c.Check(cs.req.Method, check.Equals, "GET") + c.Check(cs.req.URL.Path, check.Equals, "/v2/find") + c.Check(cs.req.URL.Query(), check.DeepEquals, url.Values{ + "q": []string{""}, "select": []string{"refresh"}, + }) } func (cs *clientSuite) TestClientSnapsInvalidSnapsJSON(c *check.C) { @@ -39,7 +52,7 @@ "type": "sync", "result": "not a list of snaps" }` - _, err := cs.cli.ListSnaps(nil) + _, err := cs.cli.List(nil) c.Check(err, check.ErrorMatches, `.*cannot unmarshal.*`) } @@ -58,11 +71,13 @@ "resource": "/v2/snaps/hello-world.canonical", "status": "available", "type": "app", - "version": "1.0.18" + "version": "1.0.18", + "confinement": "strict", + "private": true }], "suggested-currency": "GBP" }` - applications, err := cs.cli.ListSnaps(nil) + applications, err := cs.cli.List(nil) c.Check(err, check.IsNil) c.Check(applications, check.DeepEquals, []*client.Snap{{ ID: "funky-snap-id", @@ -76,14 +91,17 @@ Status: client.StatusAvailable, Type: client.TypeApp, Version: "1.0.18", + Confinement: client.StrictConfinement, + Private: true, + DevMode: false, }}) - otherApps, err := cs.cli.ListSnaps([]string{"foo"}) + otherApps, err := cs.cli.List([]string{"foo"}) c.Check(err, check.IsNil) c.Check(otherApps, check.HasLen, 0) } func (cs *clientSuite) TestClientFilterSnaps(c *check.C) { - _, _, _ = cs.cli.FindSnaps("foo") + _, _, _ = cs.cli.Find(&client.FindOptions{Query: "foo"}) c.Check(cs.req.URL.Path, check.Equals, "/v2/find") c.Check(cs.req.URL.RawQuery, check.Equals, "q=foo") } @@ -108,7 +126,11 @@ "resource": "/v2/snaps/chatroom.ogra", "status": "active", "type": "app", - "version": "0.1-8" + "version": "0.1-8", + "confinement": "strict", + "private": true, + "devmode": true, + "trymode": true } }` pkg, _, err := cs.cli.Snap(pkgName) @@ -128,5 +150,9 @@ Status: client.StatusActive, Type: client.TypeApp, Version: "0.1-8", + Confinement: client.StrictConfinement, + Private: true, + DevMode: true, + TryMode: true, }) } diff -Nru snapd-2.0.5/client/revision.go snapd-2.0.8/client/revision.go --- snapd-2.0.5/client/revision.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/client/revision.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,111 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-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 client + +import ( + "fmt" + "strconv" +) + +// Keep this in sync between snap and client packages. + +type Revision struct { + N int +} + +func (r Revision) String() string { + if r.N == 0 { + return "unset" + } + if r.N < 0 { + return fmt.Sprintf("x%d", -r.N) + } + return strconv.Itoa(int(r.N)) +} + +func (r Revision) Unset() bool { + return r.N == 0 +} + +func (r Revision) Local() bool { + return r.N < 0 +} + +func (r Revision) Store() bool { + return r.N > 0 +} + +func (r Revision) MarshalJSON() ([]byte, error) { + return []byte(`"` + r.String() + `"`), nil +} + +func (r *Revision) UnmarshalJSON(data []byte) error { + if len(data) > 0 && data[0] == '"' && data[len(data)-1] == '"' { + parsed, err := ParseRevision(string(data[1 : len(data)-1])) + if err == nil { + *r = parsed + return nil + } + } else { + n, err := strconv.ParseInt(string(data), 10, 64) + if err == nil { + r.N = int(n) + return nil + } + } + return fmt.Errorf("invalid snap revision: %q", data) +} + +// ParseRevisions returns the representation in r as a revision. +// See R for a function more suitable for hardcoded revisions. +func ParseRevision(s string) (Revision, error) { + if s == "unset" { + return Revision{}, nil + } + if s != "" && s[0] == 'x' { + i, err := strconv.Atoi(s[1:]) + if err == nil && i > 0 { + return Revision{-i}, nil + } + } + i, err := strconv.Atoi(s) + if err == nil && i > 0 { + return Revision{i}, nil + } + return Revision{}, fmt.Errorf("invalid snap revision: %#v", s) +} + +// R returns a Revision given an int or a string. +// Providing an invalid revision type or value causes a runtime panic. +// See ParseRevision for a polite function that does not panic. +func R(r interface{}) Revision { + switch r := r.(type) { + case string: + revision, err := ParseRevision(r) + if err != nil { + panic(err) + } + return revision + case int: + return Revision{r} + default: + panic(fmt.Errorf("cannot use %v (%T) as a snap revision", r, r)) + } +} diff -Nru snapd-2.0.5/client/revision_test.go snapd-2.0.8/client/revision_test.go --- snapd-2.0.5/client/revision_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/client/revision_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,171 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-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 client_test + +import ( + "encoding/json" + "strconv" + + . "gopkg.in/check.v1" + + . "github.com/snapcore/snapd/snap" +) + +// Keep this in sync between snap and client packages. + +type revisionSuite struct{} + +var _ = Suite(&revisionSuite{}) + +func (s revisionSuite) TestString(c *C) { + c.Assert(Revision{0}.String(), Equals, "unset") + c.Assert(Revision{10}.String(), Equals, "10") + c.Assert(Revision{-9}.String(), Equals, "x9") +} + +func (s revisionSuite) TestUnset(c *C) { + c.Assert(Revision{0}.Unset(), Equals, true) + c.Assert(Revision{10}.Unset(), Equals, false) + c.Assert(Revision{-9}.Unset(), Equals, false) +} + +func (s revisionSuite) TestLocal(c *C) { + c.Assert(Revision{0}.Local(), Equals, false) + c.Assert(Revision{10}.Local(), Equals, false) + c.Assert(Revision{-9}.Local(), Equals, true) +} + +func (s revisionSuite) TestStore(c *C) { + c.Assert(Revision{0}.Store(), Equals, false) + c.Assert(Revision{10}.Store(), Equals, true) + c.Assert(Revision{-9}.Store(), Equals, false) +} + +func (s revisionSuite) TestJSON(c *C) { + for _, n := range []int{0, 10, -9} { + r := Revision{n} + data, err := json.Marshal(Revision{n}) + c.Assert(err, IsNil) + c.Assert(string(data), Equals, `"`+r.String()+`"`) + + var got Revision + err = json.Unmarshal(data, &got) + c.Assert(err, IsNil) + c.Assert(got, Equals, r) + + got = Revision{} + err = json.Unmarshal([]byte(strconv.Itoa(r.N)), &got) + c.Assert(err, IsNil) + c.Assert(got, Equals, r) + } +} + +func (s revisionSuite) ParseRevision(c *C) { + type testItem struct { + s string + n int + e string + } + + var tests = []testItem{{ + s: "unset", + n: 0, + }, { + s: "x1", + n: -1, + }, { + s: "1", + n: 1, + }, { + s: "x-1", + e: `invalid snap revision: "x-1"`, + }, { + s: "x0", + e: `invalid snap revision: "x0"`, + }, { + s: "-1", + e: `invalid snap revision: "-1"`, + }, { + s: "0", + e: `invalid snap revision: "0"`, + }} + + for _, test := range tests { + r, err := ParseRevision(test.s) + if test.e != "" { + c.Assert(err.Error(), Equals, test.e) + continue + } + c.Assert(r, Equals, Revision{test.n}) + } +} + +func (s *revisionSuite) TestR(c *C) { + type testItem struct { + v interface{} + n int + e string + } + + var tests = []testItem{{ + v: 0, + n: 0, + }, { + v: -1, + n: -1, + }, { + v: 1, + n: 1, + }, { + v: "unset", + n: 0, + }, { + v: "x1", + n: -1, + }, { + v: "1", + n: 1, + }, { + v: "x-1", + e: `invalid snap revision: "x-1"`, + }, { + v: "x0", + e: `invalid snap revision: "x0"`, + }, { + v: "-1", + e: `invalid snap revision: "-1"`, + }, { + v: "0", + e: `invalid snap revision: "0"`, + }, { + v: int64(1), + e: `cannot use 1 \(int64\) as a snap revision`, + }} + + for _, test := range tests { + if test.e != "" { + f := func() { R(test.v) } + c.Assert(f, PanicMatches, test.e) + continue + } + + c.Assert(R(test.v), Equals, Revision{test.n}) + } +} diff -Nru snapd-2.0.5/client/snap_op.go snapd-2.0.8/client/snap_op.go --- snapd-2.0.5/client/snap_op.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/snap_op.go 2016-06-08 05:58:01.000000000 +0000 @@ -98,6 +98,26 @@ return client.doAsync("POST", "/v2/snaps", nil, headers, pr) } +// Try +func (client *Client) Try(path string, options *SnapOptions) (changeID string, err error) { + if options == nil { + options = &SnapOptions{} + } + + buf := bytes.NewBuffer(nil) + mw := multipart.NewWriter(buf) + mw.WriteField("action", "try") + mw.WriteField("snap-path", path) + mw.WriteField("devmode", strconv.FormatBool(options.DevMode)) + mw.Close() + + headers := map[string]string{ + "Content-Type": mw.FormDataContentType(), + } + + return client.doAsync("POST", "/v2/snaps", nil, headers, buf) +} + func sendSnapFile(snapPath string, snapFile *os.File, pw *io.PipeWriter, mw *multipart.Writer, action *actionData) { defer snapFile.Close() diff -Nru snapd-2.0.5/client/snap_op_test.go snapd-2.0.8/client/snap_op_test.go --- snapd-2.0.5/client/snap_op_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/client/snap_op_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,12 +23,16 @@ "encoding/json" "errors" "fmt" + "io" "io/ioutil" + "mime" + "mime/multipart" "path/filepath" + "strconv" "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/client" + "github.com/snapcore/snapd/client" ) var chanName = "achan" @@ -140,3 +144,51 @@ c.Assert(cs.req.Header.Get("Content-Type"), check.Matches, "multipart/form-data; boundary=.*") c.Check(id, check.Equals, "66b3") } + +func formToMap(c *check.C, mr *multipart.Reader) map[string]string { + formData := map[string]string{} + for { + p, err := mr.NextPart() + if err == io.EOF { + break + } + c.Assert(err, check.IsNil) + slurp, err := ioutil.ReadAll(p) + c.Assert(err, check.IsNil) + formData[p.FormName()] = string(slurp) + } + return formData +} + +func (cs *clientSuite) TestClientOpTryMode(c *check.C) { + cs.rsp = `{ + "change": "66b3", + "status-code": 202, + "type": "async" + }` + snapdir := filepath.Join(c.MkDir(), "/some/path") + + for _, opts := range []*client.SnapOptions{ + {DevMode: false}, + {DevMode: true}, + } { + id, err := cs.cli.Try(snapdir, opts) + c.Assert(err, check.IsNil) + + // ensure we send the right form-data + _, params, err := mime.ParseMediaType(cs.req.Header.Get("Content-Type")) + c.Assert(err, check.IsNil) + mr := multipart.NewReader(cs.req.Body, params["boundary"]) + formData := formToMap(c, mr) + c.Check(formData, check.DeepEquals, map[string]string{ + "action": "try", + "snap-path": snapdir, + "devmode": strconv.FormatBool(opts.DevMode), + }) + + 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") + } +} diff -Nru snapd-2.0.5/cmd/cmd.go snapd-2.0.8/cmd/cmd.go --- snapd-2.0.5/cmd/cmd.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/cmd/cmd.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,82 @@ +// -*- 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 ( + "os" + "path/filepath" + "strconv" + "syscall" + + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/release" +) + +// 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 key = "SNAP_REEXEC" + +// newCore is the place to look for the core snap; everything in this +// location will be new enough to re-exec into. +const 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. +const oldCore = "/snap/ubuntu-core/current" + +// old ubuntu-core snaps older than this aren't suitable targets for re-execage +const minOldRevno = 126 + +// ExecInCoreSnap makes sure you're executing the binary that ships in +// the core snap. +func ExecInCoreSnap() { + if !release.OnClassic { + // you're already the real deal, natch + return + } + + if os.Getenv(key) == "0" { + return + } + + exe, err := os.Readlink("/proc/self/exe") + if err != nil { + return + } + + full := filepath.Join(newCore, exe) + if !osutil.FileExists(full) { + if rev, err := os.Readlink(oldCore); err != nil { + return + } else if revno, err := strconv.Atoi(rev); err != nil || revno < minOldRevno { + return + } + + full = filepath.Join(oldCore, exe) + if !osutil.FileExists(full) { + return + } + } + + env := append(os.Environ(), key+"=0") + panic(syscall.Exec(full, os.Args, env)) +} diff -Nru snapd-2.0.5/cmd/snap/cmd_abort.go snapd-2.0.8/cmd/snap/cmd_abort.go --- snapd-2.0.5/cmd/snap/cmd_abort.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_abort.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( "github.com/jessevdk/go-flags" - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/i18n" ) type cmdAbort struct { diff -Nru snapd-2.0.5/cmd/snap/cmd_ack.go snapd-2.0.8/cmd/snap/cmd_ack.go --- snapd-2.0.5/cmd/snap/cmd_ack.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_ack.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( "io/ioutil" - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/i18n" "github.com/jessevdk/go-flags" ) diff -Nru snapd-2.0.5/cmd/snap/cmd_booted.go snapd-2.0.8/cmd/snap/cmd_booted.go --- snapd-2.0.5/cmd/snap/cmd_booted.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_booted.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,8 +24,8 @@ "github.com/jessevdk/go-flags" - "github.com/ubuntu-core/snappy/partition" - "github.com/ubuntu-core/snappy/snappy" + "github.com/snapcore/snapd/partition" + "github.com/snapcore/snapd/snappy" ) type cmdBooted struct{} diff -Nru snapd-2.0.5/cmd/snap/cmd_changes.go snapd-2.0.8/cmd/snap/cmd_changes.go --- snapd-2.0.5/cmd/snap/cmd_changes.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_changes.go 2016-06-08 05:58:01.000000000 +0000 @@ -21,27 +21,39 @@ import ( "fmt" + "os" + "regexp" "sort" + "time" - "github.com/ubuntu-core/snappy/client" - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/i18n" "github.com/jessevdk/go-flags" - "time" ) var shortChangesHelp = i18n.G("List system changes") +var shortChangeHelp = i18n.G("List a change's tasks") var longChangesHelp = i18n.G(` The changes command displays a summary of the recent system changes performed.`) +var longChangeHelp = i18n.G(` +The change command displays a summary of tasks associated to an individual change.`) type cmdChanges struct { Positional struct { - Id string `positional-arg-name:""` + Snap string `positional-arg-name:""` + } `positional-args:"yes"` +} + +type cmdChange struct { + Positional struct { + Id string `positional-arg-name:"" required:"yes"` } `positional-args:"yes"` } func init() { addCommand("changes", shortChangesHelp, longChangesHelp, func() flags.Commander { return &cmdChanges{} }) + addCommand("change", shortChangeHelp, longChangeHelp, func() flags.Commander { return &cmdChange{} }) } type changesByTime []*client.Change @@ -50,19 +62,26 @@ func (s changesByTime) Less(i, j int) bool { return s[i].SpawnTime.Before(s[j].SpawnTime) } func (s changesByTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +var allDigits = regexp.MustCompile(`^[0-9]+$`).MatchString + func (c *cmdChanges) Execute([]string) error { - if c.Positional.Id == "everything" { + if allDigits(c.Positional.Snap) { + return fmt.Errorf(`%s changes command expects a snap name, try: %[1]s change %s`, os.Args[0], c.Positional.Snap) + } + + if c.Positional.Snap == "everything" { fmt.Fprintln(Stdout, "Yes, yes it does.") return nil } - if c.Positional.Id != "" { - return c.showChange(c.Positional.Id) + opts := client.ChangesOptions{ + SnapName: c.Positional.Snap, + Selector: client.ChangesAll, } cli := Client() - changes, err := cli.Changes(client.ChangesAll) + changes, err := cli.Changes(&opts) if err != nil { return err } @@ -91,9 +110,9 @@ return nil } -func (c *cmdChanges) showChange(id string) error { +func (c *cmdChange) Execute([]string) error { cli := Client() - chg, err := cli.Change(id) + chg, err := cli.Change(c.Positional.Id) if err != nil { return err } diff -Nru snapd-2.0.5/cmd/snap/cmd_classic.go snapd-2.0.8/cmd/snap/cmd_classic.go --- snapd-2.0.5/cmd/snap/cmd_classic.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_classic.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,9 +24,9 @@ //"github.com/jessevdk/go-flags" - "github.com/ubuntu-core/snappy/classic" - "github.com/ubuntu-core/snappy/i18n" - "github.com/ubuntu-core/snappy/progress" + "github.com/snapcore/snapd/classic" + "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/progress" ) // FIXME: Implement feature via "snap install classic" diff -Nru snapd-2.0.5/cmd/snap/cmd_connect.go snapd-2.0.8/cmd/snap/cmd_connect.go --- snapd-2.0.5/cmd/snap/cmd_connect.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_connect.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package main import ( - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/i18n" "github.com/jessevdk/go-flags" ) diff -Nru snapd-2.0.5/cmd/snap/cmd_connect_test.go snapd-2.0.8/cmd/snap/cmd_connect_test.go --- snapd-2.0.5/cmd/snap/cmd_connect_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_connect_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ . "gopkg.in/check.v1" - . "github.com/ubuntu-core/snappy/cmd/snap" + . "github.com/snapcore/snapd/cmd/snap" ) func (s *SnapSuite) TestConnectHelp(c *C) { @@ -53,6 +53,9 @@ first of these snaps that has a matching plug name is used and the command proceeds as above. +Application Options: + --version print the version and exit + Help Options: -h, --help Show this help message ` diff -Nru snapd-2.0.5/cmd/snap/cmd_disconnect.go snapd-2.0.8/cmd/snap/cmd_disconnect.go --- snapd-2.0.5/cmd/snap/cmd_disconnect.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_disconnect.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package main import ( - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/i18n" "github.com/jessevdk/go-flags" ) diff -Nru snapd-2.0.5/cmd/snap/cmd_disconnect_test.go snapd-2.0.8/cmd/snap/cmd_disconnect_test.go --- snapd-2.0.5/cmd/snap/cmd_disconnect_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_disconnect_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ . "gopkg.in/check.v1" - . "github.com/ubuntu-core/snappy/cmd/snap" + . "github.com/snapcore/snapd/cmd/snap" ) func (s *SnapSuite) TestDisconnectHelp(c *C) { @@ -48,6 +48,9 @@ Disconnects all plugs from the provided snap. +Application Options: + --version print the version and exit + Help Options: -h, --help Show this help message ` diff -Nru snapd-2.0.5/cmd/snap/cmd_experimental.go snapd-2.0.8/cmd/snap/cmd_experimental.go --- snapd-2.0.5/cmd/snap/cmd_experimental.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_experimental.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package main import ( - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/i18n" ) type cmdExperimental struct{} diff -Nru snapd-2.0.5/cmd/snap/cmd_find.go snapd-2.0.8/cmd/snap/cmd_find.go --- snapd-2.0.5/cmd/snap/cmd_find.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_find.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,10 +22,9 @@ import ( "fmt" "sort" - "text/tabwriter" - "github.com/ubuntu-core/snappy/client" - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/i18n" "github.com/jessevdk/go-flags" ) @@ -38,7 +37,7 @@ func getPrice(prices map[string]float64, currency string) string { // If there are no prices, then the snap is free if len(prices) == 0 { - return "-" + return "" } // Look up the price by currency code @@ -76,42 +75,21 @@ }) } -func hasPrices(snaps []*client.Snap) bool { - for _, snap := range snaps { - if len(snap.Prices) > 0 { - return true - } - } - return false -} - -func printWithPrices(w *tabwriter.Writer, snaps []*client.Snap, suggestedCurrency string) { - fmt.Fprintln(w, i18n.G("Name\tVersion\tPrice\tSummary")) - - for _, snap := range snaps { - price := getPrice(snap.Prices, suggestedCurrency) - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", snap.Name, snap.Version, price, snap.Summary) - - } -} - -func printNoPrices(w *tabwriter.Writer, snaps []*client.Snap) { - fmt.Fprintln(w, i18n.G("Name\tVersion\tSummary")) - - for _, snap := range snaps { - fmt.Fprintf(w, "%s\t%s\t%s\n", snap.Name, snap.Version, snap.Summary) - } +func (x *cmdFind) Execute([]string) error { + return findSnaps(&client.FindOptions{ + Query: x.Positional.Query, + }) } -func (x *cmdFind) Execute([]string) error { +func findSnaps(opts *client.FindOptions) error { cli := Client() - snaps, resInfo, err := cli.FindSnaps(x.Positional.Query) + snaps, resInfo, err := cli.Find(opts) if err != nil { return err } if len(snaps) == 0 { - return fmt.Errorf("no snaps found for %q", x.Positional.Query) + return fmt.Errorf("no snaps found for %q", opts.Query) } sort.Sort(snapsByName(snaps)) @@ -119,10 +97,16 @@ w := tabWriter() defer w.Flush() - if hasPrices(snaps) { - printWithPrices(w, snaps, resInfo.SuggestedCurrency) - } else { - printNoPrices(w, snaps) + fmt.Fprintln(w, i18n.G("Name\tVersion\tDeveloper\tNotes\tSummary")) + + for _, snap := range snaps { + notes := &Notes{ + Private: snap.Private, + Confinement: snap.Confinement, + Price: getPrice(snap.Prices, resInfo.SuggestedCurrency), + } + // TODO: get snap.Publisher, so we can only show snap.Developer if it's different + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", snap.Name, snap.Version, snap.Developer, notes, snap.Summary) } return nil diff -Nru snapd-2.0.5/cmd/snap/cmd_first_boot.go snapd-2.0.8/cmd/snap/cmd_first_boot.go --- snapd-2.0.5/cmd/snap/cmd_first_boot.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_first_boot.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( "github.com/jessevdk/go-flags" - "github.com/ubuntu-core/snappy/overlord" + "github.com/snapcore/snapd/overlord" ) type cmdInternalFirstBoot struct{} diff -Nru snapd-2.0.5/cmd/snap/cmd_help.go snapd-2.0.8/cmd/snap/cmd_help.go --- snapd-2.0.5/cmd/snap/cmd_help.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_help.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( "os" - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/i18n" "github.com/jessevdk/go-flags" ) diff -Nru snapd-2.0.5/cmd/snap/cmd_help_test.go snapd-2.0.8/cmd/snap/cmd_help_test.go --- snapd-2.0.5/cmd/snap/cmd_help_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_help_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,7 +25,7 @@ "gopkg.in/check.v1" - snap "github.com/ubuntu-core/snappy/cmd/snap" + snap "github.com/snapcore/snapd/cmd/snap" ) func (s *SnapSuite) TestHelpPrintsHelp(c *check.C) { @@ -48,6 +48,9 @@ platform. +Application Options: + +--version +print the version and exit + Help Options: +-h, --help +Show this help message diff -Nru snapd-2.0.5/cmd/snap/cmd_interfaces.go snapd-2.0.8/cmd/snap/cmd_interfaces.go --- snapd-2.0.5/cmd/snap/cmd_interfaces.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_interfaces.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( "fmt" - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/i18n" "github.com/jessevdk/go-flags" ) diff -Nru snapd-2.0.5/cmd/snap/cmd_interfaces_test.go snapd-2.0.8/cmd/snap/cmd_interfaces_test.go --- snapd-2.0.5/cmd/snap/cmd_interfaces_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_interfaces_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/client" - . "github.com/ubuntu-core/snappy/cmd/snap" + "github.com/snapcore/snapd/client" + . "github.com/snapcore/snapd/cmd/snap" ) func (s *SnapSuite) TestInterfacesHelp(c *C) { @@ -51,6 +51,9 @@ Filters the complete output so only plugs and/or slots matching the provided details are listed. +Application Options: + --version print the version and exit + Help Options: -h, --help Show this help message diff -Nru snapd-2.0.5/cmd/snap/cmd_known.go snapd-2.0.8/cmd/snap/cmd_known.go --- snapd-2.0.5/cmd/snap/cmd_known.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_known.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,8 +23,8 @@ "fmt" "strings" - "github.com/ubuntu-core/snappy/asserts" - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/i18n" "github.com/jessevdk/go-flags" ) diff -Nru snapd-2.0.5/cmd/snap/cmd_list.go snapd-2.0.8/cmd/snap/cmd_list.go --- snapd-2.0.5/cmd/snap/cmd_list.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_list.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,11 +22,10 @@ import ( "fmt" "sort" - "strconv" "text/tabwriter" - "github.com/ubuntu-core/snappy/client" - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/i18n" "github.com/jessevdk/go-flags" ) @@ -55,22 +54,29 @@ return listSnaps(x.Positional.Snaps) } -func listSnaps(args []string) error { +func listSnaps(names []string) error { cli := Client() - snaps, err := cli.ListSnaps(args) + snaps, err := cli.List(names) if err != nil { return err + } else if len(snaps) == 0 { + fmt.Fprintln(Stderr, i18n.G("No snaps are installed yet. Try 'snap install hello-world'.")) + return nil } - sort.Sort(snapsByName(snaps)) w := tabWriter() defer w.Flush() - fmt.Fprintln(w, i18n.G("Name\tVersion\tRev\tDeveloper")) + fmt.Fprintln(w, i18n.G("Name\tVersion\tRev\tDeveloper\tNotes")) for _, snap := range snaps { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", snap.Name, snap.Version, strconv.Itoa(snap.Revision), snap.Developer) + notes := &Notes{ + Private: snap.Private, + DevMode: snap.DevMode, + TryMode: snap.TryMode, + } + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", snap.Name, snap.Version, snap.Revision, snap.Developer, notes) } return nil diff -Nru snapd-2.0.5/cmd/snap/cmd_list_test.go snapd-2.0.8/cmd/snap/cmd_list_test.go --- snapd-2.0.5/cmd/snap/cmd_list_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_list_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ "gopkg.in/check.v1" - snap "github.com/ubuntu-core/snappy/cmd/snap" + snap "github.com/snapcore/snapd/cmd/snap" ) func (s *SnapSuite) TestList(c *check.C) { @@ -46,12 +46,33 @@ 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 +Developer -foo +4.2 +17 +bar + c.Check(s.Stdout(), check.Matches, `Name +Version +Rev +Developer +Notes +foo +4.2 +17 +bar +- `) c.Check(s.Stderr(), check.Equals, "") } +func (s *SnapSuite) TestListEmpty(c *check.C) { + n := 0 + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + switch n { + case 0: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/snaps") + fmt.Fprintln(w, `{"type": "sync", "result": []}`) + default: + c.Fatalf("expected to get 1 requests, now on %d", n+1) + } + + n++ + }) + rest, err := snap.Parser().ParseArgs([]string{"list"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + c.Check(s.Stdout(), check.Equals, "") + c.Check(s.Stderr(), check.Matches, "No snaps are installed yet. Try 'snap install hello-world'.\n") +} + func (s *SnapSuite) TestListWithQuery(c *check.C) { n := 0 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { @@ -70,10 +91,33 @@ 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 +Developer -foo +4.2 +17 +bar + c.Check(s.Stdout(), check.Matches, `Name +Version +Rev +Developer +Notes +foo +4.2 +17 +bar +- `) c.Check(s.Stderr(), check.Equals, "") // ensure that the fake server api was actually hit c.Check(n, check.Equals, 1) } + +func (s *SnapSuite) TestListWithNotes(c *check.C) { + n := 0 + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + switch n { + case 0: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/snaps") + fmt.Fprintln(w, `{"type": "sync", "result": [{"name": "foo", "status": "active", "version": "4.2", "developer": "bar", "revision":17, "trymode": true}]}`) + default: + c.Fatalf("expected to get 1 requests, now on %d", n+1) + } + + n++ + }) + 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 +Developer +Notes +foo +4.2 +17 +bar +try +`) + c.Check(s.Stderr(), check.Equals, "") +} diff -Nru snapd-2.0.5/cmd/snap/cmd_login.go snapd-2.0.8/cmd/snap/cmd_login.go --- snapd-2.0.5/cmd/snap/cmd_login.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_login.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ "github.com/jessevdk/go-flags" "golang.org/x/crypto/ssh/terminal" - "github.com/ubuntu-core/snappy/client" - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/i18n" ) type cmdLogin struct { diff -Nru snapd-2.0.5/cmd/snap/cmd_logout.go snapd-2.0.8/cmd/snap/cmd_logout.go --- snapd-2.0.5/cmd/snap/cmd_logout.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_logout.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( "github.com/jessevdk/go-flags" - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/i18n" ) type cmdLogout struct{} diff -Nru snapd-2.0.5/cmd/snap/cmd_shell.go snapd-2.0.8/cmd/snap/cmd_shell.go --- snapd-2.0.5/cmd/snap/cmd_shell.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_shell.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ //"github.com/jessevdk/go-flags" - "github.com/ubuntu-core/snappy/classic" - "github.com/ubuntu-core/snappy/i18n" + "github.com/snapcore/snapd/classic" + "github.com/snapcore/snapd/i18n" ) type cmdShell struct { diff -Nru snapd-2.0.5/cmd/snap/cmd_snap_op.go snapd-2.0.8/cmd/snap/cmd_snap_op.go --- snapd-2.0.5/cmd/snap/cmd_snap_op.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_snap_op.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,12 +22,13 @@ import ( "errors" "fmt" + "path/filepath" "strings" "time" - "github.com/ubuntu-core/snappy/client" - "github.com/ubuntu-core/snappy/i18n" - "github.com/ubuntu-core/snappy/progress" + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/progress" "github.com/jessevdk/go-flags" ) @@ -97,6 +98,7 @@ 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("Try an unpacked snap in the system") ) var longInstallHelp = i18n.G(` @@ -114,6 +116,13 @@ The refresh command refreshes (updates) the named snap. `) +var longTryHelp = i18n.G(` +The try command installs an unpacked snap into the system for testing purposes. +The unpacked snap content continues to be used even after installation, so +non-metadata changes there go live instantly. Metadata changes such as those +performed in snap.yaml will require reinstallation to go live. +`) + type cmdRemove struct { Positional struct { Snap string `positional-arg-name:""` @@ -180,17 +189,37 @@ } type cmdRefresh struct { + List bool `long:"list" description:"show available snaps for refresh"` Channel string `long:"channel" description:"Refresh to the latest on this channel, and track this channel henceforth"` Positional struct { Snap string `positional-arg-name:""` - } `positional-args:"yes" required:"yes"` + } `positional-args:"yes"` } -func (x *cmdRefresh) Execute([]string) error { +func refreshAll() error { + // FIXME: move this to snapd instead and have a new refresh-all endpoint cli := Client() - name := x.Positional.Snap - opts := &client.SnapOptions{Channel: x.Channel} - changeID, err := cli.Refresh(name, opts) + updates, _, err := cli.Find(&client.FindOptions{Refresh: true}) + if err != nil { + return fmt.Errorf("cannot list updates: %s", err) + } + + for _, update := range updates { + changeID, err := cli.Refresh(update.Name, &client.SnapOptions{Channel: update.Channel}) + if err != nil { + return err + } + if _, err := wait(cli, changeID); err != nil { + return err + } + } + + return listSnaps(nil) +} + +func refreshOne(name, channel string) error { + cli := Client() + changeID, err := cli.Refresh(name, &client.SnapOptions{Channel: channel}) if err != nil { return err } @@ -198,6 +227,58 @@ if _, err := wait(cli, changeID); err != nil { return err } + + return listSnaps([]string{name}) +} + +func (x *cmdRefresh) Execute([]string) error { + if x.List { + return findSnaps(&client.FindOptions{ + Refresh: true, + }) + } + if x.Positional.Snap == "" { + return refreshAll() + } + return refreshOne(x.Positional.Snap, x.Channel) +} + +type cmdTry struct { + DevMode bool `long:"devmode" description:"Install in development mode and disable confinement"` + Positional struct { + SnapDir string `positional-arg-name:""` + } `positional-args:"yes" required:"yes"` +} + +func (x *cmdTry) Execute([]string) error { + cli := Client() + name := x.Positional.SnapDir + opts := &client.SnapOptions{ + DevMode: x.DevMode, + } + + path, err := filepath.Abs(name) + if err != nil { + return fmt.Errorf("cannot get full path for %q: %s", name, err) + } + + changeID, err := cli.Try(path, opts) + if err != nil { + return err + } + + chg, err := wait(cli, changeID) + if err != nil { + return err + } + + // extract the snap name + var snapName string + if err := chg.Get("snap-name", &snapName); err != nil { + return fmt.Errorf("cannot extract the snap-name from local file %q: %s", name, err) + } + name = snapName + return listSnaps([]string{name}) } @@ -205,4 +286,5 @@ addCommand("remove", shortRemoveHelp, longRemoveHelp, func() flags.Commander { return &cmdRemove{} }) addCommand("install", shortInstallHelp, longInstallHelp, func() flags.Commander { return &cmdInstall{} }) addCommand("refresh", shortRefreshHelp, longRefreshHelp, func() flags.Commander { return &cmdRefresh{} }) + addCommand("try", shortTryHelp, longTryHelp, func() flags.Commander { return &cmdTry{} }) } diff -Nru snapd-2.0.5/cmd/snap/cmd_snap_op_test.go snapd-2.0.8/cmd/snap/cmd_snap_op_test.go --- snapd-2.0.5/cmd/snap/cmd_snap_op_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/cmd_snap_op_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,10 +25,12 @@ "io/ioutil" "net/http" "path/filepath" + "regexp" + "strconv" "gopkg.in/check.v1" - snap "github.com/ubuntu-core/snappy/cmd/snap" + snap "github.com/snapcore/snapd/cmd/snap" ) type snapOpTestServer struct { @@ -39,6 +41,8 @@ total int } +var _ = check.Suite(&SnapOpSuite{}) + func (t *snapOpTestServer) handle(w http.ResponseWriter, r *http.Request) { switch t.n { case 0: @@ -53,13 +57,9 @@ case 2: t.c.Check(r.Method, check.Equals, "GET") t.c.Check(r.URL.Path, check.Equals, "/v2/changes/42") - fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done"}}`) + 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/changes/42") - fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done"}}`) - case 4: - t.c.Check(r.Method, check.Equals, "GET") t.c.Check(r.URL.Path, check.Equals, "/v2/snaps") fmt.Fprintln(w, `{"type": "sync", "result": [{"name": "foo", "status": "active", "version": "1.0", "developer": "bar", "revision":42}]}`) default: @@ -75,25 +75,27 @@ srv snapOpTestServer } -func (s *SnapOpSuite) SetupTest(c *check.C) { +func (s *SnapOpSuite) SetUpTest(c *check.C) { + s.SnapSuite.SetUpTest(c) + s.srv = snapOpTestServer{ c: c, - total: 5, + total: 4, } } func (s *SnapOpSuite) TestInstall(c *check.C) { s.srv.checker = func(r *http.Request) { - c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo.bar") + c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ "action": "install", - "name": "foo.bar", + "name": "foo", "channel": "chan", }) } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "chan", "foo.bar"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "chan", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `(?sm).*foo\s+1.0\s+42\s+bar.*`) @@ -104,17 +106,17 @@ func (s *SnapOpSuite) TestInstallDevMode(c *check.C) { s.srv.checker = func(r *http.Request) { - c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo.bar") + c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ "action": "install", - "name": "foo.bar", + "name": "foo", "devmode": true, "channel": "chan", }) } s.RedirectClientToTestServer(s.srv.handle) - rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "chan", "--devmode", "foo.bar"}) + rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "chan", "--devmode", "foo"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) c.Check(s.Stdout(), check.Matches, `(?sm).*foo\s+1.0\s+42\s+bar.*`) @@ -172,3 +174,69 @@ // ensure that the fake server api was actually hit c.Check(s.srv.n, check.Equals, s.srv.total) } + +func (s *SnapSuite) TestRefreshList(c *check.C) { + n := 0 + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + switch n { + case 0: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/find") + c.Check(r.URL.Query().Get("select"), check.Equals, "refresh") + fmt.Fprintln(w, `{"type": "sync", "result": [{"name": "foo", "status": "active", "version": "4.2update1", "developer": "bar", "revision":17,"summary":"some summary"}]}`) + default: + c.Fatalf("expected to get 1 requests, now on %d", n+1) + } + + n++ + }) + 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 +Developer +Notes +Summary +foo +4.2update1 +bar +- +some summary +`) + c.Check(s.Stderr(), check.Equals, "") + // ensure that the fake server api was actually hit + c.Check(n, check.Equals, 1) +} + +func (s *SnapOpSuite) runTryTest(c *check.C, devmode bool) { + // pass relative path to cmd + tryDir := "some-dir" + + s.srv.checker = func(r *http.Request) { + // ensure the client always sends the absolute path + fullTryDir, err := filepath.Abs(tryDir) + c.Assert(err, check.IsNil) + + c.Check(r.URL.Path, check.Equals, "/v2/snaps") + postData, err := ioutil.ReadAll(r.Body) + c.Assert(err, check.IsNil) + c.Assert(string(postData), check.Matches, "(?s).*Content-Disposition: form-data; name=\"action\"\r\n\r\ntry\r\n.*") + c.Assert(string(postData), check.Matches, fmt.Sprintf("(?s).*Content-Disposition: form-data; name=\"snap-path\"\r\n\r\n%s\r\n.*", regexp.QuoteMeta(fullTryDir))) + c.Assert(string(postData), check.Matches, fmt.Sprintf("(?s).*Content-Disposition: form-data; name=\"devmode\"\r\n\r\n%s\r\n.*", strconv.FormatBool(devmode))) + } + + s.RedirectClientToTestServer(s.srv.handle) + + cmd := []string{"try", tryDir} + if devmode { + cmd = append(cmd, "--devmode") + } + + rest, err := snap.Parser().ParseArgs(cmd) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + c.Check(s.Stdout(), check.Matches, `(?sm).*foo\s+1.0\s+42\s+bar.*`) + 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) TestTryNoDevMode(c *check.C) { + s.runTryTest(c, false) +} +func (s *SnapOpSuite) TestTryDevMode(c *check.C) { + s.runTryTest(c, true) +} diff -Nru snapd-2.0.5/cmd/snap/integration_coverage_test.go snapd-2.0.8/cmd/snap/integration_coverage_test.go --- snapd-2.0.5/cmd/snap/integration_coverage_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/integration_coverage_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,7 +23,7 @@ import ( "testing" - "github.com/ubuntu-core/snappy/integration-tests/testutils/testutils" + "github.com/snapcore/snapd/integration-tests/testutils/testutils" ) func TestRunMain(t *testing.T) { diff -Nru snapd-2.0.5/cmd/snap/interfaces_common_test.go snapd-2.0.8/cmd/snap/interfaces_common_test.go --- snapd-2.0.5/cmd/snap/interfaces_common_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/interfaces_common_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,7 +23,7 @@ import ( . "gopkg.in/check.v1" - . "github.com/ubuntu-core/snappy/cmd/snap" + . "github.com/snapcore/snapd/cmd/snap" ) type AttributePairSuite struct{} diff -Nru snapd-2.0.5/cmd/snap/main.go snapd-2.0.8/cmd/snap/main.go --- snapd-2.0.5/cmd/snap/main.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/main.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,8 +25,10 @@ "os" "strings" - "github.com/ubuntu-core/snappy/client" - "github.com/ubuntu-core/snappy/logger" + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/cmd" + "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/logger" "github.com/jessevdk/go-flags" ) @@ -39,7 +41,7 @@ ) type options struct { - // No global options yet + Version func() `long:"version" description:"print the version and exit"` } var optionsData options @@ -92,6 +94,16 @@ // Since commands have local state a fresh parser is required to isolate tests // from each other. func Parser() *flags.Parser { + optionsData.Version = func() { + cv, err := Client().ServerVersion() + if err != nil { + cv = i18n.G("unavailable") + } + + fmt.Fprintf(Stdout, "snap %s\nsnapd %s\n", cmd.Version, cv) + + os.Exit(0) + } parser := flags.NewParser(&optionsData, flags.HelpFlag|flags.PassDoubleDash) parser.ShortDescription = "Tool to interact with snaps" parser.LongDescription = ` @@ -145,6 +157,7 @@ } func main() { + cmd.ExecInCoreSnap() if err := run(); err != nil { fmt.Fprintf(Stderr, "error: %v\n", err) os.Exit(1) diff -Nru snapd-2.0.5/cmd/snap/main_test.go snapd-2.0.8/cmd/snap/main_test.go --- snapd-2.0.5/cmd/snap/main_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snap/main_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -31,9 +31,9 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/testutil" - snap "github.com/ubuntu-core/snappy/cmd/snap" + snap "github.com/snapcore/snapd/cmd/snap" ) // Hook up check.v1 into the "go test" runner diff -Nru snapd-2.0.5/cmd/snap/notes.go snapd-2.0.8/cmd/snap/notes.go --- snapd-2.0.5/cmd/snap/notes.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/cmd/snap/notes.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,64 @@ +// -*- 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 ( + "strings" + + "github.com/snapcore/snapd/client" +) + +type Notes struct { + Confinement string + Price string + Private bool + DevMode bool + TryMode bool +} + +func (n *Notes) String() string { + var ns []string + + if n.Price != "" { + ns = append(ns, n.Price) + } + + if n.Confinement != "" { + if n.Confinement != client.StrictConfinement { + ns = append(ns, n.Confinement) + } + } else if n.DevMode { + ns = append(ns, "devmode") + } + + if n.Private { + ns = append(ns, "private") + } + + if n.TryMode { + ns = append(ns, "try") + } + + if len(ns) == 0 { + return "-" + } + + return strings.Join(ns, ",") +} diff -Nru snapd-2.0.5/cmd/snap/notes_test.go snapd-2.0.8/cmd/snap/notes_test.go --- snapd-2.0.5/cmd/snap/notes_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/cmd/snap/notes_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,61 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !integrationcoverage + +/* + * 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_test + +import ( + "gopkg.in/check.v1" + + snap "github.com/snapcore/snapd/cmd/snap" +) + +type notesSuite struct{} + +var _ = check.Suite(¬esSuite{}) + +func (notesSuite) TestNoNotes(c *check.C) { + c.Check((&snap.Notes{}).String(), check.Equals, "-") +} + +func (notesSuite) TestNotesPrice(c *check.C) { + c.Check((&snap.Notes{ + Price: "3.50GBP", + }).String(), check.Equals, "3.50GBP") +} + +func (notesSuite) TestNotesPrivate(c *check.C) { + c.Check((&snap.Notes{ + Private: true, + }).String(), check.Equals, "private") +} + +func (notesSuite) TestNotesPrivateDevmode(c *check.C) { + c.Check((&snap.Notes{ + Private: true, + Confinement: "devmode", + }).String(), check.Equals, "devmode,private") +} + +func (notesSuite) TestNotesOtherDevmode(c *check.C) { + c.Check((&snap.Notes{ + DevMode: true, + TryMode: true, + }).String(), check.Equals, "devmode,try") +} diff -Nru snapd-2.0.5/cmd/snapd/integration_coverage_test.go snapd-2.0.8/cmd/snapd/integration_coverage_test.go --- snapd-2.0.5/cmd/snapd/integration_coverage_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snapd/integration_coverage_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,7 +23,7 @@ import ( "testing" - "github.com/ubuntu-core/snappy/integration-tests/testutils/testutils" + "github.com/snapcore/snapd/integration-tests/testutils/testutils" ) func TestRunMain(t *testing.T) { diff -Nru snapd-2.0.5/cmd/snapd/main.go snapd-2.0.8/cmd/snapd/main.go --- snapd-2.0.5/cmd/snapd/main.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/cmd/snapd/main.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,8 +25,9 @@ "os/signal" "syscall" - "github.com/ubuntu-core/snappy/daemon" - "github.com/ubuntu-core/snappy/logger" + "github.com/snapcore/snapd/cmd" + "github.com/snapcore/snapd/daemon" + "github.com/snapcore/snapd/logger" ) func init() { @@ -37,6 +38,7 @@ } func main() { + cmd.ExecInCoreSnap() if err := run(); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) @@ -48,10 +50,10 @@ if err != nil { return err } - if err := d.Init(); err != nil { return err } + d.Version = cmd.Version d.Start() diff -Nru snapd-2.0.5/cmd/snap-exec/main.go snapd-2.0.8/cmd/snap-exec/main.go --- snapd-2.0.5/cmd/snap-exec/main.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/cmd/snap-exec/main.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,116 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-2015 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" + "path/filepath" + "syscall" + + "github.com/jessevdk/go-flags" + + "github.com/snapcore/snapd/snap" +) + +// for the tests +var syscallExec = syscall.Exec + +func main() { + if err := run(); err != nil { + fmt.Printf("cannot snap-exec: %s\n", err) + os.Exit(1) + } +} + +func run() error { + var opts struct { + Positional struct { + SnapApp string `positional-arg-name:"" description:"the application to run, e.g. hello-world.env"` + } `positional-args:"yes" required:"yes"` + + Command string `long:"command" description:"use a different command like {stop,post-stop} from the app"` + } + + parser := flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash) + args, err := parser.Parse() + if err != nil { + return err + } + + // the SNAP_REVISION is set by `snap run` - we can not (easily) + // find it in `snap-exec` because `snap-exec` is run inside the + // confinement and (generally) can not talk to snapd + revision := os.Getenv("SNAP_REVISION") + + snapApp := opts.Positional.SnapApp + return snapExec(snapApp, revision, opts.Command, args) +} + +func findCommand(app *snap.AppInfo, command string) (string, error) { + var cmd string + switch command { + case "stop": + cmd = app.StopCommand + case "post-stop": + cmd = app.PostStopCommand + case "": + cmd = app.Command + default: + return "", fmt.Errorf("cannot use %q command", command) + } + + if cmd == "" { + return "", fmt.Errorf("no %q command found for %q", command, app.Name) + } + return cmd, nil +} + +func snapExec(snapApp, revision, command string, args []string) error { + rev, err := snap.ParseRevision(revision) + if err != nil { + return err + } + + snapName, appName := snap.SplitSnapApp(snapApp) + info, err := snap.ReadInfo(snapName, &snap.SideInfo{ + Revision: rev, + }) + if err != nil { + return err + } + + app := info.Apps[appName] + if app == nil { + return fmt.Errorf("cannot find app %q in %q", appName, snapName) + } + + cmd, err := findCommand(app, command) + if err != nil { + return err + } + + // build the evnironment from the yamle + env := append(os.Environ(), app.Env()...) + + // run the command + fullCmd := filepath.Join(app.Snap.MountDir(), cmd) + return syscallExec(fullCmd, args, env) +} diff -Nru snapd-2.0.5/cmd/snap-exec/main_test.go snapd-2.0.8/cmd/snap-exec/main_test.go --- snapd-2.0.5/cmd/snap-exec/main_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/cmd/snap-exec/main_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,118 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !integrationcoverage + +/* + * 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" + "syscall" + "testing" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/testutil" +) + +// Hook up check.v1 into the "go test" runner +func Test(t *testing.T) { TestingT(t) } + +type snapExecSuite struct { +} + +var _ = Suite(&snapExecSuite{}) + +func (s *snapExecSuite) TearDown(c *C) { + syscallExec = syscall.Exec + dirs.SetRootDir("/") +} + +var mockYaml = []byte(`name: snapname +version: 1.0 +apps: + app: + command: run-app + stop-command: stop-app + post-stop-command: post-stop-app + environment: + LD_LIBRARY_PATH: /some/path + nostop: + command: nostop +`) + +func (s *snapExecSuite) TestFindCommand(c *C) { + info, err := snap.InfoFromSnapYaml(mockYaml) + c.Assert(err, IsNil) + + for _, t := range []struct { + cmd string + expected string + }{ + {cmd: "", expected: "run-app"}, + {cmd: "stop", expected: "stop-app"}, + {cmd: "post-stop", expected: "post-stop-app"}, + } { + cmd, err := findCommand(info.Apps["app"], t.cmd) + c.Check(err, IsNil) + c.Check(cmd, Equals, t.expected) + } +} + +func (s *snapExecSuite) TestFindCommandInvalidCommand(c *C) { + info, err := snap.InfoFromSnapYaml(mockYaml) + c.Assert(err, IsNil) + + _, err = findCommand(info.Apps["app"], "xxx") + c.Check(err, ErrorMatches, `cannot use "xxx" command`) +} + +func (s *snapExecSuite) TestFindCommandNoCommand(c *C) { + info, err := snap.InfoFromSnapYaml(mockYaml) + c.Assert(err, IsNil) + + _, err = findCommand(info.Apps["nostop"], "stop") + c.Check(err, ErrorMatches, `no "stop" command found for "nostop"`) +} + +func (s *snapExecSuite) TestSnapLaunchIntegration(c *C) { + dirs.SetRootDir(c.MkDir()) + snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ + Revision: snap.R("42"), + }) + + execArgv0 := "" + execArgs := []string{} + execEnv := []string{} + syscallExec = func(argv0 string, argv []string, env []string) error { + execArgv0 = argv0 + execArgs = argv + execEnv = env + return nil + } + + // launch and verify its run the right way + err := snapExec("snapname.app", "42", "stop", []string{"arg1", "arg2"}) + c.Assert(err, IsNil) + c.Check(execArgv0, Equals, fmt.Sprintf("%s/snapname/42/stop-app", dirs.SnapSnapsDir)) + c.Check(execArgs, DeepEquals, []string{"arg1", "arg2"}) + c.Check(execEnv, testutil.Contains, "LD_LIBRARY_PATH=/some/path\n") +} diff -Nru snapd-2.0.5/cmd/version.go snapd-2.0.8/cmd/version.go --- snapd-2.0.5/cmd/version.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/cmd/version.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,25 @@ +// -*- 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 + +//go:generate mkversion.sh + +// will be overwritten at build-time via mkversion.sh +var Version = "unknown" diff -Nru snapd-2.0.5/coreconfig/config.go snapd-2.0.8/coreconfig/config.go --- snapd-2.0.5/coreconfig/config.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/coreconfig/config.go 2016-06-08 05:58:01.000000000 +0000 @@ -32,8 +32,8 @@ "strings" "syscall" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/systemd" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/systemd" "gopkg.in/yaml.v2" ) diff -Nru snapd-2.0.5/coreconfig/config_test.go snapd-2.0.8/coreconfig/config_test.go --- snapd-2.0.5/coreconfig/config_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/coreconfig/config_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ "path/filepath" "testing" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/systemd" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/systemd" . "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/daemon/api.go snapd-2.0.8/daemon/api.go --- snapd-2.0.5/daemon/api.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/daemon/api.go 2016-06-08 05:58:01.000000000 +0000 @@ -35,20 +35,20 @@ "github.com/gorilla/mux" - "github.com/ubuntu-core/snappy/asserts" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/i18n" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/overlord/auth" - "github.com/ubuntu-core/snappy/overlord/ifacestate" - "github.com/ubuntu-core/snappy/overlord/snapstate" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/release" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snappy" - "github.com/ubuntu-core/snappy/store" + "github.com/snapcore/snapd/asserts" + "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/overlord/auth" + "github.com/snapcore/snapd/overlord/ifacestate" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/store" ) var api = []*Command{ @@ -173,7 +173,8 @@ func sysInfo(c *Command, r *http.Request, user *auth.UserState) Response { m := map[string]string{ - "series": release.Series, + "series": release.Series, + "version": c.d.Version, } return SyncResponse(m, nil) @@ -293,16 +294,6 @@ return user, err } -type metarepo interface { - Snap(string, string, store.Authenticator) (*snap.Info, error) - FindSnaps(string, string, store.Authenticator) ([]*snap.Info, error) - SuggestedCurrency() string -} - -var newRemoteRepo = func() metarepo { - return snappy.NewConfiguredUbuntuStoreSnapRepository() -} - var muxVars = mux.Vars func getSnapInfo(c *Command, r *http.Request, user *auth.UserState) Response { @@ -354,47 +345,41 @@ return result } +func getStore(c *Command) snapstate.StoreService { + return c.d.overlord.SnapManager().Store() +} + func searchStore(c *Command, r *http.Request, user *auth.UserState) Response { route := c.d.router.Get(snapCmd.Path) if route == nil { return InternalError("cannot find route for snaps") } - query := r.URL.Query() + if query.Get("select") == "refresh" { + if query.Get("q") != "" { + return BadRequest("cannot use 'q' with 'select=refresh'") + } + return storeUpdates(c, r, user) + } + auther, err := c.d.auther(r) if err != nil && err != auth.ErrInvalidAuth { return InternalError("%v", err) } - remoteRepo := newRemoteRepo() - found, err := remoteRepo.FindSnaps(query.Get("q"), query.Get("channel"), auther) + store := getStore(c) + found, err := store.Find(query.Get("q"), query.Get("channel"), auther) if err != nil { return InternalError("%v", err) } meta := &Meta{ - SuggestedCurrency: remoteRepo.SuggestedCurrency(), + SuggestedCurrency: store.SuggestedCurrency(), Sources: []string{"store"}, } - results := make([]*json.RawMessage, len(found)) - for i, x := range found { - url, err := route.URL("name", x.Name()) - if err != nil { - logger.Noticef("cannot build URL for snap %q (r%d): %v", x.Name(), x.Revision, err) - continue - } - - data, err := json.Marshal(webify(mapRemote(x), url.String())) - if err != nil { - return InternalError("%v", err) - } - raw := json.RawMessage(data) - results[i] = &raw - } - - return SyncResponse(results, meta) + return sendStorePackages(route, meta, found) } func shouldSearchStore(r *http.Request) bool { @@ -419,6 +404,69 @@ return false } +func storeUpdates(c *Command, r *http.Request, user *auth.UserState) Response { + route := c.d.router.Get(snapCmd.Path) + if route == nil { + return InternalError("cannot find route for snaps") + } + + found, err := allLocalSnapInfos(c.d.overlord.State()) + if err != nil { + return InternalError("cannot list local snaps: %v", err) + } + + candidatesInfo := make([]*store.RefreshCandidate, 0, len(found)) + for _, sn := range found { + // snaps in try mode are not considered here + if sn.snapst.TryMode() { + continue + } + + // get confinement preference from the snapstate + candidatesInfo = append(candidatesInfo, &store.RefreshCandidate{ + // the desired channel (not sn.info.Channel!) + Channel: sn.snapst.Channel, + DevMode: sn.snapst.DevMode(), + + SnapID: sn.info.SnapID, + Revision: sn.info.Revision, + Epoch: sn.info.Epoch, + }) + } + + var auther store.Authenticator + if user != nil { + auther = user.Authenticator() + } + store := getStore(c) + updates, err := store.ListRefresh(candidatesInfo, auther) + if err != nil { + return InternalError("cannot list updates: %v", err) + } + + return sendStorePackages(route, nil, updates) +} + +func sendStorePackages(route *mux.Route, meta *Meta, found []*snap.Info) Response { + results := make([]*json.RawMessage, 0, len(found)) + for _, x := range found { + url, err := route.URL("name", x.Name()) + if err != nil { + logger.Noticef("Cannot build URL for snap %q revision %s: %v", x.Name(), x.Revision, err) + continue + } + + data, err := json.Marshal(webify(mapRemote(x), url.String())) + if err != nil { + return InternalError("%v", err) + } + raw := json.RawMessage(data) + results = append(results, &raw) + } + + return SyncResponse(results, meta) +} + // plural! func getSnapsInfo(c *Command, r *http.Request, user *auth.UserState) Response { @@ -445,13 +493,13 @@ url, err := route.URL("name", name) if err != nil { - logger.Noticef("cannot build URL for snap %q (r%d): %v", name, rev, err) + logger.Noticef("cannot build URL for snap %q revision %s: %v", name, rev, err) continue } data, err := json.Marshal(webify(mapLocal(x.info, x.snapst), url.String())) if err != nil { - return InternalError("cannot serialize snap %q (r%d): %v", name, rev, err) + return InternalError("cannot serialize snap %q revision %s: %v", name, rev, err) } raw := json.RawMessage(data) results[i] = &raw @@ -485,10 +533,12 @@ type snapInstruction struct { progress.NullProgress - Action string `json:"action"` - Channel string `json:"channel"` - DevMode bool `json:"devmode"` - LeaveOld bool `json:"leave-old"` + Action string `json:"action"` + Channel string `json:"channel"` + DevMode bool `json:"devmode"` + // dropping support temporarely until flag confusion is sorted, + // this isn't supported by client atm anyway + LeaveOld bool `json:"temp-dropped-leave-old"` License *licenseData `json:"license"` // The fields below should not be unmarshalled into. Do not export them. @@ -499,6 +549,7 @@ var snapstateInstall = snapstate.Install var snapstateUpdate = snapstate.Update var snapstateInstallPath = snapstate.InstallPath +var snapstateTryPath = snapstate.TryPath var snapstateGet = snapstate.Get var errNothingToInstall = errors.New("nothing to install") @@ -541,12 +592,9 @@ } func snapInstall(inst *snapInstruction, st *state.State) (string, []*state.TaskSet, error) { - flags := snappy.DoInstallGC - if inst.LeaveOld { - flags = 0 - } + flags := snapstate.Flags(0) if inst.DevMode { - flags |= snappy.DeveloperMode + flags |= snapstate.DevMode } tsets, err := withEnsureUbuntuCore(st, inst.snap, inst.userID, @@ -566,10 +614,7 @@ } func snapUpdate(inst *snapInstruction, st *state.State) (string, []*state.TaskSet, error) { - flags := snappy.DoInstallGC - if inst.LeaveOld { - flags = 0 - } + flags := snapstate.Flags(0) ts, err := snapstateUpdate(st, inst.snap, inst.Channel, inst.userID, flags) if err != nil { @@ -585,11 +630,7 @@ } func snapRemove(inst *snapInstruction, st *state.State) (string, []*state.TaskSet, error) { - flags := snappy.DoRemoveGC - if inst.LeaveOld { - flags = 0 - } - ts, err := snapstate.Remove(st, inst.snap, flags) + ts, err := snapstate.Remove(st, inst.snap) if err != nil { return "", nil, err } @@ -657,6 +698,7 @@ } chg := newChange(state, inst.Action+"-snap", msg, tsets) + chg.Set("snap-names", []string{inst.snap}) state.EnsureBefore(0) return AsyncResponse(nil, &Meta{Change: chg.ID()}) @@ -672,6 +714,37 @@ const maxReadBuflen = 1024 * 1024 +func trySnap(c *Command, r *http.Request, user *auth.UserState, trydir string, flags snapstate.Flags) Response { + st := c.d.overlord.State() + st.Lock() + defer st.Unlock() + + if !filepath.IsAbs(trydir) { + return BadRequest("cannot try %q: need an absolute path", trydir) + } + if !osutil.IsDirectory(trydir) { + return BadRequest("cannot try %q: not a snap directory", trydir) + } + + info, err := readSnapInfo(trydir) + if err != nil { + return BadRequest("cannot read snap info for %s: %s", trydir, err) + } + + tsets, err := snapstateTryPath(st, info.Name(), trydir, flags) + if err != nil { + return BadRequest("cannot try %s: %s", trydir, err) + } + + msg := fmt.Sprintf(i18n.G("Try %q snap from %q"), info.Name(), trydir) + chg := newChange(st, "try-snap", msg, []*state.TaskSet{tsets}) + chg.Set("api-data", map[string]string{"snap-name": info.Name()}) + + st.EnsureBefore(0) + + return AsyncResponse(nil, &Meta{Change: chg.ID()}) +} + func sideloadSnap(c *Command, r *http.Request, user *auth.UserState) Response { route := c.d.router.Get(stateChangeCmd.Path) if route == nil { @@ -695,10 +768,17 @@ return BadRequest("cannot read POST form: %v", err) } - var flags snappy.InstallFlags + var flags snapstate.Flags if len(form.Value["devmode"]) > 0 && form.Value["devmode"][0] == "true" { - flags |= snappy.DeveloperMode + flags |= snapstate.DevMode + } + + if len(form.Value["action"]) > 0 && form.Value["action"][0] == "try" { + if len(form.Value["snap-path"]) == 0 { + return BadRequest("need 'snap-path' value in form") + } + return trySnap(c, r, user, form.Value["snap-path"][0], flags) } // find the file for the "snap" form field @@ -774,6 +854,7 @@ chg := newChange(st, "install-snap", msg, tsets) chg.Set("api-data", map[string]string{"snap-name": snapName}) + chg.Set("snap-names", []string{snapName}) go func() { // XXX this needs to be a task in the manager; this is a hack to keep this branch smaller @@ -904,6 +985,7 @@ } change := state.NewChange(a.Action+"-snap", summary) + change.Set("snap-names", []string{a.Plugs[0].Snap, a.Slots[0].Snap}) change.AddAll(taskset) state.EnsureBefore(0) @@ -1072,6 +1154,29 @@ return BadRequest("select should be one of: all,in-progress,ready") } + if wantedName := query.Get("for"); wantedName != "" { + outerFilter := filter + filter = func(chg *state.Change) bool { + if !outerFilter(chg) { + return false + } + + var snapNames []string + if err := chg.Get("snap-names", &snapNames); err != nil { + logger.Noticef("cannot get snap-name for change %v", chg.ID()) + return false + } + + for _, snapName := range snapNames { + if snapName == wantedName { + return true + } + } + + return false + } + } + state := c.d.overlord.State() state.Lock() defer state.Unlock() diff -Nru snapd-2.0.5/daemon/api_mock_test.go snapd-2.0.8/daemon/api_mock_test.go --- snapd-2.0.5/daemon/api_mock_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/daemon/api_mock_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,10 +22,10 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/overlord/snapstate" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" ) func (s *apiSuite) mockSnap(c *C, yamlText string) *snap.Info { @@ -33,7 +33,7 @@ panic("call s.daemon(c) in your test first") } - snapInfo := snaptest.MockSnap(c, yamlText, &snap.SideInfo{Revision: 1}) + snapInfo := snaptest.MockSnap(c, yamlText, &snap.SideInfo{Revision: snap.R(1)}) snap.AddImplicitSlots(snapInfo) st := s.d.overlord.State() diff -Nru snapd-2.0.5/daemon/api_test.go snapd-2.0.8/daemon/api_test.go --- snapd-2.0.5/daemon/api_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/daemon/api_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -37,18 +37,18 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/overlord/auth" - "github.com/ubuntu-core/snappy/overlord/ifacestate" - "github.com/ubuntu-core/snappy/overlord/snapstate" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" - "github.com/ubuntu-core/snappy/snappy" - "github.com/ubuntu-core/snappy/store" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/ifacestate" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/store" + "github.com/snapcore/snapd/testutil" ) type apiSuite struct { @@ -58,10 +58,10 @@ searchTerm string channel string suggestedCurrency string - overlord *fakeOverlord d *Daemon auther store.Authenticator restoreBackends func() + refreshCandidates []*store.RefreshCandidate } var _ = check.Suite(&apiSuite{}) @@ -74,7 +74,7 @@ return nil, s.err } -func (s *apiSuite) FindSnaps(searchTerm, channel string, auther store.Authenticator) ([]*snap.Info, error) { +func (s *apiSuite) Find(searchTerm, channel string, auther store.Authenticator) ([]*snap.Info, error) { s.searchTerm = searchTerm s.channel = channel s.auther = auther @@ -82,23 +82,29 @@ return s.rsnaps, s.err } +func (s *apiSuite) ListRefresh(snaps []*store.RefreshCandidate, auther store.Authenticator) ([]*snap.Info, error) { + s.refreshCandidates = snaps + + return s.rsnaps, s.err +} + func (s *apiSuite) SuggestedCurrency() string { return s.suggestedCurrency } +func (s *apiSuite) Download(*snap.Info, progress.Meter, store.Authenticator) (string, error) { + panic("Download not expected to be called") +} + func (s *apiSuite) muxVars(*http.Request) map[string]string { return s.vars } func (s *apiSuite) SetUpSuite(c *check.C) { - newRemoteRepo = func() metarepo { - return s - } muxVars = s.muxVars } func (s *apiSuite) TearDownSuite(c *check.C) { - newRemoteRepo = nil muxVars = nil } @@ -114,11 +120,9 @@ s.channel = "" s.err = nil s.vars = nil - s.overlord = &fakeOverlord{ - configs: map[string]string{}, - } s.auther = nil s.d = nil + s.refreshCandidates = nil // Disable real security backends for all API tests s.restoreBackends = ifacestate.MockSecurityBackends(nil) } @@ -139,31 +143,18 @@ d, err := New() c.Assert(err, check.IsNil) d.addRoutes() + + d.overlord.SnapManager().ReplaceStore(s) + s.d = d return d } -func (s *apiSuite) mkManifest(c *check.C, pkgType snap.Type) { - // creating the part to get its manifest path is cheating, a little - sideInfo := snap.SideInfo{ - OfficialName: "foo", - Developer: "bar", - Revision: 2147483647, - EditedDescription: " bla bla bla", - } - - c.Assert(snappy.SaveManifest(&snap.Info{ - Type: pkgType, - Version: "1", - SideInfo: sideInfo, - }), check.IsNil) -} - -func (s *apiSuite) mkInstalled(c *check.C, name, developer, version string, revno int, active bool, extraYaml string) *snap.Info { +func (s *apiSuite) mkInstalled(c *check.C, name, developer, version string, revno snap.Revision, active bool, extraYaml string) *snap.Info { return s.mkInstalledInState(c, nil, name, developer, version, revno, active, extraYaml) } -func (s *apiSuite) mkInstalledInState(c *check.C, daemon *Daemon, name, developer, version string, revno int, active bool, extraYaml string) *snap.Info { +func (s *apiSuite) mkInstalledInState(c *check.C, daemon *Daemon, name, developer, version string, revno snap.Revision, active bool, extraYaml string) *snap.Info { // Collect arguments into a snap.SideInfo structure sideInfo := &snap.SideInfo{ SnapID: "funky-snap-id", @@ -188,9 +179,6 @@ c.Assert(os.MkdirAll(guidir, 0755), check.IsNil) c.Check(ioutil.WriteFile(filepath.Join(guidir, "icon.svg"), []byte("yadda icon"), 0644), check.IsNil) - err := snappy.SaveManifest(snapInfo) - c.Assert(err, check.IsNil) - if daemon != nil { st := daemon.overlord.State() st.Lock() @@ -204,11 +192,6 @@ snapstate.Set(st, name, &snapst) } - if active { - err := snappy.UpdateCurrentSymlink(snapInfo, nil) - c.Assert(err, check.IsNil) - } - return snapInfo } @@ -218,7 +201,7 @@ type: gadget gadget: {store: {id: %q}} `, store) - snaptest.MockSnap(c, yamlText, &snap.SideInfo{Revision: 1}) + snaptest.MockSnap(c, yamlText, &snap.SideInfo{Revision: snap.R(1)}) c.Assert(os.Symlink("1", filepath.Join(dirs.SnapSnapsDir, "test", "current")), check.IsNil) } @@ -227,9 +210,9 @@ s.vars = map[string]string{"name": "foo"} // we have v0 [r5] installed - s.mkInstalledInState(c, d, "foo", "bar", "v0", 5, false, "") + s.mkInstalledInState(c, d, "foo", "bar", "v0", snap.R(5), false, "") // and v1 [r10] is current - s.mkInstalledInState(c, d, "foo", "bar", "v1", 10, true, "description: description\nsummary: summary") + s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, "description: description\nsummary: summary") req, err := http.NewRequest("GET", "/v2/snaps/foo", nil) c.Assert(err, check.IsNil) @@ -254,8 +237,9 @@ Result: map[string]interface{}{ "id": "funky-snap-id", "name": "foo", - "revision": 10, + "revision": snap.R(10), "version": "v1", + "channel": "stable", "summary": "summary", "description": "description", "developer": "bar", @@ -263,6 +247,10 @@ "icon": "/v2/icons/foo/icon", "type": string(snap.TypeApp), "resource": "/v2/snaps/foo", + "private": false, + "devmode": false, + "confinement": snap.StrictConfinement, + "trymode": false, }, Meta: meta, } @@ -290,9 +278,6 @@ } func (s *apiSuite) TestSnapInfoNotFound(c *check.C) { - s.vars = map[string]string{"name": "foo"} - s.err = snappy.ErrPackageNotFound - req, err := http.NewRequest("GET", "/v2/snaps/gfoo", nil) c.Assert(err, check.IsNil) c.Check(getSnapInfo(snapCmd, req, nil).(*resp).Status, check.Equals, http.StatusNotFound) @@ -352,13 +337,13 @@ "api", "maxReadBuflen", "muxVars", - "newRemoteRepo", "errNothingToInstall", // snapInstruction vars: "snapInstructionDispTable", "snapstateInstall", "snapstateUpdate", "snapstateInstallPath", + "snapstateTryPath", "snapstateGet", "readSnapInfo", } @@ -397,31 +382,15 @@ rec := httptest.NewRecorder() c.Check(sysInfoCmd.Path, check.Equals, "/v2/system-info") - sysInfoCmd.GET(sysInfoCmd, nil, nil).ServeHTTP(rec, nil) - c.Check(rec.Code, check.Equals, 200) - c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") - - expected := map[string]interface{}{ - "series": "16", - } - var rsp resp - c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil) - c.Check(rsp.Status, check.Equals, 200) - c.Check(rsp.Type, check.Equals, ResponseTypeSync) - c.Check(rsp.Result, check.DeepEquals, expected) -} - -func (s *apiSuite) TestSysInfoStore(c *check.C) { - rec := httptest.NewRecorder() - c.Check(sysInfoCmd.Path, check.Equals, "/v2/system-info") - - s.mkGadget(c, "some-store") + s.daemon(c).Version = "42b1" sysInfoCmd.GET(sysInfoCmd, nil, nil).ServeHTTP(rec, nil) c.Check(rec.Code, check.Equals, 200) + c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") expected := map[string]interface{}{ - "series": "16", + "series": "16", + "version": "42b1", } var rsp resp c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil) @@ -706,7 +675,7 @@ } for _, snp := range tsnaps { - s.mkInstalledInState(c, d, snp.name, snp.dev, snp.ver, snp.rev, false, "") + s.mkInstalledInState(c, d, snp.name, snp.dev, snp.ver, snap.R(snp.rev), false, "") } rsp, ok := getSnapsInfo(snapsCmd, req, nil).(*resp) @@ -728,8 +697,9 @@ } c.Check(got["name"], check.Equals, s.name) c.Check(got["version"], check.Equals, s.ver) - c.Check(got["revision"], check.Equals, float64(s.rev)) + c.Check(got["revision"], check.Equals, snap.R(s.rev).String()) c.Check(got["developer"], check.Equals, s.dev) + c.Check(got["confinement"], check.Equals, "strict") } } @@ -742,7 +712,7 @@ Developer: "foo", }, }} - s.mkInstalledInState(c, d, "local", "foo", "v1", 10, true, "") + s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") req, err := http.NewRequest("GET", "/v2/snaps?sources=local", nil) c.Assert(err, check.IsNil) @@ -780,6 +750,41 @@ c.Check(s.searchTerm, check.Equals, "hi") c.Check(s.channel, check.Equals, "potato") + c.Check(s.refreshCandidates, check.HasLen, 0) +} + +func (s *apiSuite) TestFindRefreshes(c *check.C) { + d := s.daemon(c) + d.overlord.Loop() + defer d.overlord.Stop() + + s.rsnaps = []*snap.Info{{ + SideInfo: snap.SideInfo{ + OfficialName: "store", + Developer: "foo", + }, + }} + s.mockSnap(c, "name: foo\nversion: 1.0") + + req, err := http.NewRequest("GET", "/v2/find?select=refresh", nil) + c.Assert(err, check.IsNil) + + rsp := searchStore(findCmd, req, nil).(*resp) + + snaps := snapList(rsp.Result) + c.Assert(snaps, check.HasLen, 1) + c.Assert(snaps[0]["name"], check.Equals, "store") + c.Check(s.refreshCandidates, check.HasLen, 1) +} + +func (s *apiSuite) TestFindRefreshNotQ(c *check.C) { + req, err := http.NewRequest("GET", "/v2/find?select=refresh&q=foo", nil) + c.Assert(err, check.IsNil) + + rsp := searchStore(findCmd, req, nil).(*resp) + c.Check(rsp.Type, check.Equals, ResponseTypeError) + c.Check(rsp.Status, check.Equals, http.StatusBadRequest) + c.Check(rsp.Result.(*errorResult).Message, check.Matches, "cannot use 'q' with 'select=refresh'") } func (s *apiSuite) TestFindPriced(c *check.C) { @@ -829,7 +834,7 @@ Developer: "foo", }, }} - s.mkInstalledInState(c, d, "local", "foo", "v1", 10, true, "") + s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") req, err := http.NewRequest("GET", "/v2/snaps?sources=store", nil) c.Assert(err, check.IsNil) @@ -846,6 +851,47 @@ c.Check(rsp.SuggestedCurrency, check.Equals, "EUR") } +func (s *apiSuite) TestSnapsStoreConfinement(c *check.C) { + s.rsnaps = []*snap.Info{ + { + // no explicit confinement in this one + SideInfo: snap.SideInfo{ + OfficialName: "foo", + }, + }, + { + Confinement: snap.StrictConfinement, + SideInfo: snap.SideInfo{ + OfficialName: "bar", + }, + }, + { + Confinement: snap.DevmodeConfinement, + SideInfo: snap.SideInfo{ + OfficialName: "baz", + }, + }, + } + + req, err := http.NewRequest("GET", "/v2/find", nil) + c.Assert(err, check.IsNil) + + rsp := searchStore(findCmd, req, nil).(*resp) + + snaps := snapList(rsp.Result) + c.Assert(snaps, check.HasLen, 3) + + for i, ss := range [][2]string{ + {"foo", string(snap.StrictConfinement)}, + {"bar", string(snap.StrictConfinement)}, + {"baz", string(snap.DevmodeConfinement)}, + } { + name, mode := ss[0], ss[1] + c.Check(snaps[i]["name"], check.Equals, name, check.Commentf(name)) + c.Check(snaps[i]["confinement"], check.Equals, mode, check.Commentf(name)) + } +} + func (s *apiSuite) TestSnapsInfoStoreWithAuth(c *check.C) { state := snapCmd.d.overlord.State() state.Lock() @@ -875,7 +921,7 @@ Developer: "foo", }, }} - s.mkInstalledInState(c, d, "local", "foo", "v1", 10, true, "") + s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") req, err := http.NewRequest("GET", "/v2/snaps?sources=local,store", nil) c.Assert(err, check.IsNil) @@ -915,7 +961,7 @@ Developer: "foo", }, }} - s.mkInstalledInState(c, d, "local", "foo", "v1", 10, true, "") + s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "") req, err := http.NewRequest("GET", "/v2/snaps", nil) c.Assert(err, check.IsNil) @@ -934,7 +980,7 @@ Developer: "foo", }, }} - s.mkInstalled(c, "local", "foo", "v1", 10, true, "") + s.mkInstalled(c, "local", "foo", "v1", snap.R(10), true, "") req, err := http.NewRequest("GET", "/v2/snaps?sources=unknown", nil) c.Assert(err, check.IsNil) @@ -1070,21 +1116,6 @@ } } -type fakeOverlord struct { - configs map[string]string -} - -func (o *fakeOverlord) Configure(s *snappy.Snap, c []byte) ([]byte, error) { - if len(c) > 0 { - o.configs[s.Name()] = string(c) - } - config, ok := o.configs[s.Name()] - if !ok { - return nil, fmt.Errorf("no config for %q", s.Name()) - } - return []byte(config), nil -} - func (s *apiSuite) TestSideloadSnap(c *check.C) { // try a multipart/form-data upload body := "" + @@ -1115,7 +1146,7 @@ "----hello--\r\n" head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"} // try a multipart/form-data upload - chgSummary := s.sideloadCheck(c, body, head, snappy.DeveloperMode, true) + chgSummary := s.sideloadCheck(c, body, head, snapstate.DevMode, true) c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`) } @@ -1145,7 +1176,70 @@ c.Assert(rsp.Result.(*errorResult).Message, check.Matches, `cannot find "snap" file field in provided multipart/form-data payload`) } -func (s *apiSuite) sideloadCheck(c *check.C, content string, head map[string]string, expectedFlags snappy.InstallFlags, hasUbuntuCore bool) string { +func (s *apiSuite) TestTrySnap(c *check.C) { + d := newTestDaemon(c) + d.overlord.Loop() + defer d.overlord.Stop() + + req, err := http.NewRequest("POST", "/v2/snaps", nil) + c.Assert(err, check.IsNil) + + // mock a try dir + tryDir := c.MkDir() + snapYaml := filepath.Join(tryDir, "meta", "snap.yaml") + err = os.MkdirAll(filepath.Dir(snapYaml), 0755) + c.Assert(err, check.IsNil) + err = ioutil.WriteFile(snapYaml, []byte("name: foo\nversion: 1.0\n"), 0644) + c.Assert(err, check.IsNil) + + tryWasCalled := true + snapstateTryPath = func(s *state.State, name, path string, flags snapstate.Flags) (*state.TaskSet, error) { + tryWasCalled = true + t := s.NewTask("fake-install-snap", "Doing a fake install") + return state.NewTaskSet(t), nil + } + + // try the snap + rsp := trySnap(snapsCmd, req, nil, tryDir, 0).(*resp) + c.Assert(rsp.Type, check.Equals, ResponseTypeAsync) + c.Assert(tryWasCalled, check.Equals, true) + + st := d.overlord.State() + st.Lock() + defer st.Unlock() + chg := st.Change(rsp.Change) + c.Assert(chg, check.NotNil) + + c.Assert(chg.Tasks(), check.HasLen, 1) + + st.Unlock() + <-chg.Ready() + st.Lock() + + c.Check(chg.Kind(), check.Equals, "try-snap") + c.Check(chg.Summary(), check.Equals, fmt.Sprintf(`Try "%s" snap from %q`, "foo", tryDir)) + +} + +func (s *apiSuite) TestTrySnapRelative(c *check.C) { + req, err := http.NewRequest("POST", "/v2/snaps", nil) + c.Assert(err, check.IsNil) + + rsp := trySnap(snapsCmd, req, nil, "relative-path", 0).(*resp) + c.Assert(rsp.Type, check.Equals, ResponseTypeError) + c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, "need an absolute path") +} + +func (s *apiSuite) TestTrySnapNotDir(c *check.C) { + req, err := http.NewRequest("POST", "/v2/snaps", nil) + c.Assert(err, check.IsNil) + + rsp := trySnap(snapsCmd, req, nil, "/does/not/exist", 0).(*resp) + c.Assert(rsp.Type, check.Equals, ResponseTypeError) + c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, "not a snap directory") +} + +func (s *apiSuite) sideloadCheck(c *check.C, content string, head map[string]string, expectedFlags snapstate.Flags, hasUbuntuCore bool) string { d := newTestDaemon(c) d.overlord.Loop() defer d.overlord.Stop() @@ -1163,16 +1257,16 @@ // pretend we do not have a state for ubuntu-core return state.ErrNoState } - snapstateInstall = func(s *state.State, name, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { + snapstateInstall = func(s *state.State, name, channel string, userID int, flags snapstate.Flags) (*state.TaskSet, error) { // NOTE: ubuntu-core is not installed in developer mode - c.Check(flags, check.Equals, snappy.InstallFlags(0)) + c.Check(flags, check.Equals, snapstate.Flags(0)) installQueue = append(installQueue, name) t := s.NewTask("fake-install-snap", "Doing a fake install") return state.NewTaskSet(t), nil } - snapstateInstallPath = func(s *state.State, name, path, channel string, flags snappy.InstallFlags) (*state.TaskSet, error) { + snapstateInstallPath = func(s *state.State, name, path, channel string, flags snapstate.Flags) (*state.TaskSet, error) { c.Check(flags, check.Equals, expectedFlags) bs, err := ioutil.ReadFile(path) @@ -1223,7 +1317,7 @@ d := s.daemon(c) // have an active foo in the system - info := s.mkInstalledInState(c, d, "foo", "bar", "v1", 10, true, "") + info := s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, "") // have an icon for it in the package itself iconfile := filepath.Join(info.MountDir(), "meta", "gui", "icon.ick") @@ -1245,7 +1339,7 @@ d := s.daemon(c) // have an *in*active foo in the system - info := s.mkInstalledInState(c, d, "foo", "bar", "v1", 10, false, "") + info := s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), false, "") // have an icon for it in the package itself iconfile := filepath.Join(info.MountDir(), "meta", "gui", "icon.ick") @@ -1267,7 +1361,7 @@ d := s.daemon(c) // have an *in*active foo in the system - info := s.mkInstalledInState(c, d, "foo", "bar", "v1", 10, true, "") + info := s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, "") // NO ICON! err := os.RemoveAll(filepath.Join(info.MountDir(), "meta", "gui", "icon.svg")) @@ -1297,14 +1391,14 @@ } func (s *apiSuite) TestInstall(c *check.C) { - calledFlags := snappy.InstallFlags(42) + calledFlags := snapstate.Flags(42) installQueue := []string{} snapstateGet = func(s *state.State, name string, snapst *snapstate.SnapState) error { // we have ubuntu-core return nil } - snapstateInstall = func(s *state.State, name, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { + snapstateInstall = func(s *state.State, name, channel string, userID int, flags snapstate.Flags) (*state.TaskSet, error) { calledFlags = flags installQueue = append(installQueue, name) @@ -1339,7 +1433,7 @@ st.Lock() c.Check(chg.Status(), check.Equals, state.DoneStatus) - c.Check(calledFlags, check.Equals, snappy.DoInstallGC) + c.Check(calledFlags, check.Equals, snapstate.Flags(0)) c.Check(err, check.IsNil) c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) c.Check(chg.Kind(), check.Equals, "install-snap") @@ -1347,7 +1441,7 @@ } func (s *apiSuite) TestRefresh(c *check.C) { - calledFlags := snappy.InstallFlags(42) + calledFlags := snapstate.Flags(42) calledUserID := 0 installQueue := []string{} @@ -1355,7 +1449,7 @@ // we have ubuntu-core return nil } - snapstateUpdate = func(s *state.State, name, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { + snapstateUpdate = func(s *state.State, name, channel string, userID int, flags snapstate.Flags) (*state.TaskSet, error) { calledFlags = flags calledUserID = userID installQueue = append(installQueue, name) @@ -1377,7 +1471,7 @@ summary, _, err := inst.dispatch()(inst, st) c.Check(err, check.IsNil) - c.Check(calledFlags, check.Equals, snappy.DoInstallGC) + c.Check(calledFlags, check.Equals, snapstate.Flags(0)) c.Check(calledUserID, check.Equals, 17) c.Check(err, check.IsNil) c.Check(installQueue, check.DeepEquals, []string{"some-snap"}) @@ -1391,7 +1485,7 @@ // pretend we do not have a state for ubuntu-core return state.ErrNoState } - snapstateInstall = func(s *state.State, name, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { + snapstateInstall = func(s *state.State, name, channel string, userID int, flags snapstate.Flags) (*state.TaskSet, error) { t1 := s.NewTask("fake-install-snap", name) t2 := s.NewTask("fake-install-snap", "second task is just here so that we can check that the wait is correctly added to all tasks") installQueue = append(installQueue, t1, t2) @@ -1440,7 +1534,7 @@ // pretend we do not have a state for ubuntu-core return state.ErrNoState } - snapstateInstall = func(s *state.State, name, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { + snapstateInstall = func(s *state.State, name, channel string, userID int, flags snapstate.Flags) (*state.TaskSet, error) { t1 := s.NewTask("fake-install-snap", name) t2 := s.NewTask("fake-install-snap", "second task is just here so that we can check that the wait is correctly added to all tasks") installQueue = append(installQueue, t1, t2) @@ -1472,7 +1566,7 @@ return nil } - snapstateInstall = func(s *state.State, name, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { + snapstateInstall = func(s *state.State, name, channel string, userID int, flags snapstate.Flags) (*state.TaskSet, error) { t := s.NewTask("fake-install-snap-error", "Install task") return state.NewTaskSet(t), nil } @@ -1506,9 +1600,10 @@ } func (s *apiSuite) TestInstallLeaveOld(c *check.C) { - calledFlags := snappy.InstallFlags(42) + c.Skip("temporarily dropped half-baked support while sorting out flag mess") + calledFlags := snapstate.Flags(42) - snapstateInstall = func(s *state.State, name, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { + snapstateInstall = func(s *state.State, name, channel string, userID int, flags snapstate.Flags) (*state.TaskSet, error) { calledFlags = flags t := s.NewTask("fake-install-snap", "Doing a fake install") @@ -1527,14 +1622,14 @@ _, _, err := inst.dispatch()(inst, st) c.Assert(err, check.IsNil) - c.Check(calledFlags, check.Equals, snappy.InstallFlags(0)) + c.Check(calledFlags, check.Equals, snapstate.Flags(0)) c.Check(err, check.IsNil) } func (s *apiSuite) TestInstallDevMode(c *check.C) { - var calledFlags snappy.InstallFlags + var calledFlags snapstate.Flags - snapstateInstall = func(s *state.State, name, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { + snapstateInstall = func(s *state.State, name, channel string, userID int, flags snapstate.Flags) (*state.TaskSet, error) { calledFlags = flags t := s.NewTask("fake-install-snap", "Doing a fake install") @@ -1554,8 +1649,8 @@ _, _, err := inst.dispatch()(inst, st) c.Check(err, check.IsNil) - // DevMode was converted to the snappy.DeveloperMode flag - c.Check(calledFlags&snappy.DeveloperMode, check.Equals, snappy.DeveloperMode) + // DevMode was converted to the snapstate.DevMode flag + c.Check(calledFlags&snapstate.DevMode, check.Equals, snapstate.Flags(snapstate.DevMode)) } func snapList(rawSnaps interface{}) []map[string]interface{} { @@ -2307,6 +2402,7 @@ func setupChanges(st *state.State) []string { chg1 := st.NewChange("install", "install...") + chg1.Set("snap-names", []string{"funky-snap-name"}) t1 := st.NewTask("download", "1...") t2 := st.NewTask("activate", "2...") chg1.AddAll(state.NewTaskSet(t1, t2)) @@ -2428,6 +2524,35 @@ c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"remove","summary":"remove..","status":"Error","tasks":\[{"id":"\w+","kind":"unlink","summary":"1...","status":"Error","log":\["2016-04-21T01:02:03Z ERROR rm failed"],"progress":{"done":1,"total":1},"spawn-time":"2016-04-21T01:02:03Z","ready-time":"2016-04-21T01:02:03Z"}.*],"ready":true,"err":"[^"]+".*`) } +func (s *apiSuite) TestStateChangesForSnapName(c *check.C) { + restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) + defer restore() + + // Setup + d := newTestDaemon(c) + st := d.overlord.State() + st.Lock() + setupChanges(st) + st.Unlock() + + // Execute + req, err := http.NewRequest("GET", "/v2/changes?for=funky-snap-name&select=all", nil) + c.Assert(err, check.IsNil) + rsp := getChanges(stateChangesCmd, req, nil).(*resp) + + // Verify + c.Check(rsp.Type, check.Equals, ResponseTypeSync) + c.Check(rsp.Status, check.Equals, http.StatusOK) + c.Assert(rsp.Result, check.FitsTypeOf, []*changeInfo(nil)) + + res := rsp.Result.([]*changeInfo) + c.Assert(res, check.HasLen, 1) + c.Check(res[0].Kind, check.Equals, `install`) + + _, err = rsp.MarshalJSON() + c.Assert(err, check.IsNil) +} + func (s *apiSuite) TestStateChange(c *check.C) { restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC)) defer restore() diff -Nru snapd-2.0.5/daemon/daemon.go snapd-2.0.8/daemon/daemon.go --- snapd-2.0.5/daemon/daemon.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/daemon/daemon.go 2016-06-08 05:58:01.000000000 +0000 @@ -30,16 +30,17 @@ "github.com/gorilla/mux" "gopkg.in/tomb.v2" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/notifications" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/overlord" - "github.com/ubuntu-core/snappy/overlord/auth" - "github.com/ubuntu-core/snappy/store" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/notifications" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/overlord" + "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/store" ) // A Daemon listens for requests and routes them to the right command type Daemon struct { + Version string overlord *overlord.Overlord listener net.Listener tomb tomb.Tomb diff -Nru snapd-2.0.5/daemon/daemon_test.go snapd-2.0.8/daemon/daemon_test.go --- snapd-2.0.5/daemon/daemon_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/daemon/daemon_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,7 +27,7 @@ "github.com/gorilla/mux" "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/overlord/auth" + "github.com/snapcore/snapd/overlord/auth" ) // Hook up check.v1 into the "go test" runner diff -Nru snapd-2.0.5/daemon/response.go snapd-2.0.8/daemon/response.go --- snapd-2.0.5/daemon/response.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/daemon/response.go 2016-06-08 05:58:01.000000000 +0000 @@ -29,9 +29,9 @@ "github.com/gorilla/websocket" - "github.com/ubuntu-core/snappy/asserts" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/notifications" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/notifications" ) // ResponseType is the response type diff -Nru snapd-2.0.5/daemon/snap.go snapd-2.0.8/daemon/snap.go --- snapd-2.0.5/daemon/snap.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/daemon/snap.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,9 +26,9 @@ "path/filepath" "time" - "github.com/ubuntu-core/snappy/overlord/snapstate" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" ) var errNoSnap = errors.New("no snap installed") @@ -111,6 +111,13 @@ return about, firstErr } +func effectiveConfinement(snapst *snapstate.SnapState) snap.ConfinementType { + if snapst.DevMode() { + return snap.DevmodeConfinement + } + return snap.StrictConfinement +} + func mapLocal(localSnap *snap.Info, snapst *snapstate.SnapState) map[string]interface{} { status := "installed" if snapst.Active { @@ -130,6 +137,11 @@ "summary": localSnap.Summary(), "type": string(localSnap.Type), "version": localSnap.Version, + "channel": localSnap.Channel, + "confinement": localSnap.Confinement, + "devmode": snapst.DevMode(), + "trymode": snapst.TryMode(), + "private": localSnap.Private, } } @@ -139,6 +151,11 @@ status = "priced" } + confinement := remoteSnap.Confinement + if confinement == "" { + confinement = snap.StrictConfinement + } + result := map[string]interface{}{ "description": remoteSnap.Description(), "developer": remoteSnap.Developer, @@ -151,6 +168,9 @@ "summary": remoteSnap.Summary(), "type": string(remoteSnap.Type), "version": remoteSnap.Version, + "channel": remoteSnap.Channel, + "private": remoteSnap.Private, + "confinement": confinement, } if len(remoteSnap.Prices) > 0 { diff -Nru snapd-2.0.5/debian/changelog snapd-2.0.8/debian/changelog --- snapd-2.0.5/debian/changelog 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/changelog 2016-06-08 05:58:01.000000000 +0000 @@ -1,3 +1,99 @@ +snapd (2.0.8) xenial; urgency=medium + + * New upstream release: LP: #1589534 + - debian: make `snap refresh` times more random (LP: #1537793) + - cmd: ExecInCoreSnap looks in "core" snap first, and only in + "ubuntu-core" snap if rev>125. + - cmd/snap: have 'snap list' display helper message on stderr + (LP: #1587445) + - snap: make app names more restrictive. + + -- Michael Vogt Wed, 08 Jun 2016 07:56:58 +0200 + +snapd (2.0.7) xenial; urgency=medium + + * New upstream release: LP: #1589534 + - debian: do not ship /etc/ld.so.conf.d/snappy.conf (LP: #1589006) + - debian: fix snapd.refresh.service install and usage (LP: #1588977) + - ovlerlord/state: actually support task setting themself as + done/undone + - snap: do not use "." import in revision_test.go, as this breaks + gccgo-6 (fix build failure on powerpc) + - interfaces: add fcitx and mozc input methods to unity7 + - interfaces: add global gsettings interfaces + - interfaces: autoconnect home and doc updates (LP: #1588886) + - integration-tests: remove + abortSuite.TestAbortWithValidIdInDoingStatus + - many: adding backward compatible code to upgrade SnapSetup.Flags + - overlord/snapstate: handle sideloading over an old sideloaded snap + without panicing + - interfaces: add socketcall() to the network/network-bind + interfaces (LP: #1588100) + - overlord/snapstate,snappy: move over CanRemoveThis moves over the + CanRemove check to snapstate itself.overlord/snapstate + - snappy: move over CanRemove + - overlord/snapstate,snappy: move over CopyData and Remove*Data code + + -- Michael Vogt Mon, 06 Jun 2016 16:35:50 +0200 + +snapd (2.0.6) xenial; urgency=medium + + * New upstream release: LP: #1588052: + - many: repository moved to snapcore/snapd + - debian: add transitional pkg for the github location change + - snap: ensure `snap try` work with relative paths + - debian: drop run/build dependency on lsb-release + - asserts/tool: gpg key pair manager + - many: add new snap-exec + - many: implement `snap refresh --list` and `snap refresh` + - snap: add parsing support for hooks. + - many: add the cups interface + - interfaces: misc policy fixes (LP: #1583794) + - many: add `snap try` + - interfaces: allow using sysctl and scmp_sys_resolver for parsing + kernel logs + - debian: make snapd get its environ from /etc/environment + - daemon,client,snap: revisions are now strings + - interfaces: allow access to new ibus abstract socket path + LP: #1580463 + - integration-tests: add remove tests + - asserts: stronger crypto choices and follow better latest designs + - snappy,daemon: hollow out more of snappy (either removing or not + exporting stuff on its way out), snappy/gadget.go is gone + - asserts: rename device-serial to serial + - asserts: rename identity to account (and username access) + - integration-tests: add changes tests + - backend: add tests for environment wrapper generation + - interfaces/builtin: add location-control interface + - overlord/snapstate: move over check snap logic from snappy + - release: use os-release instead of lsb-release for cross-distro + use + - asserts: allow empty snap-name for snap-declaration + - interfaces/builtin,docs,snap: add the pulseaudio interface + - many: add support for an environment map inside snap.yaml + - overlord/snapstate: increase robustness of doLinkSnap/undoLinkSnap + with sanity unit tests + - snap: parse epoch property + - snappy: do nothing in SetNextBoot when running on classic + - snap: validate snap type + - integration-tests: extend find command tests + - asserts: extend tests to cover mandatory and empty headers + - tests: stop the update-pot check in run-checks + - snap: parse confinement property. + - store: change applyUbuntuStoreHeaders to not take accept, and to + take a channel + - many: struct-based revisions, new representation + - interfaces: remove 'audit deny' rules from network_control.go + - interfaces: add com.canonical.UrlLauncher.XdgOpen to unity7 + interface + - interfaces: firewall-control can access xtables lock file + - interfaces: allow unity7 AppMenu + - interfaces: allow unity7 launcher API + - interfaces/builtin: add location-observe interface + - snap: fixed snap empty list text LP: #1587445 + + -- Michael Vogt Thu, 02 Jun 2016 08:23:50 +0200 + snapd (2.0.5) xenial; urgency=medium * New upstream release: LP: #1583085 diff -Nru snapd-2.0.5/debian/control snapd-2.0.8/debian/control --- snapd-2.0.5/debian/control 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/control 2016-06-08 05:58:01.000000000 +0000 @@ -4,7 +4,7 @@ Maintainer: Ubuntu Developers Build-Depends: bash-completion, debhelper (>= 9), - dh-golang, + dh-golang (>=1.7), dh-systemd, fakeroot, gettext, @@ -23,26 +23,32 @@ golang-yaml.v2-dev, golang-gopkg-tomb.v2-dev, golang-websocket-dev, - lsb-release, python3, python3-markdown, squashfs-tools Standards-Version: 3.9.7 -Homepage: https://github.com/ubuntu-core/snappy -Vcs-Browser: https://github.com/ubuntu-core/snappy -Vcs-Git: https://github.com/ubuntu-core/snappy.git +Homepage: https://github.com/snapcore/snapd +Vcs-Browser: https://github.com/snapcore/snapd +Vcs-Git: https://github.com/snapcore/snapd.git Package: golang-github-ubuntu-core-snappy-dev Architecture: all -Breaks: golang-snappy-dev (<< 1.7.3+20160303ubuntu4) -Replaces: golang-snappy-dev (<< 1.7.3+20160303ubuntu4) +Depends: ${misc:Depends}, golang-github-snapcore-snapd-dev +Section: oldlibs +Description: transitional dummy package + This is a transitional dummy package. It can safely be removed. + +Package: golang-github-snapcore-snapd-dev +Architecture: all +Breaks: golang-snappy-dev (<< 1.7.3+20160303ubuntu4), golang-github-ubuntu-core-snappy-dev (<< 2.0.6) +Replaces: golang-snappy-dev (<< 1.7.3+20160303ubuntu4), golang-github-ubuntu-core-snappy-dev (<< 2.0.6) Depends: ${misc:Depends} Description: snappy development go packages. Use these to use the snappy API. Package: snapd Architecture: any -Depends: ${misc:Depends}, ${shlibs:Depends}, adduser, lsb-release, +Depends: ${misc:Depends}, ${shlibs:Depends}, adduser, squashfs-tools, ubuntu-core-launcher (>= 1.0.23), Replaces: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9) Breaks: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9) diff -Nru snapd-2.0.5/debian/copyright snapd-2.0.8/debian/copyright --- snapd-2.0.5/debian/copyright 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/copyright 2016-06-08 05:58:01.000000000 +0000 @@ -1,6 +1,6 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: snappy -Source: https://github.com/ubuntu-core/snappy +Source: https://github.com/snapcore/snapd Files: * Copyright: Copyright (C) 2014,2015 Canonical, Ltd. diff -Nru snapd-2.0.5/debian/golang-github-snapcore-snapd-dev.install snapd-2.0.8/debian/golang-github-snapcore-snapd-dev.install --- snapd-2.0.5/debian/golang-github-snapcore-snapd-dev.install 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/debian/golang-github-snapcore-snapd-dev.install 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1 @@ +/usr/share/gocode diff -Nru snapd-2.0.5/debian/golang-github-ubuntu-core-snappy-dev.install snapd-2.0.8/debian/golang-github-ubuntu-core-snappy-dev.install --- snapd-2.0.5/debian/golang-github-ubuntu-core-snappy-dev.install 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/golang-github-ubuntu-core-snappy-dev.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -/usr/share/gocode diff -Nru snapd-2.0.5/debian/rules snapd-2.0.8/debian/rules --- snapd-2.0.5/debian/rules 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/rules 2016-06-08 05:58:01.000000000 +0000 @@ -3,9 +3,15 @@ #export DH_VERBOSE=1 export DH_OPTIONS -export DH_GOPKG := github.com/ubuntu-core/snappy +export DH_GOPKG := github.com/snapcore/snapd #export DEB_BUILD_OPTIONS=nocheck export DH_GOLANG_EXCLUDES=integration-tests +export DH_GOLANG_GO_GENERATE=1 + +# we need the builddir; is there a simpler way to get it? +BUILDDIR:=${CURDIR}/obj-$(shell dpkg-architecture -qDEB_TARGET_GNU_TYPE) + +export PATH:=${PATH}:${CURDIR} RELEASE = $(shell lsb_release -c -s) @@ -21,50 +27,44 @@ dh_systemd_enable \ -pubuntu-core-snapd-units \ snapd.firstboot.service - # we want the autopilot timer enabled by default -# dh_systemd_enable \ + # we want the auto-update timer enabled by default + dh_systemd_enable \ -psnapd \ - snappy-autopilot.timer - # but the autopilot service disabled -# dh_systemd_enable \ + snapd.refresh.timer + # but the auto-update service disabled + dh_systemd_enable \ --no-enable \ -psnapd \ - snappy-autopilot.service + snapd.refresh.service # enable snapd dh_systemd_enable \ -psnapd \ snapd.socket + dh_systemd_enable \ + -psnapd \ + snapd.service override_dh_systemd_start: # start boot-ok dh_systemd_start \ -pubuntu-core-snapd-units \ snapd.boot-ok.service - # we want to start the autopilot timer -# dh_systemd_start \ + # we want to start the auto-update timer + dh_systemd_start \ -psnapd \ - snappy-autopilot.timer + snapd.refresh.timer # but not start the service -# dh_systemd_start \ + dh_systemd_start \ --no-start \ -psnapd \ - snappy-autopilot.service + snapd.refresh.service # start snapd dh_systemd_start \ -psnapd \ snapd.socket - -# we need the builddir; is there a simpler way to get it? -BUILDDIR:=${CURDIR}/obj-$(shell dpkg-architecture -qDEB_TARGET_GNU_TYPE) - -override_dh_auto_build: - dh_auto_build - # this will update the i18n stuff using our build-in xgettext-go - if [ "$(RELEASE)" = "vivid" ]; then\ - GOPATH=${BUILDDIR} ./update-pot;\ - else\ - GOPATH=${BUILDDIR} go generate ./i18n;\ - fi; + dh_systemd_start \ + -psnapd \ + snapd.service override_dh_auto_install: snap.8 dh_auto_install -O--buildsystem=golang diff -Nru snapd-2.0.5/debian/snapd.dirs snapd-2.0.8/debian/snapd.dirs --- snapd-2.0.5/debian/snapd.dirs 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/snapd.dirs 2016-06-08 05:58:01.000000000 +0000 @@ -2,5 +2,6 @@ var/lib/snapd/snaps var/lib/snapd/lib/gl var/lib/snapd/desktop +var/lib/snapd/environment usr/lib/snapd var/snap diff -Nru snapd-2.0.5/debian/snapd.install snapd-2.0.8/debian/snapd.install --- snapd-2.0.5/debian/snapd.install 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/snapd.install 2016-06-08 05:58:01.000000000 +0000 @@ -1,5 +1,6 @@ /usr/bin/snap /usr/bin/snapd usr/lib/snapd +/usr/bin/snap-exec usr/lib/snapd data/completion/snap /usr/share/bash-completion/completions/ # i18n stuff ../../share /usr @@ -7,14 +8,12 @@ etc/profile.d # etc/X11/Xsession.d will add to XDG_DATA_DIRS so that we have .desktop support etc/X11 -# etc/ld.so.conf.d contains the SNAP_LIBRARY_PATH directories -etc/ld.so.conf.d # systemd stuff # auto-update -debian/*.timer /lib/systemd/system/ -debian/snappy-autopilot.service /lib/systemd/system/ +debian/snapd.refresh.timer /lib/systemd/system/ +debian/snapd.refresh.service /lib/systemd/system/ # snapd debian/*.socket /lib/systemd/system/ debian/snapd.service /lib/systemd/system/ diff -Nru snapd-2.0.5/debian/snapd.maintscript snapd-2.0.8/debian/snapd.maintscript --- snapd-2.0.5/debian/snapd.maintscript 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/snapd.maintscript 2016-06-08 05:58:01.000000000 +0000 @@ -1,3 +1,5 @@ # we used to ship a custom grub config that is no longer needed rm_conffile /etc/grub.d/09_snappy 1.7.3ubuntu1 +# keep mount point busy +rm_conffile /etc/ld.so.conf.d/snappy.conf 2.0.7~ \ No newline at end of file diff -Nru snapd-2.0.5/debian/snapd.postinst snapd-2.0.8/debian/snapd.postinst --- snapd-2.0.5/debian/snapd.postinst 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/debian/snapd.postinst 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + +#DEBHELPER# + + +case "$1" in + configure) + # ensure /var/lib/snapd/lib/gl is cleared + if dpkg --compare-versions "$2" lt-nl "2.0.7"; then + ldconfig + fi +esac diff -Nru snapd-2.0.5/debian/snapd.refresh.service snapd-2.0.8/debian/snapd.refresh.service --- snapd-2.0.5/debian/snapd.refresh.service 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/debian/snapd.refresh.service 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,9 @@ +[Unit] +Description=Automatically refresh installed snaps +After=network.target +Documentation=man:snap(1) + +# FIXME: add auto-reboot on devices +[Service] +Type=oneshot +ExecStart=/usr/bin/snap refresh diff -Nru snapd-2.0.5/debian/snapd.refresh.timer snapd-2.0.8/debian/snapd.refresh.timer --- snapd-2.0.5/debian/snapd.refresh.timer 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/debian/snapd.refresh.timer 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,11 @@ +[Unit] +Description=Timer to automatically refresh installed snaps + +[Timer] +OnCalendar=23,05,11,17:00 +RandomizedDelaySec=6h +AccuracySec=10min +Persistent=true + +[Install] +WantedBy=multi-user.target diff -Nru snapd-2.0.5/debian/snapd.service snapd-2.0.8/debian/snapd.service --- snapd-2.0.5/debian/snapd.service 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/snapd.service 2016-06-08 05:58:01.000000000 +0000 @@ -7,6 +7,7 @@ [Service] ExecStart=/usr/lib/snapd/snapd +EnvironmentFile=/etc/environment [Install] WantedBy=multi-user.target diff -Nru snapd-2.0.5/debian/snappy-autopilot.service snapd-2.0.8/debian/snappy-autopilot.service --- snapd-2.0.5/debian/snappy-autopilot.service 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/snappy-autopilot.service 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -[Unit] -Description=Ubuntu Core Snappy AutoUpdate -After=network.target - -[Service] -Type=simple -# FIXME: add auto-reboot on devices -ExecStart=/usr/bin/snap refresh diff -Nru snapd-2.0.5/debian/snappy-autopilot.timer snapd-2.0.8/debian/snappy-autopilot.timer --- snapd-2.0.5/debian/snappy-autopilot.timer 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/snappy-autopilot.timer 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -[Unit] -Description=Ubuntu Core Snappy AutoUpdate - -[Timer] -OnCalendar=23,05,11,17:00 -RandomizedDelaySec=6h -AccuracySec=10min -Persistent=true -Unit=snappy-autopilot.service - -[Install] -WantedBy=multi-user.target diff -Nru snapd-2.0.5/debian/tests/integrationtests snapd-2.0.8/debian/tests/integrationtests --- snapd-2.0.5/debian/tests/integrationtests 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/debian/tests/integrationtests 2016-06-08 05:58:01.000000000 +0000 @@ -4,39 +4,45 @@ set -ex +# for these tests, run snap and snapd from outside of the core snap +sudo mkdir -p /etc/systemd/system/snapd.service.d/ +cat <'+message, "utf8")) + return + +def run(): + server_address = ('', 8081) + httpd = HTTPServer(server_address, testRequestHandler) + httpd.serve_forever() + +if __name__ == '__main__': + sys.exit(run()) diff -Nru snapd-2.0.5/integration-tests/data/snaps/log-observe-consumer/meta/snap.yaml snapd-2.0.8/integration-tests/data/snaps/log-observe-consumer/meta/snap.yaml --- snapd-2.0.5/integration-tests/data/snaps/log-observe-consumer/meta/snap.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/data/snaps/log-observe-consumer/meta/snap.yaml 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,10 @@ +name: log-observe-consumer +version: 1.0 +summary: Basic log-observe consumer snap +description: A basic snap declaring a plug on log-observe + +apps: + log-observe-consumer: + command: bin/consumer + daemon: simple + plugs: [network-bind, log-observe] diff -Nru snapd-2.0.5/integration-tests/data/snaps/network-consumer/bin/consumer snapd-2.0.8/integration-tests/data/snaps/network-consumer/bin/consumer --- snapd-2.0.5/integration-tests/data/snaps/network-consumer/bin/consumer 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/data/snaps/network-consumer/bin/consumer 2016-06-08 05:58:01.000000000 +0000 @@ -4,7 +4,6 @@ from socket import timeout import urllib.request - if len(sys.argv) > 1: url = sys.argv[1] else: @@ -12,9 +11,12 @@ try: response = urllib.request.urlopen(url, timeout=3) - if '' in response.read().decode('utf-8'): - print('ok') + decoded_response = response.read().decode('utf-8') + if '' in decoded_response: + print(decoded_response.replace('', ''), end="") except urllib.error.URLError as e: print("Error, reason: ", e.reason) + sys.exit(1) except timeout: print("request timeout") + sys.exit(1) diff -Nru snapd-2.0.5/integration-tests/main.go snapd-2.0.8/integration-tests/main.go --- snapd-2.0.5/integration-tests/main.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/main.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,11 +26,11 @@ "os" "strconv" - "github.com/ubuntu-core/snappy/integration-tests/testutils/autopkgtest" - "github.com/ubuntu-core/snappy/integration-tests/testutils/build" - "github.com/ubuntu-core/snappy/integration-tests/testutils/config" - "github.com/ubuntu-core/snappy/integration-tests/testutils/image" - "github.com/ubuntu-core/snappy/integration-tests/testutils/testutils" + "github.com/snapcore/snapd/integration-tests/testutils/autopkgtest" + "github.com/snapcore/snapd/integration-tests/testutils/build" + "github.com/snapcore/snapd/integration-tests/testutils/config" + "github.com/snapcore/snapd/integration-tests/testutils/image" + "github.com/snapcore/snapd/integration-tests/testutils/testutils" ) const ( @@ -77,17 +77,13 @@ "If this flag is used, the image will be updated and then rolled back before running the tests.") outputDir = flag.String("output-dir", defaultOutputDir, "Directory where test artifacts will be stored.") shellOnFail = flag.Bool("shell-fail", false, "Run a shell in the testbed if the suite fails.") - testBuildTags = flag.String("test-build-tags", "", "Build tags to be passed to the go test command") + testBuildTags = flag.String("test-build-tags", "allsnaps", "Build tags to be passed to the go test command") httpProxy = flag.String("http-proxy", "", "HTTP proxy to set in the testbed.") + verbose = flag.Bool("v", false, "Show complete test output") ) flag.Parse() - build.Assets(&build.Config{ - UseSnappyFromBranch: *useSnappyFromBranch, - Arch: *arch, - TestBuildTags: *testBuildTags}) - // TODO: generate the files out of the source tree. --elopio - 2015-07-15 testutils.PrepareTargetDir(dataOutputDir) defer os.RemoveAll(dataOutputDir) @@ -104,9 +100,19 @@ Update: *update, Rollback: *rollback, FromBranch: *useSnappyFromBranch, + Verbose: *verbose, } cfg.Write() + err := build.Assets(&build.Config{ + UseSnappyFromBranch: *useSnappyFromBranch, + Arch: *arch, + TestBuildTags: *testBuildTags}) + if err != nil { + log.Printf("Assets building failed: %s", err) + os.Exit(1) + } + rootPath := testutils.RootPath() test := &autopkgtest.AutoPkgTest{ @@ -122,6 +128,7 @@ "TEST_USER_NAME": os.Getenv("TEST_USER_NAME"), "TEST_USER_PASSWORD": os.Getenv("TEST_USER_PASSWORD"), }, + Verbose: *verbose, } if !remoteTestbed { img := &image.Image{ diff -Nru snapd-2.0.5/integration-tests/manual-tests.md snapd-2.0.8/integration-tests/manual-tests.md --- snapd-2.0.5/integration-tests/manual-tests.md 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/manual-tests.md 2016-06-08 05:58:01.000000000 +0000 @@ -123,3 +123,30 @@ sudo parted -s {dev} unit % print free * Check that the writable partition was resized to occupy all the empty space. + +# Test cups interface by printing something + +1. Using ubuntu classic build and install a simple snap with lpr inside. + + name: lpr + version: 2.1.3-4 + summary: submit files for printing + description: | + lpr submits files for printing. Files named on the command line are sent to + the named printer or the default destination if no destination is specified. + If no files are listed on the command-line, lpr reads the print file from + the standard input. + apps: + lpr: + command: lpr + plugs: [cups] + parts: + lpr: + plugin: nil + stage-packages: [cups-bsd] +2. Ensure that the 'cups' interface is connected to lpr +3. Use /snap/bin/lpr to print a short text file (e.g. the snapcraft file) +4. Ensure that it was added to the queue of the default CUPS printer. This can + be checked in the ubuntu-control-center under the printers applet. Right + click on the default printer and look at the queue. Ensure it contains the + new item. diff -Nru snapd-2.0.5/integration-tests/README.md snapd-2.0.8/integration-tests/README.md --- snapd-2.0.5/integration-tests/README.md 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/README.md 2016-06-08 05:58:01.000000000 +0000 @@ -155,6 +155,40 @@ s.SnappySuite.TearDownTest(c) } +### Classic-only and all-snaps-only tests + +There are some integration tests which only work in classic ubuntu systems, and +some which only work in all-snaps systems. + +These tests are guarded by a build tag. If you develop a test that will work +only in one type of system remember to add corresponding tag to the top of the +file. + +For tests that will run only in a classic system, use: + +``` +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration,!allsnaps +... +``` + +For tests that will run only in an all-snap system, use +``` +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration,!classic +... +``` + +The autopkgtest runner uses a classic testbed so it will exclude the +all-snaps-only tests by passing the classic tag. + +The main.go runner executes by default in an all-snaps system so it will +exclude the classic-only tests by passing the all-snaps tag. If you are +using this runner to execute the tests in a classic system you can run the +right set of tests by passing the flag: + + go run integration-tests/main.go -test-build-tags=classic + ### Tests with reboots `adt-run` supports reboots during tests. A test can request a reboot specifying @@ -202,7 +236,11 @@ This allows to exclude the tests that require a reboot by passing `excludereboots` to the `test-build-tags` flag: - go run integration-tests/main.go -test-build-tags=excludereboots + go run integration-tests/main.go -test-build-tags=excludereboots,classic + +or: + + go run integration-tests/main.go -test-build-tags=excludereboots,allsnaps ### Update-rollback stress test @@ -230,21 +268,6 @@ go run integration-tests/main.go -test-build-tags=lowperformance -### Classic-only tests - -There are certain integration tests which at the moment only work in classic ubuntu systems, for -instance the unity suite, which checks features that currently are only available in desktop systems. - -These tests are guarded by the `classiconly` build tag, the autopkgtest runner is configured to -include it when building the tests' binary. If you develop a test of this type remember to add it at -the top of the file: - -``` -// -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration,classiconly -... -``` - ### Store tests We have marked some of the tests that exercise the different endpoints of the store so that they can be diff -Nru snapd-2.0.5/integration-tests/tests/apparmor_test.go snapd-2.0.8/integration-tests/tests/apparmor_test.go --- snapd-2.0.5/integration-tests/tests/apparmor_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/apparmor_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,11 +23,11 @@ import ( "os" - "github.com/ubuntu-core/snappy/integration-tests/testutils/build" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/data" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/integration-tests/testutils/build" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/data" + "github.com/snapcore/snapd/testutil" "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/integration-tests/tests/apt_test.go snapd-2.0.8/integration-tests/tests/apt_test.go --- snapd-2.0.5/integration-tests/tests/apt_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/apt_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -1,8 +1,8 @@ // -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration +// +build !excludeintegration,!classic /* - * Copyright (C) 2015 Canonical Ltd + * Copyright (C) 2015, 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 @@ -21,8 +21,8 @@ package tests import ( - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/integration-tests/tests/auth_test.go snapd-2.0.8/integration-tests/tests/auth_test.go --- snapd-2.0.5/integration-tests/tests/auth_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/auth_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,9 +26,9 @@ "os/user" "path/filepath" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/testutil" "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/integration-tests/tests/base_test.go snapd-2.0.8/integration-tests/tests/base_test.go --- snapd-2.0.5/integration-tests/tests/base_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/base_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -31,12 +31,13 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/config" - "github.com/ubuntu-core/snappy/integration-tests/testutils/partition" - "github.com/ubuntu-core/snappy/integration-tests/testutils/report" - "github.com/ubuntu-core/snappy/integration-tests/testutils/runner" - "github.com/ubuntu-core/snappy/integration-tests/testutils/wait" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/config" + "github.com/snapcore/snapd/integration-tests/testutils/partition" + "github.com/snapcore/snapd/integration-tests/testutils/report" + "github.com/snapcore/snapd/integration-tests/testutils/runner" + "github.com/snapcore/snapd/integration-tests/testutils/wait" + "github.com/snapcore/snapd/osutil" ) const ( @@ -53,8 +54,14 @@ wait.ForFunction(c, "regular", partition.Mode) if _, err := os.Stat(config.DefaultFileName); err == nil { - cli.ExecCommand(c, "sudo", "systemctl", "stop", "snappy-autopilot.timer") - cli.ExecCommand(c, "sudo", "systemctl", "disable", "snappy-autopilot.timer") + timerName := "snapd.refresh.timer" + // FIXME: compat with old os images, kill once we have released + // a stable OS snap with snapd 2.0.7 + if osutil.FileExists("/lib/systemd/system/snappy-autopilot.service") { + timerName = "snappy-autopilot.timer" + } + cli.ExecCommand(c, "sudo", "systemctl", "stop", timerName) + cli.ExecCommand(c, "sudo", "systemctl", "disable", timerName) cfg, err := config.ReadConfig(config.DefaultFileName) c.Assert(err, check.IsNil, check.Commentf("Error reading config: %v", err)) diff -Nru snapd-2.0.5/integration-tests/tests/grub_test.go snapd-2.0.8/integration-tests/tests/grub_test.go --- snapd-2.0.5/integration-tests/tests/grub_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/grub_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -1,5 +1,5 @@ // -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration +// +build !excludeintegration,!classic /* * Copyright (C) 2016 Canonical Ltd @@ -23,9 +23,9 @@ import ( "fmt" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/partition" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/partition" "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/integration-tests/tests/home_interface_test.go snapd-2.0.8/integration-tests/tests/home_interface_test.go --- snapd-2.0.5/integration-tests/tests/home_interface_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/home_interface_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,8 +25,8 @@ "os" "path/filepath" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/data" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/data" "gopkg.in/check.v1" ) @@ -35,7 +35,8 @@ interfaceSuite: interfaceSuite{ sampleSnaps: []string{data.HomeConsumerSnapName}, slot: "home", - plug: "home-consumer"}}) + plug: "home-consumer", + autoconnect: true}}) type homeInterfaceSuite struct { interfaceSuite diff -Nru snapd-2.0.5/integration-tests/tests/installApp_test.go snapd-2.0.8/integration-tests/tests/installApp_test.go --- snapd-2.0.5/integration-tests/tests/installApp_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/installApp_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,90 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration - -/* - * Copyright (C) 2015, 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 tests - -import ( - "os" - - "github.com/ubuntu-core/snappy/integration-tests/testutils/build" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/data" - "github.com/ubuntu-core/snappy/testutil" - - "gopkg.in/check.v1" -) - -var _ = check.Suite(&installAppSuite{}) - -type installAppSuite struct { - common.SnappySuite -} - -func (s *installAppSuite) TestInstallAppMustPrintPackageInformation(c *check.C) { - snapPath, err := build.LocalSnap(c, data.BasicSnapName) - defer os.Remove(snapPath) - c.Assert(err, check.IsNil, check.Commentf("Error building local snap: %s", err)) - installOutput := common.InstallSnap(c, snapPath) - defer common.RemoveSnap(c, data.BasicSnapName) - - expected := "(?ms)" + - "Name +Version +Rev +Developer\n" + - ".*" + - "^basic +.* *\n" + - ".*" - - c.Assert(installOutput, check.Matches, expected) -} - -func (s *installAppSuite) TestCallSuccessfulBinaryFromInstalledSnap(c *check.C) { - snapPath, err := build.LocalSnap(c, data.BasicBinariesSnapName) - defer os.Remove(snapPath) - c.Assert(err, check.IsNil, check.Commentf("Error building local snap: %s", err)) - common.InstallSnap(c, snapPath) - defer common.RemoveSnap(c, data.BasicBinariesSnapName) - - // Exec command does not fail. - cli.ExecCommand(c, "basic-binaries.success") -} - -func (s *installAppSuite) TestCallFailBinaryFromInstalledSnap(c *check.C) { - c.Skip("port to snapd") - - snapPath, err := build.LocalSnap(c, data.BasicBinariesSnapName) - defer os.Remove(snapPath) - c.Assert(err, check.IsNil, check.Commentf("Error building local snap: %s", err)) - common.InstallSnap(c, snapPath) - defer common.RemoveSnap(c, data.BasicBinariesSnapName) - - _, err = cli.ExecCommandErr("basic-binaries.fail") - c.Assert(err, check.NotNil, check.Commentf("The binary did not fail")) -} - -func (s *installAppSuite) TestInstallUnexistingAppMustPrintError(c *check.C) { - output, err := cli.ExecCommandErr("sudo", "snap", "install", "unexisting.canonical") - - c.Check(err, check.NotNil, - check.Commentf("Trying to install an unexisting snap did not exit with an error")) - c.Assert(string(output), testutil.Contains, - "error: cannot perform the following tasks:\n"+ - "- Download snap \"unexisting.canonical\" from channel \"stable\" (snap not found)\n", - check.Commentf("Wrong error message")) -} diff -Nru snapd-2.0.5/integration-tests/tests/installDesktopApp_test.go snapd-2.0.8/integration-tests/tests/installDesktopApp_test.go --- snapd-2.0.5/integration-tests/tests/installDesktopApp_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/installDesktopApp_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,58 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration - -/* - * Copyright (C) 2015, 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 tests - -import ( - "io/ioutil" - "os" - "path/filepath" - - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/testutil" - - "github.com/ubuntu-core/snappy/integration-tests/testutils/build" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/data" - - "gopkg.in/check.v1" -) - -var _ = check.Suite(&installDesktopAppSuite{}) - -type installDesktopAppSuite struct { - common.SnappySuite -} - -func (s *installDesktopAppSuite) TestInstallsDesktopFile(c *check.C) { - snapPath, err := build.LocalSnap(c, data.BasicDesktopSnapName) - defer os.Remove(snapPath) - c.Assert(err, check.IsNil, check.Commentf("Error building local snap: %s", err)) - common.InstallSnap(c, snapPath) - defer common.RemoveSnap(c, data.BasicDesktopSnapName) - - content, err := ioutil.ReadFile(filepath.Join(dirs.SnapDesktopFilesDir, "basic-desktop_echo.desktop")) - c.Assert(err, check.IsNil) - c.Assert(string(content), testutil.Contains, `[Desktop Entry] -Name=Echo -Comment=It echos stuff -Exec=/snap/bin/basic-desktop.echo -`) -} diff -Nru snapd-2.0.5/integration-tests/tests/interfaces_test.go snapd-2.0.8/integration-tests/tests/interfaces_test.go --- snapd-2.0.5/integration-tests/tests/interfaces_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/interfaces_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,9 +24,9 @@ "fmt" "os" - "github.com/ubuntu-core/snappy/integration-tests/testutils/build" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/build" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/integration-tests/tests/list_test.go snapd-2.0.8/integration-tests/tests/list_test.go --- snapd-2.0.5/integration-tests/tests/list_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/list_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -1,8 +1,8 @@ // -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration +// +build !excludeintegration,!classic /* - * Copyright (C) 2015 Canonical Ltd + * Copyright (C) 2015, 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 @@ -22,10 +22,14 @@ import ( "fmt" + "io/ioutil" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/partition" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/config" + "github.com/snapcore/snapd/integration-tests/testutils/partition" + "github.com/snapcore/snapd/integration-tests/testutils/refresh" + "github.com/snapcore/snapd/integration-tests/testutils/store" "gopkg.in/check.v1" ) @@ -42,9 +46,9 @@ listOutput := cli.ExecCommand(c, "snap", "list") expected := "(?ms)" + - "Name +Version +Rev +Developer *\n" + + "Name +Version +Rev +Developer +Notes *\n" + ".*" + - fmt.Sprintf("^%s +.* +%s + [0-9]+ +(canonical|sideload) *\n", partition.OSSnapName(c), verRegexp) + + fmt.Sprintf("^%s +.* +%s +[0-9]+ +canonical +- *\n", partition.OSSnapName(c), verRegexp) + ".*" c.Assert(listOutput, check.Matches, expected) } @@ -57,9 +61,44 @@ listOutput := cli.ExecCommand(c, "snap", "list") expected := "(?ms)" + - "Name +Version +Rev +Developer *\n" + + "Name +Version +Rev +Developer +Notes *\n" + ".*" + - "^hello-world +(\\d+)(\\.\\d+)* +[0-9]+ +.* *\n" + + "^hello-world +(\\d+)(\\.\\d+)* +[0-9]+ +\\S+ +-\n" + + ".*" + + c.Assert(listOutput, check.Matches, expected) +} + +func (s *listSuite) TestRefreshListSimple(c *check.C) { + snap := "hello-world" + + common.InstallSnap(c, snap) + s.AddCleanup(func() { + common.RemoveSnap(c, snap) + }) + + // fake refresh + blobDir, err := ioutil.TempDir("", "snap-fake-store-blobs-") + fakeStore := store.NewStore(blobDir) + err = fakeStore.Start() + c.Assert(err, check.IsNil) + defer fakeStore.Stop() + + env := fmt.Sprintf(`SNAPPY_FORCE_CPI_URL=%s`, fakeStore.URL()) + cfg, _ := config.ReadConfig(config.DefaultFileName) + + tearDownSnapd(c) + defer setUpSnapd(c, cfg.FromBranch, "") + setUpSnapd(c, cfg.FromBranch, env) + defer tearDownSnapd(c) + + refresh.MakeFakeRefreshForSnap(c, snap, blobDir, refresh.NoOp) + + listOutput := cli.ExecCommand(c, "snap", "refresh", "--list") + expected := "(?ms)" + + "Name +Version +Developer +Notes +Summary *\n" + + ".*" + + "^hello-world +(\\d+)(\\.\\d+)\\+fake1 +canonical +- .*\n" + ".*" c.Assert(listOutput, check.Matches, expected) diff -Nru snapd-2.0.5/integration-tests/tests/login_test.go snapd-2.0.8/integration-tests/tests/login_test.go --- snapd-2.0.5/integration-tests/tests/login_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/login_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -1,5 +1,5 @@ // -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration +// +build !excludeintegration,!classic /* * Copyright (C) 2015, 2016 Canonical Ltd @@ -34,9 +34,9 @@ "gopkg.in/check.v1" "github.com/kr/pty" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/wait" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/wait" ) const ( diff -Nru snapd-2.0.5/integration-tests/tests/log_observe_interface_test.go snapd-2.0.8/integration-tests/tests/log_observe_interface_test.go --- snapd-2.0.5/integration-tests/tests/log_observe_interface_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/log_observe_interface_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,51 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration + +/* + * 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 tests + +import ( + "gopkg.in/check.v1" + + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/data" +) + +var _ = check.Suite(&logObserveInterfaceSuite{ + interfaceSuite: interfaceSuite{ + sampleSnaps: []string{data.LogObserveConsumerSnapName, data.NetworkConsumerSnapName}, + slot: "log-observe", + plug: "log-observe-consumer"}}) + +type logObserveInterfaceSuite struct { + interfaceSuite +} + +func (s *logObserveInterfaceSuite) TestConnectedPlugAllowsLogObserve(c *check.C) { + cli.ExecCommand(c, "sudo", "snap", "connect", + s.plug+":"+s.slot, "ubuntu-core:"+s.slot) + + output := cli.ExecCommand(c, "network-consumer", "http://127.0.0.1:8081") + c.Assert(output, check.Equals, "ok\n") +} + +func (s *logObserveInterfaceSuite) TestDisconnectedPlugDisablesLogObserve(c *check.C) { + output := cli.ExecCommand(c, "network-consumer", "http://127.0.0.1:8081") + c.Assert(output, check.Equals, "error accessing log\n") +} diff -Nru snapd-2.0.5/integration-tests/tests/network_bind_interface_test.go snapd-2.0.8/integration-tests/tests/network_bind_interface_test.go --- snapd-2.0.5/integration-tests/tests/network_bind_interface_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/network_bind_interface_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,8 +23,8 @@ import ( "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/data" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/data" ) const providerURL = "http://127.0.0.1:8081" @@ -50,6 +50,6 @@ output = cli.ExecCommand(c, "snap", "interfaces") c.Assert(output, check.Matches, disconnectedPattern(s.slot, s.plug)) - output = cli.ExecCommand(c, "network-consumer", providerURL) - c.Assert(output, check.Equals, "request timeout\n") + output, err := cli.ExecCommandErr("network-consumer", providerURL) + c.Assert(err, check.NotNil) } diff -Nru snapd-2.0.5/integration-tests/tests/network_interface_test.go snapd-2.0.8/integration-tests/tests/network_interface_test.go --- snapd-2.0.5/integration-tests/tests/network_interface_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/network_interface_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -21,8 +21,8 @@ package tests import ( - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/data" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/data" "gopkg.in/check.v1" ) @@ -50,6 +50,6 @@ output = cli.ExecCommand(c, "snap", "interfaces") c.Assert(output, check.Matches, disconnectedPattern(s.slot, s.plug)) - output = cli.ExecCommand(c, "network-consumer", providerURL) - c.Assert(output, check.Equals, "Error, reason: [Errno 13] Permission denied\n") + output, err := cli.ExecCommandErr("network-consumer", providerURL) + c.Check(err, check.NotNil) } diff -Nru snapd-2.0.5/integration-tests/tests/service_test.go snapd-2.0.8/integration-tests/tests/service_test.go --- snapd-2.0.5/integration-tests/tests/service_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/service_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,12 +25,12 @@ "os" "regexp" - "github.com/ubuntu-core/snappy/integration-tests/testutils/build" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/data" - "github.com/ubuntu-core/snappy/integration-tests/testutils/wait" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/integration-tests/testutils/build" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/data" + "github.com/snapcore/snapd/integration-tests/testutils/wait" + "github.com/snapcore/snapd/testutil" "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/integration-tests/tests/snap_abort_test.go snapd-2.0.8/integration-tests/tests/snap_abort_test.go --- snapd-2.0.5/integration-tests/tests/snap_abort_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_abort_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,145 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration + +/* + * 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 tests + +import ( + "fmt" + "path/filepath" + "regexp" + + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + + "gopkg.in/check.v1" +) + +const nothingPendingErrorTpl = "error: cannot abort change %s with nothing pending\n" + +var _ = check.Suite(&abortSuite{}) + +type abortSuite struct { + common.SnappySuite +} + +// SNAP_ABORT_001: --help - print detailed help text for the abort command +func (s *abortSuite) TestAbortShowHelp(c *check.C) { + expected := "(?ms)" + + "^Usage:\n" + + ` snap \[OPTIONS\] abort.*\n` + + "\n^The abort command .*\n" + + "^Help Options:\n" + + "^ -h, --help +Show this help message\n" + + ".*" + + actual := cli.ExecCommand(c, "snap", "abort", "--help") + + c.Assert(actual, check.Matches, expected) +} + +// SNAP_ABORT_002: with invalid id +func (s *abortSuite) TestAbortWithInvalidId(c *check.C) { + id := "10000000" + + expected := fmt.Sprintf(`error: cannot find change with id "%s"\n`, id) + actual, err := cli.ExecCommandErr("sudo", "snap", "abort", id) + + c.Assert(err, check.NotNil) + c.Assert(actual, check.Matches, expected) +} + +// SNAP_ABORT_004: with valid id - error +func (s *abortSuite) TestAbortWithValidIdInErrorStatus(c *check.C) { + snapName := "hello-world" + + provokeTaskError(c, snapName) + + id := getErrorID(snapName) + c.Assert(id, check.Not(check.Equals), "") + + expected := fmt.Sprintf(nothingPendingErrorTpl, id) + actual, err := cli.ExecCommandErr("sudo", "snap", "abort", id) + + c.Assert(err, check.NotNil) + c.Assert(actual, check.Matches, expected) +} + +// SNAP_ABORT_006: with valid id - done +func (s *abortSuite) TestAbortWithValidIdInDoneStatus(c *check.C) { + snapName := "hello-world" + common.InstallSnap(c, snapName) + defer common.RemoveSnap(c, snapName) + + id := getDoneInstallID(snapName) + c.Assert(id, check.Not(check.Equals), "") + + expected := fmt.Sprintf(nothingPendingErrorTpl, id) + actual, err := cli.ExecCommandErr("sudo", "snap", "abort", id) + + c.Assert(err, check.NotNil) + c.Assert(actual, check.Matches, expected) +} + +func provokeTaskError(c *check.C, snapName string) { + // make snap uninstallable + subdirPath := filepath.Join("/snap", snapName, "current", "foo") + _, err := cli.ExecCommandErr("sudo", "mkdir", "-p", subdirPath) + c.Assert(err, check.IsNil) + defer cli.ExecCommand(c, "sudo", "rm", "-rf", filepath.Dir(subdirPath)) + + // try to install snap and see it fail + _, err = cli.ExecCommandErr("sudo", "snap", "install", snapName) + c.Assert(err, check.NotNil) +} + +func getErrorID(snapName string) string { + pattern := fmt.Sprintf(` +Error.*Install "%s" snap`, snapName) + return getID(pattern) +} + +func getDoneInstallID(snapName string) string { + pattern := fmt.Sprintf(` +Done +.*Install "%s" snap`, snapName) + return getID(pattern) +} + +func getDoingRemoveID(snapName string) string { + pattern := fmt.Sprintf(` +Doing +.*Remove "%s" snap`, snapName) + return getID(pattern) +} + +func getAbortedRemoveID(snapName string) string { + pattern := fmt.Sprintf(` +Abort +.*Remove "%s" snap`, snapName) + return getID(pattern) +} + +func getID(pattern string) string { + output, err := cli.ExecCommandErr("snap", "changes") + if err != nil && output == "error: no changes found\n" { + return "" + } + + completePattern := fmt.Sprintf(`(?msU).*\n(\d+)%s\n$`, pattern) + result := regexp.MustCompile(completePattern).FindStringSubmatch(output) + + if result == nil || len(result) < 2 { + return "" + } + return result[1] +} diff -Nru snapd-2.0.5/integration-tests/tests/snap_ack_test.go snapd-2.0.8/integration-tests/tests/snap_ack_test.go --- snapd-2.0.5/integration-tests/tests/snap_ack_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_ack_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,9 +25,9 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" ) // for cleanup diff -Nru snapd-2.0.5/integration-tests/tests/snap_changes_test.go snapd-2.0.8/integration-tests/tests/snap_changes_test.go --- snapd-2.0.5/integration-tests/tests/snap_changes_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_changes_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,63 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration + +/* + * 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 tests + +import ( + "fmt" + + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + + "gopkg.in/check.v1" +) + +var _ = check.Suite(&changesSuite{}) + +type changesSuite struct { + common.SnappySuite +} + +// SNAP_CHANGES_001: --help prints the detailed help test for the command +func (s *changesSuite) TestChangesShowsHelp(c *check.C) { + expected := "(?ms)" + + "^Usage:\n" + + ` snap \[OPTIONS\] changes.*\n` + + "\n^The changes command .*\n" + + "^Help Options:\n" + + "^ -h, --help +Show this help message\n" + + ".*" + + actual := cli.ExecCommand(c, "snap", "changes", "--help") + + c.Assert(actual, check.Matches, expected) +} + +// SNAP_CHANGES_004: with invalid id +func (s *changesSuite) TestChangesWithInvalidIdShowsError(c *check.C) { + invalidID := "10000000" + + expected := fmt.Sprintf(`error: cannot find change with id "%s"\n`, invalidID) + + actual, err := cli.ExecCommandErr("snap", "change", invalidID) + + c.Assert(err, check.NotNil) + c.Assert(actual, check.Matches, expected) +} diff -Nru snapd-2.0.5/integration-tests/tests/snap_example_test.go snapd-2.0.8/integration-tests/tests/snap_example_test.go --- snapd-2.0.5/integration-tests/tests/snap_example_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_example_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,10 +24,10 @@ "io/ioutil" "net/http" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/wait" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/wait" + "github.com/snapcore/snapd/testutil" "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/integration-tests/tests/snap_find_test.go snapd-2.0.8/integration-tests/tests/snap_find_test.go --- snapd-2.0.5/integration-tests/tests/snap_find_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_find_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -21,8 +21,10 @@ package tests import ( - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" + "runtime" + + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" "gopkg.in/check.v1" ) @@ -36,7 +38,7 @@ func (s *searchSuite) TestSearchMustPrintMatch(c *check.C) { // XXX: Summary is empty atm, waiting for store support expected := "(?ms)" + - "Name +Version +Summary *\n" + + "Name +Version +Developer +Notes +Summary *\n" + ".*" + "^hello-world +.* *\n" + ".*" @@ -47,3 +49,63 @@ c.Check(searchOutput, check.Matches, expected) } } + +// SNAP_FIND_001: list all packages available on the store +func (s *searchSuite) TestFindMustPrintCompleteList(c *check.C) { + if runtime.GOARCH != "amd64" { + c.Skip("all find results are only available on amd64") + } + + fullListPattern := "(?ms)" + + "Name +Version +Developer +Notes +Summary *\n" + + ".*" + + "^canonical-pc +.* *\n" + + ".*" + + "^canonical-pc-linux +.* *\n" + + ".*" + + "^go-example-webserver +.* *\n" + + ".*" + + "^hello-world +.* *\n" + + ".*" + + "^ubuntu-clock-app +.* *\n" + + ".*" + + "^ubuntu-core +.* *\n" + + ".*" + + searchOutput := cli.ExecCommand(c, "snap", "find") + + c.Assert(searchOutput, check.Matches, fullListPattern) +} + +// SNAP_FIND_002: find packages on store with different name formats +func (s *searchSuite) TestFindWorksWithDifferentFormats(c *check.C) { + if runtime.GOARCH != "amd64" { + c.Skip("all find results are only available on amd64") + } + + for _, snapName := range []string{"http", "ubuntu-clock-app", "go-example-webserver"} { + expected := "(?ms)" + + "Name +Version +Developer +Notes +Summary *\n" + + ".*" + + "^" + snapName + " +.* *\n" + + ".*" + searchOutput := cli.ExecCommand(c, "snap", "find", snapName) + + c.Check(searchOutput, check.Matches, expected) + } +} + +// SNAP_FIND_003: --help prints the detailed help test for the command +func (s *searchSuite) TestFindShowsHelp(c *check.C) { + expected := "(?ms)" + + "^Usage:\n" + + ` snap \[OPTIONS\] find.*\n` + + "\n^The find command .*\n" + + "^Help Options:\n" + + "^ -h, --help +Show this help message\n" + + ".*" + + actual := cli.ExecCommand(c, "snap", "find", "--help") + + c.Assert(actual, check.Matches, expected) +} diff -Nru snapd-2.0.5/integration-tests/tests/snap_install_test.go snapd-2.0.8/integration-tests/tests/snap_install_test.go --- snapd-2.0.5/integration-tests/tests/snap_install_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_install_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,192 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration + +/* + * Copyright (C) 2015, 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 tests + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/integration-tests/testutils/build" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/data" + "github.com/snapcore/snapd/testutil" + + "gopkg.in/check.v1" +) + +var _ = check.Suite(&installSuite{}) + +type installSuite struct { + common.SnappySuite +} + +func (s *installSuite) TestInstallAppMustPrintPackageInformation(c *check.C) { + snapPath, err := build.LocalSnap(c, data.BasicSnapName) + defer os.Remove(snapPath) + c.Assert(err, check.IsNil, check.Commentf("Error building local snap: %s", err)) + installOutput := common.InstallSnap(c, snapPath) + defer common.RemoveSnap(c, data.BasicSnapName) + + expected := "(?ms)" + + "Name +Version +Rev +Developer +Notes\n" + + ".*" + + "^basic +.* *\n" + + ".*" + + c.Assert(installOutput, check.Matches, expected) +} + +func (s *installSuite) TestCallSuccessfulBinaryFromInstalledSnap(c *check.C) { + snapPath, err := build.LocalSnap(c, data.BasicBinariesSnapName) + defer os.Remove(snapPath) + c.Assert(err, check.IsNil, check.Commentf("Error building local snap: %s", err)) + common.InstallSnap(c, snapPath) + defer common.RemoveSnap(c, data.BasicBinariesSnapName) + + // Exec command does not fail. + cli.ExecCommand(c, "basic-binaries.success") +} + +func (s *installSuite) TestCallFailBinaryFromInstalledSnap(c *check.C) { + c.Skip("port to snapd") + + snapPath, err := build.LocalSnap(c, data.BasicBinariesSnapName) + defer os.Remove(snapPath) + c.Assert(err, check.IsNil, check.Commentf("Error building local snap: %s", err)) + common.InstallSnap(c, snapPath) + defer common.RemoveSnap(c, data.BasicBinariesSnapName) + + _, err = cli.ExecCommandErr("basic-binaries.fail") + c.Assert(err, check.NotNil, check.Commentf("The binary did not fail")) +} + +func (s *installSuite) TestInstallUnexistingAppMustPrintError(c *check.C) { + output, err := cli.ExecCommandErr("sudo", "snap", "install", "unexisting.canonical") + + c.Check(err, check.NotNil, + check.Commentf("Trying to install an unexisting snap did not exit with an error")) + c.Assert(string(output), testutil.Contains, + "error: cannot perform the following tasks:\n"+ + "- Download snap \"unexisting.canonical\" from channel \"stable\" (snap not found)\n", + check.Commentf("Wrong error message")) +} + +// SNAP_INSTALL_001: --help - print detailed help text for the install command +func (s *installSuite) TestInstallShowHelp(c *check.C) { + expected := "(?ms)" + + "^Usage:\n" + + ` snap \[OPTIONS\] install.*\n` + + "\n^The install command .*\n" + + "^Help Options:\n" + + "^ -h, --help +Show this help message\n" + + ".*" + + actual := cli.ExecCommand(c, "snap", "install", "--help") + + c.Assert(actual, check.Matches, expected) +} + +// SNAP_INSTALL_002: without snap name shows error +func (s *installSuite) TestInstallWithoutSnapNameMustPrintError(c *check.C) { + expected := "error: the required argument `` was not provided\n" + + actual, err := cli.ExecCommandErr("snap", "install") + + c.Assert(err, check.NotNil) + c.Assert(actual, check.Matches, expected) +} + +// SNAP_INSTALL_004: with already installed snap name and same version +func (s *installSuite) TestInstallWithAlreadyInstalledSnapAndSameVersionMustFail(c *check.C) { + snapName := "hello-world" + + common.InstallSnap(c, snapName) + defer common.RemoveSnap(c, snapName) + + expected := fmt.Sprintf(`error: cannot install "%s": snap "%[1]s" already installed\n`, snapName) + actual, err := cli.ExecCommandErr("sudo", "snap", "install", snapName) + + c.Assert(err, check.NotNil) + c.Assert(actual, check.Matches, expected) +} + +// SNAP_INSTALL_008: from different channel other than default +func (s *installSuite) TestInstallFromDifferentChannels(c *check.C) { + snapName := "hello-world" + + expected := "(?ms).*\n" + + "Name +Version +Rev +Developer +Notes\n" + + snapName + " .* canonical +-\n" + + for _, channel := range []string{"edge", "beta", "candidate", "stable"} { + actual := cli.ExecCommand(c, "sudo", "snap", "install", snapName, "--channel="+channel) + + c.Check(actual, check.Matches, expected) + + common.RemoveSnap(c, snapName) + } +} + +// SNAP_INSTALL_009: with devmode option +func (s *installSuite) TestInstallWithDevmodeOption(c *check.C) { + snapName := "hello-world" + + expected := "(?ms).*\n" + + "Name +Version +Rev +Developer +Notes\n" + + snapName + " .* canonical +devmode\n" + + actual := cli.ExecCommand(c, "sudo", "snap", "install", snapName, "--devmode") + defer common.RemoveSnap(c, snapName) + + c.Assert(actual, check.Matches, expected) +} + +func (s *installSuite) TestInstallsDesktopFile(c *check.C) { + snapPath, err := build.LocalSnap(c, data.BasicDesktopSnapName) + defer os.Remove(snapPath) + c.Assert(err, check.IsNil, check.Commentf("Error building local snap: %s", err)) + common.InstallSnap(c, snapPath) + defer common.RemoveSnap(c, data.BasicDesktopSnapName) + + content, err := ioutil.ReadFile(filepath.Join(dirs.SnapDesktopFilesDir, "basic-desktop_echo.desktop")) + c.Assert(err, check.IsNil) + c.Assert(string(content), testutil.Contains, `[Desktop Entry] +Name=Echo +Comment=It echos stuff +Exec=/snap/bin/basic-desktop.echo +`) +} + +// regression test for lp #1574829 +func (s *installSuite) TestInstallsPointsToLoginWhenNotAuthenticated(c *check.C) { + cli.ExecCommandErr("snap", "logout") + + expected := ".*snap login --help.*\n" + + actual, err := cli.ExecCommandErr("snap", "install", "hello-world") + + c.Assert(err, check.NotNil) + c.Assert(actual, check.Matches, expected) +} diff -Nru snapd-2.0.5/integration-tests/tests/snap_known_test.go snapd-2.0.8/integration-tests/tests/snap_known_test.go --- snapd-2.0.5/integration-tests/tests/snap_known_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_known_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,9 +26,9 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" ) var _ = check.Suite(&snapKnownSuite{}) diff -Nru snapd-2.0.5/integration-tests/tests/snap_op_test.go snapd-2.0.8/integration-tests/tests/snap_op_test.go --- snapd-2.0.5/integration-tests/tests/snap_op_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_op_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -29,14 +29,14 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/integration-tests/testutils/build" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/data" - "github.com/ubuntu-core/snappy/integration-tests/testutils/wait" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/integration-tests/testutils/build" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/data" + "github.com/snapcore/snapd/integration-tests/testutils/wait" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/testutil" ) var _ = check.Suite(&snapOpSuite{}) @@ -48,7 +48,7 @@ func (s *snapOpSuite) testInstallRemove(c *check.C, snapName, displayName string) { installOutput := common.InstallSnap(c, snapName) expected := "(?ms)" + - "Name +Version +Rev +Developer\n" + + "Name +Version +Rev +Developer +Notes\n" + ".*" + displayName + " +.*\n" + ".*" @@ -71,8 +71,8 @@ common.InstallSnap(c, snapPath) installOutput := common.InstallSnap(c, snapPath) c.Assert(installOutput, testutil.Contains, data.BasicSnapName) - // double check, sideloaded snaps have revnos like 1000xx - revnos, _ := filepath.Glob(filepath.Join(dirs.SnapSnapsDir, data.BasicSnapName, "1*")) + // double check, sideloaded snaps have revnos like xNN + revnos, _ := filepath.Glob(filepath.Join(dirs.SnapSnapsDir, data.BasicSnapName, "x*")) c.Check(len(revnos) >= 2, check.Equals, true) removeOutput := common.RemoveSnap(c, data.BasicSnapName) @@ -108,10 +108,10 @@ wait.ForCommand(c, needle, "snap", "changes") // find change id of the remove - output := cli.ExecCommand(c, "snap", "changes") + output := cli.ExecCommand(c, "snap", "changes", data.BasicBinariesSnapName) id := regexp.MustCompile(`(?m)([0-9]+).*Doing.*Remove.*"`).FindStringSubmatch(output)[1] needle = `will retry: ` - wait.ForCommand(c, needle, "snap", "changes", id) + wait.ForCommand(c, needle, "snap", "change", id) // now stop the service that blocks the umount cli.ExecCommand(c, "sudo", "systemctl", "stop", blockerSrv) @@ -153,11 +153,11 @@ c.Assert(err, check.NotNil) // check undone and error in tasks - output := cli.ExecCommand(c, "snap", "changes") + output := cli.ExecCommand(c, "snap", "changes", snapName) expected := fmt.Sprintf(`(?ms).*\n(\d+) +Error.*Install "%s" snap\n$`, snapName) id := regexp.MustCompile(expected).FindStringSubmatch(output)[1] - output = cli.ExecCommand(c, "snap", "changes", id) + output = cli.ExecCommand(c, "snap", "change", id) type undoneCheckerFunc func(*check.C, string, string) for _, fn := range []undoneCheckerFunc{ diff -Nru snapd-2.0.5/integration-tests/tests/snap_persists_test.go snapd-2.0.8/integration-tests/tests/snap_persists_test.go --- snapd-2.0.5/integration-tests/tests/snap_persists_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_persists_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,10 +23,10 @@ import ( "os" - "github.com/ubuntu-core/snappy/integration-tests/testutils/build" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/data" + "github.com/snapcore/snapd/integration-tests/testutils/build" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/data" "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/integration-tests/tests/snap_refresh_all_test.go snapd-2.0.8/integration-tests/tests/snap_refresh_all_test.go --- snapd-2.0.5/integration-tests/tests/snap_refresh_all_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_refresh_all_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,69 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration,!excludereboots,!classic + +/* + * 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 tests + +import ( + "fmt" + "io/ioutil" + + "gopkg.in/check.v1" + + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/config" + "github.com/snapcore/snapd/integration-tests/testutils/refresh" + "github.com/snapcore/snapd/integration-tests/testutils/store" +) + +var _ = check.Suite(&snapRefreshAllSuite{}) + +type snapRefreshAllSuite struct { + common.SnappySuite +} + +func (s *snapRefreshAllSuite) TestAllRefresh(c *check.C) { + // install two snaps and also create fake refresh + snaps := []string{"hello-world", "xkcd-webserver"} + for _, snap := range snaps { + cli.ExecCommand(c, "sudo", "snap", "install", snap) + defer cli.ExecCommand(c, "sudo", "snap", "remove", snap) + } + + // create/start the store, run snapd against the fake store + blobDir, err := ioutil.TempDir("", "snap-fake-store-blobs-") + fakeStore := store.NewStore(blobDir) + err = fakeStore.Start() + c.Assert(err, check.IsNil) + defer fakeStore.Stop() + + env := fmt.Sprintf(`SNAPPY_FORCE_CPI_URL=%s`, fakeStore.URL()) + cfg, _ := config.ReadConfig(config.DefaultFileName) + + tearDownSnapd(c) + defer setUpSnapd(c, cfg.FromBranch, "") + setUpSnapd(c, cfg.FromBranch, env) + defer tearDownSnapd(c) + + // and refresh all snaps + output := refresh.CallFakeSnapRefreshAll(c, snaps, refresh.NoOp, fakeStore) + c.Assert(output, check.Matches, "(?ms).*^hello-world.*fake1.*") + c.Assert(output, check.Matches, "(?ms).*^xkcd-webserver.*fake1.*") +} diff -Nru snapd-2.0.5/integration-tests/tests/snap_refresh_app_test.go snapd-2.0.8/integration-tests/tests/snap_refresh_app_test.go --- snapd-2.0.5/integration-tests/tests/snap_refresh_app_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_refresh_app_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,72 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration,!excludereboots,!classic + +/* + * 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 tests + +import ( + "fmt" + "io/ioutil" + + "gopkg.in/check.v1" + + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/config" + "github.com/snapcore/snapd/integration-tests/testutils/refresh" + "github.com/snapcore/snapd/integration-tests/testutils/store" +) + +var _ = check.Suite(&snapRefreshAppSuite{}) + +type snapRefreshAppSuite struct { + common.SnappySuite +} + +func (s *snapRefreshAppSuite) TestAppRefresh(c *check.C) { + snap := "hello-world" + + // install edge version from the store (which is squashfs) + cli.ExecCommand(c, "sudo", "snap", "install", snap) + defer cli.ExecCommand(c, "sudo", "snap", "remove", snap) + + // make a fakestore and make it available to snapd + + // use /var/tmp is not a tempfs for space reasons + blobDir, err := ioutil.TempDir("/var/tmp", "snap-fake-store-blobs-") + c.Assert(err, check.IsNil) + defer cli.ExecCommand(c, "sudo", "rm", "-rf", blobDir) + + fakeStore := store.NewStore(blobDir) + err = fakeStore.Start() + c.Assert(err, check.IsNil) + defer fakeStore.Stop() + + env := fmt.Sprintf(`SNAPPY_FORCE_CPI_URL=%s`, fakeStore.URL()) + cfg, _ := config.ReadConfig(config.DefaultFileName) + + tearDownSnapd(c) + defer setUpSnapd(c, cfg.FromBranch, "") + setUpSnapd(c, cfg.FromBranch, env) + defer tearDownSnapd(c) + + // run the fake refresh + output := refresh.CallFakeSnapRefreshForSnap(c, snap, refresh.NoOp, fakeStore) + c.Assert(output, check.Matches, "(?ms).*^hello-world.*fake1.*") +} diff -Nru snapd-2.0.5/integration-tests/tests/snap_remove_test.go snapd-2.0.8/integration-tests/tests/snap_remove_test.go --- snapd-2.0.5/integration-tests/tests/snap_remove_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_remove_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,73 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration + +/* + * 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 tests + +import ( + "fmt" + + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + + "gopkg.in/check.v1" +) + +var _ = check.Suite(&removeSuite{}) + +type removeSuite struct { + common.SnappySuite +} + +// SNAP_REMOVE_001: --help prints the detailed help test for the command +func (s *removeSuite) TestRemoveShowsHelp(c *check.C) { + expected := "(?ms)" + + "^Usage:\n" + + ` snap \[OPTIONS\] remove.*\n` + + "\n^The remove command .*\n" + + "^Help Options:\n" + + "^ -h, --help +Show this help message\n" + + ".*" + + actual := cli.ExecCommand(c, "snap", "remove", "--help") + + c.Assert(actual, check.Matches, expected) +} + +// SNAP_REMOVE_002: - invalid pkg name +func (s *removeSuite) TestRemoveInvalidPackageShowsError(c *check.C) { + invalidPkg := "invalid-package-name" + + expected := fmt.Sprintf(`error: cannot remove "%s": cannot find snap "%[1]s"\n`, invalidPkg) + + actual, err := cli.ExecCommandErr("sudo", "snap", "remove", invalidPkg) + + c.Assert(err, check.NotNil) + c.Assert(actual, check.Matches, expected) +} + +// SNAP_REMOVE_007: - ubuntu-core +func (s *removeSuite) TestRemoveUbuntuCoreShowsError(c *check.C) { + expected := `error: cannot remove "ubuntu-core": snap "ubuntu-core" is not removable\n` + + actual, err := cli.ExecCommandErr("sudo", "snap", "remove", "ubuntu-core") + + c.Assert(err, check.NotNil) + c.Assert(actual, check.Matches, expected) +} diff -Nru snapd-2.0.5/integration-tests/tests/snap_update_app_test.go snapd-2.0.8/integration-tests/tests/snap_update_app_test.go --- snapd-2.0.5/integration-tests/tests/snap_update_app_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/snap_update_app_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration,!excludereboots - -/* - * 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 tests - -import ( - "fmt" - "io/ioutil" - - "gopkg.in/check.v1" - - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/config" - "github.com/ubuntu-core/snappy/integration-tests/testutils/store" - "github.com/ubuntu-core/snappy/integration-tests/testutils/updates" -) - -var _ = check.Suite(&snapRefreshAppSuite{}) - -type snapRefreshAppSuite struct { - common.SnappySuite -} - -func (s *snapRefreshAppSuite) TestAppUpdate(c *check.C) { - snap := "hello-world" - - // install edge version from the store (which is squashfs) - cli.ExecCommand(c, "sudo", "snap", "install", snap) - defer cli.ExecCommand(c, "sudo", "snap", "remove", snap) - - // make a fakestore and make it available to snapd - - // use /var/tmp is not a tempfs for space reasons - blobDir, err := ioutil.TempDir("/var/tmp", "snap-fake-store-blobs-") - c.Assert(err, check.IsNil) - defer cli.ExecCommand(c, "sudo", "rm", "-rf", blobDir) - - fakeStore := store.NewStore(blobDir) - err = fakeStore.Start() - c.Assert(err, check.IsNil) - defer fakeStore.Stop() - - env := fmt.Sprintf(`SNAPPY_FORCE_CPI_URL=%s`, fakeStore.URL()) - cfg, _ := config.ReadConfig(config.DefaultFileName) - - tearDownSnapd(c) - defer setUpSnapd(c, cfg.FromBranch, "") - setUpSnapd(c, cfg.FromBranch, env) - defer tearDownSnapd(c) - - // run the fake update - output := updates.CallFakeSnapRefresh(c, snap, updates.NoOp, fakeStore) - c.Assert(output, check.Matches, "(?ms).*^hello-world.*fake1.*") -} diff -Nru snapd-2.0.5/integration-tests/tests/tryapp_test.go snapd-2.0.8/integration-tests/tests/tryapp_test.go --- snapd-2.0.5/integration-tests/tests/tryapp_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/tryapp_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,109 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration + +/* + * Copyright (C) 2015, 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 tests + +import ( + "os" + "path/filepath" + + "github.com/snapcore/snapd/integration-tests/testutils/build" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/data" + "github.com/snapcore/snapd/testutil" + + "gopkg.in/check.v1" +) + +var _ = check.Suite(&trySuite{}) + +type trySuite struct { + common.SnappySuite +} + +func (s *trySuite) TestTryBasicBinaries(c *check.C) { + tryPath, err := filepath.Abs(filepath.Join(data.BaseSnapPath, data.BasicBinariesSnapName)) + c.Assert(err, check.IsNil) + + tryOutput := cli.ExecCommand(c, "sudo", "snap", "try", tryPath) + defer common.RemoveSnap(c, data.BasicBinariesSnapName) + + expected := "(?ms)" + + ".*" + + "Name +Version +Rev +Developer +Notes\n" + + "basic-binaries +.*try" + c.Check(tryOutput, check.Matches, expected) + + // can run commands from the snap-try binary + cli.ExecCommand(c, "basic-binaries.success") + + // commands can read stuff in their own dir + output := cli.ExecCommand(c, "basic-binaries.cat") + c.Check(output, testutil.Contains, "I output myself") +} + +func (s *trySuite) TestTryConfinmentDenies(c *check.C) { + tryPath, err := filepath.Abs(filepath.Join(data.BaseSnapPath, data.DevKmsg)) + c.Assert(err, check.IsNil) + + cli.ExecCommand(c, "sudo", "snap", "try", tryPath) + defer common.RemoveSnap(c, data.DevKmsg) + + // confinment works in try mode: + // dev-kmsg.reader can not read /dev/kmsg out of the box + output, err := cli.ExecCommandErr("dev-kmsg.reader") + c.Check(err, check.NotNil) + c.Check(output, check.Matches, `(?ms)dd: failed to open '/dev/kmsg': Permission denied`) +} + +func (s *trySuite) TestTryConfinmentDevModeAllows(c *check.C) { + tryPath, err := filepath.Abs(filepath.Join(data.BaseSnapPath, data.DevKmsg)) + c.Assert(err, check.IsNil) + + cli.ExecCommand(c, "sudo", "snap", "try", "--devmode", tryPath) + defer common.RemoveSnap(c, data.DevKmsg) + + // dev-mode disables confinement and we can read /dev/kmsg + output, err := cli.ExecCommandErr("dev-kmsg.reader") + c.Check(err, check.IsNil) + c.Check(output, check.Not(check.HasLen), 0) +} + +func (s *trySuite) TestTryConfinmentAllows(c *check.C) { + // provide a server for the test + snapPath, err := build.LocalSnap(c, data.NetworkBindConsumerSnapName) + c.Assert(err, check.IsNil) + defer os.Remove(snapPath) + common.InstallSnap(c, snapPath) + defer common.RemoveSnap(c, data.NetworkBindConsumerSnapName) + + // "try" client confinment (network auto-connects) + tryPath, err := filepath.Abs(filepath.Join(data.BaseSnapPath, data.NetworkConsumerSnapName)) + c.Assert(err, check.IsNil) + + cli.ExecCommand(c, "sudo", "snap", "try", tryPath) + defer common.RemoveSnap(c, data.NetworkConsumerSnapName) + + // confinment works in try mode: + providerURL := "http://127.0.0.1:8081" + output := cli.ExecCommand(c, "network-consumer", providerURL) + c.Assert(output, check.Equals, "ok\n") +} diff -Nru snapd-2.0.5/integration-tests/tests/ubuntufan_test.go snapd-2.0.8/integration-tests/tests/ubuntufan_test.go --- snapd-2.0.5/integration-tests/tests/ubuntufan_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/ubuntufan_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,220 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration,!lowperformance,!classic + +/* + * Copyright (C) 2015, 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 tests + +import ( + "fmt" + "net" + "os/exec" + "strings" + + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/wait" + + "gopkg.in/check.v1" +) + +const ( + firstOverlaySegment = "241" + baseContainer = "busybox" +) + +var _ = check.Suite(&fanTestSuite{}) + +type fanTestSuite struct { + common.SnappySuite + bridgeIP string + subjectIP string +} + +func (s *fanTestSuite) SetUpTest(c *check.C) { + if common.Release(c) == "15.04" { + c.Skip("Ubuntu Fan not available in 15.04") + } + + s.SnappySuite.SetUpTest(c) + var err error + s.subjectIP, err = getIPAddr(c) + c.Assert(err, check.IsNil, check.Commentf("Error getting IP address: %s", err)) + + s.fanCtl(c, "up") + s.bridgeIP = s.fanBridgeIP(c) +} + +func (s *fanTestSuite) TearDownTest(c *check.C) { + s.SnappySuite.TearDownTest(c) + + s.fanCtl(c, "down") +} + +func (s *fanTestSuite) TestFanCommandExists(c *check.C) { + cmd := exec.Command("fanctl") + output, _ := cmd.CombinedOutput() + + expectedPattern := `(?msi)Usage: \/usr\/sbin\/fanctl .*` + + c.Assert(string(output), check.Matches, expectedPattern, + check.Commentf("Expected output pattern %s not found in %s", expectedPattern, output)) +} + +func (s *fanTestSuite) TestFanCommandCreatesFanBridge(c *check.C) { + output := cli.ExecCommand(c, "ifconfig") + + expectedPattern := fmt.Sprintf("(?msi).*%s.*%s.*", s.fanName(), s.bridgeIP) + + c.Assert(output, check.Matches, expectedPattern, + check.Commentf("Expected pattern %s not found in %s", expectedPattern, output)) +} + +func (s *fanTestSuite) TestDockerCreatesAContainerInsideTheFan(c *check.C) { + c.Skip("Skipping until LP: #1544507 is fixed") + + setUpDocker(c) + defer tearDownDocker(c) + s.configureDockerToUseBridge(c) + defer s.removeBridgeFromDockerConf(c) + + output := cli.ExecCommand(c, "docker", "run", "-t", baseContainer, "ifconfig") + + expectedIP := strings.TrimRight(s.bridgeIP, ".1") + ".2" + expectedPattern := fmt.Sprintf("(?ms).*inet addr:%s.*", expectedIP) + + c.Assert(output, check.Matches, expectedPattern, + check.Commentf("Expected pattern %s not found in %s", expectedPattern, output)) +} + +func (s *fanTestSuite) TestContainersInTheFanAreReachable(c *check.C) { + c.Skip("Skipping until LP: #1544507 is fixed") + + setUpDocker(c) + defer tearDownDocker(c) + s.configureDockerToUseBridge(c) + defer s.removeBridgeFromDockerConf(c) + + // spin up first container + cli.ExecCommand(c, "docker", "run", "-d", "-t", baseContainer) + // the first assigned IP in the fan will end with ".2" + firstIPAddr := strings.TrimRight(s.bridgeIP, ".1") + ".2" + + // ping from a second container + output := cli.ExecCommand(c, "docker", "run", "-t", baseContainer, "ping", firstIPAddr, "-c", "1") + + expectedPattern := "(?ms).*1 packets transmitted, 1 packets received, 0% packet loss.*" + + c.Assert(output, check.Matches, expectedPattern, + check.Commentf("Expected pattern %s not found in %s", expectedPattern, output)) +} + +func getIPAddr(c *check.C) (ip string, err error) { + if ips, err := net.InterfaceAddrs(); err == nil { + for _, addr := range ips { + hostport := addr.String() + // poor's man check for ipv6 + if !strings.Contains(hostport, ":") && + !strings.Contains(hostport, "127.0.0.1") { + parts := strings.Split(hostport, "/") + return parts[0], nil + } + } + } + return +} + +func (s *fanTestSuite) fanBridgeIP(c *check.C) (bridgeIP string) { + segments := strings.Split(s.subjectIP, ".") + + // the final bridge ip is formed by the given overlay first segment, the last two from + // the interface ip and "1" at the end + return strings.Join([]string{firstOverlaySegment, segments[2], segments[3], "1"}, ".") +} + +func (s *fanTestSuite) fanCtl(c *check.C, cmd string) string { + return cli.ExecCommand(c, + "sudo", "fanctl", cmd, firstOverlaySegment+".0.0.0/8", s.subjectIP+"/16") +} + +func (s *fanTestSuite) configureDockerToUseBridge(c *check.C) { + cfgFile := dockerCfgFile(c) + + cli.ExecCommand(c, "sudo", "sed", "-i", + fmt.Sprintf(`s/DOCKER_OPTIONS=\"\"/DOCKER_OPTIONS=\"%s\"/`, s.dockerOptions()), + cfgFile) + + restartDocker(c) +} + +func (s *fanTestSuite) removeBridgeFromDockerConf(c *check.C) { + cfgFile := dockerCfgFile(c) + + cli.ExecCommand(c, "sudo", "sed", "-i", + `s/DOCKER_OPTIONS=\".*\"/DOCKER_OPTIONS=\"\"/`, + cfgFile) + + restartDocker(c) +} + +func dockerCfgFile(c *check.C) string { + dockerVersion := common.GetCurrentVersion(c, "docker") + return fmt.Sprintf("/var/snap/docker/%s/etc/docker.conf", dockerVersion) +} + +func restartDocker(c *check.C) { + dockerVersion := common.GetCurrentVersion(c, "docker") + dockerService := fmt.Sprintf("docker_docker-daemon_%s.service", dockerVersion) + + cli.ExecCommand(c, "sudo", "systemctl", "restart", dockerService) + + // we need to wait until the socket is ready, an active systemctl status is not enough + err := wait.ForActiveService(c, dockerService) + c.Assert(err, check.IsNil, check.Commentf("Expected nil error, got %s", err)) + + err = wait.ForCommand(c, `(?ms).*docker\.sock\s.*`, "ls", "/run") + c.Assert(err, check.IsNil, check.Commentf("Expected nil error, got %s", err)) +} + +func (s *fanTestSuite) fanName() string { + firstOctect := strings.Split(s.bridgeIP, ".")[0] + + return "fan-" + firstOctect +} + +func (s *fanTestSuite) dockerOptions() string { + return fmt.Sprintf("-d -b %s --mtu=1480 --iptables=false", s.fanName()) +} + +func setUpDocker(c *check.C) { + common.InstallSnap(c, "docker/edge") + dockerVersion := common.GetCurrentVersion(c, "docker") + dockerService := fmt.Sprintf("docker_docker-daemon_%s.service", dockerVersion) + + err := wait.ForActiveService(c, dockerService) + c.Assert(err, check.IsNil, check.Commentf("Error waiting for service: %s", err)) + + err = wait.ForCommand(c, `(?ms).*docker\.sock\s.*`, "ls", "/run") + c.Assert(err, check.IsNil, check.Commentf("Expected nil error, got %s", err)) + + cli.ExecCommand(c, "docker", "pull", baseContainer) +} + +func tearDownDocker(c *check.C) { + common.RemoveSnap(c, "docker") +} diff -Nru snapd-2.0.5/integration-tests/tests/ubuntuFan_test.go snapd-2.0.8/integration-tests/tests/ubuntuFan_test.go --- snapd-2.0.5/integration-tests/tests/ubuntuFan_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/ubuntuFan_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,220 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration,!lowperformance - -/* - * Copyright (C) 2015 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 tests - -import ( - "fmt" - "net" - "os/exec" - "strings" - - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/wait" - - "gopkg.in/check.v1" -) - -const ( - firstOverlaySegment = "241" - baseContainer = "busybox" -) - -var _ = check.Suite(&fanTestSuite{}) - -type fanTestSuite struct { - common.SnappySuite - bridgeIP string - subjectIP string -} - -func (s *fanTestSuite) SetUpTest(c *check.C) { - if common.Release(c) == "15.04" { - c.Skip("Ubuntu Fan not available in 15.04") - } - - s.SnappySuite.SetUpTest(c) - var err error - s.subjectIP, err = getIPAddr(c) - c.Assert(err, check.IsNil, check.Commentf("Error getting IP address: %s", err)) - - s.fanCtl(c, "up") - s.bridgeIP = s.fanBridgeIP(c) -} - -func (s *fanTestSuite) TearDownTest(c *check.C) { - s.SnappySuite.TearDownTest(c) - - s.fanCtl(c, "down") -} - -func (s *fanTestSuite) TestFanCommandExists(c *check.C) { - cmd := exec.Command("fanctl") - output, _ := cmd.CombinedOutput() - - expectedPattern := `(?msi)Usage: \/usr\/sbin\/fanctl .*` - - c.Assert(string(output), check.Matches, expectedPattern, - check.Commentf("Expected output pattern %s not found in %s", expectedPattern, output)) -} - -func (s *fanTestSuite) TestFanCommandCreatesFanBridge(c *check.C) { - output := cli.ExecCommand(c, "ifconfig") - - expectedPattern := fmt.Sprintf("(?msi).*%s.*%s.*", s.fanName(), s.bridgeIP) - - c.Assert(output, check.Matches, expectedPattern, - check.Commentf("Expected pattern %s not found in %s", expectedPattern, output)) -} - -func (s *fanTestSuite) TestDockerCreatesAContainerInsideTheFan(c *check.C) { - c.Skip("Skipping until LP: #1544507 is fixed") - - setUpDocker(c) - defer tearDownDocker(c) - s.configureDockerToUseBridge(c) - defer s.removeBridgeFromDockerConf(c) - - output := cli.ExecCommand(c, "docker", "run", "-t", baseContainer, "ifconfig") - - expectedIP := strings.TrimRight(s.bridgeIP, ".1") + ".2" - expectedPattern := fmt.Sprintf("(?ms).*inet addr:%s.*", expectedIP) - - c.Assert(output, check.Matches, expectedPattern, - check.Commentf("Expected pattern %s not found in %s", expectedPattern, output)) -} - -func (s *fanTestSuite) TestContainersInTheFanAreReachable(c *check.C) { - c.Skip("Skipping until LP: #1544507 is fixed") - - setUpDocker(c) - defer tearDownDocker(c) - s.configureDockerToUseBridge(c) - defer s.removeBridgeFromDockerConf(c) - - // spin up first container - cli.ExecCommand(c, "docker", "run", "-d", "-t", baseContainer) - // the first assigned IP in the fan will end with ".2" - firstIPAddr := strings.TrimRight(s.bridgeIP, ".1") + ".2" - - // ping from a second container - output := cli.ExecCommand(c, "docker", "run", "-t", baseContainer, "ping", firstIPAddr, "-c", "1") - - expectedPattern := "(?ms).*1 packets transmitted, 1 packets received, 0% packet loss.*" - - c.Assert(output, check.Matches, expectedPattern, - check.Commentf("Expected pattern %s not found in %s", expectedPattern, output)) -} - -func getIPAddr(c *check.C) (ip string, err error) { - if ips, err := net.InterfaceAddrs(); err == nil { - for _, addr := range ips { - hostport := addr.String() - // poor's man check for ipv6 - if !strings.Contains(hostport, ":") && - !strings.Contains(hostport, "127.0.0.1") { - parts := strings.Split(hostport, "/") - return parts[0], nil - } - } - } - return -} - -func (s *fanTestSuite) fanBridgeIP(c *check.C) (bridgeIP string) { - segments := strings.Split(s.subjectIP, ".") - - // the final bridge ip is formed by the given overlay first segment, the last two from - // the interface ip and "1" at the end - return strings.Join([]string{firstOverlaySegment, segments[2], segments[3], "1"}, ".") -} - -func (s *fanTestSuite) fanCtl(c *check.C, cmd string) string { - return cli.ExecCommand(c, - "sudo", "fanctl", cmd, firstOverlaySegment+".0.0.0/8", s.subjectIP+"/16") -} - -func (s *fanTestSuite) configureDockerToUseBridge(c *check.C) { - cfgFile := dockerCfgFile(c) - - cli.ExecCommand(c, "sudo", "sed", "-i", - fmt.Sprintf(`s/DOCKER_OPTIONS=\"\"/DOCKER_OPTIONS=\"%s\"/`, s.dockerOptions()), - cfgFile) - - restartDocker(c) -} - -func (s *fanTestSuite) removeBridgeFromDockerConf(c *check.C) { - cfgFile := dockerCfgFile(c) - - cli.ExecCommand(c, "sudo", "sed", "-i", - `s/DOCKER_OPTIONS=\".*\"/DOCKER_OPTIONS=\"\"/`, - cfgFile) - - restartDocker(c) -} - -func dockerCfgFile(c *check.C) string { - dockerVersion := common.GetCurrentVersion(c, "docker") - return fmt.Sprintf("/var/snap/docker/%s/etc/docker.conf", dockerVersion) -} - -func restartDocker(c *check.C) { - dockerVersion := common.GetCurrentVersion(c, "docker") - dockerService := fmt.Sprintf("docker_docker-daemon_%s.service", dockerVersion) - - cli.ExecCommand(c, "sudo", "systemctl", "restart", dockerService) - - // we need to wait until the socket is ready, an active systemctl status is not enough - err := wait.ForActiveService(c, dockerService) - c.Assert(err, check.IsNil, check.Commentf("Expected nil error, got %s", err)) - - err = wait.ForCommand(c, `(?ms).*docker\.sock\s.*`, "ls", "/run") - c.Assert(err, check.IsNil, check.Commentf("Expected nil error, got %s", err)) -} - -func (s *fanTestSuite) fanName() string { - firstOctect := strings.Split(s.bridgeIP, ".")[0] - - return "fan-" + firstOctect -} - -func (s *fanTestSuite) dockerOptions() string { - return fmt.Sprintf("-d -b %s --mtu=1480 --iptables=false", s.fanName()) -} - -func setUpDocker(c *check.C) { - common.InstallSnap(c, "docker/edge") - dockerVersion := common.GetCurrentVersion(c, "docker") - dockerService := fmt.Sprintf("docker_docker-daemon_%s.service", dockerVersion) - - err := wait.ForActiveService(c, dockerService) - c.Assert(err, check.IsNil, check.Commentf("Error waiting for service: %s", err)) - - err = wait.ForCommand(c, `(?ms).*docker\.sock\s.*`, "ls", "/run") - c.Assert(err, check.IsNil, check.Commentf("Expected nil error, got %s", err)) - - cli.ExecCommand(c, "docker", "pull", baseContainer) -} - -func tearDownDocker(c *check.C) { - common.RemoveSnap(c, "docker") -} diff -Nru snapd-2.0.5/integration-tests/tests/unity_test.go snapd-2.0.8/integration-tests/tests/unity_test.go --- snapd-2.0.5/integration-tests/tests/unity_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/unity_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -1,5 +1,5 @@ // -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration,classiconly +// +build !excludeintegration,!allsnaps /* * Copyright (C) 2016 Canonical Ltd @@ -25,9 +25,9 @@ "os" "os/exec" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/wait" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/wait" "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/integration-tests/tests/update_rollback_stress_test.go snapd-2.0.8/integration-tests/tests/update_rollback_stress_test.go --- snapd-2.0.5/integration-tests/tests/update_rollback_stress_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/update_rollback_stress_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ "path/filepath" "strconv" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/common" "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/integration-tests/tests/writablepaths_test.go snapd-2.0.8/integration-tests/tests/writablepaths_test.go --- snapd-2.0.5/integration-tests/tests/writablepaths_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/writablepaths_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,107 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration,!classic + +/* + * Copyright (C) 2015, 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 tests + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/snapcore/snapd/integration-tests/testutils/common" + + "gopkg.in/check.v1" +) + +const writablePathsListFile = "/etc/system-image/writable-paths" + +var _ = check.Suite(&writablePathsSuite{}) + +type writablePathsSuite struct { + common.SnappySuite +} + +var IsWritable check.Checker = &isWritable{} + +type isWritable struct { +} + +func (is *isWritable) Info() *check.CheckerInfo { + return &check.CheckerInfo{Name: "IsWritable", Params: []string{"path"}} +} + +func (is *isWritable) Check(params []interface{}, names []string) (result bool, error string) { + if path, ok := params[0].(string); ok { + filename := filepath.Join(path, "tmpfile") + + cmd := exec.Command("sudo", "touch", filename) + rmCmd := exec.Command("sudo", "rm", filename) + defer rmCmd.Run() + + if _, err := cmd.CombinedOutput(); err == nil { + result = true + } else { + error = fmt.Sprintf("Error creating file %s", filename) + } + } else { + error = fmt.Sprintf("First param of checker %v is of type %T and it should be a string", params[0], params[0]) + } + return result, error +} + +func (s *writablePathsSuite) TestWritablePathsAreWritable(c *check.C) { + for writablePath := range generateWritablePaths(c) { + c.Logf("Checking if %s is writable", writablePath) + c.Check(writablePath, IsWritable) + } +} + +func generateWritablePaths(c *check.C) chan string { + ch := make(chan string) + + go func() { + file, err := os.Open(writablePathsListFile) + + c.Assert(err, check.IsNil, + check.Commentf("Error reading writable files list %s", writablePathsListFile)) + + defer file.Close() + + reader := bufio.NewReader(file) + scanner := bufio.NewScanner(reader) + + scanner.Split(bufio.ScanLines) + + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) > 0 && fields[0] != "#" { + if src, err := os.Stat(fields[0]); err == nil && src.IsDir() { + ch <- fields[0] + } + } + } + close(ch) + }() + + return ch +} diff -Nru snapd-2.0.5/integration-tests/tests/writablePaths_test.go snapd-2.0.8/integration-tests/tests/writablePaths_test.go --- snapd-2.0.5/integration-tests/tests/writablePaths_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/tests/writablePaths_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,107 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration - -/* - * Copyright (C) 2015 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 tests - -import ( - "bufio" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - - "gopkg.in/check.v1" -) - -const writablePathsListFile = "/etc/system-image/writable-paths" - -var _ = check.Suite(&writablePathsSuite{}) - -type writablePathsSuite struct { - common.SnappySuite -} - -var IsWritable check.Checker = &isWritable{} - -type isWritable struct { -} - -func (is *isWritable) Info() *check.CheckerInfo { - return &check.CheckerInfo{Name: "IsWritable", Params: []string{"path"}} -} - -func (is *isWritable) Check(params []interface{}, names []string) (result bool, error string) { - if path, ok := params[0].(string); ok { - filename := filepath.Join(path, "tmpfile") - - cmd := exec.Command("sudo", "touch", filename) - rmCmd := exec.Command("sudo", "rm", filename) - defer rmCmd.Run() - - if _, err := cmd.CombinedOutput(); err == nil { - result = true - } else { - error = fmt.Sprintf("Error creating file %s", filename) - } - } else { - error = fmt.Sprintf("First param of checker %v is of type %T and it should be a string", params[0], params[0]) - } - return result, error -} - -func (s *writablePathsSuite) TestWritablePathsAreWritable(c *check.C) { - for writablePath := range generateWritablePaths(c) { - c.Logf("Checking if %s is writable", writablePath) - c.Check(writablePath, IsWritable) - } -} - -func generateWritablePaths(c *check.C) chan string { - ch := make(chan string) - - go func() { - file, err := os.Open(writablePathsListFile) - - c.Assert(err, check.IsNil, - check.Commentf("Error reading writable files list %s", writablePathsListFile)) - - defer file.Close() - - reader := bufio.NewReader(file) - scanner := bufio.NewScanner(reader) - - scanner.Split(bufio.ScanLines) - - for scanner.Scan() { - fields := strings.Fields(scanner.Text()) - if len(fields) > 0 && fields[0] != "#" { - if src, err := os.Stat(fields[0]); err == nil && src.IsDir() { - ch <- fields[0] - } - } - } - close(ch) - }() - - return ch -} diff -Nru snapd-2.0.5/integration-tests/testutils/autopkgtest/autopkgtest.go snapd-2.0.8/integration-tests/testutils/autopkgtest/autopkgtest.go --- snapd-2.0.5/integration-tests/testutils/autopkgtest/autopkgtest.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/autopkgtest/autopkgtest.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,8 +25,8 @@ "path/filepath" "strings" - "github.com/ubuntu-core/snappy/integration-tests/testutils/testutils" - "github.com/ubuntu-core/snappy/integration-tests/testutils/tpl" + "github.com/snapcore/snapd/integration-tests/testutils/testutils" + "github.com/snapcore/snapd/integration-tests/testutils/tpl" ) const ( @@ -56,6 +56,8 @@ ShellOnFail bool // Env is a map with the environment variables to set on the test bed and their values. Env map[string]string + // Verbose controls the amount of output printed + Verbose bool } // AdtRunLocal starts a kvm running the image passed as argument and runs the @@ -80,11 +82,15 @@ prepareTargetDir(outputDir) cmd := []string{ - "adt-run", "-B", + "adt-run", "-B"} + if !a.Verbose { + cmd = append(cmd, "-q") + } + cmd = append(cmd, []string{ "--override-control", controlFile, "--built-tree", a.SourceCodePath, "--output-dir", outputDir, - "--setup-commands", "touch /run/autopkgtest_no_reboot.stamp"} + "--setup-commands", "touch /run/autopkgtest_no_reboot.stamp"}...) for envVar, value := range a.Env { cmd = append(cmd, "--env") cmd = append(cmd, fmt.Sprintf("%s=%s", envVar, value)) diff -Nru snapd-2.0.5/integration-tests/testutils/autopkgtest/autopkgtest_test.go snapd-2.0.8/integration-tests/testutils/autopkgtest/autopkgtest_test.go --- snapd-2.0.5/integration-tests/testutils/autopkgtest/autopkgtest_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/autopkgtest/autopkgtest_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -43,7 +43,7 @@ imgPath = "imgPath" testbedIP = "1.1.1.1" testbedPort = 90 - adtrunTpl = "adt-run -B --override-control %s --built-tree %s --output-dir %s --setup-commands touch /run/autopkgtest_no_reboot.stamp %s" + adtrunTpl = "adt-run -B -q --override-control %s --built-tree %s --output-dir %s --setup-commands touch /run/autopkgtest_no_reboot.stamp %s" ) type AutoPkgTestSuite struct { @@ -224,6 +224,35 @@ check.Commentf("Expected call %s not executed 1 time", expectedCommandCall)) } +func (s *AutoPkgTestSuite) TestAdtRunLocalAddsQuietFlag(c *check.C) { + s.adtRunAddsQuietFlag(c, true) +} + +func (s *AutoPkgTestSuite) TestAdtRunRemoteAddsQuietFlag(c *check.C) { + s.adtRunAddsQuietFlag(c, false) +} + +func (s *AutoPkgTestSuite) adtRunAddsQuietFlag(c *check.C, local bool) { + s.subject.Verbose = true + + if local { + s.subject.AdtRunLocal(imgPath) + } else { + s.subject.AdtRunRemote(testbedIP, testbedPort) + } + + match := false + for call := range s.execCalls { + if strings.HasPrefix(call, "adt-run") { + if strings.Contains(call, " -q ") { + match = true + break + } + } + } + c.Assert(match, check.Equals, false, check.Commentf("quiet flag found in adt-run call with verbose=true")) +} + func tplExecuteCmd(tplFile, outputFile string, data interface{}) string { return fmt.Sprint(tplFile, outputFile, data) } diff -Nru snapd-2.0.5/integration-tests/testutils/build/build.go snapd-2.0.8/integration-tests/testutils/build/build.go --- snapd-2.0.5/integration-tests/testutils/build/build.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/build/build.go 2016-06-08 05:58:01.000000000 +0000 @@ -31,8 +31,8 @@ "runtime" "strings" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/testutils" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/testutils" ) const ( @@ -46,7 +46,7 @@ defaultGoArm = "7" testsBinDir = "integration-tests/bin/" baseBuildCmd = "go test -c -tags integrationcoverage " - projectSrcPath = "src/github.com/ubuntu-core/snappy" + projectSrcPath = "src/github.com/snapcore/snapd" ) var ( @@ -66,7 +66,7 @@ // Assets builds the snappy and integration tests binaries for the target // architecture. -func Assets(cfg *Config) { +func Assets(cfg *Config) error { tmp := "/tmp/snappy-build" _, filename, _, _ := runtime.Caller(1) dir, _ := filepath.Abs(filepath.Join(path.Dir(filename), "..")) @@ -81,36 +81,42 @@ coverpkg := getCoverPkg() // FIXME We need to build an image that has the snappy from the branch // installed. --elopio - 2015-06-25. - buildSnapd(cfg.Arch, coverpkg) - buildSnapCLI(cfg.Arch, coverpkg) + if err := buildSnapd(cfg.Arch, coverpkg); err != nil { + return err + } + if err := buildSnapCLI(cfg.Arch, coverpkg); err != nil { + return err + } + } + if err := buildSnapbuild(cfg.Arch); err != nil { + return err } - buildSnapbuild(cfg.Arch) - buildTests(cfg.Arch, cfg.TestBuildTags) + return buildTests(cfg.Arch, cfg.TestBuildTags) } -func buildSnapd(arch, coverpkg string) { +func buildSnapd(arch, coverpkg string) error { fmt.Println("Building snapd...") buildSnapdCmd := getBinaryBuildCmd("snapd", coverpkg) - goCall(arch, buildSnapdCmd) + return goCall(arch, buildSnapdCmd) } -func buildSnapCLI(arch, coverpkg string) { +func buildSnapCLI(arch, coverpkg string) error { fmt.Println("Building snap...") buildSnapCliCmd := getBinaryBuildCmd("snap", coverpkg) - goCall(arch, buildSnapCliCmd) + return goCall(arch, buildSnapCliCmd) } -func buildSnapbuild(arch string) { +func buildSnapbuild(arch string) error { fmt.Println("Building snapbuild...") buildSnapbuildCmd := "go build" + " -o " + filepath.Join(testsBinDir, filepath.Base(snapbuildPkg)) + " " + snapbuildPkg - goCall(arch, buildSnapbuildCmd) + return goCall(arch, buildSnapbuildCmd) } -func buildTests(arch, testBuildTags string) { +func buildTests(arch, testBuildTags string) error { fmt.Println("Building tests...") var tagText string @@ -119,13 +125,15 @@ } cmd := fmt.Sprintf(buildTestCmdFmt, tagText) - goCall(arch, cmd) + if err := goCall(arch, cmd); err != nil { + return err + } // XXX Go test 1.3 does not have the output flag, so we move the // binaries after they are generated. - osRename("tests.test", testsBinDir+IntegrationTestName) + return osRename("tests.test", testsBinDir+IntegrationTestName) } -func goCall(arch string, cmd string) { +func goCall(arch string, cmd string) error { if arch != "" { defer osSetenv("GOARCH", osGetenv("GOARCH")) osSetenv("GOARCH", arch) @@ -144,7 +152,11 @@ cmdElems := strings.Fields(cmd) command := exec.Command(cmdElems[0], cmdElems[1:]...) command.Dir = filepath.Join(os.Getenv("GOPATH"), projectSrcPath) - execCommand(command) + output, err := execCommand(command) + if err != nil { + return fmt.Errorf("command %q failed: %q (%s)", cmdElems, err, output) + } + return nil } func getBinaryBuildCmd(binary, coverpkg string) string { @@ -175,8 +187,8 @@ // without filtering the helper, osutil and progress packages the compilation of the tests gives these errors: // /home/fgimenez/src/go/pkg/tool/linux_amd64/link: running gcc failed: exit status 1 // /tmp/go-link-492921396/000003.o: In function `_cgo_b95aca69b89e_Cfunc_isatty': - // /home/fgimenez/workspace/gocode/src/github.com/ubuntu-core/snappy/progress/isatty.go:50: multiple definition of `_cgo_b95aca69b89e_Cfunc_isatty' - // /tmp/go-link-492921396/000002.o:/home/fgimenez/workspace/gocode/src/github/ubuntu-core/snappy/progress/isatty.go:50: first defined here + // /home/fgimenez/workspace/gocode/src/github.com/snapcore/snapd/progress/isatty.go:50: multiple definition of `_cgo_b95aca69b89e_Cfunc_isatty' + // /tmp/go-link-492921396/000002.o:/home/fgimenez/workspace/gocode/src/github/snapcore/snapd/progress/isatty.go:50: first defined here filterPattern := `.*integration-tests|helper|osutil|progress` r := regexp.MustCompile(filterPattern) diff -Nru snapd-2.0.5/integration-tests/testutils/build/snapbuild/main.go snapd-2.0.8/integration-tests/testutils/build/snapbuild/main.go --- snapd-2.0.5/integration-tests/testutils/build/snapbuild/main.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/build/snapbuild/main.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,7 +24,7 @@ "fmt" "os" - "github.com/ubuntu-core/snappy/snap/snaptest" + "github.com/snapcore/snapd/snap/snaptest" ) func main() { diff -Nru snapd-2.0.5/integration-tests/testutils/build/snap.go snapd-2.0.8/integration-tests/testutils/build/snap.go --- snapd-2.0.5/integration-tests/testutils/build/snap.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/build/snap.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,9 +25,9 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/snap/snaptest" + "github.com/snapcore/snapd/snap/snaptest" - "github.com/ubuntu-core/snappy/integration-tests/testutils/data" + "github.com/snapcore/snapd/integration-tests/testutils/data" ) const snapFilenameSufix = "_1.0_all.snap" diff -Nru snapd-2.0.5/integration-tests/testutils/cli/cli.go snapd-2.0.8/integration-tests/testutils/cli/cli.go --- snapd-2.0.5/integration-tests/testutils/cli/cli.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/cli/cli.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,7 +28,7 @@ "path/filepath" "strings" - "github.com/ubuntu-core/snappy/integration-tests/testutils/config" + "github.com/snapcore/snapd/integration-tests/testutils/config" "gopkg.in/check.v1" ) @@ -71,11 +71,19 @@ // ExecCommandWrapper decorates the execution of the given command func ExecCommandWrapper(cmd *exec.Cmd) (output string, err error) { - fmt.Println(strings.Join(cmd.Args, " ")) + cfg, err := config.ReadConfig(config.DefaultFileName) + if err != nil { + return "", err + } + if cfg.Verbose { + fmt.Println(strings.Join(cmd.Args, " ")) + } outputByte, err := cmd.CombinedOutput() output = removeCoverageInfo(string(outputByte)) - fmt.Print(output) - return + if cfg.Verbose { + fmt.Print(output) + } + return output, err } // AddOptionsToCommand inserts the required coverage options in @@ -107,9 +115,6 @@ } func addCoverageOptions(cmds []string, index int) ([]string, error) { - orig := make([]string, len(cmds)) - copy(orig, cmds) - coveragePath := getCoveragePath() err := os.MkdirAll(coveragePath, os.ModePerm) if err != nil { @@ -119,11 +124,12 @@ tmpFile := getCoverFilename() coverprofile := filepath.Join(coveragePath, tmpFile) - head := append(cmds[:index+1], - []string{"-test.run=^TestRunMain$", "-test.coverprofile=" + coverprofile}...) - tail := orig[index+1:] + output := append(cmds, []string{"", ""}...) - return append(head, tail...), nil + copy(output[index+2:], output[index:]) + output[index+1] = "-test.run=^TestRunMain$" + output[index+2] = "-test.coverprofile=" + coverprofile + return output, nil } func removeCoverageInfo(input string) string { diff -Nru snapd-2.0.5/integration-tests/testutils/cli/cli_test.go snapd-2.0.8/integration-tests/testutils/cli/cli_test.go --- snapd-2.0.5/integration-tests/testutils/cli/cli_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/cli/cli_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -29,8 +29,8 @@ "strings" "testing" - "github.com/ubuntu-core/snappy/integration-tests/testutils/config" - "github.com/ubuntu-core/snappy/integration-tests/testutils/testutils" + "github.com/snapcore/snapd/integration-tests/testutils/config" + "github.com/snapcore/snapd/integration-tests/testutils/testutils" "gopkg.in/check.v1" ) @@ -420,6 +420,61 @@ } } +func (s *cliTestSuite) TestAddOptionsDoesNotModifyOriginalCmds(c *check.C) { + for _, cmd := range s.targetCoverCmds { + cmdsIn := []string{cmd, "subcommand1", "subcommand2"} + + _, err := AddOptionsToCommand(cmdsIn) + + c.Check(err, check.IsNil) + c.Check(len(cmdsIn), check.Equals, 3) + c.Check(cmdsIn[0], check.Equals, cmd) + c.Check(cmdsIn[1], check.Equals, "subcommand1") + c.Check(cmdsIn[2], check.Equals, "subcommand2") + } +} + +func (s *cliTestSuite) TestExecCommandWrapperDoesNotWriteVerboseOutputByDefault(c *check.C) { + backStdout := os.Stdout + defer func() { os.Stdout = backStdout }() + tmp, err := ioutil.TempFile("", "") + c.Assert(err, check.IsNil) + defer os.Remove(tmp.Name()) + + os.Stdout = tmp + + _, err = ExecCommandWrapper(s.cmd) + c.Assert(err, check.IsNil) + + completeOutput, err := ioutil.ReadFile(tmp.Name()) + c.Assert(err, check.IsNil) + + c.Assert(string(completeOutput), check.Equals, "") +} + +func (s *cliTestSuite) TestExecCommandWrapperHonoursVerboseFlag(c *check.C) { + s.writeVerboseConfig(true) + + backStdout := os.Stdout + defer func() { os.Stdout = backStdout }() + tmp, err := ioutil.TempFile("", "") + c.Assert(err, check.IsNil) + defer os.Remove(tmp.Name()) + + os.Stdout = tmp + + cmdOutput, err := ExecCommandWrapper(s.cmd) + c.Assert(err, check.IsNil) + + completeOutput, err := ioutil.ReadFile(tmp.Name()) + c.Assert(err, check.IsNil) + + sentCmd, err := AddOptionsToCommand(s.cmd.Args) + + expected := fmt.Sprintf("%s\n%s", strings.Join(sentCmd, " "), cmdOutput) + c.Assert(string(completeOutput), check.Equals, expected) +} + func getParamOuput(output string, cmd *exec.Cmd) string { if len(cmd.Env) == 1 && cmd.Env[0] == defaultEnv { output += "\nEnv variables: " + strings.Join(cmd.Env, ", ") @@ -443,3 +498,13 @@ return nil } + +func (s *cliTestSuite) writeVerboseConfig(verbose bool) { + testutils.PrepareTargetDir(filepath.Dir(s.configFile)) + + cfg := config.Config{ + FileName: s.configFile, + Verbose: verbose, + } + cfg.Write() +} diff -Nru snapd-2.0.5/integration-tests/testutils/common/common.go snapd-2.0.8/integration-tests/testutils/common/common.go --- snapd-2.0.5/integration-tests/testutils/common/common.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/common/common.go 2016-06-08 05:58:01.000000000 +0000 @@ -30,10 +30,10 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/config" - "github.com/ubuntu-core/snappy/integration-tests/testutils/partition" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/config" + "github.com/snapcore/snapd/integration-tests/testutils/partition" + "github.com/snapcore/snapd/testutil" ) const ( diff -Nru snapd-2.0.5/integration-tests/testutils/common/info.go snapd-2.0.8/integration-tests/testutils/common/info.go --- snapd-2.0.5/integration-tests/testutils/common/info.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/common/info.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,7 +23,7 @@ import ( "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/osutil" ) // dependency aliasing diff -Nru snapd-2.0.5/integration-tests/testutils/config/config.go snapd-2.0.8/integration-tests/testutils/config/config.go --- snapd-2.0.5/integration-tests/testutils/config/config.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/config/config.go 2016-06-08 05:58:01.000000000 +0000 @@ -38,6 +38,7 @@ Update bool Rollback bool FromBranch bool + Verbose bool } // Write writes the config to a file that will be copied to the test bed. diff -Nru snapd-2.0.5/integration-tests/testutils/config/config_test.go snapd-2.0.8/integration-tests/testutils/config/config_test.go --- snapd-2.0.5/integration-tests/testutils/config/config_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/config/config_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -46,9 +46,9 @@ func testConfigStruct(fileName string) *Config { return &Config{ - fileName, - "testrelease", "testchannel", - true, true, true, true} + FileName: fileName, + Release: "testrelease", Channel: "testchannel", + RemoteTestbed: true, Update: true, Rollback: true, FromBranch: true, Verbose: true} } func testConfigContents(fileName string) string { return `{` + @@ -58,7 +58,8 @@ `"RemoteTestbed":true,` + `"Update":true,` + `"Rollback":true,` + - `"FromBranch":true` + + `"FromBranch":true,` + + `"Verbose":true` + `}` } @@ -103,14 +104,17 @@ `"RemoteTestbed":false,` + `"Update":true,` + `"Rollback":true,` + - `"FromBranch":true` + + `"FromBranch":true,` + + `"Verbose":true` + `}` ioutil.WriteFile(configFileName, []byte(configContents), 0644) cfg, err := ReadConfig(configFileName) - testConfigStruct := &Config{configFileName, "testrelease", "testchannel", false, true, true, true} + testConfigStruct := &Config{FileName: configFileName, + Release: "testrelease", Channel: "testchannel", + RemoteTestbed: false, Update: true, Rollback: true, FromBranch: true, Verbose: true} c.Assert(err, check.IsNil, check.Commentf("Error reading config: %v", err)) c.Assert(cfg, check.DeepEquals, testConfigStruct) diff -Nru snapd-2.0.5/integration-tests/testutils/data/data.go snapd-2.0.8/integration-tests/testutils/data/data.go --- snapd-2.0.5/integration-tests/testutils/data/data.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/data/data.go 2016-06-08 05:58:01.000000000 +0000 @@ -37,8 +37,12 @@ NetworkConsumerSnapName = "network-consumer" // NetworkBindConsumerSnapName is the name of the snap with network plug NetworkBindConsumerSnapName = "network-bind-consumer" + // LogObserveConsumerSnapName is the name of the snap with log-observe plug + LogObserveConsumerSnapName = "log-observe-consumer" // HomeConsumerSnapName is the name of the snap with home plug HomeConsumerSnapName = "home-consumer" // WrongYamlSnapName is the name of a snap with an invalid meta yaml WrongYamlSnapName = "wrong-yaml" + // DevKmsg reads /dev/kmsg and is useful to test confinement + DevKmsg = "dev-kmsg" ) diff -Nru snapd-2.0.5/integration-tests/testutils/image/image.go snapd-2.0.8/integration-tests/testutils/image/image.go --- snapd-2.0.5/integration-tests/testutils/image/image.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/image/image.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,7 +25,7 @@ "path/filepath" "strings" - "github.com/ubuntu-core/snappy/integration-tests/testutils/testutils" + "github.com/snapcore/snapd/integration-tests/testutils/testutils" ) // Image type encapsulates image actions diff -Nru snapd-2.0.5/integration-tests/testutils/refresh/refresh.go snapd-2.0.8/integration-tests/testutils/refresh/refresh.go --- snapd-2.0.5/integration-tests/testutils/refresh/refresh.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/refresh/refresh.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,120 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build !excludeintegration + +/* + * Copyright (C) 2015, 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 refresh + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + + "gopkg.in/check.v1" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/store" +) + +// ChangeFakeUpdateSnap is the type of the functions used to modify a snap before it is served as +// a fake update. +type ChangeFakeUpdateSnap func(snapPath string) error + +// NoOp leaves the snap unchanged. +func NoOp(snapPath string) error { + return nil +} + +// CallFakeSnapRefresh calls snappy update after faking a new version available for the specified snap. +// The fake is made copying the currently installed snap. +// changeFunc can be used to modify the snap before it is built and served. +func CallFakeSnapRefreshForSnap(c *check.C, snap string, changeFunc ChangeFakeUpdateSnap, fakeStore *store.Store) string { + c.Log("Preparing fake single snap and calling update.") + + blobDir := fakeStore.SnapsDir() + MakeFakeRefreshForSnap(c, snap, blobDir, changeFunc) + + cli.ExecCommand(c, "sudo", "snap", "refresh", snap) + + // FIXME: do we want an automatic `snap list` output after + // `snap update` (like in the old snappy world)? + return cli.ExecCommand(c, "snap", "list") +} + +func CallFakeSnapRefreshAll(c *check.C, snaps []string, changeFunc ChangeFakeUpdateSnap, fakeStore *store.Store) string { + c.Log("Preparing fake and calling update.") + + blobDir := fakeStore.SnapsDir() + for _, snap := range snaps { + MakeFakeRefreshForSnap(c, snap, blobDir, changeFunc) + } + + return cli.ExecCommand(c, "sudo", "snap", "refresh") +} + +func MakeFakeRefreshForSnap(c *check.C, snap, targetDir string, changeFunc ChangeFakeUpdateSnap) error { + + // make a fake update snap in /var/tmp (which is not a tempfs) + fakeUpdateDir, err := ioutil.TempDir("/var/tmp", "snap-build-") + c.Assert(err, check.IsNil) + // ensure the "." of the squashfs has sane owner/permissions + cli.ExecCommand(c, "sudo", "chown", "root:root", fakeUpdateDir) + cli.ExecCommand(c, "sudo", "chmod", "0755", fakeUpdateDir) + defer cli.ExecCommand(c, "sudo", "rm", "-rf", fakeUpdateDir) + + copySnap(c, snap, fakeUpdateDir) + + // fake new version + cli.ExecCommand(c, "sudo", "sed", "-i", `s/version:\(.*\)/version:\1+fake1/`, filepath.Join(fakeUpdateDir, "meta/snap.yaml")) + + if err := changeFunc(fakeUpdateDir); err != nil { + return err + } + buildSnap(c, fakeUpdateDir, targetDir) + return nil +} + +func copySnap(c *check.C, snap, targetDir string) { + // check for sideloaded snaps + // XXX: simplify this down to consider only the name (and not origin) + // in the directory once everything is moved to that + baseDir := filepath.Join(dirs.SnapSnapsDir, snap) + if _, err := os.Stat(baseDir); os.IsNotExist(err) { + snapName := strings.Split(snap, ".")[0] + baseDir = filepath.Join(dirs.SnapSnapsDir, snapName) + if _, err := os.Stat(baseDir); os.IsNotExist(err) { + baseDir = filepath.Join(dirs.SnapSnapsDir, snapName+".sideload") + _, err = os.Stat(baseDir) + c.Assert(err, check.IsNil, + check.Commentf("%s not found from it's original source not sideloaded", snap)) + } + } + sourceDir := filepath.Join(baseDir, "current") + files, err := filepath.Glob(filepath.Join(sourceDir, "*")) + c.Assert(err, check.IsNil) + for _, m := range files { + cli.ExecCommand(c, "sudo", "cp", "-a", m, targetDir) + } +} + +func buildSnap(c *check.C, snapDir, targetDir string) { + // build in /var/tmp (which is not a tempfs) + cli.ExecCommand(c, "sudo", "TMPDIR=/var/tmp", "snapbuild", snapDir, targetDir) +} diff -Nru snapd-2.0.5/integration-tests/testutils/report/parser.go snapd-2.0.8/integration-tests/testutils/report/parser.go --- snapd-2.0.5/integration-tests/testutils/report/parser.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/report/parser.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,7 +28,7 @@ "github.com/testing-cabal/subunit-go" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/common" ) const ( diff -Nru snapd-2.0.5/integration-tests/testutils/report/parser_test.go snapd-2.0.8/integration-tests/testutils/report/parser_test.go --- snapd-2.0.5/integration-tests/testutils/report/parser_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/report/parser_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -30,7 +30,7 @@ "github.com/testing-cabal/subunit-go" "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" + "github.com/snapcore/snapd/integration-tests/testutils/common" ) var _ = check.Suite(&ParserReportSuite{}) @@ -85,12 +85,12 @@ "testSuite.TestExists", "exists", }, { - "PASS: /tmp/snappy-tests-job/18811/src/github.com/ubuntu-core/snappy/integration-tests/tests/" + + "PASS: /tmp/snappy-tests-job/18811/src/github.com/snapcore/snapd/integration-tests/tests/" + "apt_test.go:34: testSuite.TestSuccess 0.005s\n", "testSuite.TestSuccess", "success", }, { - "FAIL: /tmp/snappy-tests-job/710/src/github.com/ubuntu-core/snappy/integration-tests/tests/" + + "FAIL: /tmp/snappy-tests-job/710/src/github.com/snapcore/snapd/integration-tests/tests/" + "installFramework_test.go:85: testSuite.TestFail\n", "testSuite.TestFail", "fail", @@ -112,7 +112,7 @@ testID := "testSuite.TestSkip" skipReason := "skip reason" s.subject.Write([]byte( - fmt.Sprintf("SKIP: /tmp/snappy-tests-job/21647/src/github.com/ubuntu-core/snappy/"+ + fmt.Sprintf("SKIP: /tmp/snappy-tests-job/21647/src/github.com/snapcore/snapd/"+ "integration-tests/tests/info_test.go:36: %s (%s)\n", testID, skipReason))) c.Check(s.spy.calls, check.HasLen, 1) @@ -210,7 +210,7 @@ testID := "testSuite.SetUpTest" skipReason := "skip reason" s.subject.Write([]byte( - fmt.Sprintf("SKIP: /tmp/snappy-tests-job/21647/src/github.com/ubuntu-core/snappy/"+ + fmt.Sprintf("SKIP: /tmp/snappy-tests-job/21647/src/github.com/snapcore/snapd/"+ "integration-tests/tests/info_test.go:36: %s (%s)\n", testID, skipReason))) c.Check(s.spy.calls, check.HasLen, 0) diff -Nru snapd-2.0.5/integration-tests/testutils/store/store.go snapd-2.0.8/integration-tests/testutils/store/store.go --- snapd-2.0.5/integration-tests/testutils/store/store.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/store/store.go 2016-06-08 05:58:01.000000000 +0000 @@ -31,7 +31,7 @@ "gopkg.in/tylerb/graceful.v1" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/snap" ) var ( @@ -80,7 +80,7 @@ mux.HandleFunc("/", rootEndpoint) mux.HandleFunc("/search", store.searchEndpoint) mux.HandleFunc("/package/", store.detailsEndpoint) - mux.HandleFunc("/click-metadata", store.bulkEndpoint) + mux.HandleFunc("/metadata", store.bulkEndpoint) mux.Handle("/download/", http.StripPrefix("/download/", http.FileServer(http.Dir(blobDir)))) return store @@ -147,6 +147,7 @@ type detailsReplyJSON struct { Name string `json:"name"` + SnapID string `json:"snap_id"` PackageName string `json:"package_name"` Developer string `json:"origin"` AnonDownloadURL string `json:"anon_download_url"` @@ -247,23 +248,36 @@ return nil } +type candidateSnap struct { + SnapID string `json:"snap_id"` +} + type bulkReqJSON struct { - Name []string + CandidateSnaps []candidateSnap `json:"snaps"` + Fields []string `json:"fields"` +} + +type payload struct { + Packages []detailsReplyJSON `json:"clickindex:package"` } type bulkReplyJSON struct { - Status string `json:"status"` - Name string `json:"name"` - PackageName string `json:"package_name"` - Developer string `json:"origin"` - AnonDownloadURL string `json:"anon_download_url"` - Version string `json:"version"` - Revision int `json:"revision"` + Payload payload `json:"_embedded"` +} + +// FIXME: find a better way to extract the snapID -> name mapping +// for the fake store +var snapIDtoName = map[string]string{ + "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ": "hello-world", + "EQPfyVOJF0AZNz9P2IJ6UKwldLFN5TzS": "xkcd-webserver", + "b8X2psL1ryVrPt5WEmpYiqfr5emixTd7": "ubuntu-core", + "bul8uZn9U3Ll4ke6BMqvNVEZjuJCSQvO": "canonical-pc", + "SkKeDk2PRgBrX89DdgULk3pyY5DJo6Jk": "canonical-pc-linux", } func (s *Store) bulkEndpoint(w http.ResponseWriter, req *http.Request) { var pkgs bulkReqJSON - var replyData []bulkReplyJSON + var replyData bulkReplyJSON decoder := json.NewDecoder(req.Body) if err := decoder.Decode(&pkgs); err != nil { @@ -273,11 +287,16 @@ s.refreshSnaps() - // check if we have downloadable snap of the given name - for _, pkgWithChannel := range pkgs.Name { - pkg := strings.Split(pkgWithChannel, "/")[0] + // check if we have downloadable snap of the given SnapID + for _, pkg := range pkgs.CandidateSnaps { + + name := snapIDtoName[pkg.SnapID] + if name == "" { + http.Error(w, fmt.Sprintf("unknown snapid: %q", pkg.SnapID), http.StatusBadRequest) + return + } - if fn, ok := s.snaps[pkg]; ok { + if fn, ok := s.snaps[name]; ok { snapFile, err := snap.Open(fn) if err != nil { http.Error(w, fmt.Sprintf("can not read: %v: %v", fn, err), http.StatusBadRequest) @@ -291,11 +310,12 @@ return } - replyData = append(replyData, bulkReplyJSON{ - Status: "Published", + replyData.Payload.Packages = append(replyData.Payload.Packages, detailsReplyJSON{ Name: fmt.Sprintf("%s.%s", info.Name(), s.defaultDeveloper), + SnapID: pkg.SnapID, PackageName: info.Name(), Developer: defaultDeveloper, + DownloadURL: fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn)), AnonDownloadURL: fmt.Sprintf("%s/download/%s", s.URL(), filepath.Base(fn)), Version: info.Version, Revision: makeRevision(info), @@ -311,4 +331,5 @@ return } w.Write(out) + } diff -Nru snapd-2.0.5/integration-tests/testutils/store/store_test.go snapd-2.0.8/integration-tests/testutils/store/store_test.go --- snapd-2.0.5/integration-tests/testutils/store/store_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/store/store_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -29,8 +29,8 @@ "path/filepath" "testing" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap/snaptest" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap/snaptest" . "gopkg.in/check.v1" ) @@ -114,6 +114,7 @@ "clickindex:package": [ { "name": "foo.canonical", + "snap_id": "", "package_name": "foo", "origin": "canonical", "anon_download_url": "%s/download/foo_1_all.snap", @@ -127,11 +128,11 @@ } func (s *storeTestSuite) TestBulkEndpoint(c *C) { - c.Skip("not relevant atm, will change") - s.makeTestSnap(c, "name: foo\nversion: 1") + s.makeTestSnap(c, "name: hello-world\nversion: 1") - resp, err := s.StorePostJSON("/click-metadata", []byte(`{ -"name": ["foo.canonical"] + // note that we send the hello-world snapID here + resp, err := s.StorePostJSON("/metadata", []byte(`{ +"snaps": [{"snap_id":"buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ","channel":"stable","revision":1}] }`)) c.Assert(err, IsNil) defer resp.Body.Close() @@ -139,17 +140,22 @@ c.Assert(resp.StatusCode, Equals, 200) body, err := ioutil.ReadAll(resp.Body) c.Assert(err, IsNil) - c.Assert(string(body), Equals, fmt.Sprintf(`[ - { - "status": "Published", - "name": "foo.canonical", - "package_name": "foo", - "origin": "canonical", - "anon_download_url": "%s/download/foo_1_all.snap", - "version": "1", - "revision": 424242 + c.Assert(string(body), Equals, fmt.Sprintf(`{ + "_embedded": { + "clickindex:package": [ + { + "name": "hello-world.canonical", + "snap_id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ", + "package_name": "hello-world", + "origin": "canonical", + "anon_download_url": "%[1]s/download/hello-world_1_all.snap", + "download_url": "%[1]s/download/hello-world_1_all.snap", + "version": "1", + "revision": 424242 + } + ] } -]`, s.store.URL())) +}`, s.store.URL())) } // FIXME: extract into snappy/testutils diff -Nru snapd-2.0.5/integration-tests/testutils/testutils/testutils.go snapd-2.0.8/integration-tests/testutils/testutils/testutils.go --- snapd-2.0.5/integration-tests/testutils/testutils/testutils.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/testutils/testutils.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,6 +26,8 @@ "os" "os/exec" "strings" + + "github.com/snapcore/snapd/integration-tests/testutils/config" ) // PrepareTargetDir creates the given target directory, removing it previously if it didn't exist @@ -48,13 +50,18 @@ // ExecCommand executes the given command and pipes the results to os.Stdout and os.Stderr, returning the resulting error func ExecCommand(cmds ...string) error { - fmt.Println(strings.Join(cmds, " ")) - + cfg, err := config.ReadConfig(config.DefaultFileName) + if err != nil { + return err + } + if cfg.Verbose { + fmt.Println(strings.Join(cmds, " ")) + } cmd := exec.Command(cmds[0], cmds[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - err := cmd.Run() + err = cmd.Run() if err != nil { log.Panicf("Error while running %s: %s\n", cmd.Args, err) } diff -Nru snapd-2.0.5/integration-tests/testutils/updates/updates.go snapd-2.0.8/integration-tests/testutils/updates/updates.go --- snapd-2.0.5/integration-tests/testutils/updates/updates.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/updates/updates.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,140 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- -// +build !excludeintegration - -/* - * Copyright (C) 2015, 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 updates - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "gopkg.in/check.v1" - - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" - "github.com/ubuntu-core/snappy/integration-tests/testutils/common" - "github.com/ubuntu-core/snappy/integration-tests/testutils/partition" - "github.com/ubuntu-core/snappy/integration-tests/testutils/store" -) - -// ChangeFakeUpdateSnap is the type of the functions used to modify a snap before it is served as -// a fake update. -type ChangeFakeUpdateSnap func(snapPath string) error - -// NoOp leaves the snap unchanged. -func NoOp(snapPath string) error { - return nil -} - -// CallFakeSnapRefresh calls snappy update after faking a new version available for the specified snap. -// The fake is made copying the currently installed snap. -// changeFunc can be used to modify the snap before it is built and served. -func CallFakeSnapRefresh(c *check.C, snap string, changeFunc ChangeFakeUpdateSnap, fakeStore *store.Store) string { - c.Log("Preparing fake and calling update.") - - blobDir := fakeStore.SnapsDir() - makeFakeUpdateForSnap(c, snap, blobDir, changeFunc) - - // FIMXE: there is no "snap refresh" that updates all snaps - cli.ExecCommand(c, "sudo", "snap", "refresh", snap) - - // FIXME: do we want an automatic `snap list` output after - // `snap update` (like in the old snappy world)? - return cli.ExecCommand(c, "snap", "list") -} - -// FIXME: remove once "snappy" the command is gone -func CallFakeUpdate(c *check.C, snap string, changeFunc ChangeFakeUpdateSnap) string { - c.Log("Preparing fake and calling update.") - - // use /var/tmp is not a tempfs - blobDir, err := ioutil.TempDir("/var/tmp", "snap-fake-store-blobs-") - c.Assert(err, check.IsNil) - defer cli.ExecCommand(c, "sudo", "rm", "-rf", blobDir) - - fakeStore := store.NewStore(blobDir) - err = fakeStore.Start() - c.Assert(err, check.IsNil) - defer fakeStore.Stop() - - makeFakeUpdateForSnap(c, snap, blobDir, changeFunc) - - return cli.ExecCommand(c, "sudo", "TMPDIR=/var/tmp", fmt.Sprintf("SNAPPY_FORCE_CPI_URL=%s", fakeStore.URL()), "snap", "refresh", snap) -} - -// CallFakeOSUpdate calls snappy update after faking a new version available for the OS snap. -func CallFakeOSUpdate(c *check.C) string { - currentVersion := common.GetCurrentUbuntuCoreVersion(c) - common.SetSavedVersion(c, currentVersion) - - return CallFakeUpdate(c, partition.OSSnapName(c)+".canonical", NoOp) -} - -func makeFakeUpdateForSnap(c *check.C, snap, targetDir string, changeFunc ChangeFakeUpdateSnap) error { - - // make a fake update snap in /var/tmp (which is not a tempfs) - fakeUpdateDir, err := ioutil.TempDir("/var/tmp", "snap-build-") - c.Assert(err, check.IsNil) - // ensure the "." of the squashfs has sane owner/permissions - cli.ExecCommand(c, "sudo", "chown", "root:root", fakeUpdateDir) - cli.ExecCommand(c, "sudo", "chmod", "0755", fakeUpdateDir) - defer cli.ExecCommand(c, "sudo", "rm", "-rf", fakeUpdateDir) - - copySnap(c, snap, fakeUpdateDir) - - // fake new version - cli.ExecCommand(c, "sudo", "sed", "-i", `s/version:\(.*\)/version:\1+fake1/`, filepath.Join(fakeUpdateDir, "meta/snap.yaml")) - - if err := changeFunc(fakeUpdateDir); err != nil { - return err - } - buildSnap(c, fakeUpdateDir, targetDir) - return nil -} - -func copySnap(c *check.C, snap, targetDir string) { - // check for sideloaded snaps - // XXX: simplify this down to consider only the name (and not origin) - // in the directory once everything is moved to that - baseDir := filepath.Join(dirs.SnapSnapsDir, snap) - if _, err := os.Stat(baseDir); os.IsNotExist(err) { - snapName := strings.Split(snap, ".")[0] - baseDir = filepath.Join(dirs.SnapSnapsDir, snapName) - if _, err := os.Stat(baseDir); os.IsNotExist(err) { - baseDir = filepath.Join(dirs.SnapSnapsDir, snapName+".sideload") - _, err = os.Stat(baseDir) - c.Assert(err, check.IsNil, - check.Commentf("%s not found from it's original source not sideloaded", snap)) - } - } - sourceDir := filepath.Join(baseDir, "current") - files, err := filepath.Glob(filepath.Join(sourceDir, "*")) - c.Assert(err, check.IsNil) - for _, m := range files { - cli.ExecCommand(c, "sudo", "cp", "-a", m, targetDir) - } -} - -func buildSnap(c *check.C, snapDir, targetDir string) { - // build in /var/tmp (which is not a tempfs) - cli.ExecCommand(c, "sudo", "TMPDIR=/var/tmp", "snapbuild", snapDir, targetDir) -} diff -Nru snapd-2.0.5/integration-tests/testutils/wait/wait.go snapd-2.0.8/integration-tests/testutils/wait/wait.go --- snapd-2.0.5/integration-tests/testutils/wait/wait.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/integration-tests/testutils/wait/wait.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,7 +27,7 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/integration-tests/testutils/cli" + "github.com/snapcore/snapd/integration-tests/testutils/cli" ) var ( diff -Nru snapd-2.0.5/interfaces/apparmor/apparmor.go snapd-2.0.8/interfaces/apparmor/apparmor.go --- snapd-2.0.5/interfaces/apparmor/apparmor.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/apparmor/apparmor.go 2016-06-08 05:58:01.000000000 +0000 @@ -33,7 +33,7 @@ "path/filepath" "strings" - "github.com/ubuntu-core/snappy/dirs" + "github.com/snapcore/snapd/dirs" ) // LoadProfile loads an apparmor profile from the given file. diff -Nru snapd-2.0.5/interfaces/apparmor/apparmor_test.go snapd-2.0.8/interfaces/apparmor/apparmor_test.go --- snapd-2.0.5/interfaces/apparmor/apparmor_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/apparmor/apparmor_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,9 +28,9 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/interfaces/apparmor" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/testutil" ) func Test(t *testing.T) { diff -Nru snapd-2.0.5/interfaces/apparmor/backend.go snapd-2.0.8/interfaces/apparmor/backend.go --- snapd-2.0.5/interfaces/apparmor/backend.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/apparmor/backend.go 2016-06-08 05:58:01.000000000 +0000 @@ -45,10 +45,10 @@ "regexp" "sort" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" ) // Backend is responsible for maintaining apparmor profiles for ubuntu-core-launcher. diff -Nru snapd-2.0.5/interfaces/apparmor/backend_test.go snapd-2.0.8/interfaces/apparmor/backend_test.go --- snapd-2.0.5/interfaces/apparmor/backend_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/apparmor/backend_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,11 +27,11 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/apparmor" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" ) type backendSuite struct { @@ -318,7 +318,7 @@ func (s *backendSuite) installSnap(c *C, devMode bool, snapYaml string, revision int) *snap.Info { snapInfo, err := snap.InfoFromSnapYaml([]byte(snapYaml)) c.Assert(err, IsNil) - snapInfo.Revision = revision + snapInfo.Revision = snap.R(revision) // this won't come from snap.yaml snapInfo.Developer = "acme" err = s.repo.AddSnap(snapInfo) @@ -332,7 +332,7 @@ func (s *backendSuite) updateSnap(c *C, oldSnapInfo *snap.Info, devMode bool, snapYaml string, revision int) *snap.Info { newSnapInfo, err := snap.InfoFromSnapYaml([]byte(snapYaml)) c.Assert(err, IsNil) - newSnapInfo.Revision = revision + newSnapInfo.Revision = snap.R(revision) // this won't come from snap.yaml newSnapInfo.Developer = "acme" c.Assert(newSnapInfo.Name(), Equals, oldSnapInfo.Name()) diff -Nru snapd-2.0.5/interfaces/apparmor/export_test.go snapd-2.0.8/interfaces/apparmor/export_test.go --- snapd-2.0.5/interfaces/apparmor/export_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/apparmor/export_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package apparmor import ( - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/testutil" ) // MockProfilesPath mocks the file read by LoadedProfiles() diff -Nru snapd-2.0.5/interfaces/apparmor/template.go snapd-2.0.8/interfaces/apparmor/template.go --- snapd-2.0.5/interfaces/apparmor/template.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/apparmor/template.go 2016-06-08 05:58:01.000000000 +0000 @@ -216,6 +216,18 @@ @{PROC}/sys/kernel/pid_max r, @{PROC}/sys/kernel/random/uuid r, + # Reads of oom_adj and oom_score_adj are safe + owner @{PROC}/@{pid}/oom_{,score_}adj r, + + # Note: for now, don't explicitly deny write access so --devmode isn't broken + # but eventually we may conditionally deny this since it allows the process + # to increase the oom heuristic of other processes (make them more likely to + # be killed). Once AppArmor kernel var is available to solve this properly, + # this can safely be allowed since non-root processes won't be able to + # decrease the value and root processes will only be able to with + # 'capability sys_resource,' which we deny be default. + # deny owner @{PROC}/@{pid}/oom_{,score_}adj w, + # Eases hardware assignment (doesn't give anything away) /etc/udev/udev.conf r, /sys/ r, diff -Nru snapd-2.0.5/interfaces/apparmor/template_vars.go snapd-2.0.8/interfaces/apparmor/template_vars.go --- snapd-2.0.5/interfaces/apparmor/template_vars.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/apparmor/template_vars.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,7 +23,7 @@ "bytes" "fmt" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/snap" ) // templateVariables returns text defining apparmor variables that can be used in the @@ -32,7 +32,7 @@ var buf bytes.Buffer fmt.Fprintf(&buf, "@{APP_NAME}=\"%s\"\n", appInfo.Name) fmt.Fprintf(&buf, "@{SNAP_NAME}=\"%s\"\n", appInfo.Snap.Name()) - fmt.Fprintf(&buf, "@{SNAP_REVISION}=\"%d\"\n", appInfo.Snap.Revision) + fmt.Fprintf(&buf, "@{SNAP_REVISION}=\"%s\"\n", appInfo.Snap.Revision) fmt.Fprintf(&buf, "@{INSTALL_DIR}=\"/snap\"") return buf.Bytes() } diff -Nru snapd-2.0.5/interfaces/backend.go snapd-2.0.8/interfaces/backend.go --- snapd-2.0.5/interfaces/backend.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/backend.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package interfaces import ( - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/snap" ) // SecurityBackend abstracts interactions between the interface system and the diff -Nru snapd-2.0.5/interfaces/builtin/all.go snapd-2.0.8/interfaces/builtin/all.go --- snapd-2.0.5/interfaces/builtin/all.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/all.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,14 +20,17 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) var allInterfaces = []interfaces.Interface{ &BoolFileInterface{}, &BluezInterface{}, + &LocationControlInterface{}, + &LocationObserveInterface{}, &NetworkManagerInterface{}, NewFirewallControlInterface(), + NewGsettingsInterface(), NewHomeInterface(), NewLocaleControlInterface(), NewLogObserveInterface(), @@ -43,6 +46,8 @@ NewUnity7Interface(), NewX11Interface(), NewOpenglInterface(), + NewPulseAudioInterface(), + NewCupsControlInterface(), } // Interfaces returns all of the built-in interfaces. diff -Nru snapd-2.0.5/interfaces/builtin/all_test.go snapd-2.0.8/interfaces/builtin/all_test.go --- snapd-2.0.5/interfaces/builtin/all_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/all_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,8 +20,8 @@ package builtin_test import ( - "github.com/ubuntu-core/snappy/interfaces/builtin" - . "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/interfaces/builtin" + . "github.com/snapcore/snapd/testutil" . "gopkg.in/check.v1" ) @@ -34,7 +34,10 @@ all := builtin.Interfaces() c.Check(all, Contains, &builtin.BoolFileInterface{}) c.Check(all, Contains, &builtin.BluezInterface{}) + c.Check(all, Contains, &builtin.LocationControlInterface{}) + c.Check(all, Contains, &builtin.LocationObserveInterface{}) c.Check(all, DeepContains, builtin.NewFirewallControlInterface()) + c.Check(all, DeepContains, builtin.NewGsettingsInterface()) c.Check(all, DeepContains, builtin.NewHomeInterface()) c.Check(all, DeepContains, builtin.NewLocaleControlInterface()) c.Check(all, DeepContains, builtin.NewLogObserveInterface()) @@ -50,4 +53,6 @@ c.Check(all, DeepContains, builtin.NewUnity7Interface()) c.Check(all, DeepContains, builtin.NewX11Interface()) c.Check(all, DeepContains, builtin.NewOpenglInterface()) + c.Check(all, DeepContains, builtin.NewPulseAudioInterface()) + c.Check(all, DeepContains, builtin.NewCupsControlInterface()) } diff -Nru snapd-2.0.5/interfaces/builtin/bluez.go snapd-2.0.8/interfaces/builtin/bluez.go --- snapd-2.0.5/interfaces/builtin/bluez.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/bluez.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( "bytes" - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) var bluezPermanentSlotAppArmor = []byte(` diff -Nru snapd-2.0.5/interfaces/builtin/bluez_test.go snapd-2.0.8/interfaces/builtin/bluez_test.go --- snapd-2.0.5/interfaces/builtin/bluez_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/bluez_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,10 +22,10 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" ) type BluezInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/bool_file.go snapd-2.0.8/interfaces/builtin/bool_file.go --- snapd-2.0.5/interfaces/builtin/bool_file.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/bool_file.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,7 +24,7 @@ "path/filepath" "regexp" - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // BoolFileInterface is the type of all the bool-file interfaces. diff -Nru snapd-2.0.5/interfaces/builtin/bool_file_test.go snapd-2.0.8/interfaces/builtin/bool_file_test.go --- snapd-2.0.5/interfaces/builtin/bool_file_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/bool_file_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,10 +26,10 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" ) func Test(t *testing.T) { diff -Nru snapd-2.0.5/interfaces/builtin/common.go snapd-2.0.8/interfaces/builtin/common.go --- snapd-2.0.5/interfaces/builtin/common.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/common.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,8 +23,8 @@ "fmt" "path/filepath" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" ) type evalSymlinksFn func(string) (string, error) diff -Nru snapd-2.0.5/interfaces/builtin/common_test.go snapd-2.0.8/interfaces/builtin/common_test.go --- snapd-2.0.5/interfaces/builtin/common_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/common_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/testutil" ) // MockEvalSymlinks replaces the path/filepath.EvalSymlinks function used inside the caps package. diff -Nru snapd-2.0.5/interfaces/builtin/cups_control.go snapd-2.0.8/interfaces/builtin/cups_control.go --- snapd-2.0.5/interfaces/builtin/cups_control.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/cups_control.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,44 @@ +// -*- 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 builtin + +import "github.com/snapcore/snapd/interfaces" + +const cupsControlConnectedPlugAppArmor = ` +# Description: Can access cups control socket. This is restricted because it provides +# privileged access to configure printing. + +#include +` + +const cupsControlConnectedPlugSecComp = ` +setsockopt +` + +// NewCupsControlInterface returns a new "cups" interface. +func NewCupsControlInterface() interfaces.Interface { + return &commonInterface{ + name: "cups-control", + connectedPlugAppArmor: cupsControlConnectedPlugAppArmor, + connectedPlugSecComp: cupsControlConnectedPlugSecComp, + reservedForOS: true, + autoConnect: false, + } +} diff -Nru snapd-2.0.5/interfaces/builtin/firewall_control.go snapd-2.0.8/interfaces/builtin/firewall_control.go --- snapd-2.0.5/interfaces/builtin/firewall_control.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/firewall_control.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/firewall-control @@ -51,6 +51,7 @@ # snappy needs to have iptable_filter and ip6table_filter loaded, # they don't autoload. unix (bind) type=stream addr="@xtables", +/{,var/}run/xtables.lock rwk, @{PROC}/sys/kernel/modprobe r, @{PROC}/@{pid}/net/ r, diff -Nru snapd-2.0.5/interfaces/builtin/firewall_control_test.go snapd-2.0.8/interfaces/builtin/firewall_control_test.go --- snapd-2.0.5/interfaces/builtin/firewall_control_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/firewall_control_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type FirewallControlInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/gsettings.go snapd-2.0.8/interfaces/builtin/gsettings.go --- snapd-2.0.5/interfaces/builtin/gsettings.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/gsettings.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,67 @@ +// -*- 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 builtin + +import ( + "github.com/snapcore/snapd/interfaces" +) + +const gsettingsConnectedPlugAppArmor = ` +# Description: Can access global gsettings of the user's session. Restricted +# because this gives privileged access to sensitive information stored in +# gsettings and allows adjusting settings of other applications. +# Usage: reserved + +#include + +#include +owner /{,var/}run/user/*/dconf/user w, +owner @{HOME}/.config/dconf/user w, +dbus (receive, send) + bus=session + interface="ca.desrt.dconf.Writer" + peer=(label=unconfined), +` + +const gsettingsConnectedPlugSecComp = ` +# Description: Can access global gsettings of the user's session. Restricted +# because this gives privileged access to sensitive information stored in +# gsettings and allows adjusting settings of other applications. + +# dbus +connect +getsockname +recvmsg +send +sendto +sendmsg +socket +` + +// NewGsettingsInterface returns a new "gsettings" interface. +func NewGsettingsInterface() interfaces.Interface { + return &commonInterface{ + name: "gsettings", + connectedPlugAppArmor: gsettingsConnectedPlugAppArmor, + connectedPlugSecComp: gsettingsConnectedPlugSecComp, + reservedForOS: true, + autoConnect: true, + } +} diff -Nru snapd-2.0.5/interfaces/builtin/gsettings_test.go snapd-2.0.8/interfaces/builtin/gsettings_test.go --- snapd-2.0.5/interfaces/builtin/gsettings_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/gsettings_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,132 @@ +// -*- 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 builtin_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" +) + +type GsettingsInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&GsettingsInterfaceSuite{ + iface: builtin.NewGsettingsInterface(), + slot: &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "ubuntu-core", Type: snap.TypeOS}, + Name: "gsettings", + Interface: "gsettings", + }, + }, + plug: &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "other"}, + Name: "gsettings", + Interface: "gsettings", + }, + }, +}) + +func (s *GsettingsInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "gsettings") +} + +func (s *GsettingsInterfaceSuite) TestSanitizeSlot(c *C) { + err := s.iface.SanitizeSlot(s.slot) + c.Assert(err, IsNil) + err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "gsettings", + Interface: "gsettings", + }}) + c.Assert(err, ErrorMatches, "gsettings slots are reserved for the operating system snap") +} + +func (s *GsettingsInterfaceSuite) TestSanitizePlug(c *C) { + err := s.iface.SanitizePlug(s.plug) + c.Assert(err, IsNil) +} + +func (s *GsettingsInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { + c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, + PanicMatches, `slot is not of interface "gsettings"`) + c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, + PanicMatches, `plug is not of interface "gsettings"`) +} + +func (s *GsettingsInterfaceSuite) TestUnusedSecuritySystems(c *C) { + systems := [...]interfaces.SecuritySystem{interfaces.SecurityAppArmor, + interfaces.SecuritySecComp, interfaces.SecurityDBus, + interfaces.SecurityUDev} + for _, system := range systems { + snippet, err := s.iface.PermanentPlugSnippet(s.plug, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + } + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityDBus) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityUDev) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) +} + +func (s *GsettingsInterfaceSuite) TestUsedSecuritySystems(c *C) { + // connected plugs have a non-nil security snippet for apparmor + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + // connected plugs have a non-nil security snippet for seccomp + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecuritySecComp) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) +} + +func (s *GsettingsInterfaceSuite) TestUnexpectedSecuritySystems(c *C) { + snippet, err := s.iface.PermanentPlugSnippet(s.plug, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) +} + +func (s *GsettingsInterfaceSuite) TestAutoConnect(c *C) { + c.Check(s.iface.AutoConnect(), Equals, true) +} diff -Nru snapd-2.0.5/interfaces/builtin/home.go snapd-2.0.8/interfaces/builtin/home.go --- snapd-2.0.5/interfaces/builtin/home.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/home.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/home @@ -49,5 +49,6 @@ name: "home", connectedPlugAppArmor: homeConnectedPlugAppArmor, reservedForOS: true, + autoConnect: true, } } diff -Nru snapd-2.0.5/interfaces/builtin/home_test.go snapd-2.0.8/interfaces/builtin/home_test.go --- snapd-2.0.5/interfaces/builtin/home_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/home_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type HomeInterfaceSuite struct { @@ -124,5 +124,5 @@ } func (s *HomeInterfaceSuite) TestAutoConnect(c *C) { - c.Check(s.iface.AutoConnect(), Equals, false) + c.Check(s.iface.AutoConnect(), Equals, true) } diff -Nru snapd-2.0.5/interfaces/builtin/locale_control.go snapd-2.0.8/interfaces/builtin/locale_control.go --- snapd-2.0.5/interfaces/builtin/locale_control.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/locale_control.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/locale-control diff -Nru snapd-2.0.5/interfaces/builtin/locale_control_test.go snapd-2.0.8/interfaces/builtin/locale_control_test.go --- snapd-2.0.5/interfaces/builtin/locale_control_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/locale_control_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type LocaleControlInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/location_control.go snapd-2.0.8/interfaces/builtin/location_control.go --- snapd-2.0.5/interfaces/builtin/location_control.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/location_control.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,211 @@ +// -*- 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 builtin + +import ( + "bytes" + + "github.com/snapcore/snapd/interfaces" +) + +var locationControlPermanentSlotAppArmor = []byte(` +# Description: Allow operating as the location service. Reserved because this +# gives privileged access to the system. +# Usage: reserved + +# DBus accesses +#include +dbus (send) + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member="{Request,Release}Name" + peer=(name=org.freedesktop.DBus, label=unconfined), + +dbus (send) + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member="GetConnectionUnix{ProcessID,User}" + peer=(label=unconfined), + +# Allow binding the service to the requested connection name +dbus (bind) + bus=system + name="com.ubuntu.location.Service", + +dbus (receive, send) + bus=system + path=/com/ubuntu/location/Service{,/**} + interface=org.freedesktop.DBus** + peer=(label=unconfined), +`) + +var locationControlConnectedSlotAppArmor = []byte(` +# Allow connected clients to interact with the service + +# Allow clients to query/modify service properties +dbus (receive) + bus=system + path=/com/ubuntu/location/Service + interface=org.freedesktop.DBus.Properties + member="{Get,Set}" + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (send) + bus=system + path=/com/ubuntu/location/Service + interface=org.freedesktop.DBus.Properties + member=PropertiesChanged + peer=(label=###PLUG_SECURITY_TAGS###), +`) + +var locationControlConnectedPlugAppArmor = []byte(` +# Description: Allow using location service. Reserved because this gives +# privileged access to the service. +# Usage: reserved + +#include + +# Allow clients to query service properties +dbus (send) + bus=system + path=/com/ubuntu/location/Service + interface=org.freedesktop.DBus.Properties + member="{Get,Set}" + peer=(label=###SLOT_SECURITY_TAGS###), + +dbus (receive) + bus=system + path=/com/ubuntu/location/Service + interface=org.freedesktop.DBus.Properties + member=PropertiesChanged + peer=(label=###SLOT_SECURITY_TAGS###), + +dbus (receive) + bus=system + path=/ + interface=org.freedesktop.DBus.ObjectManager + peer=(label=unconfined), +`) + +var locationControlPermanentSlotSecComp = []byte(` +getsockname +recvmsg +sendmsg +sendto +`) + +var locationControlConnectedPlugSecComp = []byte(` +getsockname +recvmsg +sendmsg +sendto +`) + +var locationControlPermanentSlotDBus = []byte(` + + + + + +`) + +var locationControlConnectedPlugDBus = []byte(` + + + + + +`) + +type LocationControlInterface struct{} + +func (iface *LocationControlInterface) Name() string { + return "location-control" +} + +func (iface *LocationControlInterface) PermanentPlugSnippet(plug *interfaces.Plug, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityDBus, interfaces.SecurityAppArmor, interfaces.SecuritySecComp, interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *LocationControlInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor: + old := []byte("###SLOT_SECURITY_TAGS###") + new := slotAppLabelExpr(slot) + snippet := bytes.Replace(locationControlConnectedPlugAppArmor, old, new, -1) + return snippet, nil + case interfaces.SecurityDBus: + return locationControlConnectedPlugDBus, nil + case interfaces.SecuritySecComp: + return locationControlConnectedPlugSecComp, nil + case interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *LocationControlInterface) PermanentSlotSnippet(slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor: + return locationControlPermanentSlotAppArmor, nil + case interfaces.SecurityDBus: + return locationControlPermanentSlotDBus, nil + case interfaces.SecuritySecComp: + return locationControlPermanentSlotSecComp, nil + case interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *LocationControlInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor: + old := []byte("###PLUG_SECURITY_TAGS###") + new := plugAppLabelExpr(plug) + snippet := bytes.Replace(locationControlConnectedSlotAppArmor, old, new, -1) + return snippet, nil + case interfaces.SecurityDBus, interfaces.SecuritySecComp, interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *LocationControlInterface) SanitizePlug(slot *interfaces.Plug) error { + return nil +} + +func (iface *LocationControlInterface) SanitizeSlot(slot *interfaces.Slot) error { + return nil +} + +func (iface *LocationControlInterface) AutoConnect() bool { + return false +} diff -Nru snapd-2.0.5/interfaces/builtin/location_control_test.go snapd-2.0.8/interfaces/builtin/location_control_test.go --- snapd-2.0.5/interfaces/builtin/location_control_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/location_control_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,230 @@ +// -*- 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 builtin_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" +) + +type LocationControlInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&LocationControlInterfaceSuite{ + iface: &builtin.LocationControlInterface{}, + slot: &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "location"}, + Name: "location", + Interface: "location-control", + }, + }, + plug: &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "location"}, + Name: "location-client", + Interface: "location-control", + }, + }, +}) + +func (s *LocationControlInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "location-control") +} + +// The label glob when all apps are bound to the location slot +func (s *LocationControlInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { + app1 := &snap.AppInfo{Name: "app1"} + app2 := &snap.AppInfo{Name: "app2"} + slot := &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + } + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.*"),`) +} + +// The label uses alternation when some, but not all, apps is bound to the location slot +func (s *LocationControlInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelSome(c *C) { + app1 := &snap.AppInfo{Name: "app1"} + app2 := &snap.AppInfo{Name: "app2"} + app3 := &snap.AppInfo{Name: "app3"} + slot := &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + } + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.{app1,app2}"),`) +} + +// The label uses short form when exactly one app is bound to the location slot +func (s *LocationControlInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { + app := &snap.AppInfo{Name: "app"} + slot := &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + } + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.app"),`) +} + +// The label glob when all apps are bound to the location plug +func (s *LocationControlInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelAll(c *C) { + app1 := &snap.AppInfo{Name: "app1"} + app2 := &snap.AppInfo{Name: "app2"} + plug := &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + } + snippet, err := s.iface.ConnectedSlotSnippet(plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.*"),`) +} + +// The label uses alternation when some, but not all, apps is bound to the location plug +func (s *LocationControlInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelSome(c *C) { + app1 := &snap.AppInfo{Name: "app1"} + app2 := &snap.AppInfo{Name: "app2"} + app3 := &snap.AppInfo{Name: "app3"} + plug := &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + } + snippet, err := s.iface.ConnectedSlotSnippet(plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.{app1,app2}"),`) +} + +// The label uses short form when exactly one app is bound to the location plug +func (s *LocationControlInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelOne(c *C) { + app := &snap.AppInfo{Name: "app"} + plug := &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + } + snippet, err := s.iface.ConnectedSlotSnippet(plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.app"),`) +} + +func (s *LocationControlInterfaceSuite) TestUnusedSecuritySystems(c *C) { + systems := [...]interfaces.SecuritySystem{interfaces.SecuritySecComp, + interfaces.SecurityDBus, interfaces.SecurityUDev} + for _, system := range systems { + snippet, err := s.iface.PermanentPlugSnippet(s.plug, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + } + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityUDev) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, interfaces.SecurityUDev) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentPlugSnippet(s.plug, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) +} + +func (s *LocationControlInterfaceSuite) TestUsedSecuritySystems(c *C) { + systems := [...]interfaces.SecuritySystem{interfaces.SecurityAppArmor, + interfaces.SecuritySecComp, interfaces.SecurityDBus} + for _, system := range systems { + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + } + snippet, err := s.iface.ConnectedSlotSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) +} + +func (s *LocationControlInterfaceSuite) TestUnexpectedSecuritySystems(c *C) { + snippet, err := s.iface.PermanentPlugSnippet(s.plug, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) +} diff -Nru snapd-2.0.5/interfaces/builtin/location_observe.go snapd-2.0.8/interfaces/builtin/location_observe.go --- snapd-2.0.5/interfaces/builtin/location_observe.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/location_observe.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,297 @@ +// -*- 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 builtin + +import ( + "bytes" + + "github.com/snapcore/snapd/interfaces" +) + +var locationObservePermanentSlotAppArmor = []byte(` +# Description: Allow operating as the location service. Reserved because this +# gives privileged access to the system. +# Usage: reserved + +# DBus accesses +#include +dbus (send) + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member="{Request,Release}Name" + peer=(name=org.freedesktop.DBus, label=unconfined), + +dbus (send) + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member="GetConnectionUnix{ProcessID,User}" + peer=(label=unconfined), + +# Allow binding the service to the requested connection name +dbus (bind) + bus=system + name="com.ubuntu.location.Service", + +dbus (receive, send) + bus=system + path=/com/ubuntu/location/Service{,/**} + interface=org.freedesktop.DBus** + peer=(label=unconfined), +`) + +var locationObserveConnectedSlotAppArmor = []byte(` +# Allow connected clients to interact with the service + +# Allow the service to host sessions +dbus (bind) + bus=system + name="com.ubuntu.location.Service.Session", + +# Allow clients to create a session +dbus (receive) + bus=system + path=/com/ubuntu/location/Service + interface=com.ubuntu.location.Service + member=CreateSessionForCriteria + peer=(label=###PLUG_SECURITY_TAGS###), + +# Allow clients to query service properties +dbus (receive) + bus=system + path=/com/ubuntu/location/Service + interface=org.freedesktop.DBus.Properties + member=Get + peer=(label=###PLUG_SECURITY_TAGS###), + +# Allow clients to request starting/stopping updates +dbus (receive) + bus=system + path=/sessions/* + interface=com.ubuntu.location.Service.Session + member="{Start,Stop}PositionUpdates" + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (receive) + bus=system + path=/sessions/* + interface=com.ubuntu.location.Service.Session + member="{Start,Stop}HeadingUpdates" + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (receive) + bus=system + path=/sessions/* + interface=com.ubuntu.location.Service.Session + member="{Start,Stop}VelocityUpdates" + peer=(label=###PLUG_SECURITY_TAGS###), + +# Allow the service to send updates to clients +dbus (send) + bus=system + path=/sessions/* + interface=com.ubuntu.location.Service.Session + member="Update{Position,Heading,Velocity}" + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (send) + bus=system + path=/com/ubuntu/location/Service + interface=org.freedesktop.DBus.Properties + member=PropertiesChanged + peer=(label=###PLUG_SECURITY_TAGS###), +`) + +var locationObserveConnectedPlugAppArmor = []byte(` +# Description: Allow using location service. Reserved because this gives +# privileged access to the service. +# Usage: reserved + +#include + +# Allow clients to query service properties +dbus (send) + bus=system + path=/com/ubuntu/location/Service + interface=org.freedesktop.DBus.Properties + member=Get + peer=(label=###SLOT_SECURITY_TAGS###), + +# Allow clients to create a session +dbus (send) + bus=system + path=/com/ubuntu/location/Service + interface=com.ubuntu.location.Service + member=CreateSessionForCriteria + peer=(label=###SLOT_SECURITY_TAGS###), + +# Allow clients to request starting/stopping updates +dbus (send) + bus=system + path=/sessions/* + interface=com.ubuntu.location.Service.Session + member="{Start,Stop}PositionUpdates" + peer=(label=###SLOT_SECURITY_TAGS###), + +dbus (send) + bus=system + path=/sessions/* + interface=com.ubuntu.location.Service.Session + member="{Start,Stop}HeadingUpdates" + peer=(label=###SLOT_SECURITY_TAGS###), + +dbus (send) + bus=system + path=/sessions/* + interface=com.ubuntu.location.Service.Session + member="{Start,Stop}VelocityUpdates" + peer=(label=###SLOT_SECURITY_TAGS###), + +# Allow clients to receive updates from the service +dbus (receive) + bus=system + path=/sessions/* + interface=com.ubuntu.location.Service.Session + member="Update{Position,Heading,Velocity}" + peer=(label=###SLOT_SECURITY_TAGS###), + +dbus (receive) + bus=system + path=/com/ubuntu/location/Service + interface=org.freedesktop.DBus.Properties + member=PropertiesChanged + peer=(label=###SLOT_SECURITY_TAGS###), + +dbus (receive) + bus=system + path=/ + interface=org.freedesktop.DBus.ObjectManager + peer=(label=unconfined), +`) + +var locationObservePermanentSlotSecComp = []byte(` +getsockname +recvmsg +sendmsg +sendto +`) + +var locationObserveConnectedPlugSecComp = []byte(` +getsockname +recvmsg +sendmsg +sendto +`) + +var locationObservePermanentSlotDBus = []byte(` + + + + + + + + +`) + +var locationObserveConnectedPlugDBus = []byte(` + + + + + + + +`) + +type LocationObserveInterface struct{} + +func (iface *LocationObserveInterface) Name() string { + return "location-observe" +} + +func (iface *LocationObserveInterface) PermanentPlugSnippet(plug *interfaces.Plug, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityDBus, interfaces.SecurityAppArmor, interfaces.SecuritySecComp, interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *LocationObserveInterface) ConnectedPlugSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor: + old := []byte("###SLOT_SECURITY_TAGS###") + new := slotAppLabelExpr(slot) + snippet := bytes.Replace(locationObserveConnectedPlugAppArmor, old, new, -1) + return snippet, nil + case interfaces.SecurityDBus: + return locationObserveConnectedPlugDBus, nil + case interfaces.SecuritySecComp: + return locationObserveConnectedPlugSecComp, nil + case interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *LocationObserveInterface) PermanentSlotSnippet(slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor: + return locationObservePermanentSlotAppArmor, nil + case interfaces.SecurityDBus: + return locationObservePermanentSlotDBus, nil + case interfaces.SecuritySecComp: + return locationObservePermanentSlotSecComp, nil + case interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *LocationObserveInterface) ConnectedSlotSnippet(plug *interfaces.Plug, slot *interfaces.Slot, securitySystem interfaces.SecuritySystem) ([]byte, error) { + switch securitySystem { + case interfaces.SecurityAppArmor: + old := []byte("###PLUG_SECURITY_TAGS###") + new := plugAppLabelExpr(plug) + snippet := bytes.Replace(locationObserveConnectedSlotAppArmor, old, new, -1) + return snippet, nil + case interfaces.SecurityDBus, interfaces.SecuritySecComp, interfaces.SecurityUDev: + return nil, nil + default: + return nil, interfaces.ErrUnknownSecurity + } +} + +func (iface *LocationObserveInterface) SanitizePlug(slot *interfaces.Plug) error { + return nil +} + +func (iface *LocationObserveInterface) SanitizeSlot(slot *interfaces.Slot) error { + return nil +} + +func (iface *LocationObserveInterface) AutoConnect() bool { + return false +} diff -Nru snapd-2.0.5/interfaces/builtin/location_observe_test.go snapd-2.0.8/interfaces/builtin/location_observe_test.go --- snapd-2.0.5/interfaces/builtin/location_observe_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/location_observe_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,230 @@ +// -*- 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 builtin_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" +) + +type LocationObserveInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&LocationObserveInterfaceSuite{ + iface: &builtin.LocationObserveInterface{}, + slot: &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "location"}, + Name: "location", + Interface: "location-observe", + }, + }, + plug: &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "location"}, + Name: "location-client", + Interface: "location-observe", + }, + }, +}) + +func (s *LocationObserveInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "location-observe") +} + +// The label glob when all apps are bound to the location slot +func (s *LocationObserveInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { + app1 := &snap.AppInfo{Name: "app1"} + app2 := &snap.AppInfo{Name: "app2"} + slot := &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + } + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.*"),`) +} + +// The label uses alternation when some, but not all, apps is bound to the location slot +func (s *LocationObserveInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelSome(c *C) { + app1 := &snap.AppInfo{Name: "app1"} + app2 := &snap.AppInfo{Name: "app2"} + app3 := &snap.AppInfo{Name: "app3"} + slot := &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + } + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.{app1,app2}"),`) +} + +// The label uses short form when exactly one app is bound to the location slot +func (s *LocationObserveInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { + app := &snap.AppInfo{Name: "app"} + slot := &interfaces.Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + } + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.app"),`) +} + +// The label glob when all apps are bound to the location plug +func (s *LocationObserveInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelAll(c *C) { + app1 := &snap.AppInfo{Name: "app1"} + app2 := &snap.AppInfo{Name: "app2"} + plug := &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + } + snippet, err := s.iface.ConnectedSlotSnippet(plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.*"),`) +} + +// The label uses alternation when some, but not all, apps is bound to the location plug +func (s *LocationObserveInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelSome(c *C) { + app1 := &snap.AppInfo{Name: "app1"} + app2 := &snap.AppInfo{Name: "app2"} + app3 := &snap.AppInfo{Name: "app3"} + plug := &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + } + snippet, err := s.iface.ConnectedSlotSnippet(plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.{app1,app2}"),`) +} + +// The label uses short form when exactly one app is bound to the location plug +func (s *LocationObserveInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelOne(c *C) { + app := &snap.AppInfo{Name: "app"} + plug := &interfaces.Plug{ + PlugInfo: &snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + } + snippet, err := s.iface.ConnectedSlotSnippet(plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(string(snippet), testutil.Contains, `peer=(label="snap.location.app"),`) +} + +func (s *LocationObserveInterfaceSuite) TestUnusedSecuritySystems(c *C) { + systems := [...]interfaces.SecuritySystem{interfaces.SecuritySecComp, + interfaces.SecurityDBus, interfaces.SecurityUDev} + for _, system := range systems { + snippet, err := s.iface.PermanentPlugSnippet(s.plug, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + } + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, interfaces.SecurityUDev) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, interfaces.SecurityUDev) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentPlugSnippet(s.plug, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(snippet, IsNil) +} + +func (s *LocationObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { + systems := [...]interfaces.SecuritySystem{interfaces.SecurityAppArmor, + interfaces.SecuritySecComp, interfaces.SecurityDBus} + for _, system := range systems { + snippet, err := s.iface.ConnectedPlugSnippet(s.plug, s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, system) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) + } + snippet, err := s.iface.ConnectedSlotSnippet(s.plug, s.slot, interfaces.SecurityAppArmor) + c.Assert(err, IsNil) + c.Assert(snippet, Not(IsNil)) +} + +func (s *LocationObserveInterfaceSuite) TestUnexpectedSecuritySystems(c *C) { + snippet, err := s.iface.PermanentPlugSnippet(s.plug, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedPlugSnippet(s.plug, s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.PermanentSlotSnippet(s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) + snippet, err = s.iface.ConnectedSlotSnippet(s.plug, s.slot, "foo") + c.Assert(err, Equals, interfaces.ErrUnknownSecurity) + c.Assert(snippet, IsNil) +} diff -Nru snapd-2.0.5/interfaces/builtin/log_observe.go snapd-2.0.8/interfaces/builtin/log_observe.go --- snapd-2.0.5/interfaces/builtin/log_observe.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/log_observe.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/log-observe @@ -32,8 +32,12 @@ /var/log/** r, # Allow sysctl -w kernel.printk_ratelimit=# +/{,usr/}sbin/sysctl ixr, @{PROC}/sys/kernel/printk_ratelimit rw, +# Allow resolving kernel seccomp denials +/usr/bin/scmp_sys_resolver ixr, + # Needed since we are root and the owner/group doesn't match :\ # So long as we have this, the cap must be reserved. capability dac_override, diff -Nru snapd-2.0.5/interfaces/builtin/log_observe_test.go snapd-2.0.8/interfaces/builtin/log_observe_test.go --- snapd-2.0.5/interfaces/builtin/log_observe_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/log_observe_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type LogObserveInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/mount_observe.go snapd-2.0.8/interfaces/builtin/mount_observe.go --- snapd-2.0.5/interfaces/builtin/mount_observe.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/mount_observe.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/mount-observe diff -Nru snapd-2.0.5/interfaces/builtin/mount_observe_test.go snapd-2.0.8/interfaces/builtin/mount_observe_test.go --- snapd-2.0.5/interfaces/builtin/mount_observe_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/mount_observe_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type MountObserveInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/network_bind.go snapd-2.0.8/interfaces/builtin/network_bind.go --- snapd-2.0.5/interfaces/builtin/network_bind.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/network_bind.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/network-bind @@ -89,7 +89,7 @@ # of socket(), bind(), connect(), etc individually. While we could allow it, # we wouldn't be able to properly arg filter socketcall for AF_INET/AF_INET6 # when LP: #1446748 is implemented. -#socketcall +socketcall ` // NewNetworkBindInterface returns a new "network-bind" interface. diff -Nru snapd-2.0.5/interfaces/builtin/network_bind_test.go snapd-2.0.8/interfaces/builtin/network_bind_test.go --- snapd-2.0.5/interfaces/builtin/network_bind_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/network_bind_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type NetworkBindInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/network_control.go snapd-2.0.8/interfaces/builtin/network_control.go --- snapd-2.0.5/interfaces/builtin/network_control.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/network_control.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/network-control @@ -68,11 +68,9 @@ /{,usr/}{,s}bin/bridge ixr, /{,usr/}{,s}bin/dhclient Pxr, # use ixr instead if want to limit to snap dirs /{,usr/}{,s}bin/ifconfig ixr, -audit deny /{,usr/}{,s}bin/if{up,down} r, # the system uses these, snaps shouldn't /{,usr/}{,s}bin/ip ixr, /{,usr/}{,s}bin/ipmaddr ixr, /{,usr/}{,s}bin/iptunnel ixr, -audit deny /{,usr/}{,s}bin/mii-tool r, # needs capability sys_module /{,usr/}{,s}bin/nameif ixr, /{,usr/}{,s}bin/netstat ixr, # -p not supported /{,usr/}{,s}bin/nstat ixr, diff -Nru snapd-2.0.5/interfaces/builtin/network_control_test.go snapd-2.0.8/interfaces/builtin/network_control_test.go --- snapd-2.0.5/interfaces/builtin/network_control_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/network_control_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type NetworkControlInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/network.go snapd-2.0.8/interfaces/builtin/network.go --- snapd-2.0.5/interfaces/builtin/network.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/network.go 2016-06-08 05:58:01.000000000 +0000 @@ -19,7 +19,7 @@ package builtin -import "github.com/ubuntu-core/snappy/interfaces" +import "github.com/snapcore/snapd/interfaces" // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/network const networkConnectedPlugAppArmor = ` @@ -57,7 +57,7 @@ # of socket(), bind(), connect(), etc individually. While we could allow it, # we wouldn't be able to properly arg filter socketcall for AF_INET/AF_INET6 # when LP: #1446748 is implemented. -#socketcall +socketcall ` // NewNetworkInterface returns a new "network" interface. diff -Nru snapd-2.0.5/interfaces/builtin/network_manager.go snapd-2.0.8/interfaces/builtin/network_manager.go --- snapd-2.0.5/interfaces/builtin/network_manager.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/network_manager.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,8 +22,8 @@ import ( "bytes" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/release" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/release" ) var networkManagerPermanentSlotAppArmor = []byte(` diff -Nru snapd-2.0.5/interfaces/builtin/network_manager_test.go snapd-2.0.8/interfaces/builtin/network_manager_test.go --- snapd-2.0.5/interfaces/builtin/network_manager_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/network_manager_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,11 +22,11 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/release" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" ) type NetworkManagerInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/network_observe.go snapd-2.0.8/interfaces/builtin/network_observe.go --- snapd-2.0.5/interfaces/builtin/network_observe.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/network_observe.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/network-observe diff -Nru snapd-2.0.5/interfaces/builtin/network_observe_test.go snapd-2.0.8/interfaces/builtin/network_observe_test.go --- snapd-2.0.5/interfaces/builtin/network_observe_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/network_observe_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type NetworkObserveInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/network_test.go snapd-2.0.8/interfaces/builtin/network_test.go --- snapd-2.0.5/interfaces/builtin/network_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/network_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type NetworkInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/opengl.go snapd-2.0.8/interfaces/builtin/opengl.go --- snapd-2.0.5/interfaces/builtin/opengl.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/opengl.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,23 +20,26 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) const openglConnectedPlugAppArmor = ` -# Description: Can access opengl. +# Description: Can access opengl. # Usage: reserved # specific gl libs /var/lib/snapd/lib/gl/** rm, # nvidia - /proc/driver/nvidia/params r, - /sys/bus/pci/devices/** r, + @{PROC}/driver/nvidia/params r, + @{PROC}/modules r, /dev/nvidiactl rw, - /proc/modules r, /dev/nvidia-modeset rw, /dev/nvidia* rw, + + # FIXME: this is an information leak and snapd should instead query udev for + # the specific accesses associated with the above devices. + /sys/bus/pci/devices/** r, ` const openglConnectedPlugSecComp = ` diff -Nru snapd-2.0.5/interfaces/builtin/pulseaudio.go snapd-2.0.8/interfaces/builtin/pulseaudio.go --- snapd-2.0.5/interfaces/builtin/pulseaudio.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/pulseaudio.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,49 @@ +// -*- 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 builtin + +import ( + "github.com/snapcore/snapd/interfaces" +) + +const pulseaudioConnectedPlugAppArmor = ` +/etc/pulse/ r, +/etc/pulse/* r, +/{run,dev}/shm/pulse-shm-* rk, +owner /{,var/}run/user/*/pulse/ r, +owner /{,var/}run/user/*/pulse/native rwk, +` + +const pulseaudioConnectedPlugSecComp = ` +setsockopt +connect +sendto +` + +// NewPulseAudioInterface returns a new "pulseaudio" interface. +func NewPulseAudioInterface() interfaces.Interface { + return &commonInterface{ + name: "pulseaudio", + connectedPlugAppArmor: pulseaudioConnectedPlugAppArmor, + connectedPlugSecComp: pulseaudioConnectedPlugSecComp, + reservedForOS: true, + autoConnect: true, + } +} diff -Nru snapd-2.0.5/interfaces/builtin/snapd_control.go snapd-2.0.8/interfaces/builtin/snapd_control.go --- snapd-2.0.5/interfaces/builtin/snapd_control.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/snapd_control.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/snapd-control diff -Nru snapd-2.0.5/interfaces/builtin/snapd_control_test.go snapd-2.0.8/interfaces/builtin/snapd_control_test.go --- snapd-2.0.5/interfaces/builtin/snapd_control_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/snapd_control_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type SnapdControlInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/system_observe.go snapd-2.0.8/interfaces/builtin/system_observe.go --- snapd-2.0.5/interfaces/builtin/system_observe.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/system_observe.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/system-observe @@ -48,6 +48,14 @@ # Other miscellaneous accesses for observing the system @{PROC}/vmstat r, + +# These are not process-specific (/proc/*/... and /proc/*/task/*/...) +@{PROC}/*/{,task/,task/*/} r, +@{PROC}/*/{,task/*/}auxv r, +@{PROC}/*/{,task/*/}cmdline r, +@{PROC}/*/{,task/*/}stat r, +@{PROC}/*/{,task/*/}statm r, +@{PROC}/*/{,task/*/}status r, ` // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/seccomp/policygroups/ubuntu-core/16.04/system-observe diff -Nru snapd-2.0.5/interfaces/builtin/system_observe_test.go snapd-2.0.8/interfaces/builtin/system_observe_test.go --- snapd-2.0.5/interfaces/builtin/system_observe_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/system_observe_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type SystemObserveInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/timeserver_control.go snapd-2.0.8/interfaces/builtin/timeserver_control.go --- snapd-2.0.5/interfaces/builtin/timeserver_control.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/timeserver_control.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/timeserver-control diff -Nru snapd-2.0.5/interfaces/builtin/timeserver_control_test.go snapd-2.0.8/interfaces/builtin/timeserver_control_test.go --- snapd-2.0.5/interfaces/builtin/timeserver_control_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/timeserver_control_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type TimeserverControlInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/timezone_control.go snapd-2.0.8/interfaces/builtin/timezone_control.go --- snapd-2.0.5/interfaces/builtin/timezone_control.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/timezone_control.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/timezone-control diff -Nru snapd-2.0.5/interfaces/builtin/timezone_control_test.go snapd-2.0.8/interfaces/builtin/timezone_control_test.go --- snapd-2.0.5/interfaces/builtin/timezone_control_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/timezone_control_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type TimezoneControlInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/unity7.go snapd-2.0.8/interfaces/builtin/unity7.go --- snapd-2.0.5/interfaces/builtin/unity7.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/unity7.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/unity7 @@ -66,12 +66,102 @@ #owner @{HOME}/.themes/** r, +# input methods (ibus) # subset of ibus abstraction /usr/lib/@{multiarch}/gtk-2.0/[0-9]*/immodules/im-ibus.so mr, owner @{HOME}/.config/ibus/ r, owner @{HOME}/.config/ibus/bus/ r, owner @{HOME}/.config/ibus/bus/* r, +# allow communicating with ibus-daemon (this allows sniffing key events) +unix (connect, receive, send) + type=stream + peer=(addr="@/tmp/ibus/dbus-*"), + + +# input methods (mozc) +# allow communicating with mozc server (TODO: investigate if allows sniffing) +unix (connect, receive, send) + type=stream + peer=(addr="@tmp/.mozc.*"), + + +# input methods (fcitx) +# allow communicating with fcitx dbus service +dbus send + bus=fcitx + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member={Hello,AddMatch,RemoveMatch,GetNameOwner,NameHasOwner,StartServiceByName} + peer=(name=org.freedesktop.DBus), + +owner @{HOME}/.config/fcitx/dbus/* r, + +# allow creating an input context +dbus send + bus=fcitx + path=/inputmethod + interface=org.fcitx.Fcitx.InputMethod + member=CreateIC* + peer=(label=unconfined), + +# allow setting up and tearing down the input context +dbus send + bus=fcitx + path=/inputcontext_[0-9]* + interface=org.fcitx.Fcitx.InputContext + member="{Close,Destroy,Enable}IC" + peer=(label=unconfined), + +dbus send + bus=fcitx + path=/inputcontext_[0-9]* + interface=org.fcitx.Fcitx.InputContext + member=Reset + peer=(label=unconfined), + +# allow service to send us signals +dbus receive + bus=fcitx + peer=(label=unconfined), + +# use the input context +dbus send + bus=fcitx + path=/inputcontext_[0-9]* + interface=org.fcitx.Fcitx.InputContext + member="Focus{In,Out}" + peer=(label=unconfined), + +dbus send + bus=fcitx + path=/inputcontext_[0-9]* + interface=org.fcitx.Fcitx.InputContext + member="{CommitPreedit,Set*}" + peer=(label=unconfined), + +# this is an information leak and allows key and mouse sniffing. If the input +# context path were tied to the process' security label, this would not be an +# issue. +dbus send + bus=fcitx + path=/inputcontext_[0-9]* + interface=org.fcitx.Fcitx.InputContext + member="{MouseEvent,ProcessKeyEvent}" + peer=(label=unconfined), + +# this method does not exist with the sunpinyin backend (at least), so allow +# it for other input methods. This may consitute an information leak (which, +# again, could be avoided if the path were tied to the process' security +# label). +dbus send + bus=fcitx + path=/inputcontext_[0-9]* + interface=org.freedesktop.DBus.Properties + member=GetAll + peer=(label=unconfined), + + # subset of freedesktop.org /usr/share/mime/** r, owner @{HOME}/.local/share/mime/** r, @@ -117,32 +207,43 @@ peer=(label=unconfined), # gmenu +# Note: the gmenu DBus api was not designed for application isolation and apps +# may specify anything as their 'path'. For example, these work in the many +# cases: +# - /org/gtk/Application/anonymous{,/**} +# - /com/canonical/unity/gtk/window/[0-9]* +# but libreoffice does: +# - /org/libreoffice{,/**} +# As such, cannot mediate by DBus path so we'll be as strict as we can in the +# other mediated parts dbus (send) bus=session interface=org.gtk.Actions - path={/org/gtk/Application/anonymous{,/**},/com/canonical/unity/gtk/window/[0-9]*} member=Changed - peer=(label=unconfined), + peer=(name=org.freedesktop.DBus, label=unconfined), dbus (receive) bus=session interface=org.gtk.Actions - path={/org/gtk/Application/anonymous{,/**},/com/canonical/unity/gtk/window/[0-9]*} member={Activate,DescribeAll,SetState} peer=(label=unconfined), dbus (receive) bus=session interface=org.gtk.Menus - path={/org/gtk/Application/anonymous{,/**},/com/canonical/unity/gtk/window/[0-9]*} member={Start,End} peer=(label=unconfined), -dbus (receive,send) +dbus (send) bus=session interface=org.gtk.Menus - path={/org/gtk/Application/anonymous{,/**},/com/canonical/unity/gtk/window/[0-9]*} member=Changed + peer=(name=org.freedesktop.DBus, label=unconfined), + +# url helper +dbus (send) + bus=session + interface=com.canonical.SafeLauncher.OpenURL peer=(label=unconfined), # dbusmenu @@ -228,11 +329,71 @@ member=NotificationClosed peer=(label=unconfined), +# unity launcher +dbus (send) + bus=session + path=/com/canonical/unity/launcherentry/[0-9]* + interface=com.canonical.Unity.LauncherEntry + member=Update + peer=(name=org.freedesktop.DBus, label=unconfined), + +dbus (send) + bus=session + path=/com/canonical/unity/launcherentry/[0-9]* + interface=com.canonical.dbusmenu + member="{LayoutUpdated,ItemsPropertiesUpdated}" + peer=(name=org.freedesktop.DBus, label=unconfined), + +dbus (receive) + bus=session + path=/com/canonical/unity/launcherentry/[0-9]* + interface="{com.canonical.dbusmenu,org.freedesktop.DBus.Properties}" + member=Get* + peer=(label=unconfined), + +# This rule is meant to be covered by abstractions/dbus-session-strict but +# the unity launcher code has a typo that uses /org/freedesktop/dbus as the +# path instead of /org/freedesktop/DBus, so we need to all it here. +dbus (send) + bus=session + path=/org/freedesktop/dbus + interface=org.freedesktop.DBus + member=NameHasOwner + peer=(name=org.freedesktop.DBus, label=unconfined), + +# appmenu +dbus (send) + bus=session + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=ListNames + peer=(name=org.freedesktop.DBus, label=unconfined), + +dbus (send) + bus=session + path=/com/canonical/AppMenu/Registrar + interface=com.canonical.AppMenu.Registrar + member="{RegisterWindow,UnregisterWindow}" + peer=(label=unconfined), + +dbus (send) + bus=session + path=/com/canonical/AppMenu/Registrar + interface=com.canonical.dbusmenu + member=UnregisterWindow + peer=(label=unconfined), + +dbus (receive) + bus=session + path=/com/canonical/menu/[0-9]* + interface="{org.freedesktop.DBus.Properties,com.canonical.dbusmenu}" + member="{GetAll,GetLayout}" + peer=(label=unconfined), + + # Lttng tracing is very noisy and should not be allowed by confined apps. Can # safely deny. LP: #1260491 deny /{,var/}run/shm/lttng-ust-* r, - -# TODO: pull in modern items from ubuntu-unity7-base abstraction, eg, HUD, etc ` // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/seccomp/policygroups/ubuntu-core/16.04/unity7 diff -Nru snapd-2.0.5/interfaces/builtin/unity7_test.go snapd-2.0.8/interfaces/builtin/unity7_test.go --- snapd-2.0.5/interfaces/builtin/unity7_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/unity7_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,9 +22,9 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" ) type Unity7InterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/builtin/utils.go snapd-2.0.8/interfaces/builtin/utils.go --- snapd-2.0.5/interfaces/builtin/utils.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/utils.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,28 +24,29 @@ "fmt" "sort" - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" ) -// slotAppLabelExpr returns the specification of the apparmor label describing +// AppLabelExpr returns the specification of the apparmor label describing // all the apps bound to a given slot. The result has one of three forms, // depending on how apps are bound to the slot: // // - "snap.$snap.$app" if there is exactly one app bound // - "snap.$snap.{$app1,...$appN}" if there are some, but not all, apps bound // - "snap.$snap.*" if all apps are bound to the slot -func slotAppLabelExpr(slot *interfaces.Slot) []byte { +func appLabelExpr(apps map[string]*snap.AppInfo, snap *snap.Info) []byte { var buf bytes.Buffer - fmt.Fprintf(&buf, `"snap.%s.`, slot.Snap.Name()) - if len(slot.Apps) == 1 { - for appName := range slot.Apps { + fmt.Fprintf(&buf, `"snap.%s.`, snap.Name()) + if len(apps) == 1 { + for appName := range apps { buf.WriteString(appName) } - } else if len(slot.Apps) == len(slot.Snap.Apps) { + } else if len(apps) == len(snap.Apps) { buf.WriteByte('*') } else { - appNames := make([]string, 0, len(slot.Apps)) - for appName := range slot.Apps { + appNames := make([]string, 0, len(apps)) + for appName := range apps { appNames = append(appNames, appName) } sort.Strings(appNames) @@ -60,3 +61,11 @@ buf.WriteByte('"') return buf.Bytes() } + +func slotAppLabelExpr(slot *interfaces.Slot) []byte { + return appLabelExpr(slot.Apps, slot.Snap) +} + +func plugAppLabelExpr(plug *interfaces.Plug) []byte { + return appLabelExpr(plug.Apps, plug.Snap) +} diff -Nru snapd-2.0.5/interfaces/builtin/x11.go snapd-2.0.8/interfaces/builtin/x11.go --- snapd-2.0.5/interfaces/builtin/x11.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/x11.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package builtin import ( - "github.com/ubuntu-core/snappy/interfaces" + "github.com/snapcore/snapd/interfaces" ) // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/x diff -Nru snapd-2.0.5/interfaces/builtin/x11_test.go snapd-2.0.8/interfaces/builtin/x11_test.go --- snapd-2.0.5/interfaces/builtin/x11_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/builtin/x11_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,10 +22,10 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" ) type X11InterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/core.go snapd-2.0.8/interfaces/core.go --- snapd-2.0.5/interfaces/core.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/core.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,7 +24,7 @@ "fmt" "regexp" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/snap" ) // Plug represents the potential of a given snap to connect to a slot. diff -Nru snapd-2.0.5/interfaces/core_test.go snapd-2.0.8/interfaces/core_test.go --- snapd-2.0.5/interfaces/core_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/core_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,7 +24,7 @@ . "gopkg.in/check.v1" - . "github.com/ubuntu-core/snappy/interfaces" + . "github.com/snapcore/snapd/interfaces" ) func Test(t *testing.T) { diff -Nru snapd-2.0.5/interfaces/dbus/backend.go snapd-2.0.8/interfaces/dbus/backend.go --- snapd-2.0.5/interfaces/dbus/backend.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/dbus/backend.go 2016-06-08 05:58:01.000000000 +0000 @@ -32,10 +32,10 @@ "fmt" "os" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" ) // Backend is responsible for maintaining DBus policy files. diff -Nru snapd-2.0.5/interfaces/dbus/backend_test.go snapd-2.0.8/interfaces/dbus/backend_test.go --- snapd-2.0.5/interfaces/dbus/backend_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/dbus/backend_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,10 +26,10 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/dbus" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/snap" ) type backendSuite struct { diff -Nru snapd-2.0.5/interfaces/dbus/dbus_test.go snapd-2.0.8/interfaces/dbus/dbus_test.go --- snapd-2.0.5/interfaces/dbus/dbus_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/dbus/dbus_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( "testing" - "github.com/ubuntu-core/snappy/interfaces/dbus" + "github.com/snapcore/snapd/interfaces/dbus" . "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/interfaces/json_test.go snapd-2.0.8/interfaces/json_test.go --- snapd-2.0.5/interfaces/json_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/json_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,8 +24,8 @@ . "gopkg.in/check.v1" - . "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/snap" + . "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" ) type JSONSuite struct{} diff -Nru snapd-2.0.5/interfaces/naming_test.go snapd-2.0.8/interfaces/naming_test.go --- snapd-2.0.5/interfaces/naming_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/naming_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( . "gopkg.in/check.v1" - . "github.com/ubuntu-core/snappy/interfaces" + . "github.com/snapcore/snapd/interfaces" ) type NamingSuite struct{} diff -Nru snapd-2.0.5/interfaces/repo.go snapd-2.0.8/interfaces/repo.go --- snapd-2.0.5/interfaces/repo.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/repo.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ "strings" "sync" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/snap" ) // Repository stores all known snappy plugs and slots and ifaces. diff -Nru snapd-2.0.5/interfaces/repo_test.go snapd-2.0.8/interfaces/repo_test.go --- snapd-2.0.5/interfaces/repo_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/repo_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,9 +24,9 @@ . "gopkg.in/check.v1" - . "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/testutil" + . "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" ) type RepositorySuite struct { diff -Nru snapd-2.0.5/interfaces/seccomp/backend.go snapd-2.0.8/interfaces/seccomp/backend.go --- snapd-2.0.5/interfaces/seccomp/backend.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/seccomp/backend.go 2016-06-08 05:58:01.000000000 +0000 @@ -37,10 +37,10 @@ "fmt" "os" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" ) // Backend is responsible for maintaining seccomp profiles for ubuntu-core-launcher. diff -Nru snapd-2.0.5/interfaces/seccomp/backend_test.go snapd-2.0.8/interfaces/seccomp/backend_test.go --- snapd-2.0.5/interfaces/seccomp/backend_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/seccomp/backend_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,11 +26,11 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/seccomp" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" ) type backendSuite struct { diff -Nru snapd-2.0.5/interfaces/testbackend.go snapd-2.0.8/interfaces/testbackend.go --- snapd-2.0.5/interfaces/testbackend.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/testbackend.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package interfaces import ( - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/snap" ) // TestSecurityBackend is a security backend intended for testing. diff -Nru snapd-2.0.5/interfaces/testtype_test.go snapd-2.0.8/interfaces/testtype_test.go --- snapd-2.0.5/interfaces/testtype_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/testtype_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,8 +24,8 @@ . "gopkg.in/check.v1" - . "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/snap" + . "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" ) type TestInterfaceSuite struct { diff -Nru snapd-2.0.5/interfaces/udev/backend.go snapd-2.0.8/interfaces/udev/backend.go --- snapd-2.0.5/interfaces/udev/backend.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/udev/backend.go 2016-06-08 05:58:01.000000000 +0000 @@ -29,10 +29,10 @@ "fmt" "os" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" ) // Backend is responsible for maintaining udev rules. diff -Nru snapd-2.0.5/interfaces/udev/backend_test.go snapd-2.0.8/interfaces/udev/backend_test.go --- snapd-2.0.5/interfaces/udev/backend_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/udev/backend_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,11 +26,11 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/udev" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" ) type backendSuite struct { diff -Nru snapd-2.0.5/interfaces/udev/udev_test.go snapd-2.0.8/interfaces/udev/udev_test.go --- snapd-2.0.5/interfaces/udev/udev_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/interfaces/udev/udev_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,8 +24,8 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/interfaces/udev" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/testutil" ) func Test(t *testing.T) { diff -Nru snapd-2.0.5/logger/logger_test.go snapd-2.0.8/logger/logger_test.go --- snapd-2.0.5/logger/logger_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/logger/logger_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ "os" "testing" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/testutil" . "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/mkversion.sh snapd-2.0.8/mkversion.sh --- snapd-2.0.5/mkversion.sh 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/mkversion.sh 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,18 @@ +#!/bin/sh +set -e + +if [ -z "$GOPACKAGE" ]; then + # not being run from 'go generate' + cd "$(dirname "$0")"/cmd +fi + +v="$( git describe --dirty --always | sed -e 's/-/+git/;y/-/./' )" + +cat < version_generated.go +// generated by mkversion.sh; do not edit +package cmd + +func init() { + Version = "$v" +} +EOF diff -Nru snapd-2.0.5/notifications/subscriber.go snapd-2.0.8/notifications/subscriber.go --- snapd-2.0.5/notifications/subscriber.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/notifications/subscriber.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ "github.com/gorilla/websocket" - "github.com/ubuntu-core/snappy/strutil" + "github.com/snapcore/snapd/strutil" ) // A Subscriber is interested in receiving notifications diff -Nru snapd-2.0.5/osutil/io.go snapd-2.0.8/osutil/io.go --- snapd-2.0.5/osutil/io.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/osutil/io.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,7 +23,7 @@ "os" "path/filepath" - "github.com/ubuntu-core/snappy/strutil" + "github.com/snapcore/snapd/strutil" ) // AtomicWriteFlags are a bitfield of flags for AtomicWriteFile diff -Nru snapd-2.0.5/osutil/io_test.go snapd-2.0.8/osutil/io_test.go --- snapd-2.0.5/osutil/io_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/osutil/io_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,7 +25,7 @@ "os" "path/filepath" - "github.com/ubuntu-core/snappy/strutil" + "github.com/snapcore/snapd/strutil" . "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/osutil/sync_dir_test.go snapd-2.0.8/osutil/sync_dir_test.go --- snapd-2.0.5/osutil/sync_dir_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/osutil/sync_dir_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,7 +27,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/osutil" ) type EnsureDirStateSuite struct { diff -Nru snapd-2.0.5/overlord/assertstate/assertmgr.go snapd-2.0.8/overlord/assertstate/assertmgr.go --- snapd-2.0.5/overlord/assertstate/assertmgr.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/assertstate/assertmgr.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,11 +24,11 @@ import ( "os" - "github.com/ubuntu-core/snappy/asserts" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord/state" ) // AssertManager is responsible for the enforcement of assertions in diff -Nru snapd-2.0.5/overlord/assertstate/assertmgr_test.go snapd-2.0.8/overlord/assertstate/assertmgr_test.go --- snapd-2.0.5/overlord/assertstate/assertmgr_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/assertstate/assertmgr_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,11 +24,11 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" - "github.com/ubuntu-core/snappy/dirs" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/dirs" - "github.com/ubuntu-core/snappy/overlord/assertstate" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord/assertstate" + "github.com/snapcore/snapd/overlord/state" ) func TestAssertManager(t *testing.T) { TestingT(t) } diff -Nru snapd-2.0.5/overlord/auth/auth.go snapd-2.0.8/overlord/auth/auth.go --- snapd-2.0.5/overlord/auth/auth.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/auth/auth.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,7 +25,7 @@ "net/http" "sort" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord/state" ) // AuthState represents current authenticated users as tracked in state diff -Nru snapd-2.0.5/overlord/auth/auth_test.go snapd-2.0.8/overlord/auth/auth_test.go --- snapd-2.0.5/overlord/auth/auth_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/auth/auth_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,8 +25,8 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/overlord/auth" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/state" ) // Hook up gocheck into the "go test" runner. diff -Nru snapd-2.0.5/overlord/backend.go snapd-2.0.8/overlord/backend.go --- snapd-2.0.5/overlord/backend.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/backend.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( "time" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/osutil" ) type overlordStateBackend struct { diff -Nru snapd-2.0.5/overlord/firstboot.go snapd-2.0.8/overlord/firstboot.go --- snapd-2.0.5/overlord/firstboot.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/firstboot.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,11 +22,11 @@ import ( "fmt" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/overlord/snapstate" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/snappy" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snappy" ) func populateStateFromInstalled() error { diff -Nru snapd-2.0.5/overlord/ifacestate/handlers.go snapd-2.0.8/overlord/ifacestate/handlers.go --- snapd-2.0.5/overlord/ifacestate/handlers.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/ifacestate/handlers.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,11 +24,11 @@ "gopkg.in/tomb.v2" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/overlord/snapstate" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" ) func (m *InterfaceManager) doSetupProfiles(task *state.Task, _ *tomb.Tomb) error { @@ -55,11 +55,7 @@ // Set DevMode flag if SnapSetup.Flags indicates it should be done // but remember the old value in the task in case we undo. task.Set("old-devmode", snapState.DevMode()) - if ss.DevMode() { - snapState.Flags |= snapstate.DevMode - } else { - snapState.Flags &= ^snapstate.DevMode - } + snapState.SetDevMode(ss.DevMode()) snapstate.Set(task.State(), snapName, &snapState) // The snap may have been updated so perform the following operation to @@ -143,11 +139,7 @@ } // Restore the state of DevMode flag if old-devmode was saved in the task. if err == nil { - if oldDevMode { - snapState.Flags |= snapstate.DevMode - } else { - snapState.Flags &= ^snapstate.DevMode - } + snapState.SetDevMode(oldDevMode) snapstate.Set(st, snapName, &snapState) } diff -Nru snapd-2.0.5/overlord/ifacestate/helpers.go snapd-2.0.8/overlord/ifacestate/helpers.go --- snapd-2.0.5/overlord/ifacestate/helpers.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/ifacestate/helpers.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,16 +23,17 @@ "fmt" "strings" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/interfaces/apparmor" - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/interfaces/dbus" - "github.com/ubuntu-core/snappy/interfaces/seccomp" - "github.com/ubuntu-core/snappy/interfaces/udev" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/overlord/snapstate" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) func (m *InterfaceManager) initialize(extra []interfaces.Interface) error { @@ -221,5 +222,13 @@ } var securityBackends = []interfaces.SecurityBackend{ - &apparmor.Backend{}, &seccomp.Backend{}, &dbus.Backend{}, &udev.Backend{}, + &seccomp.Backend{}, &dbus.Backend{}, &udev.Backend{}, +} + +func init() { + switch release.ReleaseInfo.ID { + case "ubuntu": + // Enable apparmor support when running on Ubuntu + securityBackends = append(securityBackends, &apparmor.Backend{}) + } } diff -Nru snapd-2.0.5/overlord/ifacestate/ifacemgr.go snapd-2.0.8/overlord/ifacestate/ifacemgr.go --- snapd-2.0.5/overlord/ifacestate/ifacemgr.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/ifacestate/ifacemgr.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,9 +24,9 @@ import ( "fmt" - "github.com/ubuntu-core/snappy/i18n" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/overlord/state" ) // InterfaceManager is responsible for the maintenance of interfaces in diff -Nru snapd-2.0.5/overlord/ifacestate/ifacemgr_test.go snapd-2.0.8/overlord/ifacestate/ifacemgr_test.go --- snapd-2.0.5/overlord/ifacestate/ifacemgr_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/ifacestate/ifacemgr_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,14 +24,13 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/interfaces" - "github.com/ubuntu-core/snappy/overlord/ifacestate" - "github.com/ubuntu-core/snappy/overlord/snapstate" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" - "github.com/ubuntu-core/snappy/snappy" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/overlord/ifacestate" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" ) func TestInterfaceManager(t *testing.T) { TestingT(t) } @@ -212,7 +211,7 @@ } func (s *interfaceManagerSuite) mockUpdatedSnap(c *C, yamlText string, revision int) *snap.Info { - sideInfo := &snap.SideInfo{Revision: revision} + sideInfo := &snap.SideInfo{Revision: snap.R(revision)} snapInfo := snaptest.MockSnap(c, yamlText, sideInfo) s.state.Lock() @@ -451,7 +450,10 @@ // Ensure that we have slots on the OS snap. repo := mgr.Repository() slots := repo.Slots(snapInfo.Name()) - c.Assert(slots, HasLen, 17) + // NOTE: This is not an exact test as it duplicates functionality elsewhere + // and is was a pain to update each time. This is correctly handled by the + // implicit slot tests in snap/implicit_test.go + c.Assert(len(slots) > 18, Equals, true) } func (s *interfaceManagerSuite) TestDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOnPlugSide(c *C) { @@ -466,7 +468,7 @@ s.testDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOn(c, snapInfo.Name(), snapInfo.Revision) } -func (s *interfaceManagerSuite) testDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOn(c *C, snapName string, revision int) { +func (s *interfaceManagerSuite) testDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOn(c *C, snapName string, revision snap.Revision) { s.mockIface(c, &interfaces.TestInterface{InterfaceName: "test"}) s.state.Lock() @@ -499,8 +501,8 @@ c.Check(slot.Connections[0], DeepEquals, interfaces.PlugRef{Snap: "consumer", Name: "plug"}) } -// The setup-profiles task will honor snappy.DeveloperMode flag by storing it -// in the SnapState.Flags (as DevMode) and by actually setting up security +// The setup-profiles task will honor snapstate.DevMode flag by storing it +// in the SnapState.Flags and by actually setting up security // using that flag. Old copy of SnapState.Flag's DevMode is saved for the undo // handler under `old-devmode`. func (s *interfaceManagerSuite) TestSetupProfilesHonorsDevMode(c *C) { @@ -513,7 +515,7 @@ // Run the setup-profiles task and let it finish. // Note that the task will see SnapSetup.Flags equal to DeveloperMode. change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{ - Name: snapInfo.Name(), Flags: int(snappy.DeveloperMode), Revision: snapInfo.Revision}) + Name: snapInfo.Name(), Flags: snapstate.DevMode, Revision: snapInfo.Revision}) mgr.Ensure() mgr.Wait() mgr.Stop() @@ -570,7 +572,7 @@ // Sanity check, the revisions are different. c.Assert(oldSnapInfo.Revision, Not(Equals), 42) - c.Assert(newSnapInfo.Revision, Equals, 42) + c.Assert(newSnapInfo.Revision, Equals, snap.R(42)) // Run the setup-profiles task for the new revision and let it finish. change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{ @@ -602,7 +604,7 @@ // // This variant checks restoring DevMode to true func (s *interfaceManagerSuite) TestSetupProfilesUndoDevModeTrue(c *C) { - s.undoDevModeCheck(c, snappy.InstallFlags(0), true) + s.undoDevModeCheck(c, 0, true) } // The undo handler of the setup-profiles task will honor `old-devmode` that @@ -611,10 +613,10 @@ // // This variant checks restoring DevMode to false func (s *interfaceManagerSuite) TestSetupProfilesUndoDevModeFalse(c *C) { - s.undoDevModeCheck(c, snappy.InstallFlags(0), false) + s.undoDevModeCheck(c, 0, false) } -func (s *interfaceManagerSuite) undoDevModeCheck(c *C, flags snappy.InstallFlags, devMode bool) { +func (s *interfaceManagerSuite) undoDevModeCheck(c *C, flags snapstate.Flags, devMode bool) { // Put the OS and sample snaps in place. s.mockSnap(c, osSnapYaml) snapInfo := s.mockSnap(c, sampleSnapYaml) @@ -624,7 +626,10 @@ // Run the setup-profiles task in UndoMode and let it finish. change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{ - Name: snapInfo.Name(), Flags: int(flags), Revision: snapInfo.Revision}) + Name: snapInfo.Name(), + Flags: snapstate.SnapSetupFlags(flags), + Revision: snapInfo.Revision, + }) s.state.Lock() task := change.Tasks()[0] // Inject the old value of DevMode flag for the task handler to restore diff -Nru snapd-2.0.5/overlord/overlord.go snapd-2.0.8/overlord/overlord.go --- snapd-2.0.5/overlord/overlord.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/overlord.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,13 +28,13 @@ "gopkg.in/tomb.v2" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" - "github.com/ubuntu-core/snappy/overlord/assertstate" - "github.com/ubuntu-core/snappy/overlord/ifacestate" - "github.com/ubuntu-core/snappy/overlord/snapstate" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord/assertstate" + "github.com/snapcore/snapd/overlord/ifacestate" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" ) var ( diff -Nru snapd-2.0.5/overlord/overlord_test.go snapd-2.0.8/overlord/overlord_test.go --- snapd-2.0.5/overlord/overlord_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/overlord_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -30,11 +30,11 @@ . "gopkg.in/check.v1" "gopkg.in/tomb.v2" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/testutil" - "github.com/ubuntu-core/snappy/overlord" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord" + "github.com/snapcore/snapd/overlord/state" ) func TestOverlord(t *testing.T) { TestingT(t) } diff -Nru snapd-2.0.5/overlord/snapstate/backend/copydata.go snapd-2.0.8/overlord/snapstate/backend/copydata.go --- snapd-2.0.5/overlord/snapstate/backend/copydata.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/backend/copydata.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,65 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-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 backend + +import ( + "os" + + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" +) + +// CopySnapData makes a copy of oldSnap data for newSnap in its data directories. +func (b Backend) CopySnapData(newSnap, oldSnap *snap.Info, meter progress.Meter) error { + // deal with the old data or + // otherwise just create a empty data dir + + // Make sure the common data directory exists, even if this isn't a new + // install. + if err := os.MkdirAll(newSnap.CommonDataDir(), 0755); err != nil { + return err + } + + if oldSnap == nil { + return os.MkdirAll(newSnap.DataDir(), 0755) + } + + return copySnapData(oldSnap, newSnap) +} + +// UndoCopySnapData removes the copy that may have been done for newInfo snap of oldInfo snap data and also the data directories that may have been created for newInfo snap. +func (b Backend) UndoCopySnapData(newInfo *snap.Info, oldInfo *snap.Info, meter progress.Meter) error { + err1 := b.RemoveSnapData(newInfo) + if err1 != nil { + logger.Noticef("Cannot remove data directories for %q: %v", newInfo.Name(), err1) + } + + var err2 error + if oldInfo == nil { + // first install, remove created common data dir + err2 = b.RemoveSnapCommonData(newInfo) + if err2 != nil { + logger.Noticef("Cannot remove common data directories for %q: %v", newInfo.Name(), err2) + } + } + + return firstErr(err1, err2) +} diff -Nru snapd-2.0.5/overlord/snapstate/backend/copydata_test.go snapd-2.0.8/overlord/snapstate/backend/copydata_test.go --- snapd-2.0.5/overlord/snapstate/backend/copydata_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/backend/copydata_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,376 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-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 backend_test + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" + + "github.com/snapcore/snapd/overlord/snapstate/backend" +) + +type copydataSuite struct { + be backend.Backend + nullProgress progress.NullProgress + tempdir string +} + +var _ = Suite(©dataSuite{}) + +func (s *copydataSuite) SetUpTest(c *C) { + s.tempdir = c.MkDir() + dirs.SetRootDir(s.tempdir) +} + +func (s *copydataSuite) TearDownTest(c *C) { + dirs.SetRootDir("") +} + +const ( + helloYaml1 = `name: hello +version: 1.0 +` + helloYaml2 = `name: hello +version: 2.0 +` +) + +func (s *copydataSuite) TestCopyData(c *C) { + homedir := filepath.Join(s.tempdir, "home", "user1", "snap") + homeData := filepath.Join(homedir, "hello/10") + err := os.MkdirAll(homeData, 0755) + c.Assert(err, IsNil) + homeCommonData := filepath.Join(homedir, "hello/common") + err = os.MkdirAll(homeCommonData, 0755) + c.Assert(err, IsNil) + + canaryData := []byte("ni ni ni") + + v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + // just creates data dirs in this case + err = s.be.CopySnapData(v1, nil, &s.nullProgress) + c.Assert(err, IsNil) + + canaryDataFile := filepath.Join(v1.DataDir(), "canary.txt") + err = ioutil.WriteFile(canaryDataFile, canaryData, 0644) + c.Assert(err, IsNil) + canaryDataFile = filepath.Join(v1.CommonDataDir(), "canary.common") + err = ioutil.WriteFile(canaryDataFile, canaryData, 0644) + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(homeData, "canary.home"), canaryData, 0644) + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(homeCommonData, "canary.common_home"), canaryData, 0644) + c.Assert(err, IsNil) + + v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + err = s.be.CopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, IsNil) + + newCanaryDataFile := filepath.Join(dirs.SnapDataDir, "hello/20", "canary.txt") + content, err := ioutil.ReadFile(newCanaryDataFile) + c.Assert(err, IsNil) + c.Assert(content, DeepEquals, canaryData) + + // ensure common data file is still there (even though it didn't get copied) + newCanaryDataFile = filepath.Join(dirs.SnapDataDir, "hello", "common", "canary.common") + content, err = ioutil.ReadFile(newCanaryDataFile) + c.Assert(err, IsNil) + c.Assert(content, DeepEquals, canaryData) + + newCanaryDataFile = filepath.Join(homedir, "hello/20", "canary.home") + content, err = ioutil.ReadFile(newCanaryDataFile) + c.Assert(err, IsNil) + c.Assert(content, DeepEquals, canaryData) + + // ensure home common data file is still there (even though it didn't get copied) + newCanaryDataFile = filepath.Join(homedir, "hello", "common", "canary.common_home") + content, err = ioutil.ReadFile(newCanaryDataFile) + c.Assert(err, IsNil) + c.Assert(content, DeepEquals, canaryData) +} + +// ensure that even with no home dir there is no error and the +// system data gets copied +func (s *copydataSuite) TestCopyDataNoUserHomes(c *C) { + // this home dir path does not exist + oldSnapDataHomeGlob := dirs.SnapDataHomeGlob + defer func() { dirs.SnapDataHomeGlob = oldSnapDataHomeGlob }() + dirs.SnapDataHomeGlob = filepath.Join(s.tempdir, "no-such-home", "*", "snap") + + v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + err := s.be.CopySnapData(v1, nil, &s.nullProgress) + c.Assert(err, IsNil) + + canaryDataFile := filepath.Join(v1.DataDir(), "canary.txt") + err = ioutil.WriteFile(canaryDataFile, []byte(""), 0644) + c.Assert(err, IsNil) + canaryDataFile = filepath.Join(v1.CommonDataDir(), "canary.common") + err = ioutil.WriteFile(canaryDataFile, []byte(""), 0644) + c.Assert(err, IsNil) + + v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + err = s.be.CopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, IsNil) + + _, err = os.Stat(filepath.Join(v2.DataDir(), "canary.txt")) + c.Assert(err, IsNil) + _, err = os.Stat(filepath.Join(v2.CommonDataDir(), "canary.common")) + c.Assert(err, IsNil) + + // sanity atm + c.Check(v1.DataDir(), Not(Equals), v2.DataDir()) + c.Check(v1.CommonDataDir(), Equals, v2.CommonDataDir()) +} + +func (s *copydataSuite) populateData(c *C, revision snap.Revision) { + datadir := filepath.Join(dirs.SnapDataDir, "hello/"+revision.String()) + subdir := filepath.Join(datadir, "random-subdir") + err := os.MkdirAll(subdir, 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(subdir, "canary"), nil, 0644) + c.Assert(err, IsNil) +} + +func (s copydataSuite) populateHomeData(c *C, user string, revision snap.Revision) (homedir string) { + homedir = filepath.Join(s.tempdir, "home", user, "snap") + homeData := filepath.Join(homedir, "hello/"+revision.String()) + err := os.MkdirAll(homeData, 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(homeData, "canary.home"), nil, 0644) + c.Assert(err, IsNil) + return +} + +func (s *copydataSuite) TestCopyDataDoUndo(c *C) { + v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + s.populateData(c, snap.R(10)) + homedir := s.populateHomeData(c, "user1", snap.R(10)) + + // pretend we install a new version + v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + + // copy data + err := s.be.CopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, IsNil) + v2data := filepath.Join(dirs.SnapDataDir, "hello/20") + l, err := filepath.Glob(filepath.Join(v2data, "*")) + c.Assert(err, IsNil) + c.Assert(l, HasLen, 1) + v2HomeData := filepath.Join(homedir, "hello/20") + l, err = filepath.Glob(filepath.Join(v2HomeData, "*")) + c.Assert(err, IsNil) + c.Assert(l, HasLen, 1) + + err = s.be.UndoCopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, IsNil) + + // now removed + _, err = os.Stat(v2data) + c.Assert(os.IsNotExist(err), Equals, true) + _, err = os.Stat(v2HomeData) + c.Assert(os.IsNotExist(err), Equals, true) +} + +func (s *copydataSuite) TestCopyDataDoUndoNoUserHomes(c *C) { + // this home dir path does not exist + oldSnapDataHomeGlob := dirs.SnapDataHomeGlob + defer func() { dirs.SnapDataHomeGlob = oldSnapDataHomeGlob }() + dirs.SnapDataHomeGlob = filepath.Join(s.tempdir, "no-such-home", "*", "snap") + + v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + s.populateData(c, snap.R(10)) + + // pretend we install a new version + v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + + // copy data + err := s.be.CopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, IsNil) + v2data := filepath.Join(dirs.SnapDataDir, "hello/20") + l, err := filepath.Glob(filepath.Join(v2data, "*")) + c.Assert(err, IsNil) + c.Assert(l, HasLen, 1) + + err = s.be.UndoCopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, IsNil) + + // now removed + _, err = os.Stat(v2data) + c.Assert(os.IsNotExist(err), Equals, true) +} + +func (s *copydataSuite) TestCopyDataDoUndoFirstInstall(c *C) { + v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + + // first install + err := s.be.CopySnapData(v1, nil, &s.nullProgress) + c.Assert(err, IsNil) + _, err = os.Stat(v1.DataDir()) + c.Assert(err, IsNil) + _, err = os.Stat(v1.CommonDataDir()) + c.Assert(err, IsNil) + + err = s.be.UndoCopySnapData(v1, nil, &s.nullProgress) + c.Assert(err, IsNil) + _, err = os.Stat(v1.DataDir()) + c.Check(os.IsNotExist(err), Equals, true) + _, err = os.Stat(v1.CommonDataDir()) + c.Check(os.IsNotExist(err), Equals, true) +} + +func (s *copydataSuite) TestCopyDataDoIdempotent(c *C) { + // make sure that a retry wouldn't stumble on partial work + + v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + + s.populateData(c, snap.R(10)) + homedir := s.populateHomeData(c, "user1", snap.R(10)) + + // pretend we install a new version + v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + + // copy data + err := s.be.CopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, IsNil) + + err = s.be.CopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, IsNil) + + v2data := filepath.Join(dirs.SnapDataDir, "hello/20") + l, err := filepath.Glob(filepath.Join(v2data, "*")) + c.Assert(err, IsNil) + c.Assert(l, HasLen, 1) + v2HomeData := filepath.Join(homedir, "hello/20") + l, err = filepath.Glob(filepath.Join(v2HomeData, "*")) + c.Assert(err, IsNil) + c.Assert(l, HasLen, 1) +} + +func (s *copydataSuite) TestCopyDataUndoIdempotent(c *C) { + // make sure that a retry wouldn't stumble on partial work + + v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + s.populateData(c, snap.R(10)) + homedir := s.populateHomeData(c, "user1", snap.R(10)) + + // pretend we install a new version + v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + + // copy data + err := s.be.CopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, IsNil) + + v2data := filepath.Join(dirs.SnapDataDir, "hello/20") + + err = s.be.UndoCopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, IsNil) + + err = s.be.UndoCopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, IsNil) + + // now removed + _, err = os.Stat(v2data) + c.Assert(os.IsNotExist(err), Equals, true) + v2HomeData := filepath.Join(homedir, "hello/20") + _, err = os.Stat(v2HomeData) + c.Assert(os.IsNotExist(err), Equals, true) +} + +func (s *copydataSuite) TestCopyDataDoFirstInstallIdempotent(c *C) { + v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + + // first install + err := s.be.CopySnapData(v1, nil, &s.nullProgress) + c.Assert(err, IsNil) + + err = s.be.CopySnapData(v1, nil, &s.nullProgress) + c.Assert(err, IsNil) + + _, err = os.Stat(v1.DataDir()) + c.Assert(err, IsNil) + _, err = os.Stat(v1.CommonDataDir()) + c.Assert(err, IsNil) + + err = s.be.UndoCopySnapData(v1, nil, &s.nullProgress) + c.Assert(err, IsNil) + _, err = os.Stat(v1.DataDir()) + c.Check(os.IsNotExist(err), Equals, true) + _, err = os.Stat(v1.CommonDataDir()) + c.Check(os.IsNotExist(err), Equals, true) +} + +func (s *copydataSuite) TestCopyDataUndoFirstInstallIdempotent(c *C) { + v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + + // first install + err := s.be.CopySnapData(v1, nil, &s.nullProgress) + c.Assert(err, IsNil) + _, err = os.Stat(v1.DataDir()) + c.Assert(err, IsNil) + _, err = os.Stat(v1.CommonDataDir()) + c.Assert(err, IsNil) + + err = s.be.UndoCopySnapData(v1, nil, &s.nullProgress) + c.Assert(err, IsNil) + + err = s.be.UndoCopySnapData(v1, nil, &s.nullProgress) + c.Assert(err, IsNil) + + _, err = os.Stat(v1.DataDir()) + c.Check(os.IsNotExist(err), Equals, true) + _, err = os.Stat(v1.CommonDataDir()) + c.Check(os.IsNotExist(err), Equals, true) +} + +func (s *copydataSuite) TestCopyDataCopyFailure(c *C) { + v1 := snaptest.MockSnap(c, helloYaml1, &snap.SideInfo{Revision: snap.R(10)}) + s.populateData(c, snap.R(10)) + + // pretend we install a new version + v2 := snaptest.MockSnap(c, helloYaml2, &snap.SideInfo{Revision: snap.R(20)}) + + fakeBinDir := filepath.Join(s.tempdir, "bin") + err := os.MkdirAll(fakeBinDir, 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(fakeBinDir, "cp"), []byte( + `#!/bin/sh +echo cp: boom +exit 3 +`), 0755) + c.Assert(err, IsNil) + + oldPATH := os.Getenv("PATH") + defer os.Setenv("PATH", oldPATH) + os.Setenv("PATH", fakeBinDir+":"+oldPATH) + + // copy data will fail + err = s.be.CopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, ErrorMatches, regexp.QuoteMeta(fmt.Sprintf("cannot copy %s to %s: cp: boom", v1.DataDir(), v2.DataDir()))) +} diff -Nru snapd-2.0.5/overlord/snapstate/backend/link.go snapd-2.0.8/overlord/snapstate/backend/link.go --- snapd-2.0.5/overlord/snapstate/backend/link.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/backend/link.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,12 +24,12 @@ "os" "path/filepath" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" // XXX: eventually not needed - "github.com/ubuntu-core/snappy/snappy" - "github.com/ubuntu-core/snappy/wrappers" + "github.com/snapcore/snapd/snappy" + "github.com/snapcore/snapd/wrappers" ) func updateCurrentSymlinks(info *snap.Info) error { diff -Nru snapd-2.0.5/overlord/snapstate/backend/link_test.go snapd-2.0.8/overlord/snapstate/backend/link_test.go --- snapd-2.0.5/overlord/snapstate/backend/link_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/backend/link_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,14 +25,14 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" - "github.com/ubuntu-core/snappy/systemd" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/systemd" - "github.com/ubuntu-core/snappy/overlord/snapstate/backend" + "github.com/snapcore/snapd/overlord/snapstate/backend" ) func TestBackend(t *testing.T) { TestingT(t) } @@ -62,6 +62,9 @@ func (s *linkSuite) TestLinkDoUndoGenerateWrappers(c *C) { const yaml = `name: hello version: 1.0 +environment: + KEY: value + apps: bin: command: bin @@ -70,7 +73,7 @@ daemon: simple ` - info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: 11}) + info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) err := s.be.LinkSnap(info) c.Assert(err, IsNil) @@ -99,7 +102,7 @@ version: 1.0 ` - info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: 11}) + info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) err := s.be.LinkSnap(info) c.Assert(err, IsNil) @@ -130,6 +133,8 @@ const yaml = `name: hello version: 1.0 +environment: + KEY: value apps: bin: command: bin @@ -138,7 +143,7 @@ daemon: simple ` - info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: 11}) + info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) err := s.be.LinkSnap(info) c.Assert(err, IsNil) @@ -166,7 +171,7 @@ c.Assert(currentDataDir, Equals, dataDir) } -func (s *linkSuite) TestLinkUnoIdempotent(c *C) { +func (s *linkSuite) TestLinkUndoIdempotent(c *C) { // make sure that a retry wouldn't stumble on partial work const yaml = `name: hello @@ -179,7 +184,7 @@ daemon: simple ` - info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: 11}) + info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) err := s.be.LinkSnap(info) c.Assert(err, IsNil) diff -Nru snapd-2.0.5/overlord/snapstate/backend/snapdata.go snapd-2.0.8/overlord/snapstate/backend/snapdata.go --- snapd-2.0.5/overlord/snapstate/backend/snapdata.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/backend/snapdata.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,127 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-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 backend + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/snapcore/snapd/snap" +) + +// RemoveSnapData removes the data for the given version of the given snap. +func (b Backend) RemoveSnapData(snap *snap.Info) error { + dirs, err := snapDataDirs(snap) + if err != nil { + return err + } + + return removeDirs(dirs) +} + +// RemoveSnapCommonData removes the data common between versions of the given snap. +func (b Backend) RemoveSnapCommonData(snap *snap.Info) error { + dirs, err := snapCommonDataDirs(snap) + if err != nil { + return err + } + + return removeDirs(dirs) +} + +func removeDirs(dirs []string) error { + for _, dir := range dirs { + if err := os.RemoveAll(dir); err != nil { + return err + } + + // Attempt to remove the parent directory as well (ignore any failure) + os.Remove(filepath.Dir(dir)) + } + + return nil +} + +// snapDataDirs returns the list of data directories for the given snap version +func snapDataDirs(snap *snap.Info) ([]string, error) { + // collect the directories, homes first + found, err := filepath.Glob(snap.DataHomeDir()) + if err != nil { + return nil, err + } + // then system data + found = append(found, snap.DataDir()) + + return found, nil +} + +// snapCommonDataDirs returns the list of data directories common between versions of the given snap +func snapCommonDataDirs(snap *snap.Info) ([]string, error) { + // collect the directories, homes first + found, err := filepath.Glob(snap.CommonDataHomeDir()) + if err != nil { + return nil, err + } + // then system data + found = append(found, snap.CommonDataDir()) + + return found, nil +} + +// Copy all data for oldSnap to newSnap +// (but never overwrite) +func copySnapData(oldSnap, newSnap *snap.Info) (err error) { + oldDataDirs, err := snapDataDirs(oldSnap) + if err != nil { + return err + } + + newSuffix := filepath.Base(newSnap.DataDir()) + for _, oldDir := range oldDataDirs { + // replace the trailing "../$old-suffix" with the "../$new-suffix" + newDir := filepath.Join(filepath.Dir(oldDir), newSuffix) + if err := copySnapDataDirectory(oldDir, newDir); err != nil { + return err + } + } + + return nil +} + +// Lowlevel copy the snap data (but never override existing data) +func copySnapDataDirectory(oldPath, newPath string) (err error) { + if _, err := os.Stat(oldPath); err == nil { + if _, err := os.Stat(newPath); err != nil { + // there is no golang "CopyFile" + cmd := exec.Command("cp", "-a", oldPath, newPath) + if output, err := cmd.CombinedOutput(); err != nil { + output = bytes.TrimSpace(output) + if len(output) > 0 { + err = fmt.Errorf("%s", output) + } + return fmt.Errorf("cannot copy %s to %s: %v", oldPath, newPath, err) + } + } + } + return nil +} diff -Nru snapd-2.0.5/overlord/snapstate/backend.go snapd-2.0.8/overlord/snapstate/backend.go --- snapd-2.0.5/overlord/snapstate/backend.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/backend.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,32 +20,41 @@ package snapstate import ( - "github.com/ubuntu-core/snappy/overlord/snapstate/backend" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snappy" - "github.com/ubuntu-core/snappy/store" + "github.com/snapcore/snapd/overlord/snapstate/backend" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snappy" + "github.com/snapcore/snapd/store" ) +// A StoreService can find, list available updates and offer for download snaps. +type StoreService interface { + Snap(string, string, store.Authenticator) (*snap.Info, error) + Find(string, string, store.Authenticator) ([]*snap.Info, error) + ListRefresh([]*store.RefreshCandidate, store.Authenticator) ([]*snap.Info, error) + SuggestedCurrency() string + + Download(*snap.Info, progress.Meter, store.Authenticator) (string, error) +} + type managerBackend interface { // install releated - Download(name, channel string, checker func(*snap.Info) error, meter progress.Meter, auther store.Authenticator) (*snap.Info, string, error) - CheckSnap(snapFilePath string, curInfo *snap.Info, flags int) error - SetupSnap(snapFilePath string, si *snap.SideInfo, flags int) error - CopySnapData(newSnap, oldSnap *snap.Info, flags int) error + Download(name, channel string, checker func(*snap.Info) error, meter progress.Meter, store StoreService, auther store.Authenticator) (*snap.Info, string, error) + SetupSnap(snapFilePath string, si *snap.SideInfo) error + CopySnapData(newSnap, oldSnap *snap.Info, meter progress.Meter) error LinkSnap(info *snap.Info) error // the undoers for install UndoSetupSnap(s snap.PlaceInfo) error - UndoCopySnapData(newSnap *snap.Info, flags int) error + UndoCopySnapData(newSnap, oldSnap *snap.Info, meter progress.Meter) error // remove releated - CanRemove(info *snap.Info, active bool) bool UnlinkSnap(info *snap.Info, meter progress.Meter) error RemoveSnapFiles(s snap.PlaceInfo, meter progress.Meter) error RemoveSnapData(info *snap.Info) error RemoveSnapCommonData(info *snap.Info) error // testing helpers + Current(cur *snap.Info) Candidate(sideInfo *snap.SideInfo) } @@ -55,10 +64,10 @@ } func (b *defaultBackend) Candidate(*snap.SideInfo) {} +func (b *defaultBackend) Current(*snap.Info) {} -func (b *defaultBackend) Download(name, channel string, checker func(*snap.Info) error, meter progress.Meter, auther store.Authenticator) (*snap.Info, string, error) { - mStore := snappy.NewConfiguredUbuntuStoreSnapRepository() - snap, err := mStore.Snap(name, channel, auther) +func (b *defaultBackend) Download(name, channel string, checker func(*snap.Info) error, meter progress.Meter, stor StoreService, auther store.Authenticator) (*snap.Info, string, error) { + snap, err := stor.Snap(name, channel, auther) if err != nil { return nil, "", err } @@ -68,7 +77,7 @@ return nil, "", err } - downloadedSnapFile, err := mStore.Download(snap, meter, auther) + downloadedSnapFile, err := stor.Download(snap, meter, auther) if err != nil { return nil, "", err } @@ -76,46 +85,21 @@ return snap, downloadedSnapFile, nil } -func (b *defaultBackend) CheckSnap(snapFilePath string, curInfo *snap.Info, flags int) error { - meter := &progress.NullProgress{} - return snappy.CheckSnap(snapFilePath, curInfo, snappy.InstallFlags(flags), meter) -} - -func (b *defaultBackend) SetupSnap(snapFilePath string, sideInfo *snap.SideInfo, flags int) error { +func (b *defaultBackend) SetupSnap(snapFilePath string, sideInfo *snap.SideInfo) error { meter := &progress.NullProgress{} - _, err := snappy.SetupSnap(snapFilePath, sideInfo, snappy.InstallFlags(flags), meter) + // XXX: pass 0 for flags temporarely, until SetupSnap is moved over, + // anyway they aren't used atm, and probably we don't want to pass flags + // as before but more precise information + _, err := snappy.SetupSnap(snapFilePath, sideInfo, 0, meter) return err } -func (b *defaultBackend) CopySnapData(newInfo, oldInfo *snap.Info, flags int) error { - meter := &progress.NullProgress{} - return snappy.CopyData(newInfo, oldInfo, snappy.InstallFlags(flags), meter) -} - func (b *defaultBackend) UndoSetupSnap(s snap.PlaceInfo) error { meter := &progress.NullProgress{} snappy.UndoSetupSnap(s, meter) return nil } -func (b *defaultBackend) UndoCopySnapData(newInfo *snap.Info, flags int) error { - meter := &progress.NullProgress{} - snappy.UndoCopyData(newInfo, snappy.InstallFlags(flags), meter) - return nil -} - -func (b *defaultBackend) CanRemove(info *snap.Info, active bool) bool { - return snappy.CanRemove(info, active) -} - func (b *defaultBackend) RemoveSnapFiles(s snap.PlaceInfo, meter progress.Meter) error { return snappy.RemoveSnapFiles(s, meter) } - -func (b *defaultBackend) RemoveSnapData(info *snap.Info) error { - return snappy.RemoveSnapData(info) -} - -func (b *defaultBackend) RemoveSnapCommonData(info *snap.Info) error { - return snappy.RemoveSnapCommonData(info) -} diff -Nru snapd-2.0.5/overlord/snapstate/backend_test.go snapd-2.0.8/overlord/snapstate/backend_test.go --- snapd-2.0.5/overlord/snapstate/backend_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/backend_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,12 +23,12 @@ "errors" "strings" - "github.com/ubuntu-core/snappy/overlord/auth" - "github.com/ubuntu-core/snappy/overlord/snapstate" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/store" + "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/store" ) type fakeOp struct { @@ -36,9 +36,8 @@ macaroon string name string - revno int + revno snap.Revision channel string - flags int active bool sinfo snap.SideInfo @@ -54,7 +53,7 @@ linkSnapFailTrigger string } -func (f *fakeSnappyBackend) Download(name, channel string, checker func(*snap.Info) error, p progress.Meter, auther store.Authenticator) (*snap.Info, string, error) { +func (f *fakeSnappyBackend) Download(name, channel string, checker func(*snap.Info) error, p progress.Meter, stor snapstate.StoreService, auther store.Authenticator) (*snap.Info, string, error) { p.Notify("download") var macaroon string if auther != nil { @@ -69,9 +68,9 @@ p.SetTotal(float64(f.fakeTotalProgress)) p.Set(float64(f.fakeCurrentProgress)) - revno := 11 + revno := snap.R(11) if channel == "channel-for-7" { - revno = 7 + revno.N = 7 } info := &snap.Info{ @@ -92,29 +91,28 @@ return info, "downloaded-snap-path", nil } -func (f *fakeSnappyBackend) CheckSnap(snapFilePath string, curInfo *snap.Info, flags int) error { - cur := "" - if curInfo != nil { - cur = curInfo.MountDir() +func (f *fakeSnappyBackend) OpenSnapFile(snapFilePath string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { + op := fakeOp{ + op: "open-snap-file", + name: snapFilePath, } - f.ops = append(f.ops, fakeOp{ - op: "check-snap", - name: snapFilePath, - old: cur, - flags: flags, - }) - return nil + + if si != nil { + op.sinfo = *si + } + + f.ops = append(f.ops, op) + return &snap.Info{Architectures: []string{"all"}}, nil, nil } -func (f *fakeSnappyBackend) SetupSnap(snapFilePath string, si *snap.SideInfo, flags int) error { - revno := 0 +func (f *fakeSnappyBackend) SetupSnap(snapFilePath string, si *snap.SideInfo) error { + revno := snap.R(0) if si != nil { revno = si.Revision } f.ops = append(f.ops, fakeOp{ op: "setup-snap", name: snapFilePath, - flags: flags, revno: revno, }) return nil @@ -122,19 +120,23 @@ func (f *fakeSnappyBackend) ReadInfo(name string, si *snap.SideInfo) (*snap.Info, error) { // naive emulation for now, always works - return &snap.Info{SuggestedName: name, SideInfo: *si}, nil + info := &snap.Info{SuggestedName: name, SideInfo: *si} + if name == "gadget" { + info.Type = snap.TypeGadget + } + return info, nil } -func (f *fakeSnappyBackend) CopySnapData(newInfo, oldInfo *snap.Info, flags int) error { +func (f *fakeSnappyBackend) CopySnapData(newInfo, oldInfo *snap.Info, p progress.Meter) error { + p.Notify("copy-data") old := "" if oldInfo != nil { old = oldInfo.MountDir() } f.ops = append(f.ops, fakeOp{ - op: "copy-data", - name: newInfo.MountDir(), - flags: flags, - old: old, + op: "copy-data", + name: newInfo.MountDir(), + old: old, }) return nil } @@ -163,23 +165,20 @@ return nil } -func (f *fakeSnappyBackend) UndoCopySnapData(newInfo *snap.Info, flags int) error { +func (f *fakeSnappyBackend) UndoCopySnapData(newInfo *snap.Info, oldInfo *snap.Info, p progress.Meter) error { + p.Notify("undo-copy-data") + old := "" + if oldInfo != nil { + old = oldInfo.MountDir() + } f.ops = append(f.ops, fakeOp{ op: "undo-copy-snap-data", name: newInfo.MountDir(), + old: old, }) return nil } -func (f *fakeSnappyBackend) CanRemove(info *snap.Info, active bool) bool { - f.ops = append(f.ops, fakeOp{ - op: "can-remove", - name: info.MountDir(), - active: active, - }) - return true -} - func (f *fakeSnappyBackend) UnlinkSnap(info *snap.Info, meter progress.Meter) error { meter.Notify("unlink") f.ops = append(f.ops, fakeOp{ @@ -225,6 +224,17 @@ }) } +func (f *fakeSnappyBackend) Current(curInfo *snap.Info) { + old := "" + if curInfo != nil { + old = curInfo.MountDir() + } + f.ops = append(f.ops, fakeOp{ + op: "current", + old: old, + }) +} + func (f *fakeSnappyBackend) ForeignTask(kind string, status state.Status, ss *snapstate.SnapSetup) { f.ops = append(f.ops, fakeOp{ op: kind + ":" + status.String(), diff -Nru snapd-2.0.5/overlord/snapstate/check_snap.go snapd-2.0.8/overlord/snapstate/check_snap.go --- snapd-2.0.5/overlord/snapstate/check_snap.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/check_snap.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,110 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-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 snapstate + +import ( + "fmt" + "strings" + + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" +) + +// featureSet contains the flag values that can be listed in assumes entries +// that this ubuntu-core actually provides. +var featureSet = map[string]bool{ + // Support for common data directory across revisions of a snap. + "common-data-dir": true, +} + +func checkAssumes(s *snap.Info) error { + missing := ([]string)(nil) + for _, flag := range s.Assumes { + if !featureSet[flag] { + missing = append(missing, flag) + } + } + if len(missing) > 0 { + return fmt.Errorf("snap %q assumes unsupported features: %s (try new ubuntu-core)", s.Name(), strings.Join(missing, ", ")) + } + return nil +} + +// openSnapFile opens a snap blob returning both a snap.Info completed +// with sideInfo (if not nil) and a corresponding snap.Container. +func openSnapFileImpl(snapPath string, sideInfo *snap.SideInfo) (*snap.Info, snap.Container, error) { + snapf, err := snap.Open(snapPath) + if err != nil { + return nil, nil, err + } + + info, err := snap.ReadInfoFromSnapFile(snapf, sideInfo) + if err != nil { + return nil, nil, err + } + + return info, snapf, nil +} + +var openSnapFile = openSnapFileImpl + +// checkSnap ensures that the snap can be installed. +func checkSnap(state *state.State, snapFilePath string, curInfo *snap.Info, flags Flags) error { + // XXX: actually verify snap before using content from it unless dev-mode + + s, _, err := openSnapFile(snapFilePath, nil) + if err != nil { + return err + } + + // verify we have a valid architecture + if !arch.IsSupportedArchitecture(s.Architectures) { + return fmt.Errorf("snap %q supported architectures (%s) are incompatible with this system (%s)", s.Name(), strings.Join(s.Architectures, ", "), arch.UbuntuArchitecture()) + } + + // check assumes + err = checkAssumes(s) + if err != nil { + return err + } + + if s.Type != snap.TypeGadget { + return nil + } + state.Lock() + defer state.Unlock() + + if currentGadget, err := GadgetInfo(state); err == nil { + // TODO: actually compare snap ids, from current gadget and candidate + if currentGadget.Name() == s.Name() { + return nil + } + + return fmt.Errorf("cannot replace gadget snap with a different one") + } else if release.OnClassic { + // for the time being + return fmt.Errorf("cannot install a gadget snap on classic") + } + + // there should always be a gadget snap on devices + return fmt.Errorf("cannot find original gadget snap") +} diff -Nru snapd-2.0.5/overlord/snapstate/check_snap_test.go snapd-2.0.8/overlord/snapstate/check_snap_test.go --- snapd-2.0.5/overlord/snapstate/check_snap_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/check_snap_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,283 @@ +// -*- 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 snapstate_test + +import ( + "fmt" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/snap/squashfs" + + "github.com/snapcore/snapd/overlord/snapstate" +) + +type checkSnapSuite struct{} + +var _ = Suite(&checkSnapSuite{}) + +func (s *checkSnapSuite) SetUpTest(c *C) { + dirs.SetRootDir(c.MkDir()) +} + +func (s *checkSnapSuite) TearDownTest(c *C) { + dirs.SetRootDir("") +} + +func (s *checkSnapSuite) TestOpenSnapFile(c *C) { + const yaml = `name: hello +version: 1.0 +apps: + bin: + command: bin +` + + snapPath := makeTestSnap(c, yaml) + info, snapf, err := snapstate.OpenSnapFileImpl(snapPath, nil) + c.Assert(err, IsNil) + + c.Assert(snapf, FitsTypeOf, &squashfs.Snap{}) + c.Check(info.Name(), Equals, "hello") +} + +func (s *checkSnapSuite) TestOpenSnapFilebSideInfo(c *C) { + const yaml = `name: foo +apps: + bar: + command: bin/bar +plugs: + plug: +slots: + slot: +` + + snapPath := makeTestSnap(c, yaml) + si := snap.SideInfo{OfficialName: "blessed", Revision: snap.R(42)} + info, _, err := snapstate.OpenSnapFileImpl(snapPath, &si) + c.Assert(err, IsNil) + + // check side info + c.Check(info.Name(), Equals, "blessed") + c.Check(info.Revision, Equals, snap.R(42)) + + c.Check(info.SideInfo, DeepEquals, si) + + // ensure that all leaf objects link back to the same snap.Info + // and not to some copy. + // (we had a bug around this) + c.Check(info.Apps["bar"].Snap, Equals, info) + c.Check(info.Plugs["plug"].Snap, Equals, info) + c.Check(info.Slots["slot"].Snap, Equals, info) + +} + +func (s *checkSnapSuite) TestCheckSnapErrorOnUnsupportedArchitecture(c *C) { + const yaml = `name: hello +version: 1.10 +architectures: + - yadayada + - blahblah +` + info, err := snap.InfoFromSnapYaml([]byte(yaml)) + c.Assert(err, IsNil) + + var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { + c.Check(path, Equals, "snap-path") + c.Check(si, IsNil) + return info, nil, nil + } + restore := snapstate.MockOpenSnapFile(openSnapFile) + defer restore() + + err = snapstate.CheckSnap(nil, "snap-path", nil, 0) + + errorMsg := fmt.Sprintf(`snap "hello" supported architectures (yadayada, blahblah) are incompatible with this system (%s)`, arch.UbuntuArchitecture()) + c.Assert(err.Error(), Equals, errorMsg) +} + +func (s *checkSnapSuite) TestCheckSnapInstallMissingAssumes(c *C) { + const yaml = `name: foo +version: 1.0 +assumes: [f1, f2]` + + info, err := snap.InfoFromSnapYaml([]byte(yaml)) + c.Assert(err, IsNil) + + var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { + return info, nil, nil + } + restore := snapstate.MockOpenSnapFile(openSnapFile) + defer restore() + + err = snapstate.CheckSnap(nil, "snap-path", nil, 0) + c.Check(err, ErrorMatches, `snap "foo" assumes unsupported features: f1, f2.*`) +} + +func (s *checkSnapSuite) TestCheckSnapInstallProvidedAssumes(c *C) { + const yaml = `name: foo +version: 1.0 +assumes: [common-data-dir]` + + info, err := snap.InfoFromSnapYaml([]byte(yaml)) + c.Assert(err, IsNil) + + var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { + return info, nil, nil + } + restore := snapstate.MockOpenSnapFile(openSnapFile) + defer restore() + + err = snapstate.CheckSnap(nil, "snap-path", nil, 0) + c.Check(err, IsNil) +} + +func (s *checkSnapSuite) TestCheckSnapGadgetUpdate(c *C) { + st := state.New(nil) + st.Lock() + defer st.Unlock() + + si := &snap.SideInfo{Revision: snap.R(2)} + snaptest.MockSnap(c, ` +name: gadget +type: gadget +version: 1 +`, si) + snapstate.Set(st, "gadget", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si}, + }) + + const yaml = `name: gadget +type: gadget +version: 2 +` + + info, err := snap.InfoFromSnapYaml([]byte(yaml)) + c.Assert(err, IsNil) + + var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { + return info, nil, nil + } + restore := snapstate.MockOpenSnapFile(openSnapFile) + defer restore() + + st.Unlock() + err = snapstate.CheckSnap(st, "snap-path", nil, 0) + st.Lock() + c.Check(err, IsNil) +} + +func (s *checkSnapSuite) TestCheckSnapGadgetAdditionProhibited(c *C) { + st := state.New(nil) + st.Lock() + defer st.Unlock() + + si := &snap.SideInfo{Revision: snap.R(2)} + snaptest.MockSnap(c, ` +name: gadget +type: gadget +version: 1 +`, si) + snapstate.Set(st, "gadget", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si}, + }) + + const yaml = `name: zgadget +type: gadget +version: 2 +` + + info, err := snap.InfoFromSnapYaml([]byte(yaml)) + c.Assert(err, IsNil) + + var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { + return info, nil, nil + } + restore := snapstate.MockOpenSnapFile(openSnapFile) + defer restore() + + st.Unlock() + err = snapstate.CheckSnap(st, "snap-path", nil, 0) + st.Lock() + c.Check(err, ErrorMatches, "cannot replace gadget snap with a different one") +} + +func (s *checkSnapSuite) TestCheckSnapGadgetMissingPrior(c *C) { + reset := release.MockOnClassic(false) + defer reset() + + st := state.New(nil) + st.Lock() + defer st.Unlock() + + const yaml = `name: gadget +type: gadget +version: 1 +` + + info, err := snap.InfoFromSnapYaml([]byte(yaml)) + c.Assert(err, IsNil) + + var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { + return info, nil, nil + } + restore := snapstate.MockOpenSnapFile(openSnapFile) + defer restore() + + st.Unlock() + err = snapstate.CheckSnap(st, "snap-path", nil, 0) + st.Lock() + c.Check(err, ErrorMatches, "cannot find original gadget snap") +} + +func (s *checkSnapSuite) TestCheckSnapGadgetCannotBeInstalledOnClassic(c *C) { + reset := release.MockOnClassic(true) + defer reset() + + st := state.New(nil) + st.Lock() + defer st.Unlock() + + const yaml = `name: gadget +type: gadget +version: 1 +` + + info, err := snap.InfoFromSnapYaml([]byte(yaml)) + c.Assert(err, IsNil) + + var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { + return info, nil, nil + } + restore := snapstate.MockOpenSnapFile(openSnapFile) + defer restore() + + st.Unlock() + err = snapstate.CheckSnap(st, "snap-path", nil, 0) + st.Lock() + c.Check(err, ErrorMatches, "cannot install a gadget snap on classic") +} diff -Nru snapd-2.0.5/overlord/snapstate/export_test.go snapd-2.0.8/overlord/snapstate/export_test.go --- snapd-2.0.5/overlord/snapstate/export_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/export_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,8 +24,8 @@ "gopkg.in/tomb.v2" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" ) type ManagerBackend managerBackend @@ -34,10 +34,6 @@ s.backend = b } -func SetSnapstateBackend(b ManagerBackend) { - be = b -} - type ForeignTaskTracker interface { ForeignTask(kind string, status state.Status, ss *SnapSetup) } @@ -70,7 +66,25 @@ m.runner.AddHandler("error-trigger", erroringHandler, nil) } -func MockReadInfo(mock func(name string, si *snap.SideInfo) (*snap.Info, error)) func() { +func MockReadInfo(mock func(name string, si *snap.SideInfo) (*snap.Info, error)) (restore func()) { readInfo = mock return func() { readInfo = snap.ReadInfo } } + +var OpenSnapFileImpl = openSnapFileImpl + +func MockOpenSnapFile(mock func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error)) (restore func()) { + openSnapFile = mock + return func() { openSnapFile = openSnapFileImpl } +} + +var ( + CheckSnap = checkSnap + CanRemove = canRemove +) + +// flagscompat +const ( + InterimUnusableFlagValueMin = interimUnusableLegacyFlagValueMin + InterimUnusableFlagValueLast = interimUnusableLegacyFlagValueLast +) diff -Nru snapd-2.0.5/overlord/snapstate/flagscompat_test.go snapd-2.0.8/overlord/snapstate/flagscompat_test.go --- snapd-2.0.5/overlord/snapstate/flagscompat_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/flagscompat_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,157 @@ +// -*- 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 snapstate_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snappy" +) + +type flagscompatSuite struct{} + +var _ = Suite(&flagscompatSuite{}) + +const ( + // copy here of the legacy values for when we drop snappy + + snappyAllowUnauthenticated = 1 << iota + snappyInhibitHooks + snappyDoInstallGC + snappyAllowGadget + + snappyDeveloperMode + snappyTryMode +) + +// this is the minimum value larger than all the legacy/interim flags +// combinations, so also at the same time the first flag value usable +// again +const interimUnusableFlagValueTop = snapstate.InterimUnusableFlagValueLast << 1 + +func (s *flagscompatSuite) TestCopiedConstsSanity(c *C) { + // have this sanity test at the start at least, can be dropped + // when we drop snappy + c.Check(snappy.LegacyInstallFlags(snappyAllowUnauthenticated), Equals, snappy.LegacyAllowUnauthenticated) + c.Check(snappy.LegacyInstallFlags(snappyInhibitHooks), Equals, snappy.LegacyInhibitHooks) + c.Check(snappy.LegacyInstallFlags(snappyDoInstallGC), Equals, snappy.LegacyDoInstallGC) + c.Check(snappy.LegacyInstallFlags(snappyAllowGadget), Equals, snappy.LegacyAllowGadget) + +} + +func (s *flagscompatSuite) TestSnapSetupNewValuesUnchanged(c *C) { + // test that new snapstate flags based SnapSetup.Flags is + // unmarshalled as is + + st := state.New(nil) + st.Lock() + defer st.Unlock() + + t := st.NewTask("t", "...") + + values := []int{ + snapstate.DevMode, + snapstate.TryMode, + snapstate.DevMode | snapstate.TryMode, + interimUnusableFlagValueTop, + interimUnusableFlagValueTop | snapstate.DevMode, + interimUnusableFlagValueTop<<1 | snapstate.TryMode, + interimUnusableFlagValueTop << 4, + } + + for _, f := range values { + + t.Set("ss", snapstate.SnapSetup{ + Flags: snapstate.SnapSetupFlags(f), + }) + + var ss snapstate.SnapSetup + err := t.Get("ss", &ss) + c.Assert(err, IsNil) + + c.Check(ss.Flags, Equals, snapstate.SnapSetupFlags(f)) + } + +} + +func (s *flagscompatSuite) TestRangeCapturesLegacyInterim(c *C) { + // double check that the reserved unusable flag range + // captures (aka contains) the old legacy/interim flags + // to protect them correctly + + values := []int{ + // these overlap but weren't used in snapd actually + //snappyAllowUnauthenticated, + //snappyInhibitHooks, + snappyDoInstallGC, + snappyAllowGadget, + snappyDeveloperMode, + snappyTryMode, + } + + for _, v := range values { + c.Check(v < int(interimUnusableFlagValueTop), Equals, true) + c.Check(v >= int(snapstate.InterimUnusableFlagValueMin), Equals, true) + } + + c.Check(snappyDoInstallGC, Equals, snapstate.InterimUnusableFlagValueMin) + c.Check(snappyTryMode, Equals, snapstate.InterimUnusableFlagValueLast) + +} + +func (s *flagscompatSuite) TestSnapSetupInterimValuesUpgrade(c *C) { + // test that the old snappy.* flags based SnapSetup.Flags + // are updated correctly to the new snapstate single shared flag set + + st := state.New(nil) + st.Lock() + defer st.Unlock() + + t := st.NewTask("t", "...") + + tests := []struct { + interim, new int + }{ + {snappyDeveloperMode, snapstate.DevMode}, + {snappyTryMode, snapstate.TryMode}, + {snappyDeveloperMode | snappyTryMode, snapstate.DevMode | snapstate.TryMode}, + {snappyDeveloperMode | snappyDoInstallGC, snapstate.DevMode}, + {snappyTryMode | snappyDoInstallGC, snapstate.TryMode}, + {snappyDeveloperMode | snappyTryMode | snappyDoInstallGC, snapstate.DevMode | snapstate.TryMode}, + {snappyDoInstallGC, 0}, + {interimUnusableFlagValueTop - 1, snapstate.DevMode | snapstate.TryMode}, + } + + for _, tst := range tests { + + t.Set("ss", snapstate.SnapSetup{ + Flags: snapstate.SnapSetupFlags(tst.interim), + }) + + var ss snapstate.SnapSetup + err := t.Get("ss", &ss) + c.Assert(err, IsNil) + + c.Check(ss.Flags, Equals, snapstate.SnapSetupFlags(tst.new)) + } + +} diff -Nru snapd-2.0.5/overlord/snapstate/link_snap_test.go snapd-2.0.8/overlord/snapstate/link_snap_test.go --- snapd-2.0.5/overlord/snapstate/link_snap_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/link_snap_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,181 @@ +// -*- 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 snapstate_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" +) + +type linkSnapSuite struct { + state *state.State + snapmgr *snapstate.SnapManager + + fakeBackend *fakeSnappyBackend + + reset func() +} + +var _ = Suite(&linkSnapSuite{}) + +func (s *linkSnapSuite) SetUpTest(c *C) { + s.fakeBackend = &fakeSnappyBackend{} + s.state = state.New(nil) + + var err error + s.snapmgr, err = snapstate.Manager(s.state) + c.Assert(err, IsNil) + s.snapmgr.AddForeignTaskHandlers(s.fakeBackend) + + snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend) + + s.reset = snapstate.MockReadInfo(s.fakeBackend.ReadInfo) +} + +func (s *linkSnapSuite) TearDownTest(c *C) { + s.reset() +} + +func (s *linkSnapSuite) TestDoLinkSnapSuccess(c *C) { + s.state.Lock() + snapstate.Set(s.state, "foo", &snapstate.SnapState{ + Candidate: &snap.SideInfo{ + OfficialName: "foo", + Revision: snap.R(33), + }, + }) + t := s.state.NewTask("link-snap", "test") + t.Set("snap-setup", &snapstate.SnapSetup{ + Name: "foo", + Channel: "beta", + }) + s.state.NewChange("dummy", "...").AddTask(t) + + s.state.Unlock() + + s.snapmgr.Ensure() + s.snapmgr.Wait() + + s.state.Lock() + defer s.state.Unlock() + var snapst snapstate.SnapState + err := snapstate.Get(s.state, "foo", &snapst) + c.Assert(err, IsNil) + c.Check(snapst.Active, Equals, true) + c.Check(snapst.Sequence, HasLen, 1) + c.Check(snapst.Candidate, IsNil) + c.Check(snapst.Channel, Equals, "beta") + c.Check(t.Status(), Equals, state.DoneStatus) +} + +func (s *linkSnapSuite) TestDoUndoLinkSnap(c *C) { + s.state.Lock() + defer s.state.Unlock() + si := &snap.SideInfo{ + OfficialName: "foo", + Revision: snap.R(33), + } + snapstate.Set(s.state, "foo", &snapstate.SnapState{ + Candidate: si, + }) + t := s.state.NewTask("link-snap", "test") + t.Set("snap-setup", &snapstate.SnapSetup{ + Name: "foo", + Channel: "beta", + }) + chg := s.state.NewChange("dummy", "...") + chg.AddTask(t) + + terr := s.state.NewTask("error-trigger", "provoking total undo") + terr.WaitFor(t) + chg.AddTask(terr) + + s.state.Unlock() + + for i := 0; i < 3; i++ { + s.snapmgr.Ensure() + s.snapmgr.Wait() + } + + s.state.Lock() + var snapst snapstate.SnapState + err := snapstate.Get(s.state, "foo", &snapst) + c.Assert(err, IsNil) + c.Check(snapst.Active, Equals, false) + c.Check(snapst.Sequence, HasLen, 0) + c.Check(snapst.Candidate, DeepEquals, si) + c.Check(snapst.Channel, Equals, "") + c.Check(t.Status(), Equals, state.UndoneStatus) +} + +func (s *linkSnapSuite) TestDoLinkSnapTryToCleanupOnError(c *C) { + s.state.Lock() + defer s.state.Unlock() + si := &snap.SideInfo{ + OfficialName: "foo", + Revision: snap.R(35), + } + snapstate.Set(s.state, "foo", &snapstate.SnapState{ + Candidate: si, + }) + t := s.state.NewTask("link-snap", "test") + t.Set("snap-setup", &snapstate.SnapSetup{ + Name: "foo", + Channel: "beta", + }) + + s.fakeBackend.linkSnapFailTrigger = "/snap/foo/35" + s.state.NewChange("dummy", "...").AddTask(t) + s.state.Unlock() + + s.snapmgr.Ensure() + s.snapmgr.Wait() + + s.state.Lock() + + // state as expected + var snapst snapstate.SnapState + err := snapstate.Get(s.state, "foo", &snapst) + c.Assert(err, IsNil) + c.Check(snapst.Active, Equals, false) + c.Check(snapst.Sequence, HasLen, 0) + c.Check(snapst.Candidate, DeepEquals, si) + c.Check(snapst.Channel, Equals, "") + c.Check(t.Status(), Equals, state.ErrorStatus) + + // tried to cleanup + c.Check(s.fakeBackend.ops, DeepEquals, []fakeOp{ + { + op: "candidate", + sinfo: *si, + }, + { + op: "link-snap.failed", + name: "/snap/foo/35", + }, + { + op: "unlink-snap", + name: "/snap/foo/35", + }, + }) +} diff -Nru snapd-2.0.5/overlord/snapstate/progress.go snapd-2.0.8/overlord/snapstate/progress.go --- snapd-2.0.5/overlord/snapstate/progress.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/progress.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package snapstate import ( - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord/state" ) // TaskProgressAdapter adapts the progress.Meter to the task progress diff -Nru snapd-2.0.5/overlord/snapstate/progress_test.go snapd-2.0.8/overlord/snapstate/progress_test.go --- snapd-2.0.5/overlord/snapstate/progress_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/progress_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package snapstate import ( - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord/state" . "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/overlord/snapstate/snapmgr.go snapd-2.0.8/overlord/snapstate/snapmgr.go --- snapd-2.0.5/overlord/snapstate/snapmgr.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/snapmgr.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,32 +22,59 @@ import ( "fmt" + "os" + "strconv" "gopkg.in/tomb.v2" - "github.com/ubuntu-core/snappy/overlord/auth" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snappy" - "github.com/ubuntu-core/snappy/store" + "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/store" ) // SnapManager is responsible for the installation and removal of snaps. type SnapManager struct { state *state.State backend managerBackend + store StoreService runner *state.TaskRunner } +// SnapSetupFlags are flags stored in SnapSetup to control snap manager tasks. +type SnapSetupFlags Flags + +// backward compatibility: upgrade old flags based on snappy.* flags values +// to Flags if needed +// XXX: this can be dropped and potentially the type at the earliest +// in 2.0.9 (after being out for about two prune cycles), at the +// latest when we need to recover the reserved unusable flag values, +// or this gets annoying for other reasons +func (ssfl *SnapSetupFlags) UnmarshalJSON(b []byte) error { + f, err := strconv.Atoi(string(b)) + if err != nil { + return fmt.Errorf("invalid snap-setup flags: %v", err) + } + if f >= interimUnusableLegacyFlagValueMin && f < (interimUnusableLegacyFlagValueLast<<1) { + // snappy.DeveloperMode was 0x10, TryMode was 0x20, + // snapstate values are 1 and 2 so this does what we need + f >>= 4 + } + + *ssfl = SnapSetupFlags(f) + + return nil +} + // SnapSetup holds the necessary snap details to perform most snap manager tasks. type SnapSetup struct { - Name string `json:"name"` - Revision int `json:"revision,omitempty"` - Channel string `json:"channel,omitempty"` - UserID int `json:"user-id,omitempty"` + Name string `json:"name"` + Revision snap.Revision `json:"revision,omitempty"` + Channel string `json:"channel,omitempty"` + UserID int `json:"user-id,omitempty"` - Flags int `json:"flags,omitempty"` + Flags SnapSetupFlags `json:"flags,omitempty"` SnapPath string `json:"snap-path,omitempty"` } @@ -60,17 +87,18 @@ return snap.MountDir(ss.Name, ss.Revision) } +// DevMode returns true if the snap is being installed in developer mode. func (ss *SnapSetup) DevMode() bool { - return ss.Flags&int(snappy.DeveloperMode) != 0 + return ss.Flags&DevMode != 0 } -// SnapStateFlags are flags stored in SnapState. -type SnapStateFlags int +// TryMode returns true if the snap is being installed in try mode directly from a directory. +func (ss *SnapSetup) TryMode() bool { + return ss.Flags&TryMode != 0 +} -const ( - // DevMode switches confinement to non-enforcing mode. - DevMode = 1 << iota -) +// SnapStateFlags are flags stored in SnapState. +type SnapStateFlags Flags // SnapState holds the state for a snap installed in the system. type SnapState struct { @@ -80,7 +108,7 @@ Channel string `json:"channel,omitempty"` Flags SnapStateFlags `json:"flags,omitempty"` // incremented revision used for local installs - LocalRevision int `json:"local-revision,omitempty"` + LocalRevision snap.Revision `json:"local-revision,omitempty"` } // Current returns the side info for the current revision in the snap revision sequence if there is one. @@ -97,13 +125,48 @@ return snapst.Flags&DevMode != 0 } +// SetDevMode sets/clears the DevMode flag in the SnapState. +func (snapst *SnapState) SetDevMode(active bool) { + if active { + snapst.Flags |= DevMode + } else { + snapst.Flags &= ^DevMode + } +} + +// TryMode returns true if the snap is installed in `try` mode as an +// unpacked directory. +func (snapst *SnapState) TryMode() bool { + return snapst.Flags&TryMode != 0 +} + +// SetTryMode sets/clears the TryMode flag in the SnapState. +func (snapst *SnapState) SetTryMode(active bool) { + if active { + snapst.Flags |= TryMode + } else { + snapst.Flags &= ^TryMode + } +} + // Manager returns a new snap manager. func Manager(s *state.State) (*SnapManager, error) { runner := state.NewTaskRunner(s) backend := &defaultBackend{} + + storeID := "" + // TODO: set the store-id here from the model information + if cand := os.Getenv("UBUNTU_STORE_ID"); cand != "" { + storeID = cand + } + store := store.NewUbuntuStoreSnapRepository(nil, storeID) + // TODO: if needed we could also put the store on the state using + // the Cache mechanism and an accessor function + m := &SnapManager{ state: s, backend: backend, + store: store, runner: runner, } @@ -112,7 +175,7 @@ return nil }, nil) - // install/update releated + // install/update related runner.AddHandler("prepare-snap", m.doPrepareSnap, m.undoPrepareSnap) runner.AddHandler("download-snap", m.doDownloadSnap, m.undoPrepareSnap) runner.AddHandler("mount-snap", m.doMountSnap, m.undoMountSnap) @@ -122,7 +185,7 @@ // FIXME: port to native tasks and rename //runner.AddHandler("garbage-collect", m.doGarbageCollect, nil) - // remove releated + // remove related runner.AddHandler("unlink-snap", m.doUnlinkSnap, nil) runner.AddHandler("clear-snap", m.doClearSnapData, nil) runner.AddHandler("discard-snap", m.doDiscardSnap, nil) @@ -138,17 +201,25 @@ return m, nil } -func checkRevisionIsNew(name string, snapst *SnapState, revision int) error { +// Store returns the store service used by the manager. +func (m *SnapManager) Store() StoreService { + return m.store +} + +// ReplaceStore replaces the store used by manager. +func (m *SnapManager) ReplaceStore(store StoreService) { + m.store = store +} + +func checkRevisionIsNew(name string, snapst *SnapState, revision snap.Revision) error { for _, si := range snapst.Sequence { if si.Revision == revision { - return fmt.Errorf("revision %d of snap %q already installed", revision, name) + return fmt.Errorf("revision %s of snap %q already installed", revision, name) } } return nil } -const firstLocalRevision = 100001 - func (m *SnapManager) doPrepareSnap(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() @@ -158,16 +229,18 @@ return err } - if ss.Revision == 0 { // sideloading - // to not clash with not sideload installs - // and to not have clashes between them - // use incremental revisions starting at 100001 - // for sideloads + if ss.Revision.Unset() { + // Local revisions start at -1 and go down. + // (unless it's a really old local revision in which case it needs fixing) revision := snapst.LocalRevision - if revision == 0 { - revision = firstLocalRevision + if revision.Unset() || revision.N > 0 { + // if revision.N>0 this fixes it + revision = snap.R(-1) } else { - revision++ + revision.N-- + } + if !revision.Local() { + panic("internal error: invalid local revision built: " + revision.String()) } snapst.LocalRevision = revision ss.Revision = revision @@ -225,7 +298,7 @@ auther = user.Authenticator() } - storeInfo, downloadedSnapFile, err := m.backend.Download(ss.Name, ss.Channel, checker, pb, auther) + storeInfo, downloadedSnapFile, err := m.backend.Download(ss.Name, ss.Channel, checker, pb, m.store, auther) if err != nil { return err } @@ -358,6 +431,7 @@ m.runner.Stop() } +// TaskSnapSetup returns the SnapSetup with task params hold by or referred to by the the task. func TaskSnapSetup(t *state.Task) (*SnapSetup, error) { var ss SnapSetup @@ -397,7 +471,7 @@ func (m *SnapManager) undoMountSnap(t *state.Task, _ *tomb.Tomb) error { t.State().Lock() - ss, err := TaskSnapSetup(t) + ss, _, err := snapSetupAndState(t) t.State().Unlock() if err != nil { return err @@ -424,13 +498,15 @@ } - if err := m.backend.CheckSnap(ss.SnapPath, curInfo, ss.Flags); err != nil { + m.backend.Current(curInfo) + + if err := checkSnap(t.State(), ss.SnapPath, curInfo, Flags(ss.Flags)); err != nil { return err } // TODO Use ss.Revision to obtain the right info to mount // instead of assuming the candidate is the right one. - return m.backend.SetupSnap(ss.SnapPath, snapst.Candidate, ss.Flags) + return m.backend.SetupSnap(ss.SnapPath, snapst.Candidate) } func (m *SnapManager) undoUnlinkCurrentSnap(t *state.Task, _ *tomb.Tomb) error { @@ -507,7 +583,18 @@ return err } - return m.backend.UndoCopySnapData(newInfo, ss.Flags) + var oldInfo *snap.Info + if cur := snapst.Current(); cur != nil { + var err error + oldInfo, err = readInfo(ss.Name, cur) + if err != nil { + return err + } + + } + + pb := &TaskProgressAdapter{task: t} + return m.backend.UndoCopySnapData(newInfo, oldInfo, pb) } func (m *SnapManager) doCopySnapData(t *state.Task, _ *tomb.Tomb) error { @@ -533,7 +620,8 @@ } - return m.backend.CopySnapData(newInfo, oldInfo, ss.Flags) + pb := &TaskProgressAdapter{task: t} + return m.backend.CopySnapData(newInfo, oldInfo, pb) } func (m *SnapManager) doLinkSnap(t *state.Task, _ *tomb.Tomb) error { @@ -557,6 +645,8 @@ if ss.Channel != "" { snapst.Channel = ss.Channel } + oldTryMode := snapst.TryMode() + snapst.SetTryMode(ss.TryMode()) newInfo, err := readInfo(ss.Name, cand) if err != nil { @@ -564,15 +654,29 @@ } st.Unlock() + // XXX: this block is slightly ugly, find a pattern when we have more examples err = m.backend.LinkSnap(newInfo) + if err != nil { + pb := &TaskProgressAdapter{task: t} + err := m.backend.UnlinkSnap(newInfo, pb) + if err != nil { + st.Lock() + t.Errorf("cannot cleanup failed attempt at making snap %q available to the system: %v", ss.Name, err) + st.Unlock() + } + } st.Lock() if err != nil { return err } + // save for undoLinkSnap + t.Set("old-trymode", oldTryMode) t.Set("old-channel", oldChannel) // Do at the end so we only preserve the new state if it worked. Set(st, ss.Name, snapst) + // Make sure if state commits and snapst is mutated we won't be rerun + t.SetStatus(state.DoneStatus) return nil } @@ -592,6 +696,11 @@ if err != nil { return err } + var oldTryMode bool + err = t.Get("old-trymode", &oldTryMode) + if err != nil { + return err + } // relinking of the old snap is done in the undo of unlink-current-snap @@ -599,6 +708,7 @@ snapst.Sequence = snapst.Sequence[:len(snapst.Sequence)-1] snapst.Active = false snapst.Channel = oldChannel + snapst.SetTryMode(oldTryMode) newInfo, err := readInfo(ss.Name, snapst.Candidate) if err != nil { @@ -615,5 +725,7 @@ // mark as inactive Set(st, ss.Name, snapst) + // Make sure if state commits and snapst is mutated we won't be rerun + t.SetStatus(state.UndoneStatus) return nil } diff -Nru snapd-2.0.5/overlord/snapstate/snapmgr_test.go snapd-2.0.8/overlord/snapstate/snapmgr_test.go --- snapd-2.0.5/overlord/snapstate/snapmgr_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/snapmgr_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,14 +27,13 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/overlord/auth" - "github.com/ubuntu-core/snappy/overlord/snapstate" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" - "github.com/ubuntu-core/snappy/snappy" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" ) func TestSnapManager(t *testing.T) { TestingT(t) } @@ -72,11 +71,15 @@ c.Assert(err, IsNil) s.snapmgr.AddForeignTaskHandlers(s.fakeBackend) - // XXX: have just one, reset! snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend) - snapstate.SetSnapstateBackend(s.fakeBackend) - s.reset = snapstate.MockReadInfo(s.fakeBackend.ReadInfo) + restore1 := snapstate.MockReadInfo(s.fakeBackend.ReadInfo) + restore2 := snapstate.MockOpenSnapFile(s.fakeBackend.OpenSnapFile) + + s.reset = func() { + restore2() + restore1() + } s.state.Lock() s.user, err = auth.NewUser(s.state, "username", "macaroon", []string{"discharge"}) @@ -88,6 +91,13 @@ s.reset() } +func (s *snapmgrTestSuite) TestStore(c *C) { + c.Check(s.snapmgr.Store(), NotNil) + + s.snapmgr.ReplaceStore(nil) + c.Check(s.snapmgr.Store(), IsNil) +} + func verifyInstallUpdateTasks(c *C, curActive bool, ts *state.TaskSet, st *state.State) { i := 0 n := 5 @@ -169,7 +179,7 @@ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ Active: true, Channel: "edge", - Sequence: []*snap.SideInfo{{OfficialName: "some-snap", Revision: 11}}, + Sequence: []*snap.SideInfo{{OfficialName: "some-snap", Revision: snap.R(11)}}, }) ts, err := snapstate.Update(s.state, "some-snap", "some-channel", s.user.ID, 0) @@ -190,7 +200,7 @@ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ Active: true, Channel: "edge", - Sequence: []*snap.SideInfo{{OfficialName: "some-snap", Revision: 11}}, + Sequence: []*snap.SideInfo{{OfficialName: "some-snap", Revision: snap.R(11)}}, }) ts, err := snapstate.Update(s.state, "some-snap", "", s.user.ID, 0) @@ -232,7 +242,7 @@ }, }) - ts, err := snapstate.Remove(s.state, "foo", 0) + ts, err := snapstate.Remove(s.state, "foo") c.Assert(err, IsNil) i := 0 @@ -259,16 +269,16 @@ Sequence: []*snap.SideInfo{{OfficialName: "some-snap"}}, }) - ts, err := snapstate.Remove(s.state, "some-snap", 0) + ts, err := snapstate.Remove(s.state, "some-snap") c.Assert(err, IsNil) // need a change to make the tasks visible s.state.NewChange("remove", "...").AddAll(ts) - _, err = snapstate.Remove(s.state, "some-snap", 0) + _, err = snapstate.Remove(s.state, "some-snap") c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`) } -func (s *snapmgrTestSuite) TestInstallIntegration(c *C) { +func (s *snapmgrTestSuite) TestInstallRunThrough(c *C) { s.state.Lock() defer s.state.Unlock() @@ -291,14 +301,17 @@ channel: "some-channel", }, fakeOp{ - op: "check-snap", + op: "current", + old: "", + }, + fakeOp{ + op: "open-snap-file", name: "downloaded-snap-path", - old: "", }, fakeOp{ op: "setup-snap", name: "downloaded-snap-path", - revno: 11, + revno: snap.R(11), }, fakeOp{ op: "copy-data", @@ -308,7 +321,7 @@ fakeOp{ op: "setup-profiles:Doing", name: "some-snap", - revno: 11, + revno: snap.R(11), }, fakeOp{ op: "candidate", @@ -316,7 +329,7 @@ OfficialName: "some-snap", Channel: "some-channel", SnapID: "snapIDsnapidsnapidsnapidsnapidsn", - Revision: 11, + Revision: snap.R(11), }, }, fakeOp{ @@ -337,7 +350,7 @@ c.Assert(err, IsNil) c.Assert(ss, DeepEquals, snapstate.SnapSetup{ Name: "some-snap", - Revision: 11, + Revision: snap.R(11), Channel: "some-channel", UserID: s.user.ID, SnapPath: "downloaded-snap-path", @@ -356,14 +369,14 @@ OfficialName: "some-snap", Channel: "some-channel", SnapID: "snapIDsnapidsnapidsnapidsnapidsn", - Revision: 11, + Revision: snap.R(11), }) } -func (s *snapmgrTestSuite) TestUpdateIntegration(c *C) { +func (s *snapmgrTestSuite) TestUpdateRunThrough(c *C) { si := snap.SideInfo{ OfficialName: "some-snap", - Revision: 7, + Revision: snap.R(7), } s.state.Lock() @@ -375,7 +388,7 @@ }) chg := s.state.NewChange("install", "install a snap") - ts, err := snapstate.Update(s.state, "some-snap", "some-channel", s.user.ID, snappy.DoInstallGC) + ts, err := snapstate.Update(s.state, "some-snap", "some-channel", s.user.ID, 0) c.Assert(err, IsNil) chg.AddAll(ts) @@ -392,31 +405,31 @@ channel: "some-channel", }, fakeOp{ - op: "check-snap", - name: "downloaded-snap-path", - flags: int(snappy.DoInstallGC), - old: "/snap/some-snap/7", + op: "current", + old: "/snap/some-snap/7", + }, + fakeOp{ + op: "open-snap-file", + name: "downloaded-snap-path", }, fakeOp{ op: "setup-snap", name: "downloaded-snap-path", - flags: int(snappy.DoInstallGC), - revno: 11, + revno: snap.R(11), }, fakeOp{ op: "unlink-snap", name: "/snap/some-snap/7", }, fakeOp{ - op: "copy-data", - name: "/snap/some-snap/11", - flags: int(snappy.DoInstallGC), - old: "/snap/some-snap/7", + op: "copy-data", + name: "/snap/some-snap/11", + old: "/snap/some-snap/7", }, fakeOp{ op: "setup-profiles:Doing", name: "some-snap", - revno: 11, + revno: snap.R(11), }, fakeOp{ op: "candidate", @@ -424,7 +437,7 @@ OfficialName: "some-snap", SnapID: "snapIDsnapidsnapidsnapidsnapidsn", Channel: "some-channel", - Revision: 11, + Revision: snap.R(11), }, }, fakeOp{ @@ -449,10 +462,10 @@ c.Assert(ss, DeepEquals, snapstate.SnapSetup{ Name: "some-snap", Channel: "some-channel", - Flags: int(snappy.DoInstallGC), + Flags: 0, UserID: s.user.ID, - Revision: 11, + Revision: snap.R(11), SnapPath: "downloaded-snap-path", }) @@ -468,20 +481,20 @@ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ OfficialName: "some-snap", Channel: "", - Revision: 7, + Revision: snap.R(7), }) c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{ OfficialName: "some-snap", Channel: "some-channel", SnapID: "snapIDsnapidsnapidsnapidsnapidsn", - Revision: 11, + Revision: snap.R(11), }) } -func (s *snapmgrTestSuite) TestUpdateUndoIntegration(c *C) { +func (s *snapmgrTestSuite) TestUpdateUndoRunThrough(c *C) { si := snap.SideInfo{ OfficialName: "some-snap", - Revision: 7, + Revision: snap.R(7), } s.state.Lock() @@ -493,7 +506,7 @@ }) chg := s.state.NewChange("install", "install a snap") - ts, err := snapstate.Update(s.state, "some-snap", "some-channel", s.user.ID, snappy.DoInstallGC) + ts, err := snapstate.Update(s.state, "some-snap", "some-channel", s.user.ID, 0) c.Assert(err, IsNil) chg.AddAll(ts) @@ -512,31 +525,31 @@ channel: "some-channel", }, { - op: "check-snap", - name: "downloaded-snap-path", - flags: int(snappy.DoInstallGC), - old: "/snap/some-snap/7", + op: "current", + old: "/snap/some-snap/7", + }, + { + op: "open-snap-file", + name: "downloaded-snap-path", }, { op: "setup-snap", name: "downloaded-snap-path", - flags: int(snappy.DoInstallGC), - revno: 11, + revno: snap.R(11), }, { op: "unlink-snap", name: "/snap/some-snap/7", }, { - op: "copy-data", - name: "/snap/some-snap/11", - flags: int(snappy.DoInstallGC), - old: "/snap/some-snap/7", + op: "copy-data", + name: "/snap/some-snap/11", + old: "/snap/some-snap/7", }, { op: "setup-profiles:Doing", name: "some-snap", - revno: 11, + revno: snap.R(11), }, { op: "candidate", @@ -544,22 +557,26 @@ OfficialName: "some-snap", SnapID: "snapIDsnapidsnapidsnapidsnapidsn", Channel: "some-channel", - Revision: 11, + Revision: snap.R(11), }, }, { op: "link-snap.failed", name: "/snap/some-snap/11", }, - // no unlink-snap here is expected! + { + op: "unlink-snap", + name: "/snap/some-snap/11", + }, { op: "setup-profiles:Undoing", name: "some-snap", - revno: 11, + revno: snap.R(11), }, { op: "undo-copy-snap-data", name: "/snap/some-snap/11", + old: "/snap/some-snap/7", }, { op: "link-snap", @@ -585,14 +602,14 @@ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ OfficialName: "some-snap", Channel: "", - Revision: 7, + Revision: snap.R(7), }) } -func (s *snapmgrTestSuite) TestUpdateTotalUndoIntegration(c *C) { +func (s *snapmgrTestSuite) TestUpdateTotalUndoRunThrough(c *C) { si := snap.SideInfo{ OfficialName: "some-snap", - Revision: 7, + Revision: snap.R(7), } s.state.Lock() @@ -605,7 +622,7 @@ }) chg := s.state.NewChange("install", "install a snap") - ts, err := snapstate.Update(s.state, "some-snap", "some-channel", s.user.ID, snappy.DoInstallGC) + ts, err := snapstate.Update(s.state, "some-snap", "some-channel", s.user.ID, 0) c.Assert(err, IsNil) chg.AddAll(ts) @@ -629,31 +646,31 @@ channel: "some-channel", }, { - op: "check-snap", - name: "downloaded-snap-path", - flags: int(snappy.DoInstallGC), - old: "/snap/some-snap/7", + op: "current", + old: "/snap/some-snap/7", + }, + { + op: "open-snap-file", + name: "downloaded-snap-path", }, { op: "setup-snap", name: "downloaded-snap-path", - flags: int(snappy.DoInstallGC), - revno: 11, + revno: snap.R(11), }, { op: "unlink-snap", name: "/snap/some-snap/7", }, { - op: "copy-data", - name: "/snap/some-snap/11", - flags: int(snappy.DoInstallGC), - old: "/snap/some-snap/7", + op: "copy-data", + name: "/snap/some-snap/11", + old: "/snap/some-snap/7", }, { op: "setup-profiles:Doing", name: "some-snap", - revno: 11, + revno: snap.R(11), }, { op: "candidate", @@ -661,7 +678,7 @@ OfficialName: "some-snap", SnapID: "snapIDsnapidsnapidsnapidsnapidsn", Channel: "some-channel", - Revision: 11, + Revision: snap.R(11), }, }, { @@ -676,11 +693,12 @@ { op: "setup-profiles:Undoing", name: "some-snap", - revno: 11, + revno: snap.R(11), }, { op: "undo-copy-snap-data", name: "/snap/some-snap/11", + old: "/snap/some-snap/7", }, { op: "link-snap", @@ -707,14 +725,14 @@ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ OfficialName: "some-snap", Channel: "", - Revision: 7, + Revision: snap.R(7), }) } -func (s *snapmgrTestSuite) TestUpdateSameRevisionIntegration(c *C) { +func (s *snapmgrTestSuite) TestUpdateSameRevisionRunThrough(c *C) { si := snap.SideInfo{ OfficialName: "some-snap", - Revision: 7, + Revision: snap.R(7), } s.state.Lock() @@ -726,7 +744,7 @@ }) chg := s.state.NewChange("install", "install a snap") - ts, err := snapstate.Update(s.state, "some-snap", "channel-for-7", s.user.ID, snappy.DoInstallGC) + ts, err := snapstate.Update(s.state, "some-snap", "channel-for-7", s.user.ID, 0) c.Assert(err, IsNil) chg.AddAll(ts) @@ -761,7 +779,7 @@ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ OfficialName: "some-snap", Channel: "", - Revision: 7, + Revision: snap.R(7), }) } @@ -781,7 +799,10 @@ } -func (s *snapmgrTestSuite) TestInstallFirstLocalIntegration(c *C) { +func (s *snapmgrTestSuite) TestInstallFirstLocalRunThrough(c *C) { + // use the real thing for this one + snapstate.MockOpenSnapFile(snapstate.OpenSnapFileImpl) + s.state.Lock() defer s.state.Unlock() @@ -797,15 +818,19 @@ s.settle() s.state.Lock() - // ensure only local install was run, i.e. first action is check-snap + // ensure only local install was run, i.e. first actions are pseudo-action current c.Assert(s.fakeBackend.ops, HasLen, 6) - c.Check(s.fakeBackend.ops[0].op, Equals, "check-snap") - c.Check(s.fakeBackend.ops[0].name, Matches, `.*/mock_1.0_all.snap`) + c.Check(s.fakeBackend.ops[0].op, Equals, "current") + c.Check(s.fakeBackend.ops[0].old, Equals, "") + // and setup-snap + c.Check(s.fakeBackend.ops[1].op, Equals, "setup-snap") + c.Check(s.fakeBackend.ops[1].name, Matches, `.*/mock_1.0_all.snap`) + c.Check(s.fakeBackend.ops[1].revno, Equals, snap.R("x1")) c.Check(s.fakeBackend.ops[4].op, Equals, "candidate") - c.Check(s.fakeBackend.ops[4].sinfo, DeepEquals, snap.SideInfo{Revision: 100001}) + c.Check(s.fakeBackend.ops[4].sinfo, DeepEquals, snap.SideInfo{Revision: snap.R(-1)}) c.Check(s.fakeBackend.ops[5].op, Equals, "link-snap") - c.Check(s.fakeBackend.ops[5].name, Equals, "/snap/mock/100001") + c.Check(s.fakeBackend.ops[5].name, Equals, "/snap/mock/x1") // verify snapSetup info var ss snapstate.SnapSetup @@ -814,7 +839,7 @@ c.Assert(err, IsNil) c.Assert(ss, DeepEquals, snapstate.SnapSetup{ Name: "mock", - Revision: 100001, + Revision: snap.R(-1), SnapPath: mockSnap, }) @@ -828,19 +853,22 @@ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{ OfficialName: "", Channel: "", - Revision: 100001, + Revision: snap.R(-1), }) - c.Assert(snapst.LocalRevision, Equals, 100001) + c.Assert(snapst.LocalRevision, Equals, snap.R(-1)) } -func (s *snapmgrTestSuite) TestInstallSubequentLocalIntegration(c *C) { +func (s *snapmgrTestSuite) TestInstallSubsequentLocalRunThrough(c *C) { + // use the real thing for this one + snapstate.MockOpenSnapFile(snapstate.OpenSnapFileImpl) + s.state.Lock() defer s.state.Unlock() snapstate.Set(s.state, "mock", &snapstate.SnapState{ Active: true, - Sequence: []*snap.SideInfo{{Revision: 100002}}, - LocalRevision: 100002, + Sequence: []*snap.SideInfo{{Revision: snap.R(-2)}}, + LocalRevision: snap.R(-2), }) mockSnap := makeTestSnap(c, `name: mock @@ -855,26 +883,30 @@ s.settle() s.state.Lock() - // ensure only local install was run, i.e. first action is check-snap + // ensure only local install was run, i.e. first action is pseudo-action current c.Assert(s.fakeBackend.ops, HasLen, 7) - c.Check(s.fakeBackend.ops[0].op, Equals, "check-snap") - c.Check(s.fakeBackend.ops[0].name, Matches, `.*/mock_1.0_all.snap`) + c.Check(s.fakeBackend.ops[0].op, Equals, "current") + c.Check(s.fakeBackend.ops[0].old, Equals, "/snap/mock/x2") + // and setup-snap + c.Check(s.fakeBackend.ops[1].op, Equals, "setup-snap") + c.Check(s.fakeBackend.ops[1].name, Matches, `.*/mock_1.0_all.snap`) + c.Check(s.fakeBackend.ops[1].revno, Equals, snap.R("x3")) c.Check(s.fakeBackend.ops[2].op, Equals, "unlink-snap") - c.Check(s.fakeBackend.ops[2].name, Equals, "/snap/mock/100002") + c.Check(s.fakeBackend.ops[2].name, Equals, "/snap/mock/x2") c.Check(s.fakeBackend.ops[3].op, Equals, "copy-data") - c.Check(s.fakeBackend.ops[3].name, Equals, "/snap/mock/100003") - c.Check(s.fakeBackend.ops[3].old, Equals, "/snap/mock/100002") + c.Check(s.fakeBackend.ops[3].name, Equals, "/snap/mock/x3") + c.Check(s.fakeBackend.ops[3].old, Equals, "/snap/mock/x2") c.Check(s.fakeBackend.ops[4].op, Equals, "setup-profiles:Doing") c.Check(s.fakeBackend.ops[4].name, Equals, "mock") - c.Check(s.fakeBackend.ops[4].revno, Equals, 100003) + c.Check(s.fakeBackend.ops[4].revno, Equals, snap.R(-3)) c.Check(s.fakeBackend.ops[5].op, Equals, "candidate") - c.Check(s.fakeBackend.ops[5].sinfo, DeepEquals, snap.SideInfo{Revision: 100003}) + c.Check(s.fakeBackend.ops[5].sinfo, DeepEquals, snap.SideInfo{Revision: snap.R(-3)}) c.Check(s.fakeBackend.ops[6].op, Equals, "link-snap") - c.Check(s.fakeBackend.ops[6].name, Equals, "/snap/mock/100003") + c.Check(s.fakeBackend.ops[6].name, Equals, "/snap/mock/x3") // verify snapSetup info var ss snapstate.SnapSetup @@ -883,7 +915,7 @@ c.Assert(err, IsNil) c.Assert(ss, DeepEquals, snapstate.SnapSetup{ Name: "mock", - Revision: 100003, + Revision: snap.R(-3), SnapPath: mockSnap, }) @@ -898,15 +930,65 @@ c.Assert(snapst.Current(), DeepEquals, &snap.SideInfo{ OfficialName: "", Channel: "", - Revision: 100003, + Revision: snap.R(-3), }) - c.Assert(snapst.LocalRevision, Equals, 100003) + c.Assert(snapst.LocalRevision, Equals, snap.R(-3)) } -func (s *snapmgrTestSuite) TestRemoveIntegration(c *C) { +func (s *snapmgrTestSuite) TestInstallOldSubsequentLocalRunThrough(c *C) { + // use the real thing for this one + snapstate.MockOpenSnapFile(snapstate.OpenSnapFileImpl) + + s.state.Lock() + defer s.state.Unlock() + + snapstate.Set(s.state, "mock", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{{Revision: snap.R(100001)}}, + LocalRevision: snap.R(100001), + }) + + mockSnap := makeTestSnap(c, `name: mock +version: 1.0`) + chg := s.state.NewChange("install", "install a local snap") + ts, err := snapstate.InstallPath(s.state, "mock", mockSnap, "", 0) + c.Assert(err, IsNil) + chg.AddAll(ts) + + s.state.Unlock() + defer s.snapmgr.Stop() + s.settle() + s.state.Lock() + + // ensure only local install was run, i.e. first action is pseudo-action current + ops := s.fakeBackend.ops + c.Assert(ops, HasLen, 7) + c.Check(ops[0].op, Equals, "current") + c.Check(ops[0].old, Equals, "/snap/mock/100001") + // and setup-snap + c.Check(ops[1].op, Equals, "setup-snap") + c.Check(ops[1].name, Matches, `.*/mock_1.0_all.snap`) + c.Check(ops[1].revno, Equals, snap.R("x1")) + + var snapst snapstate.SnapState + err = snapstate.Get(s.state, "mock", &snapst) + c.Assert(err, IsNil) + + c.Assert(snapst.Active, Equals, true) + c.Assert(snapst.Candidate, IsNil) + c.Assert(snapst.Sequence, HasLen, 2) + c.Assert(snapst.Current(), DeepEquals, &snap.SideInfo{ + OfficialName: "", + Channel: "", + Revision: snap.R(-1), + }) + c.Assert(snapst.LocalRevision, Equals, snap.R(-1)) +} + +func (s *snapmgrTestSuite) TestRemoveRunThrough(c *C) { si := snap.SideInfo{ OfficialName: "some-snap", - Revision: 7, + Revision: snap.R(7), } s.state.Lock() @@ -918,7 +1000,7 @@ }) chg := s.state.NewChange("remove", "remove a snap") - ts, err := snapstate.Remove(s.state, "some-snap", 0) + ts, err := snapstate.Remove(s.state, "some-snap") c.Assert(err, IsNil) chg.AddAll(ts) @@ -927,21 +1009,16 @@ s.settle() s.state.Lock() - c.Assert(s.fakeBackend.ops, HasLen, 7) + c.Assert(s.fakeBackend.ops, HasLen, 6) expected := []fakeOp{ fakeOp{ - op: "can-remove", - name: "/snap/some-snap/7", - active: true, - }, - fakeOp{ op: "unlink-snap", name: "/snap/some-snap/7", }, fakeOp{ op: "remove-profiles:Doing", name: "some-snap", - revno: 7, + revno: snap.R(7), }, fakeOp{ op: "remove-snap-data", @@ -976,7 +1053,7 @@ } else { expSnapSetup = &snapstate.SnapSetup{ Name: "some-snap", - Revision: 7, + Revision: snap.R(7), } } c.Check(ss, DeepEquals, expSnapSetup, Commentf(t.Kind())) @@ -988,20 +1065,20 @@ c.Assert(err, Equals, state.ErrNoState) } -func (s *snapmgrTestSuite) TestRemoveWithManyRevisionsIntegration(c *C) { +func (s *snapmgrTestSuite) TestRemoveWithManyRevisionsRunThrough(c *C) { si3 := snap.SideInfo{ OfficialName: "some-snap", - Revision: 3, + Revision: snap.R(3), } si5 := snap.SideInfo{ OfficialName: "some-snap", - Revision: 5, + Revision: snap.R(5), } si7 := snap.SideInfo{ OfficialName: "some-snap", - Revision: 7, + Revision: snap.R(7), } s.state.Lock() @@ -1013,7 +1090,7 @@ }) chg := s.state.NewChange("remove", "remove a snap") - ts, err := snapstate.Remove(s.state, "some-snap", 0) + ts, err := snapstate.Remove(s.state, "some-snap") c.Assert(err, IsNil) chg.AddAll(ts) @@ -1022,21 +1099,16 @@ s.settle() s.state.Lock() - c.Assert(s.fakeBackend.ops, HasLen, 11) + c.Assert(s.fakeBackend.ops, HasLen, 10) expected := []fakeOp{ { - op: "can-remove", - name: "/snap/some-snap/7", - active: true, - }, - { op: "unlink-snap", name: "/snap/some-snap/7", }, { op: "remove-profiles:Doing", name: "some-snap", - revno: 7, + revno: snap.R(7), }, { op: "remove-snap-data", @@ -1075,7 +1147,7 @@ // verify snapSetup info tasks := ts.Tasks() - revnos := []int{7, 3, 5} + revnos := []snap.Revision{{7}, {3}, {5}} whichRevno := 0 for _, t := range tasks { ss, err := snapstate.TaskSnapSetup(t) @@ -1106,6 +1178,25 @@ c.Assert(err, Equals, state.ErrNoState) } +func (s *snapmgrTestSuite) TestRemoveRefused(c *C) { + si := snap.SideInfo{ + OfficialName: "gadget", + Revision: snap.R(7), + } + + s.state.Lock() + defer s.state.Unlock() + + snapstate.Set(s.state, "gadget", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{&si}, + }) + + _, err := snapstate.Remove(s.state, "gadget") + + c.Check(err, ErrorMatches, `snap "gadget" is not removable`) +} + type snapmgrQuerySuite struct { st *state.State } @@ -1122,8 +1213,8 @@ dirs.SetRootDir(c.MkDir()) // Write a snap.yaml with fake name - sideInfo11 := &snap.SideInfo{OfficialName: "name1", Revision: 11, EditedSummary: "s11"} - sideInfo12 := &snap.SideInfo{OfficialName: "name1", Revision: 12, EditedSummary: "s12"} + sideInfo11 := &snap.SideInfo{OfficialName: "name1", Revision: snap.R(11), EditedSummary: "s11"} + sideInfo12 := &snap.SideInfo{OfficialName: "name1", Revision: snap.R(12), EditedSummary: "s12"} snaptest.MockSnap(c, ` name: name0 version: 1.1 @@ -1149,11 +1240,11 @@ st.Lock() defer st.Unlock() - info, err := snapstate.Info(st, "name1", 11) + info, err := snapstate.Info(st, "name1", snap.R(11)) c.Assert(err, IsNil) c.Check(info.Name(), Equals, "name1") - c.Check(info.Revision, Equals, 11) + c.Check(info.Revision, Equals, snap.R(11)) c.Check(info.Summary(), Equals, "s11") c.Check(info.Version, Equals, "1.1") c.Check(info.Description(), Equals, "Lots of text") @@ -1168,7 +1259,7 @@ c.Assert(err, IsNil) c.Check(info.Name(), Equals, "name1") - c.Check(info.Revision, Equals, 12) + c.Check(info.Revision, Equals, snap.R(12)) } func (s *snapmgrQuerySuite) TestActiveInfos(c *C) { @@ -1182,7 +1273,7 @@ c.Check(infos, HasLen, 1) c.Check(infos[0].Name(), Equals, "name1") - c.Check(infos[0].Revision, Equals, 12) + c.Check(infos[0].Revision, Equals, snap.R(12)) c.Check(infos[0].Summary(), Equals, "s12") c.Check(infos[0].Version, Equals, "1.2") c.Check(infos[0].Description(), Equals, "Lots of text") @@ -1196,7 +1287,7 @@ _, err := snapstate.GadgetInfo(st) c.Assert(err, Equals, state.ErrNoState) - sideInfoGadget := &snap.SideInfo{Revision: 2} + sideInfoGadget := &snap.SideInfo{Revision: snap.R(2)} snaptest.MockSnap(c, ` name: gadget type: gadget @@ -1211,7 +1302,7 @@ c.Assert(err, IsNil) c.Check(info.Name(), Equals, "gadget") - c.Check(info.Revision, Equals, 2) + c.Check(info.Revision, Equals, snap.R(2)) c.Check(info.Version, Equals, "gadget") c.Check(info.Type, Equals, snap.TypeGadget) } @@ -1240,7 +1331,7 @@ c.Assert(err, IsNil) c.Check(info12.Name(), Equals, "name1") - c.Check(info12.Revision, Equals, 12) + c.Check(info12.Revision, Equals, snap.R(12)) c.Check(info12.Summary(), Equals, "s12") c.Check(info12.Version, Equals, "1.2") c.Check(info12.Description(), Equals, "Lots of text") @@ -1249,7 +1340,7 @@ c.Assert(err, IsNil) c.Check(info11.Name(), Equals, "name1") - c.Check(info11.Revision, Equals, 11) + c.Check(info11.Revision, Equals, snap.R(11)) c.Check(info11.Version, Equals, "1.1") } @@ -1275,6 +1366,70 @@ c.Check(snapStates, HasLen, 0) } +func (s *snapmgrTestSuite) TestTrySetsTryMode(c *C) { + s.state.Lock() + defer s.state.Unlock() + + // make mock try dir + tryYaml := filepath.Join(c.MkDir(), "meta", "snap.yaml") + err := os.MkdirAll(filepath.Dir(tryYaml), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(tryYaml, []byte("name: foo\nversion: 1.0"), 0644) + c.Assert(err, IsNil) + + chg := s.state.NewChange("try", "try snap") + ts, err := snapstate.TryPath(s.state, "foo", filepath.Dir(filepath.Dir(tryYaml)), 0) + c.Assert(err, IsNil) + chg.AddAll(ts) + + s.state.Unlock() + defer s.snapmgr.Stop() + s.settle() + s.state.Lock() + + // verify snap is in TryMode + var snapst snapstate.SnapState + err = snapstate.Get(s.state, "foo", &snapst) + c.Assert(err, IsNil) + c.Check(snapst.TryMode(), Equals, true) +} + +func (s *snapmgrTestSuite) TestTryUndoRemovesTryFlag(c *C) { + s.state.Lock() + defer s.state.Unlock() + + // simulate existing state for foo + var snapst snapstate.SnapState + snapst.Sequence = []*snap.SideInfo{ + { + OfficialName: "foo", + Revision: snap.R(23), + }, + } + snapstate.Set(s.state, "foo", &snapst) + c.Check(snapst.TryMode(), Equals, false) + + chg := s.state.NewChange("try", "try snap") + ts, err := snapstate.TryPath(s.state, "foo", c.MkDir(), 0) + c.Assert(err, IsNil) + chg.AddAll(ts) + + last := ts.Tasks()[len(ts.Tasks())-1] + terr := s.state.NewTask("error-trigger", "provoking total undo") + terr.WaitFor(last) + chg.AddTask(terr) + + s.state.Unlock() + defer s.snapmgr.Stop() + s.settle() + s.state.Lock() + + // verify snap is not in try mode, the state got undone + err = snapstate.Get(s.state, "foo", &snapst) + c.Assert(err, IsNil) + c.Check(snapst.TryMode(), Equals, false) +} + type snapStateSuite struct{} var _ = Suite(&snapStateSuite{}) @@ -1286,6 +1441,32 @@ c.Check(snapst.DevMode(), Equals, true) } +func (s *snapStateSuite) TestSnapStateModeSetters(c *C) { + snapst := &snapstate.SnapState{} + c.Check(snapst.DevMode(), Equals, false) + c.Check(snapst.TryMode(), Equals, false) + + snapst.SetTryMode(true) + c.Check(snapst.DevMode(), Equals, false) + c.Check(snapst.TryMode(), Equals, true) + + snapst.SetDevMode(true) + c.Check(snapst.DevMode(), Equals, true) + c.Check(snapst.TryMode(), Equals, true) + + snapst.SetTryMode(false) + c.Check(snapst.DevMode(), Equals, true) + c.Check(snapst.TryMode(), Equals, false) + + snapst.SetDevMode(false) + c.Check(snapst.DevMode(), Equals, false) + c.Check(snapst.TryMode(), Equals, false) + + snapst.SetDevMode(true) + c.Check(snapst.DevMode(), Equals, true) + c.Check(snapst.TryMode(), Equals, false) +} + type snapSetupSuite struct{} var _ = Suite(&snapSetupSuite{}) @@ -1293,6 +1474,47 @@ func (s *snapSetupSuite) TestDevMode(c *C) { ss := &snapstate.SnapSetup{} c.Check(ss.DevMode(), Equals, false) - ss.Flags = int(snappy.DeveloperMode) + ss.Flags = snapstate.DevMode c.Check(ss.DevMode(), Equals, true) } + +type canRemoveSuite struct{} + +var _ = Suite(&canRemoveSuite{}) + +func (s *canRemoveSuite) TestAppAreAlwaysOKToRemove(c *C) { + info := &snap.Info{ + Type: snap.TypeApp, + } + info.OfficialName = "foo" + + c.Check(snapstate.CanRemove(info, false), Equals, true) + c.Check(snapstate.CanRemove(info, true), Equals, true) +} + +func (s *canRemoveSuite) TestActiveGadgetsAreNotOK(c *C) { + info := &snap.Info{ + Type: snap.TypeGadget, + } + info.OfficialName = "foo" + + c.Check(snapstate.CanRemove(info, false), Equals, true) + c.Check(snapstate.CanRemove(info, true), Equals, false) +} + +func (s *canRemoveSuite) TestActiveOSAndKernelAreNotOK(c *C) { + os := &snap.Info{ + Type: snap.TypeOS, + } + os.OfficialName = "os" + kernel := &snap.Info{ + Type: snap.TypeKernel, + } + kernel.OfficialName = "krnl" + + c.Check(snapstate.CanRemove(os, false), Equals, true) + c.Check(snapstate.CanRemove(os, true), Equals, false) + + c.Check(snapstate.CanRemove(kernel, false), Equals, true) + c.Check(snapstate.CanRemove(kernel, true), Equals, false) +} diff -Nru snapd-2.0.5/overlord/snapstate/snapstate.go snapd-2.0.8/overlord/snapstate/snapstate.go --- snapd-2.0.5/overlord/snapstate/snapstate.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/snapstate/snapstate.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,18 +24,40 @@ "encoding/json" "fmt" - "github.com/ubuntu-core/snappy/i18n" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snappy" + "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" ) -// allow exchange in the tests -// XXX once we reimplent CanRemove directly here, this goes away -var be managerBackend = &defaultBackend{} +// Flags are used to pass additional flags to operations and to keep track of snap modes. +type Flags int -func doInstall(s *state.State, curActive bool, snapName, snapPath, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { +const ( + // DevMode switches confinement to non-enforcing mode. + DevMode = 1 << iota + // TryMode is set for snaps installed to try directly from a local directory. + TryMode + + // the following flag values cannot be used until we drop the + // backward compatible support for flags values in SnapSetup + // that were based on snappy.* flags, after that we can + // start using them + interimUnusableLegacyFlagValueMin + interimUnusableLegacyFlagValue1 + interimUnusableLegacyFlagValue2 + interimUnusableLegacyFlagValueLast + + // the following flag value is the first that can be grabbed + // for use in the interim time while we have the backward compatible + // support + firstInterimUsableFlagValue + // if we need flags for just SnapSetup it may be easier + // to start a new sequence from the other end with: + // 0x40000000 >> iota +) + +func doInstall(s *state.State, curActive bool, snapName, snapPath, channel string, userID int, flags Flags) (*state.TaskSet, error) { if err := checkChangeConflict(s, snapName); err != nil { return nil, err } @@ -48,7 +70,7 @@ ss := SnapSetup{ Channel: channel, UserID: userID, - Flags: int(flags), + Flags: SnapSetupFlags(flags), } ss.Name = snapName ss.SnapPath = snapPath @@ -116,7 +138,7 @@ // Install returns a set of tasks for installing snap. // Note that the state must be locked by the caller. -func Install(s *state.State, name, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { +func Install(s *state.State, name, channel string, userID int, flags Flags) (*state.TaskSet, error) { var snapst SnapState err := Get(s, name, &snapst) if err != nil && err != state.ErrNoState { @@ -131,7 +153,7 @@ // InstallPath returns a set of tasks for installing snap from a file path. // Note that the state must be locked by the caller. -func InstallPath(s *state.State, name, path, channel string, flags snappy.InstallFlags) (*state.TaskSet, error) { +func InstallPath(s *state.State, name, path, channel string, flags Flags) (*state.TaskSet, error) { var snapst SnapState err := Get(s, name, &snapst) if err != nil && err != state.ErrNoState { @@ -141,9 +163,17 @@ return doInstall(s, snapst.Active, name, path, channel, 0, flags) } +// TryPath returns a set of tasks for trying a snap from a file path. +// Note that the state must be locked by the caller. +func TryPath(s *state.State, name, path string, flags Flags) (*state.TaskSet, error) { + flags |= TryMode + + return InstallPath(s, name, path, "", flags) +} + // Update initiates a change updating a snap. // Note that the state must be locked by the caller. -func Update(s *state.State, name, channel string, userID int, flags snappy.InstallFlags) (*state.TaskSet, error) { +func Update(s *state.State, name, channel string, userID int, flags Flags) (*state.TaskSet, error) { var snapst SnapState err := Get(s, name, &snapst) if err != nil && err != state.ErrNoState { @@ -161,11 +191,10 @@ return doInstall(s, snapst.Active, name, "", channel, userID, flags) } -func removeInactiveRevision(s *state.State, name string, revision int, flags snappy.RemoveFlags) *state.TaskSet { +func removeInactiveRevision(s *state.State, name string, revision snap.Revision) *state.TaskSet { ss := SnapSetup{ Name: name, Revision: revision, - Flags: int(flags), } clearData := s.NewTask("clear-snap", fmt.Sprintf(i18n.G("Remove data for snap %q"), name)) @@ -178,9 +207,27 @@ return state.NewTaskSet(clearData, discardSnap) } +// canRemove verifies that a snap can be removed. +func canRemove(s *snap.Info, active bool) bool { + // Gadget snaps should not be removed as they are a key + // building block for Gadgets. Pruning non active ones + // is acceptable. + if s.Type == snap.TypeGadget && active { + return false + } + + // You never want to remove an active kernel or OS + if (s.Type == snap.TypeKernel || s.Type == snap.TypeOS) && active { + return false + } + // TODO: on classic likely let remove core even if active if it's only snap left. + + return true +} + // Remove returns a set of tasks for removing snap. // Note that the state must be locked by the caller. -func Remove(s *state.State, name string, flags snappy.RemoveFlags) (*state.TaskSet, error) { +func Remove(s *state.State, name string) (*state.TaskSet, error) { if err := checkChangeConflict(s, name); err != nil { return nil, err } @@ -205,8 +252,7 @@ } // check if this is something that can be removed - // XXX: move CanRemove impl directly in snapstate - if !be.CanRemove(info, active) { + if !canRemove(info, active) { return nil, fmt.Errorf("snap %q is not removable", name) } @@ -214,7 +260,6 @@ ss := SnapSetup{ Name: name, Revision: revision, - Flags: int(flags), } // trigger remove @@ -245,7 +290,7 @@ seq := snapst.Sequence for i := len(seq) - 1; i >= 0; i-- { si := seq[i] - addNext(removeInactiveRevision(s, name, si.Revision, flags)) + addNext(removeInactiveRevision(s, name, si.Revision)) } discardConns := s.NewTask("discard-conns", fmt.Sprintf(i18n.G("Discard interface connections for snap %q"), name)) @@ -267,7 +312,7 @@ // Info returns the information about the snap with given name and revision. // Works also for a mounted candidate snap in the process of being installed. -func Info(s *state.State, name string, revision int) (*snap.Info, error) { +func Info(s *state.State, name string, revision snap.Revision) (*snap.Info, error) { var snapst SnapState err := Get(s, name, &snapst) if err == state.ErrNoState { @@ -287,7 +332,7 @@ return readInfo(name, snapst.Candidate) } - return nil, fmt.Errorf("cannot find snap %q at revision %d", name, revision) + return nil, fmt.Errorf("cannot find snap %q at revision %s", name, revision.String()) } // Current returns the information about the current revision of a snap with the given name. diff -Nru snapd-2.0.5/overlord/state/change_test.go snapd-2.0.8/overlord/state/change_test.go --- snapd-2.0.5/overlord/state/change_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/state/change_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord/state" "time" ) diff -Nru snapd-2.0.5/overlord/state/state.go snapd-2.0.8/overlord/state/state.go --- snapd-2.0.5/overlord/state/state.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/state/state.go 2016-06-08 05:58:01.000000000 +0000 @@ -30,7 +30,7 @@ "sync/atomic" "time" - "github.com/ubuntu-core/snappy/logger" + "github.com/snapcore/snapd/logger" ) // A Backend is used by State to checkpoint on every unlock operation diff -Nru snapd-2.0.5/overlord/state/state_test.go snapd-2.0.8/overlord/state/state_test.go --- snapd-2.0.5/overlord/state/state_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/state/state_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,7 +27,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord/state" ) func TestState(t *testing.T) { TestingT(t) } diff -Nru snapd-2.0.5/overlord/state/task.go snapd-2.0.8/overlord/state/task.go --- snapd-2.0.5/overlord/state/task.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/state/task.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,7 +23,7 @@ "encoding/json" "fmt" - "github.com/ubuntu-core/snappy/logger" + "github.com/snapcore/snapd/logger" "time" ) diff -Nru snapd-2.0.5/overlord/state/taskrunner.go snapd-2.0.8/overlord/state/taskrunner.go --- snapd-2.0.5/overlord/state/taskrunner.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/state/taskrunner.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,7 +25,7 @@ "gopkg.in/tomb.v2" - "github.com/ubuntu-core/snappy/logger" + "github.com/snapcore/snapd/logger" ) // HandlerFunc is the type of function for the handlers @@ -123,12 +123,16 @@ switch t.Status() { case DoingStatus: t.SetStatus(DoneStatus) + fallthrough + case DoneStatus: next = t.HaltTasks() case AbortStatus: t.SetStatus(DoneStatus) // Not actually aborted. r.tryUndo(t) case UndoingStatus: t.SetStatus(UndoneStatus) + fallthrough + case UndoneStatus: next = t.WaitTasks() } if len(next) > 0 { diff -Nru snapd-2.0.5/overlord/state/taskrunner_test.go snapd-2.0.8/overlord/state/taskrunner_test.go --- snapd-2.0.5/overlord/state/taskrunner_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/state/taskrunner_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,7 +28,7 @@ . "gopkg.in/check.v1" "gopkg.in/tomb.v2" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord/state" ) type taskRunnerSuite struct{} @@ -117,7 +117,15 @@ }, { setup: "t31:do-error t21:undo-error", result: "t11:do t12:do t21:do t31:do t31:do-error t32:do t32:undo t21:undo t21:undo-error t11:undo", -}} +}, { + setup: "t21:do-set-ready", + result: "t11:do t12:do t21:do t31:do t32:do", +}, + { + setup: "t31:do-error t21:undo-set-ready", + result: "t11:do t12:do t21:do t31:do t31:do-error t32:do t32:undo t21:undo t11:undo", + }, +} func (ts *taskRunnerSuite) TestSequenceTests(c *C) { sb := &stateBackend{} @@ -148,6 +156,14 @@ ch <- task.Summary() + ":" + label + "-error" return errors.New("boom") } + if task.Get(label+"-set-ready", &isSet) == nil && isSet { + switch task.Status() { + case state.DoingStatus: + task.SetStatus(state.DoneStatus) + case state.UndoingStatus: + task.SetStatus(state.UndoneStatus) + } + } return nil } } diff -Nru snapd-2.0.5/overlord/state/task_test.go snapd-2.0.8/overlord/state/task_test.go --- snapd-2.0.5/overlord/state/task_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/state/task_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,8 +25,8 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/overlord/state" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/testutil" "time" ) diff -Nru snapd-2.0.5/overlord/stateengine.go snapd-2.0.8/overlord/stateengine.go --- snapd-2.0.5/overlord/stateengine.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/stateengine.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,9 +23,9 @@ "fmt" "sync" - "github.com/ubuntu-core/snappy/logger" + "github.com/snapcore/snapd/logger" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord/state" ) // StateManager is implemented by types responsible for observing diff -Nru snapd-2.0.5/overlord/stateengine_test.go snapd-2.0.8/overlord/stateengine_test.go --- snapd-2.0.5/overlord/stateengine_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/overlord/stateengine_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,8 +24,8 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/overlord" - "github.com/ubuntu-core/snappy/overlord/state" + "github.com/snapcore/snapd/overlord" + "github.com/snapcore/snapd/overlord/state" ) type stateEngineSuite struct{} diff -Nru snapd-2.0.5/partition/bootloader_test.go snapd-2.0.8/partition/bootloader_test.go --- snapd-2.0.5/partition/bootloader_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/partition/bootloader_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,7 +25,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" + "github.com/snapcore/snapd/dirs" ) // Hook up check.v1 into the "go test" runner diff -Nru snapd-2.0.5/partition/grub.go snapd-2.0.8/partition/grub.go --- snapd-2.0.5/partition/grub.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/partition/grub.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,8 +23,8 @@ "fmt" "path/filepath" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" "github.com/mvo5/goconfigparser" ) diff -Nru snapd-2.0.5/partition/grub_test.go snapd-2.0.8/partition/grub_test.go --- snapd-2.0.5/partition/grub_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/partition/grub_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,7 +24,7 @@ "io/ioutil" "os" - "github.com/ubuntu-core/snappy/dirs" + "github.com/snapcore/snapd/dirs" . "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/partition/uboot.go snapd-2.0.8/partition/uboot.go --- snapd-2.0.5/partition/uboot.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/partition/uboot.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,8 +22,8 @@ import ( "path/filepath" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" "github.com/mvo5/uboot-go/uenv" ) diff -Nru snapd-2.0.5/po/snappy.pot snapd-2.0.8/po/snappy.pot --- snapd-2.0.5/po/snappy.pot 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/po/snappy.pot 2016-06-08 05:58:01.000000000 +0000 @@ -7,7 +7,7 @@ msgid "" msgstr "Project-Id-Version: snappy\n" "Report-Msgid-Bugs-To: snappy-devel@lists.ubuntu.com\n" - "POT-Creation-Date: 2016-05-18 23:53-0300\n" + "POT-Creation-Date: 2016-06-07 10:40+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -102,6 +102,9 @@ msgid "Install a snap to the system" msgstr "" +msgid "List a change's tasks" +msgstr "" + msgid "List installed snaps" msgstr "" @@ -133,13 +136,13 @@ msgid "Mount snap %q" msgstr "" -msgid "Name\tVersion\tPrice\tSummary" +msgid "Name\tVersion\tDeveloper\tNotes\tSummary" msgstr "" -msgid "Name\tVersion\tRev\tDeveloper" +msgid "Name\tVersion\tRev\tDeveloper\tNotes" msgstr "" -msgid "Name\tVersion\tSummary" +msgid "No snaps are installed yet. Try 'snap install hello-world'." msgstr "" msgid "Password: " @@ -202,6 +205,13 @@ msgid "This command logs the current user out of the store" msgstr "" +#, c-format +msgid "Try %q snap from %q" +msgstr "" + +msgid "Try an unpacked snap in the system" +msgstr "" + msgid "Two-factor code: " msgstr "" @@ -228,6 +238,10 @@ msgstr "" msgid "\n" + "The change command displays a summary of tasks associated to an individual change." +msgstr "" + +msgid "\n" "The changes command displays a summary of the recent system changes performed." msgstr "" @@ -335,6 +349,13 @@ msgstr "" msgid "\n" + "The try command installs an unpacked snap into the system for testing purposes.\n" + "The unpacked snap content continues to be used even after installation, so\n" + "non-metadata changes there go live instantly. Metadata changes such as those\n" + "performed in snap.yaml will require reinstallation to go live.\n" +msgstr "" + +msgid "\n" "\n" "The home directory is shared between snappy and the classic dimension.\n" "Run \"exit\" to leave the classic shell.\n" @@ -346,6 +367,9 @@ msgid "no interfaces found" msgstr "" +msgid "unavailable" +msgstr "" + #, c-format msgid "unsupported shell %v" msgstr "" diff -Nru snapd-2.0.5/policy/policy.go snapd-2.0.8/policy/policy.go --- snapd-2.0.5/policy/policy.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/policy/policy.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ "os" "path/filepath" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/osutil" ) var ( diff -Nru snapd-2.0.5/provisioning/provisioning.go snapd-2.0.8/provisioning/provisioning.go --- snapd-2.0.5/provisioning/provisioning.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/provisioning/provisioning.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,9 +23,9 @@ "path/filepath" "time" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/partition" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/partition" "gopkg.in/yaml.v2" ) diff -Nru snapd-2.0.5/provisioning/provisioning_test.go snapd-2.0.8/provisioning/provisioning_test.go --- snapd-2.0.5/provisioning/provisioning_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/provisioning/provisioning_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ "path/filepath" "testing" - "github.com/ubuntu-core/snappy/partition" + "github.com/snapcore/snapd/partition" . "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/README.md snapd-2.0.8/README.md --- snapd-2.0.5/README.md 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/README.md 2016-06-08 05:58:01.000000000 +0000 @@ -36,7 +36,7 @@ The easiest way to get the source for `snappy` is to use the `go get` command. - go get -d -v github.com/ubuntu-core/snappy/... + go get -d -v github.com/snapcore/snapd/... This command will checkout the source of `snappy` and inspect it for any unmet Go package dependencies, downloading those as well. `go get` will also build @@ -46,7 +46,7 @@ go help get At this point you will have the git local repository of the `snappy` source at -`$GOPATH/github.com/ubuntu-core/snappy`. The source for any +`$GOPATH/github.com/snapcore/snapd`. The source for any dependent packages will also be available inside `$GOPATH`. ### Dependencies handling @@ -69,12 +69,12 @@ To build, once the sources are available and `GOPATH` is set, you can just run - go build -o /tmp/snap github.com/ubuntu-core/snappy/cmd/snap + go build -o /tmp/snap github.com/snapcore/snapd/cmd/snap to get the `snap` binary in /tmp (or without -o to get it in the current working directory). Alternatively: - go install github.com/ubuntu-core/snappy/... + go install github.com/snapcore/snapd/... to have it available in `$GOPATH/bin` @@ -85,7 +85,7 @@ http://www.ubuntu.com/legal/contributors Snappy can be found on Github, so in order to fork the source and contribute, -go to https://github.com/ubuntu-core/snappy. Check out [Github's help +go to https://github.com/snapcore/snapd. Check out [Github's help pages](https://help.github.com/) to find out how to set up your local branch, commit changes and create pull requests. @@ -132,8 +132,8 @@ connect). -[travis-image]: https://travis-ci.org/ubuntu-core/snappy.svg?branch=master -[travis-url]: https://travis-ci.org/ubuntu-core/snappy +[travis-image]: https://travis-ci.org/snapcore/snapd.svg?branch=master +[travis-url]: https://travis-ci.org/snapcore/snapd -[coveralls-image]: https://coveralls.io/repos/ubuntu-core/snappy/badge.svg?branch=master&service=github -[coveralls-url]: https://coveralls.io/github/ubuntu-core/snappy?branch=master +[coveralls-image]: https://coveralls.io/repos/snapcore/snapd/badge.svg?branch=master&service=github +[coveralls-url]: https://coveralls.io/github/snapcore/snapd?branch=master diff -Nru snapd-2.0.5/release/export_test.go snapd-2.0.8/release/export_test.go --- snapd-2.0.5/release/export_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/release/export_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -19,8 +19,10 @@ package release -func MockLSBReleasePath(filename string) (restore func()) { - old := lsbReleasePath - lsbReleasePath = filename - return func() { lsbReleasePath = old } +var ReadOSRelease = readOSRelease + +func MockOSReleasePath(filename string) (restore func()) { + old := osReleasePath + osReleasePath = filename + return func() { osReleasePath = old } } diff -Nru snapd-2.0.5/release/release.go snapd-2.0.8/release/release.go --- snapd-2.0.5/release/release.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/release/release.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,54 +24,81 @@ "io/ioutil" "strings" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/osutil" ) // Series holds the Ubuntu Core series for snapd to use. var Series = "16" -// LSB contains the /etc/lsb-release information of the system. -type LSB struct { +// OS contains information about the system extracted from /etc/os-release. +type OS struct { ID string + Name string Release string Codename string } -var lsbReleasePath = "/etc/lsb-release" +var osReleasePath = "/etc/os-release" -// ReadLSB returns the lsb-release information of the current system. -func ReadLSB() (*LSB, error) { - lsb := &LSB{} +// readOSRelease returns the os-release information of the current system. +func readOSRelease() (*OS, error) { + osRelease := &OS{} - content, err := ioutil.ReadFile(lsbReleasePath) + content, err := ioutil.ReadFile(osReleasePath) if err != nil { - return nil, fmt.Errorf("cannot read lsb-release: %s", err) + return nil, fmt.Errorf("cannot read os-release: %s", err) } for _, line := range strings.Split(string(content), "\n") { - if strings.HasPrefix(line, "DISTRIB_ID=") { + if strings.HasPrefix(line, "ID=") { tmp := strings.SplitN(line, "=", 2) - lsb.ID = tmp[1] + osRelease.ID = strings.Trim(tmp[1], "\"") } - if strings.HasPrefix(line, "DISTRIB_RELEASE=") { + if strings.HasPrefix(line, "NAME=") { tmp := strings.SplitN(line, "=", 2) - lsb.Release = tmp[1] + osRelease.Name = strings.Trim(tmp[1], "\"") } - if strings.HasPrefix(line, "DISTRIB_CODENAME=") { + if strings.HasPrefix(line, "VERSION_ID=") { tmp := strings.SplitN(line, "=", 2) - lsb.Codename = tmp[1] + osRelease.Release = strings.Trim(tmp[1], "\"") } + if strings.HasPrefix(line, "UBUNTU_CODENAME=") { + tmp := strings.SplitN(line, "=", 2) + osRelease.Codename = strings.Trim(tmp[1], "\"") + } + } + if osRelease.Codename == "" { + osRelease.Codename = "xenial" } - return lsb, nil + return osRelease, nil } // OnClassic states whether the process is running inside a // classic Ubuntu system or a native Ubuntu Core image. var OnClassic bool +// ReleaseInfo contains data loaded from /etc/os-release on startup. +var ReleaseInfo OS + func init() { - OnClassic = osutil.FileExists("/var/lib/dpkg/status") + osRelease, err := readOSRelease() + if err != nil { + // Values recommended by os-release(5) as defaults + osRelease = &OS{ + Name: "Linux", + ID: "linux", + } + } + ReleaseInfo = *osRelease + // Assume that we are running on Classic + OnClassic = true + // On Ubuntu, dpkg is not present in an all-snap image so the presence of + // dpkg status file can be used as an indicator for a classic vs all-snap + // system. + if osRelease.ID == "ubuntu" { + OnClassic = osutil.FileExists("/var/lib/dpkg/status") + } } // MockOnClassic forces the process to appear inside a classic @@ -81,3 +108,11 @@ OnClassic = onClassic return func() { OnClassic = old } } + +// MockReleaseInfo fakes a given information to appear in ReleaseInfo, +// as if it was read /etc/os-release on startup. +func MockReleaseInfo(osRelease *OS) (restore func()) { + old := ReleaseInfo + ReleaseInfo = *osRelease + return func() { ReleaseInfo = old } +} diff -Nru snapd-2.0.5/release/release_test.go snapd-2.0.8/release/release_test.go --- snapd-2.0.5/release/release_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/release/release_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,7 +26,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/release" + "github.com/snapcore/snapd/release" ) // Hook up check.v1 into the "go test" runner @@ -41,39 +41,46 @@ c.Check(release.Series, Equals, "16") } -func makeMockLSBRelease(c *C) string { +func mockOSRelease(c *C) string { // FIXME: use AddCleanup here once available so that we // can do release.SetLSBReleasePath() here directly - mockLSBRelease := filepath.Join(c.MkDir(), "mock-lsb-release") + mockOSRelease := filepath.Join(c.MkDir(), "mock-os-release") s := ` -DISTRIB_ID=Ubuntu -DISTRIB_RELEASE=18.09 -DISTRIB_CODENAME=awsome -DISTRIB_DESCRIPTION=I'm not real! +NAME="Ubuntu" +VERSION="18.09 (Awesome Artichoke)" +ID=ubuntu +ID_LIKE=debian +PRETTY_NAME="I'm not real!" +VERSION_ID="18.09" +HOME_URL="http://www.ubuntu.com/" +SUPPORT_URL="http://help.ubuntu.com/" +BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" +UBUNTU_CODENAME=awesome ` - err := ioutil.WriteFile(mockLSBRelease, []byte(s), 0644) + err := ioutil.WriteFile(mockOSRelease, []byte(s), 0644) c.Assert(err, IsNil) - return mockLSBRelease + return mockOSRelease } -func (s *ReleaseTestSuite) TestReadLSB(c *C) { - reset := release.MockLSBReleasePath(makeMockLSBRelease(c)) +func (s *ReleaseTestSuite) TestReadOSRelease(c *C) { + reset := release.MockOSReleasePath(mockOSRelease(c)) defer reset() - lsb, err := release.ReadLSB() + os, err := release.ReadOSRelease() c.Assert(err, IsNil) - c.Assert(lsb.ID, Equals, "Ubuntu") - c.Assert(lsb.Release, Equals, "18.09") - c.Assert(lsb.Codename, Equals, "awsome") + c.Assert(os.ID, Equals, "ubuntu") + c.Assert(os.Name, Equals, "Ubuntu") + c.Assert(os.Release, Equals, "18.09") + c.Assert(os.Codename, Equals, "awesome") } -func (s *ReleaseTestSuite) TestReadLSBNotFound(c *C) { - reset := release.MockLSBReleasePath("not-there") +func (s *ReleaseTestSuite) TestReadOSReleaseNotFound(c *C) { + reset := release.MockOSReleasePath("not-there") defer reset() - _, err := release.ReadLSB() - c.Assert(err, ErrorMatches, "cannot read lsb-release:.*") + _, err := release.ReadOSRelease() + c.Assert(err, ErrorMatches, "cannot read os-release:.*") } func (s *ReleaseTestSuite) TestOnClassic(c *C) { @@ -85,3 +92,11 @@ defer reset() c.Assert(release.OnClassic, Equals, false) } + +func (s *ReleaseTestSuite) TestReleaseInfo(c *C) { + reset := release.MockReleaseInfo(&release.OS{ + ID: "distro-id", + }) + defer reset() + c.Assert(release.ReleaseInfo.ID, Equals, "distro-id") +} diff -Nru snapd-2.0.5/run-checks snapd-2.0.8/run-checks --- snapd-2.0.5/run-checks 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/run-checks 2016-06-08 05:58:01.000000000 +0000 @@ -108,28 +108,12 @@ # cmd/* test sources are tagged so they are not included in # special integration test builds echo Checking 'cmd/*/*_test.go' build tagging - bad="" - for cmd in cmd/* ; do - nottagged=$(ls ${cmd}/*_test.go|grep -v integration_coverage_test|grep -v -F "$(grep -l '!integrationcoverage' ${cmd}/*_test.go)" || true) - - if [ -n "${nottagged}" ] ; then - echo these ${cmd} test files miss the '!integrationcoverage' tag: ${nottagged} - bad=1 - fi - done - if [ -n "${bad}" ] ; then + nottagged=$(grep -r --include '*_test.go' --exclude integration_coverage_test.go -L '!integrationcoverage' cmd/*/) + if [ -n "${nottagged}" ] ; then + echo these test files miss the '!integrationcoverage' tag: ${nottagged} exit 1 fi - # pot file - echo Checking translations - TMPF="$(mktemp)" - addtrap "rm -f \"$TMPF\"" - ./update-pot "$TMPF" - if ! diff -u --ignore-matching-lines=.*POT-Creation-Date.* po/snappy.pot $TMPF; then - echo "You need to run ./update-pot" - exit 1 - fi fi if [ ! -z "$UNIT" ]; then @@ -139,7 +123,7 @@ echo "mode: set" > .coverage/coverage.out echo Building - go build -tags=excludeintegration -v github.com/ubuntu-core/snappy/... + go build -tags=excludeintegration -v github.com/snapcore/snapd/... # tests echo Running tests from $(pwd) @@ -151,7 +135,7 @@ echo Building the integration tests TMP_INTEGRATION="$(mktemp)" addtrap "rm -f \"$TMP_INTEGRATION\"" - go build -v -o $TMP_INTEGRATION github.com/ubuntu-core/snappy/integration-tests/ + go build -v -o $TMP_INTEGRATION github.com/snapcore/snapd/integration-tests/ # the rabbit hole echo Running the tests for the integration testutils diff -Nru snapd-2.0.5/snap/container.go snapd-2.0.8/snap/container.go --- snapd-2.0.5/snap/container.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/snap/container.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,82 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-2015 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 snap + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap/snapdir" + "github.com/snapcore/snapd/snap/squashfs" +) + +// Container is the interface to interact with the low-level snap files +type Container interface { + // ReadFile returns the content of a single file from the snap. + ReadFile(relative string) (content []byte, err error) + + // Install copies the snap file to targetPath (and possibly unpacks it to mountDir) + Install(targetPath, mountDir string) error +} + +// backend implements a specific snap format +type snapFormat struct { + magic []byte + open func(fn string) (Container, error) +} + +// formatHandlers is the registry of known formats, squashfs is the only one atm. +var formatHandlers = []snapFormat{ + {squashfs.Magic, func(p string) (Container, error) { + return squashfs.New(p), nil + }}, +} + +// Open opens a given snap file with the right backend +func Open(path string) (Container, error) { + + // see if it's a snapdir first + if osutil.FileExists(filepath.Join(path, "meta", "snap.yaml")) { + return snapdir.New(path), nil + } + + // open the file and check magic + f, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("cannot open snap: %v", err) + } + defer f.Close() + + header := make([]byte, 20) + if _, err := f.ReadAt(header, 0); err != nil { + return nil, fmt.Errorf("cannot read snap: %v", err) + } + + for _, h := range formatHandlers { + if bytes.HasPrefix(header, h.magic) { + return h.open(path) + } + } + + return nil, fmt.Errorf("cannot open snap: unknown header: %q", header) +} diff -Nru snapd-2.0.5/snap/container_test.go snapd-2.0.8/snap/container_test.go --- snapd-2.0.5/snap/container_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/snap/container_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,48 @@ +// -*- 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 snap_test + +import ( + "io/ioutil" + "os" + "path/filepath" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snapdir" +) + +type FileSuite struct{} + +var _ = Suite(&FileSuite{}) + +func (s *FileSuite) TestFileOpenForSnapDir(c *C) { + sd := c.MkDir() + snapYaml := filepath.Join(sd, "meta", "snap.yaml") + err := os.MkdirAll(filepath.Dir(snapYaml), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(snapYaml, []byte(`name: foo`), 0644) + c.Assert(err, IsNil) + + f, err := snap.Open(sd) + c.Assert(err, IsNil) + c.Assert(f, FitsTypeOf, &snapdir.SnapDir{}) +} diff -Nru snapd-2.0.5/snap/file.go snapd-2.0.8/snap/file.go --- snapd-2.0.5/snap/file.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/file.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2014-2015 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 snap - -import ( - "bytes" - "fmt" - "os" - - "github.com/ubuntu-core/snappy/snap/squashfs" -) - -// File is the interface to interact with the low-level snap files -type File interface { - // ReadFile returns the content of a single file from the snap. - ReadFile(relative string) (content []byte, err error) - - // Install copies the snap file to targetPath (and possibly unpacks it to mountDir) - Install(targetPath, mountDir string) error -} - -// backend implements a specific snap format -type snapFormat struct { - magic []byte - open func(fn string) (File, error) -} - -// formatHandlers is the registry of known formats, squashfs is the only one atm. -var formatHandlers = []snapFormat{ - {squashfs.Magic, func(p string) (File, error) { - return squashfs.New(p), nil - }}, -} - -// Open opens a given snap file with the right backend -func Open(path string) (File, error) { - f, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("cannot open snap: %v", err) - } - defer f.Close() - - header := make([]byte, 20) - if _, err := f.ReadAt(header, 0); err != nil { - return nil, fmt.Errorf("cannot read snap: %v", err) - } - - for _, h := range formatHandlers { - if bytes.HasPrefix(header, h.magic) { - return h.open(path) - } - } - - return nil, fmt.Errorf("cannot open snap: unknown header: %q", header) -} diff -Nru snapd-2.0.5/snap/implicit.go snapd-2.0.8/snap/implicit.go --- snapd-2.0.5/snap/implicit.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/implicit.go 2016-06-08 05:58:01.000000000 +0000 @@ -19,7 +19,7 @@ package snap -import "github.com/ubuntu-core/snappy/release" +import "github.com/snapcore/snapd/release" var implicitSlots = []string{ "firewall-control", @@ -38,10 +38,13 @@ } var implicitClassicSlots = []string{ + "cups-control", + "gsettings", + "network-manager", + "opengl", + "pulseaudio", "unity7", "x11", - "opengl", - "network-manager", } // AddImplicitSlots adds implicitly defined slots to a given snap. diff -Nru snapd-2.0.5/snap/implicit_test.go snapd-2.0.8/snap/implicit_test.go --- snapd-2.0.5/snap/implicit_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/implicit_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,9 +20,9 @@ package snap_test import ( - "github.com/ubuntu-core/snappy/interfaces/builtin" - "github.com/ubuntu-core/snappy/release" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" . "gopkg.in/check.v1" ) @@ -56,7 +56,7 @@ c.Assert(info.Slots["unity7"].Interface, Equals, "unity7") c.Assert(info.Slots["unity7"].Name, Equals, "unity7") c.Assert(info.Slots["unity7"].Snap, Equals, info) - c.Assert(info.Slots, HasLen, 17) + c.Assert(info.Slots, HasLen, 20) } func (s *InfoSnapYamlTestSuite) TestImplicitSlotsAreRealInterfaces(c *C) { diff -Nru snapd-2.0.5/snap/info.go snapd-2.0.8/snap/info.go --- snapd-2.0.5/snap/info.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/info.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,11 +24,11 @@ "io/ioutil" "os" "path/filepath" - "strconv" + "strings" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/systemd" - "github.com/ubuntu-core/snappy/timeout" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/systemd" + "github.com/snapcore/snapd/timeout" ) // PlaceInfo offers all the information about where a snap and its data are located and exposed in the filesystem. @@ -56,13 +56,13 @@ } // MinimalPlaceInfo returns a PlaceInfo with just the location information for a snap of the given name and revision. -func MinimalPlaceInfo(name string, revision int) PlaceInfo { +func MinimalPlaceInfo(name string, revision Revision) PlaceInfo { return &Info{SideInfo: SideInfo{OfficialName: name, Revision: revision}} } // MountDir returns the base directory where it gets mounted of the snap with the given name and revision. -func MountDir(name string, revision int) string { - return filepath.Join(dirs.SnapSnapsDir, name, strconv.Itoa(revision)) +func MountDir(name string, revision Revision) string { + return filepath.Join(dirs.SnapSnapsDir, name, revision.String()) } // SideInfo holds snap metadata that is crucial for the tracking of @@ -78,16 +78,16 @@ // from the store but is not required for working offline should not // end up in SideInfo. type SideInfo struct { - OfficialName string `yaml:"name,omitempty" json:"name,omitempty"` - SnapID string `yaml:"snap-id" json:"snap-id"` - Revision int `yaml:"revision" json:"revision"` - Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` - Developer string `yaml:"developer,omitempty" json:"developer,omitempty"` - EditedSummary string `yaml:"summary,omitempty" json:"summary,omitempty"` - EditedDescription string `yaml:"description,omitempty" json:"description,omitempty"` - Size int64 `yaml:"size,omitempty" json:"size,omitempty"` - Sha512 string `yaml:"sha512,omitempty" json:"sha512,omitempty"` - Private bool `yaml:"private,omitempty" json:"private,omitempty"` + OfficialName string `yaml:"name,omitempty" json:"name,omitempty"` + SnapID string `yaml:"snap-id" json:"snap-id"` + Revision Revision `yaml:"revision" json:"revision"` + Channel string `yaml:"channel,omitempty" json:"channel,omitempty"` + Developer string `yaml:"developer,omitempty" json:"developer,omitempty"` + EditedSummary string `yaml:"summary,omitempty" json:"summary,omitempty"` + EditedDescription string `yaml:"description,omitempty" json:"description,omitempty"` + Size int64 `yaml:"size,omitempty" json:"size,omitempty"` + Sha512 string `yaml:"sha512,omitempty" json:"sha512,omitempty"` + Private bool `yaml:"private,omitempty" json:"private,omitempty"` } // Info provides information about snaps. @@ -101,9 +101,14 @@ OriginalSummary string OriginalDescription string + Environment map[string]string + LicenseAgreement string LicenseVersion string + Epoch string + Confinement ConfinementType Apps map[string]*AppInfo + Hooks map[string]*HookInfo Plugs map[string]*PlugInfo Slots map[string]*SlotInfo @@ -143,10 +148,6 @@ return s.OriginalDescription } -func (s *Info) strRevno() string { - return strconv.Itoa(s.Revision) -} - // MountDir returns the base directory of the snap where it gets mounted. func (s *Info) MountDir() string { return MountDir(s.Name(), s.Revision) @@ -154,12 +155,12 @@ // MountFile returns the path where the snap file that is mounted is installed. func (s *Info) MountFile() string { - return filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_%d.snap", s.Name(), s.Revision)) + return filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_%s.snap", s.Name(), s.Revision)) } // DataDir returns the data directory of the snap. func (s *Info) DataDir() string { - return filepath.Join(dirs.SnapDataDir, s.Name(), s.strRevno()) + return filepath.Join(dirs.SnapDataDir, s.Name(), s.Revision.String()) } // CommonDataDir returns the data directory common across revisions of the snap. @@ -169,7 +170,7 @@ // DataHomeDir returns the per user data directory of the snap. func (s *Info) DataHomeDir() string { - return filepath.Join(dirs.SnapDataHomeGlob, s.Name(), s.strRevno()) + return filepath.Join(dirs.SnapDataHomeGlob, s.Name(), s.Revision.String()) } // CommonDataHomeDir returns the per user data directory common across revisions of the snap. @@ -189,6 +190,7 @@ Attrs map[string]interface{} Label string Apps map[string]*AppInfo + Hooks map[string]*HookInfo } // SlotInfo provides information about a slot. @@ -221,11 +223,21 @@ // TODO: this should go away once we have more plumbing and can change // things vs refactor - // https://github.com/ubuntu-core/snappy/pull/794#discussion_r58688496 + // https://github.com/snapcore/snapd/pull/794#discussion_r58688496 BusName string Plugs map[string]*PlugInfo Slots map[string]*SlotInfo + + Environment map[string]string +} + +// HookInfo provides information about a hook. +type HookInfo struct { + Snap *Info + + Name string + Plugs map[string]*PlugInfo } // SecurityTag returns application-specific security tag. @@ -279,6 +291,28 @@ return filepath.Join(dirs.SnapServicesDir, app.SecurityTag()+".socket") } +func copyEnv(in map[string]string) map[string]string { + out := make(map[string]string) + for k, v := range in { + out[k] = v + } + + return out +} + +// Env returns the app specific environment overrides +func (app *AppInfo) Env() []string { + env := []string{} + appEnv := copyEnv(app.Snap.Environment) + for k, v := range app.Environment { + appEnv[k] = v + } + for k, v := range appEnv { + env = append(env, fmt.Sprintf("%s=%s\n", k, v)) + } + return env +} + func infoFromSnapYamlWithSideInfo(meta []byte, si *SideInfo) (*Info, error) { info, err := InfoFromSnapYaml(meta) if err != nil { @@ -297,7 +331,7 @@ snapYamlFn := filepath.Join(MountDir(name, si.Revision), "meta", "snap.yaml") meta, err := ioutil.ReadFile(snapYamlFn) if os.IsNotExist(err) { - return nil, fmt.Errorf("cannot find mounted snap %q at revision %d", name, si.Revision) + return nil, fmt.Errorf("cannot find mounted snap %q at revision %s", name, si.Revision) } if err != nil { return nil, err @@ -308,7 +342,7 @@ // ReadInfoFromSnapFile reads the snap information from the given File // and completes it with the given side-info if this is not nil. -func ReadInfoFromSnapFile(snapf File, si *SideInfo) (*Info, error) { +func ReadInfoFromSnapFile(snapf Container, si *SideInfo) (*Info, error) { meta, err := snapf.ReadFile("meta/snap.yaml") if err != nil { return nil, err @@ -326,3 +360,14 @@ return info, nil } + +// SplitSnapApp will split a string of the form `snap.app` into +// the `snap` and the `app` part. It also deals with the special +// case of snapName == appName. +func SplitSnapApp(snapApp string) (snap, app string) { + l := strings.SplitN(snapApp, ".", 2) + if len(l) < 2 { + return l[0], l[0] + } + return l[0], l[1] +} diff -Nru snapd-2.0.5/snap/info_snap_yaml.go snapd-2.0.8/snap/info_snap_yaml.go --- snapd-2.0.5/snap/info_snap_yaml.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/info_snap_yaml.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ "gopkg.in/yaml.v2" - "github.com/ubuntu-core/snappy/systemd" - "github.com/ubuntu-core/snappy/timeout" + "github.com/snapcore/snapd/systemd" + "github.com/snapcore/snapd/timeout" ) type snapYaml struct { @@ -40,9 +40,13 @@ Summary string `yaml:"summary"` LicenseAgreement string `yaml:"license-agreement,omitempty"` LicenseVersion string `yaml:"license-version,omitempty"` + Epoch string `yaml:"epoch,omitempty"` + Confinement ConfinementType `yaml:"confinement,omitempty"` + Environment map[string]string `yaml:"environment,omitempty"` Plugs map[string]interface{} `yaml:"plugs,omitempty"` Slots map[string]interface{} `yaml:"slots,omitempty"` Apps map[string]appYaml `yaml:"apps,omitempty"` + Hooks map[string]hookYaml `yaml:"hooks,omitempty"` } type plugYaml struct { @@ -74,11 +78,17 @@ BusName string `yaml:"bus-name,omitempty"` + Environment map[string]string `yaml:"environment,omitempty"` + Socket bool `yaml:"socket,omitempty"` ListenStream string `yaml:"listen-stream,omitempty"` SocketMode string `yaml:"socket-mode,omitempty"` } +type hookYaml struct { + PlugNames []string `yaml:"plugs,omitempty"` +} + // InfoFromSnapYaml creates a new info based on the given snap.yaml data func InfoFromSnapYaml(yamlData []byte) (*Info, error) { var y snapYaml @@ -86,7 +96,49 @@ if err != nil { return nil, fmt.Errorf("info failed to parse: %s", err) } - // Defaults + + snap := infoSkeletonFromSnapYaml(y) + setEnvironmentFromSnapYaml(y, snap) + + // Collect top-level definitions of plugs and slots + if err := setPlugsFromSnapYaml(y, snap); err != nil { + return nil, err + } + if err := setSlotsFromSnapYaml(y, snap); err != nil { + return nil, err + } + + // At this point snap.Plugs and snap.Slots only contain globally-declared + // plugs and slots. We're about to change that, but we need to remember the + // global ones for later, so save their names. + globalPlugNames := make([]string, 0, len(snap.Plugs)) + for plugName := range snap.Plugs { + globalPlugNames = append(globalPlugNames, plugName) + } + + globalSlotNames := make([]string, 0, len(snap.Slots)) + for slotName := range snap.Slots { + globalSlotNames = append(globalSlotNames, slotName) + } + + // Collect all apps and hooks + setAppsFromSnapYaml(y, snap) + setHooksFromSnapYaml(y, snap) + + // Bind unbound plugs to all apps and hooks + bindUnboundPlugs(globalPlugNames, snap) + + // Bind unbound slots to all apps + bindUnboundSlots(globalSlotNames, snap) + + // FIXME: validation of the fields + return snap, nil +} + +// infoSkeletonFromSnapYaml initializes an Info without apps, hook, plugs, or +// slots +func infoSkeletonFromSnapYaml(y snapYaml) *Info { + // Prepare defaults architectures := []string{"all"} if len(y.Architectures) != 0 { architectures = y.Architectures @@ -95,7 +147,16 @@ if y.Type != "" { typ = y.Type } - // Construct snap skeleton, without apps, plugs and slots + epoch := "0" + if y.Epoch != "" { + epoch = y.Epoch + } + confinement := StrictConfinement + if y.Confinement != "" { + confinement = y.Confinement + } + + // Construct snap skeleton without apps, hooks, plugs, or slots snap := &Info{ SuggestedName: y.Name, Version: y.Version, @@ -106,16 +167,31 @@ OriginalSummary: y.Summary, LicenseAgreement: y.LicenseAgreement, LicenseVersion: y.LicenseVersion, + Epoch: epoch, + Confinement: confinement, Apps: make(map[string]*AppInfo), + Hooks: make(map[string]*HookInfo), Plugs: make(map[string]*PlugInfo), Slots: make(map[string]*SlotInfo), + Environment: y.Environment, } + sort.Strings(snap.Assumes) - // Collect top-level definitions of plugs + + return snap +} + +func setEnvironmentFromSnapYaml(y snapYaml, snap *Info) { + for k, v := range y.Environment { + snap.Environment[k] = v + } +} + +func setPlugsFromSnapYaml(y snapYaml, snap *Info) error { for name, data := range y.Plugs { iface, label, attrs, err := convertToSlotOrPlugData("plug", name, data) if err != nil { - return nil, err + return err } snap.Plugs[name] = &PlugInfo{ Snap: snap, @@ -127,12 +203,19 @@ if len(y.Apps) > 0 { snap.Plugs[name].Apps = make(map[string]*AppInfo) } + if len(y.Hooks) > 0 { + snap.Plugs[name].Hooks = make(map[string]*HookInfo) + } } - // Collect top-level definitions of slots + + return nil +} + +func setSlotsFromSnapYaml(y snapYaml, snap *Info) error { for name, data := range y.Slots { iface, label, attrs, err := convertToSlotOrPlugData("slot", name, data) if err != nil { - return nil, err + return err } snap.Slots[name] = &SlotInfo{ Snap: snap, @@ -145,6 +228,11 @@ snap.Slots[name].Apps = make(map[string]*AppInfo) } } + + return nil +} + +func setAppsFromSnapYaml(y snapYaml, snap *Info) { for appName, yApp := range y.Apps { // Collect all apps app := &AppInfo{ @@ -160,6 +248,7 @@ SocketMode: yApp.SocketMode, ListenStream: yApp.ListenStream, BusName: yApp.BusName, + Environment: yApp.Environment, } if len(y.Plugs) > 0 || len(yApp.PlugNames) > 0 { app.Plugs = make(map[string]*PlugInfo) @@ -199,16 +288,72 @@ slot.Apps[appName] = app } } - // Bind plugs/slots that are not app-bound to all apps - for plugName, plug := range snap.Plugs { - if len(plug.Apps) == 0 { +} + +func setHooksFromSnapYaml(y snapYaml, snap *Info) { + for hookName, yHook := range y.Hooks { + // Collect all hooks + hook := &HookInfo{ + Snap: snap, + Name: hookName, + } + if len(y.Plugs) > 0 || len(yHook.PlugNames) > 0 { + hook.Plugs = make(map[string]*PlugInfo) + } + snap.Hooks[hookName] = hook + // Bind all plugs/slots listed in this hook + for _, plugName := range yHook.PlugNames { + plug, ok := snap.Plugs[plugName] + if !ok { + // Create implicit plug definitions if required + plug = &PlugInfo{ + Snap: snap, + Name: plugName, + Interface: plugName, + Hooks: make(map[string]*HookInfo), + } + snap.Plugs[plugName] = plug + } else if plug.Hooks == nil { + plug.Hooks = make(map[string]*HookInfo) + } + hook.Plugs[plugName] = plug + plug.Hooks[hookName] = hook + } + } +} + +func bindUnboundPlugs(plugNames []string, snap *Info) error { + for _, plugName := range plugNames { + plug, ok := snap.Plugs[plugName] + if !ok { + return fmt.Errorf("no plug named %q", plugName) + } + + // A plug is considered unbound if it isn't being used by any apps + // or hooks. In which case we bind them to all apps and hooks. + if len(plug.Apps) == 0 && len(plug.Hooks) == 0 { for appName, app := range snap.Apps { app.Plugs[plugName] = plug plug.Apps[appName] = app } + + for hookName, hook := range snap.Hooks { + hook.Plugs[plugName] = plug + plug.Hooks[hookName] = hook + } } } - for slotName, slot := range snap.Slots { + + return nil +} + +func bindUnboundSlots(slotNames []string, snap *Info) error { + for _, slotName := range slotNames { + slot, ok := snap.Slots[slotName] + if !ok { + return fmt.Errorf("no slot named %q", slotName) + } + if len(slot.Apps) == 0 { for appName, app := range snap.Apps { app.Slots[slotName] = slot @@ -216,8 +361,8 @@ } } } - // FIXME: validation of the fields - return snap, nil + + return nil } func convertToSlotOrPlugData(plugOrSlot, name string, data interface{}) (iface, label string, attrs map[string]interface{}, err error) { diff -Nru snapd-2.0.5/snap/info_snap_yaml_test.go snapd-2.0.8/snap/info_snap_yaml_test.go --- snapd-2.0.5/snap/info_snap_yaml_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/info_snap_yaml_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,9 +25,9 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/systemd" - "github.com/ubuntu-core/snappy/timeout" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/systemd" + "github.com/snapcore/snapd/timeout" ) // Hook up check.v1 into the "go test" runner @@ -205,6 +205,44 @@ }) } +func (s *YamlSuite) TestUnmarshalGlobalPlugBoundToOneApp(c *C) { + // NOTE: yaml content cannot use tabs, indent the section with spaces. + info, err := snap.InfoFromSnapYaml([]byte(` +name: snap +plugs: + network-client: +apps: + with-plug: + plugs: [network-client] + without-plug: +`)) + c.Assert(err, IsNil) + c.Check(info.Name(), Equals, "snap") + c.Check(info.Plugs, HasLen, 1) + c.Check(info.Slots, HasLen, 0) + c.Check(info.Apps, HasLen, 2) + + plug := info.Plugs["network-client"] + withPlugApp := info.Apps["with-plug"] + withoutPlugApp := info.Apps["without-plug"] + c.Assert(plug, DeepEquals, &snap.PlugInfo{ + Snap: info, + Name: "network-client", + Interface: "network-client", + Apps: map[string]*snap.AppInfo{withPlugApp.Name: withPlugApp}, + }) + c.Assert(withPlugApp, DeepEquals, &snap.AppInfo{ + Snap: info, + Name: "with-plug", + Plugs: map[string]*snap.PlugInfo{plug.Name: plug}, + }) + c.Assert(withoutPlugApp, DeepEquals, &snap.AppInfo{ + Snap: info, + Name: "without-plug", + Plugs: map[string]*snap.PlugInfo{}, + }) +} + func (s *YamlSuite) TestUnmarshalPlugsExplicitlyDefinedExplicitlyBoundToApps(c *C) { // NOTE: yaml content cannot use tabs, indent the section with spaces. info, err := snap.InfoFromSnapYaml([]byte(` @@ -649,6 +687,212 @@ c.Assert(err, ErrorMatches, `slot "serial" uses reserved attribute "\$baud-rate"`) } +func (s *YamlSuite) TestUnmarshalHook(c *C) { + // NOTE: yaml content cannot use tabs, indent the section with spaces. + info, err := snap.InfoFromSnapYaml([]byte(` +name: snap +hooks: + test-hook: +`)) + c.Assert(err, IsNil) + c.Check(info.Name(), Equals, "snap") + c.Check(info.Plugs, HasLen, 0) + c.Check(info.Slots, HasLen, 0) + c.Check(info.Apps, HasLen, 0) + c.Check(info.Hooks, HasLen, 1) + + hook, ok := info.Hooks["test-hook"] + c.Assert(ok, Equals, true, Commentf("Expected hooks to include 'test-hook'")) + + c.Check(hook, DeepEquals, &snap.HookInfo{ + Snap: info, + Name: "test-hook", + Plugs: nil, + }) +} + +func (s *YamlSuite) TestUnmarshalHookWithPlug(c *C) { + // NOTE: yaml content cannot use tabs, indent the section with spaces. + info, err := snap.InfoFromSnapYaml([]byte(` +name: snap +hooks: + test-hook: + plugs: [test-plug] +`)) + c.Assert(err, IsNil) + c.Check(info.Name(), Equals, "snap") + c.Check(info.Plugs, HasLen, 1) + c.Check(info.Slots, HasLen, 0) + c.Check(info.Apps, HasLen, 0) + c.Check(info.Hooks, HasLen, 1) + + plug, ok := info.Plugs["test-plug"] + c.Assert(ok, Equals, true, Commentf("Expected plugs to include 'test-plug'")) + hook, ok := info.Hooks["test-hook"] + c.Assert(ok, Equals, true, Commentf("Expected hooks to include 'test-hook'")) + + c.Check(plug, DeepEquals, &snap.PlugInfo{ + Snap: info, + Name: "test-plug", + Interface: "test-plug", + Hooks: map[string]*snap.HookInfo{hook.Name: hook}, + }) + c.Check(hook, DeepEquals, &snap.HookInfo{ + Snap: info, + Name: "test-hook", + Plugs: map[string]*snap.PlugInfo{plug.Name: plug}, + }) +} + +func (s *YamlSuite) TestUnmarshalGlobalPlugsBindToHooks(c *C) { + // NOTE: yaml content cannot use tabs, indent the section with spaces. + info, err := snap.InfoFromSnapYaml([]byte(` +name: snap +plugs: + test-plug: +hooks: + test-hook: +`)) + c.Assert(err, IsNil) + c.Check(info.Name(), Equals, "snap") + c.Check(info.Plugs, HasLen, 1) + c.Check(info.Slots, HasLen, 0) + c.Check(info.Apps, HasLen, 0) + c.Check(info.Hooks, HasLen, 1) + + plug, ok := info.Plugs["test-plug"] + c.Assert(ok, Equals, true, Commentf("Expected plugs to include 'test-plug'")) + hook, ok := info.Hooks["test-hook"] + c.Assert(ok, Equals, true, Commentf("Expected hooks to include 'test-hook'")) + + c.Check(plug, DeepEquals, &snap.PlugInfo{ + Snap: info, + Name: "test-plug", + Interface: "test-plug", + Hooks: map[string]*snap.HookInfo{hook.Name: hook}, + }) + c.Check(hook, DeepEquals, &snap.HookInfo{ + Snap: info, + Name: "test-hook", + Plugs: map[string]*snap.PlugInfo{plug.Name: plug}, + }) +} + +func (s *YamlSuite) TestUnmarshalGlobalPlugBoundToOneHook(c *C) { + // NOTE: yaml content cannot use tabs, indent the section with spaces. + info, err := snap.InfoFromSnapYaml([]byte(` +name: snap +plugs: + test-plug: +hooks: + with-plug: + plugs: [test-plug] + without-plug: +`)) + c.Assert(err, IsNil) + c.Check(info.Name(), Equals, "snap") + c.Check(info.Plugs, HasLen, 1) + c.Check(info.Slots, HasLen, 0) + c.Check(info.Apps, HasLen, 0) + c.Check(info.Hooks, HasLen, 2) + + plug := info.Plugs["test-plug"] + withPlugHook := info.Hooks["with-plug"] + withoutPlugHook := info.Hooks["without-plug"] + c.Assert(plug, DeepEquals, &snap.PlugInfo{ + Snap: info, + Name: "test-plug", + Interface: "test-plug", + Hooks: map[string]*snap.HookInfo{withPlugHook.Name: withPlugHook}, + }) + c.Assert(withPlugHook, DeepEquals, &snap.HookInfo{ + Snap: info, + Name: "with-plug", + Plugs: map[string]*snap.PlugInfo{plug.Name: plug}, + }) + c.Assert(withoutPlugHook, DeepEquals, &snap.HookInfo{ + Snap: info, + Name: "without-plug", + Plugs: map[string]*snap.PlugInfo{}, + }) +} + +func (s *YamlSuite) TestUnmarshalExplicitGlobalPlugBoundToHook(c *C) { + // NOTE: yaml content cannot use tabs, indent the section with spaces. + info, err := snap.InfoFromSnapYaml([]byte(` +name: snap +plugs: + test-plug: test-interface +hooks: + test-hook: + plugs: ["test-plug"] +`)) + c.Assert(err, IsNil) + c.Check(info.Name(), Equals, "snap") + c.Check(info.Plugs, HasLen, 1) + c.Check(info.Slots, HasLen, 0) + c.Check(info.Apps, HasLen, 0) + c.Check(info.Hooks, HasLen, 1) + + plug, ok := info.Plugs["test-plug"] + c.Assert(ok, Equals, true, Commentf("Expected plugs to include 'test-plug'")) + hook, ok := info.Hooks["test-hook"] + c.Assert(ok, Equals, true, Commentf("Expected hooks to include 'test-hook'")) + + c.Check(plug, DeepEquals, &snap.PlugInfo{ + Snap: info, + Name: "test-plug", + Interface: "test-interface", + Hooks: map[string]*snap.HookInfo{hook.Name: hook}, + }) + c.Check(hook, DeepEquals, &snap.HookInfo{ + Snap: info, + Name: "test-hook", + Plugs: map[string]*snap.PlugInfo{plug.Name: plug}, + }) +} + +func (s *YamlSuite) TestUnmarshalGlobalPlugBoundToHookNotApp(c *C) { + // NOTE: yaml content cannot use tabs, indent the section with spaces. + info, err := snap.InfoFromSnapYaml([]byte(` +name: snap +plugs: + test-plug: +hooks: + test-hook: + plugs: [test-plug] +apps: + test-app: +`)) + c.Assert(err, IsNil) + c.Check(info.Name(), Equals, "snap") + c.Check(info.Plugs, HasLen, 1) + c.Check(info.Slots, HasLen, 0) + c.Check(info.Apps, HasLen, 1) + c.Check(info.Hooks, HasLen, 1) + + plug := info.Plugs["test-plug"] + hook := info.Hooks["test-hook"] + app := info.Apps["test-app"] + c.Assert(plug, DeepEquals, &snap.PlugInfo{ + Snap: info, + Name: "test-plug", + Interface: "test-plug", + Apps: map[string]*snap.AppInfo{}, + Hooks: map[string]*snap.HookInfo{hook.Name: hook}, + }) + c.Assert(hook, DeepEquals, &snap.HookInfo{ + Snap: info, + Name: "test-hook", + Plugs: map[string]*snap.PlugInfo{plug.Name: plug}, + }) + c.Assert(app, DeepEquals, &snap.AppInfo{ + Snap: info, + Name: "test-app", + Plugs: map[string]*snap.PlugInfo{}, + }) +} + func (s *YamlSuite) TestUnmarshalComplexExample(c *C) { // NOTE: yaml content cannot use tabs, indent the section with spaces. info, err := snap.InfoFromSnapYaml([]byte(` @@ -656,6 +900,8 @@ version: 1.2 summary: foo app type: app +epoch: 1* +confinement: devmode description: | Foo provides useful services apps: @@ -666,6 +912,9 @@ foo: command: fooctl plugs: [foo-socket] +hooks: + test-hook: + plugs: [foo-socket] plugs: foo-socket: interface: socket @@ -684,6 +933,8 @@ c.Check(info.Name(), Equals, "foo") c.Check(info.Version, Equals, "1.2") c.Check(info.Type, Equals, snap.TypeApp) + c.Check(info.Epoch, Equals, "1*") + c.Check(info.Confinement, Equals, snap.DevmodeConfinement) c.Check(info.Summary(), Equals, "foo app") c.Check(info.Description(), Equals, "Foo provides useful services\n") c.Check(info.Apps, HasLen, 2) @@ -695,6 +946,7 @@ app1 := info.Apps["daemon"] app2 := info.Apps["foo"] + hook := info.Hooks["test-hook"] plug1 := info.Plugs["network"] plug2 := info.Plugs["network-bind"] plug3 := info.Plugs["foo-socket"] @@ -728,6 +980,15 @@ c.Check(app2.Slots, DeepEquals, map[string]*snap.SlotInfo{ slot2.Name: slot2}) + // hook1 has two plugs ("foo-socket", "logging"). The plug "logging" is + // global while "foo-socket" is hook-bound. + + c.Assert(hook, NotNil) + c.Check(hook.Snap, Equals, info) + c.Check(hook.Name, Equals, "test-hook") + c.Check(hook.Plugs, DeepEquals, map[string]*snap.PlugInfo{ + plug3.Name: plug3, plug4.Name: plug4}) + // plug1 ("network") is implicitly defined and app-bound to "daemon" c.Assert(plug1, Not(IsNil)) @@ -803,6 +1064,24 @@ c.Assert(info.Type, Equals, snap.TypeApp) } +func (s *YamlSuite) TestSnapYamlEpochDefault(c *C) { + y := []byte(`name: binary +version: 1.0 +`) + info, err := snap.InfoFromSnapYaml(y) + c.Assert(err, IsNil) + c.Assert(info.Epoch, Equals, "0") +} + +func (s *YamlSuite) TestSnapYamlConfinementDefault(c *C) { + y := []byte(`name: binary +version: 1.0 +`) + info, err := snap.InfoFromSnapYaml(y) + c.Assert(err, IsNil) + c.Assert(info.Confinement, Equals, snap.StrictConfinement) +} + func (s *YamlSuite) TestSnapYamlMultipleArchitecturesParsing(c *C) { y := []byte(`name: binary version: 1.0 @@ -921,3 +1200,37 @@ }, }) } + +func (s *YamlSuite) TestSnapYamlGlobalEnvironment(c *C) { + y := []byte(` +name: foo +version: 1.0 +environment: + foo: bar + baz: boom +`) + info, err := snap.InfoFromSnapYaml(y) + c.Assert(err, IsNil) + c.Assert(info.Environment, DeepEquals, map[string]string{ + "foo": "bar", + "baz": "boom", + }) +} + +func (s *YamlSuite) TestSnapYamlPerAppEnvironment(c *C) { + y := []byte(` +name: foo +version: 1.0 +apps: + foo: + environment: + k1: v1 + k2: v2 +`) + info, err := snap.InfoFromSnapYaml(y) + c.Assert(err, IsNil) + c.Assert(info.Apps["foo"].Environment, DeepEquals, map[string]string{ + "k1": "v1", + "k2": "v2", + }) +} diff -Nru snapd-2.0.5/snap/info_test.go snapd-2.0.8/snap/info_test.go --- snapd-2.0.5/snap/info_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/info_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,16 +20,18 @@ package snap_test import ( + "fmt" "io/ioutil" "os" "path/filepath" + "sort" . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" - "github.com/ubuntu-core/snappy/snap/squashfs" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/snap/squashfs" ) type infoSuite struct{} @@ -55,14 +57,14 @@ OfficialName: "newname", EditedSummary: "fixed summary", EditedDescription: "fixed desc", - Revision: 1, + Revision: snap.R(1), SnapID: "snapidsnapidsnapidsnapidsnapidsn", } c.Check(info.Name(), Equals, "newname") c.Check(info.Summary(), Equals, "fixed summary") c.Check(info.Description(), Equals, "fixed desc") - c.Check(info.Revision, Equals, 1) + c.Check(info.Revision, Equals, snap.R(1)) c.Check(info.SnapID, Equals, "snapidsnapidsnapidsnapidsnapidsn") } @@ -94,7 +96,7 @@ command: bar-bin -x `)) c.Assert(err, IsNil) - info.Revision = 42 + info.Revision = snap.R(42) c.Check(info.Apps["bar"].LauncherCommand(), Equals, "/usr/bin/ubuntu-core-launcher snap.foo.bar snap.foo.bar /snap/foo/42/bar-bin -x") c.Check(info.Apps["foo"].LauncherCommand(), Equals, "/usr/bin/ubuntu-core-launcher snap.foo.foo snap.foo.foo /snap/foo/42/foo-bin") @@ -109,7 +111,7 @@ ` func (s *infoSuite) TestReadInfo(c *C) { - si := &snap.SideInfo{Revision: 42, EditedSummary: "esummary"} + si := &snap.SideInfo{Revision: snap.R(42), EditedSummary: "esummary"} snapInfo1 := snaptest.MockSnap(c, sampleYaml, si) @@ -117,7 +119,7 @@ c.Assert(err, IsNil) c.Check(snapInfo2.Name(), Equals, "sample") - c.Check(snapInfo2.Revision, Equals, 42) + c.Check(snapInfo2.Revision, Equals, snap.R(42)) c.Check(snapInfo2.Summary(), Equals, "esummary") c.Check(snapInfo2.Apps["app"].Command, Equals, "foo") @@ -146,6 +148,27 @@ func (s *infoSuite) TestReadInfoFromSnapFile(c *C) { yaml := `name: foo version: 1.0 +type: app +epoch: 1* +confinement: devmode` + snapPath := makeTestSnap(c, yaml) + + snapf, err := snap.Open(snapPath) + c.Assert(err, IsNil) + + info, err := snap.ReadInfoFromSnapFile(snapf, nil) + c.Assert(err, IsNil) + c.Check(info.Name(), Equals, "foo") + c.Check(info.Version, Equals, "1.0") + c.Check(info.Type, Equals, snap.TypeApp) + c.Check(info.Revision, Equals, snap.R(0)) + c.Check(info.Epoch, Equals, "1*") + c.Check(info.Confinement, Equals, snap.DevmodeConfinement) +} + +func (s *infoSuite) TestReadInfoFromSnapFileMissingEpoch(c *C) { + yaml := `name: foo +version: 1.0 type: app` snapPath := makeTestSnap(c, yaml) @@ -157,7 +180,8 @@ c.Check(info.Name(), Equals, "foo") c.Check(info.Version, Equals, "1.0") c.Check(info.Type, Equals, snap.TypeApp) - c.Check(info.Revision, Equals, 0) + c.Check(info.Revision, Equals, snap.R(0)) + c.Check(info.Epoch, Equals, "0") // Defaults to 0 } func (s *infoSuite) TestReadInfoFromSnapFileWithSideInfo(c *C) { @@ -171,13 +195,13 @@ info, err := snap.ReadInfoFromSnapFile(snapf, &snap.SideInfo{ OfficialName: "baz", - Revision: 42, + Revision: snap.R(42), }) c.Assert(err, IsNil) c.Check(info.Name(), Equals, "baz") c.Check(info.Version, Equals, "1.0") c.Check(info.Type, Equals, snap.TypeApp) - c.Check(info.Revision, Equals, 42) + c.Check(info.Revision, Equals, snap.R(42)) } func (s *infoSuite) TestReadInfoFromSnapFileValidates(c *C) { @@ -192,3 +216,116 @@ _, err = snap.ReadInfoFromSnapFile(snapf, nil) c.Assert(err, ErrorMatches, "invalid snap name.*") } + +func (s *infoSuite) TestReadInfoFromSnapFileCatchesInvalidType(c *C) { + yaml := `name: foo +version: 1.0 +type: foo` + snapPath := makeTestSnap(c, yaml) + + snapf, err := snap.Open(snapPath) + c.Assert(err, IsNil) + + _, err = snap.ReadInfoFromSnapFile(snapf, nil) + c.Assert(err, ErrorMatches, ".*invalid snap type.*") +} + +func (s *infoSuite) TestReadInfoFromSnapFileCatchesInvalidConfinement(c *C) { + yaml := `name: foo +version: 1.0 +confinement: foo` + snapPath := makeTestSnap(c, yaml) + + snapf, err := snap.Open(snapPath) + c.Assert(err, IsNil) + + _, err = snap.ReadInfoFromSnapFile(snapf, nil) + c.Assert(err, ErrorMatches, ".*invalid confinement type.*") +} + +func (s *infoSuite) TestAppEnvSimple(c *C) { + yaml := `name: foo +version: 1.0 +type: app +environment: + global-k: global-v +apps: + foo: + environment: + app-k: app-v +` + info, err := snap.InfoFromSnapYaml([]byte(yaml)) + c.Assert(err, IsNil) + + env := info.Apps["foo"].Env() + sort.Strings(env) + c.Check(env, DeepEquals, []string{ + "app-k=app-v\n", + "global-k=global-v\n", + }) +} + +func (s *infoSuite) TestAppEnvOverrideGlobal(c *C) { + yaml := `name: foo +version: 1.0 +type: app +environment: + global-k: global-v + global-and-local: global-v +apps: + foo: + environment: + app-k: app-v + global-and-local: local-v +` + info, err := snap.InfoFromSnapYaml([]byte(yaml)) + c.Assert(err, IsNil) + + env := info.Apps["foo"].Env() + sort.Strings(env) + c.Check(env, DeepEquals, []string{ + "app-k=app-v\n", + "global-and-local=local-v\n", + "global-k=global-v\n", + }) +} + +func (s *infoSuite) TestSplitSnapApp(c *C) { + for _, t := range []struct { + in string + out []string + }{ + // normal cases + {"foo.bar", []string{"foo", "bar"}}, + {"foo.bar.baz", []string{"foo", "bar.baz"}}, + // special case, snapName == appName + {"foo", []string{"foo", "foo"}}, + } { + snap, app := snap.SplitSnapApp(t.in) + c.Check([]string{snap, app}, DeepEquals, t.out) + } +} + +func ExampleSpltiSnapApp() { + fmt.Println(snap.SplitSnapApp("hello-world.env")) + // Output: hello-world env +} + +func ExampleSpltiSnapAppShort() { + fmt.Println(snap.SplitSnapApp("hello-world")) + // Output: hello-world hello-world +} + +func (s *infoSuite) TestReadInfoFromSnapFileCatchesInvalidHook(c *C) { + yaml := `name: foo +version: 1.0 +hooks: + abc123:` + snapPath := makeTestSnap(c, yaml) + + snapf, err := snap.Open(snapPath) + c.Assert(err, IsNil) + + _, err = snap.ReadInfoFromSnapFile(snapf, nil) + c.Assert(err, ErrorMatches, ".*invalid hook name.*") +} diff -Nru snapd-2.0.5/snap/kernel_test.go snapd-2.0.8/snap/kernel_test.go --- snapd-2.0.5/snap/kernel_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/kernel_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -25,9 +25,9 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" ) type KernelYamlTestSuite struct { @@ -47,13 +47,13 @@ } func (s *KernelYamlTestSuite) TestReadKernelYamlMissing(c *C) { - info := snaptest.MockSnap(c, mockKernelYaml, &snap.SideInfo{Revision: 42}) + info := snaptest.MockSnap(c, mockKernelYaml, &snap.SideInfo{Revision: snap.R(42)}) _, err := snap.ReadKernelInfo(info) c.Assert(err, ErrorMatches, ".*meta/kernel.yaml: no such file or directory") } func (s *KernelYamlTestSuite) TestReadKernelYamlValid(c *C) { - info := snaptest.MockSnap(c, mockKernelYaml, &snap.SideInfo{Revision: 42}) + info := snaptest.MockSnap(c, mockKernelYaml, &snap.SideInfo{Revision: snap.R(42)}) err := ioutil.WriteFile(filepath.Join(info.MountDir(), "meta", "kernel.yaml"), []byte(`version: 4.2`), 0644) c.Assert(err, IsNil) diff -Nru snapd-2.0.5/snap/revision.go snapd-2.0.8/snap/revision.go --- snapd-2.0.5/snap/revision.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/snap/revision.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,111 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-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 snap + +import ( + "fmt" + "strconv" +) + +// Keep this in sync between snap and client packages. + +type Revision struct { + N int +} + +func (r Revision) String() string { + if r.N == 0 { + return "unset" + } + if r.N < 0 { + return fmt.Sprintf("x%d", -r.N) + } + return strconv.Itoa(int(r.N)) +} + +func (r Revision) Unset() bool { + return r.N == 0 +} + +func (r Revision) Local() bool { + return r.N < 0 +} + +func (r Revision) Store() bool { + return r.N > 0 +} + +func (r Revision) MarshalJSON() ([]byte, error) { + return []byte(`"` + r.String() + `"`), nil +} + +func (r *Revision) UnmarshalJSON(data []byte) error { + if len(data) > 0 && data[0] == '"' && data[len(data)-1] == '"' { + parsed, err := ParseRevision(string(data[1 : len(data)-1])) + if err == nil { + *r = parsed + return nil + } + } else { + n, err := strconv.ParseInt(string(data), 10, 64) + if err == nil { + r.N = int(n) + return nil + } + } + return fmt.Errorf("invalid snap revision: %q", data) +} + +// ParseRevisions returns the representation in r as a revision. +// See R for a function more suitable for hardcoded revisions. +func ParseRevision(s string) (Revision, error) { + if s == "unset" { + return Revision{}, nil + } + if s != "" && s[0] == 'x' { + i, err := strconv.Atoi(s[1:]) + if err == nil && i > 0 { + return Revision{-i}, nil + } + } + i, err := strconv.Atoi(s) + if err == nil && i > 0 { + return Revision{i}, nil + } + return Revision{}, fmt.Errorf("invalid snap revision: %#v", s) +} + +// R returns a Revision given an int or a string. +// Providing an invalid revision type or value causes a runtime panic. +// See ParseRevision for a polite function that does not panic. +func R(r interface{}) Revision { + switch r := r.(type) { + case string: + revision, err := ParseRevision(r) + if err != nil { + panic(err) + } + return revision + case int: + return Revision{r} + default: + panic(fmt.Errorf("cannot use %v (%T) as a snap revision", r, r)) + } +} diff -Nru snapd-2.0.5/snap/revision_test.go snapd-2.0.8/snap/revision_test.go --- snapd-2.0.5/snap/revision_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/snap/revision_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,171 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-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 snap_test + +import ( + "encoding/json" + "strconv" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/snap" +) + +// Keep this in sync between snap and client packages. + +type revisionSuite struct{} + +var _ = Suite(&revisionSuite{}) + +func (s revisionSuite) TestString(c *C) { + c.Assert(snap.R(0).String(), Equals, "unset") + c.Assert(snap.R(10).String(), Equals, "10") + c.Assert(snap.R(-9).String(), Equals, "x9") +} + +func (s revisionSuite) TestUnset(c *C) { + c.Assert(snap.R(0).Unset(), Equals, true) + c.Assert(snap.R(10).Unset(), Equals, false) + c.Assert(snap.R(-9).Unset(), Equals, false) +} + +func (s revisionSuite) TestLocal(c *C) { + c.Assert(snap.R(0).Local(), Equals, false) + c.Assert(snap.R(10).Local(), Equals, false) + c.Assert(snap.R(-9).Local(), Equals, true) +} + +func (s revisionSuite) TestStore(c *C) { + c.Assert(snap.R(0).Store(), Equals, false) + c.Assert(snap.R(10).Store(), Equals, true) + c.Assert(snap.R(-9).Store(), Equals, false) +} + +func (s revisionSuite) TestJSON(c *C) { + for _, n := range []int{0, 10, -9} { + r := snap.R(n) + data, err := json.Marshal(snap.R(n)) + c.Assert(err, IsNil) + c.Assert(string(data), Equals, `"`+r.String()+`"`) + + var got snap.Revision + err = json.Unmarshal(data, &got) + c.Assert(err, IsNil) + c.Assert(got, Equals, r) + + got = snap.Revision{} + err = json.Unmarshal([]byte(strconv.Itoa(r.N)), &got) + c.Assert(err, IsNil) + c.Assert(got, Equals, r) + } +} + +func (s revisionSuite) ParseRevision(c *C) { + type testItem struct { + s string + n int + e string + } + + var tests = []testItem{{ + s: "unset", + n: 0, + }, { + s: "x1", + n: -1, + }, { + s: "1", + n: 1, + }, { + s: "x-1", + e: `invalid snap revision: "x-1"`, + }, { + s: "x0", + e: `invalid snap revision: "x0"`, + }, { + s: "-1", + e: `invalid snap revision: "-1"`, + }, { + s: "0", + e: `invalid snap revision: "0"`, + }} + + for _, test := range tests { + r, err := snap.ParseRevision(test.s) + if test.e != "" { + c.Assert(err.Error(), Equals, test.e) + continue + } + c.Assert(r, Equals, snap.R(test.n)) + } +} + +func (s *revisionSuite) TestR(c *C) { + type testItem struct { + v interface{} + n int + e string + } + + var tests = []testItem{{ + v: 0, + n: 0, + }, { + v: -1, + n: -1, + }, { + v: 1, + n: 1, + }, { + v: "unset", + n: 0, + }, { + v: "x1", + n: -1, + }, { + v: "1", + n: 1, + }, { + v: "x-1", + e: `invalid snap revision: "x-1"`, + }, { + v: "x0", + e: `invalid snap revision: "x0"`, + }, { + v: "-1", + e: `invalid snap revision: "-1"`, + }, { + v: "0", + e: `invalid snap revision: "0"`, + }, { + v: int64(1), + e: `cannot use 1 \(int64\) as a snap revision`, + }} + + for _, test := range tests { + if test.e != "" { + f := func() { snap.R(test.v) } + c.Assert(f, PanicMatches, test.e) + continue + } + + c.Assert(snap.R(test.v), Equals, snap.R(test.n)) + } +} diff -Nru snapd-2.0.5/snap/snapdir/snapdir.go snapd-2.0.8/snap/snapdir/snapdir.go --- snapd-2.0.5/snap/snapdir/snapdir.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/snap/snapdir/snapdir.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,49 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-2015 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 snapdir + +import ( + "io/ioutil" + "os" + "path/filepath" +) + +// SnapDir is the snapdir based snap. +type SnapDir struct { + path string +} + +// Path returns the path of the backing container. +func (s *SnapDir) Path() string { + return s.path +} + +// New returns a new snap directory container. +func New(path string) *SnapDir { + return &SnapDir{path: path} +} + +func (s *SnapDir) Install(targetPath, mountDir string) error { + return os.Symlink(s.path, targetPath) +} + +func (s *SnapDir) ReadFile(file string) (content []byte, err error) { + return ioutil.ReadFile(filepath.Join(s.path, file)) +} diff -Nru snapd-2.0.5/snap/snapdir/snapdir_test.go snapd-2.0.8/snap/snapdir/snapdir_test.go --- snapd-2.0.5/snap/snapdir/snapdir_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.0.8/snap/snapdir/snapdir_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -0,0 +1,63 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-2015 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 snapdir_test + +import ( + "io/ioutil" + "path/filepath" + "testing" + + "github.com/snapcore/snapd/snap/snapdir" + + . "gopkg.in/check.v1" +) + +// Hook up check.v1 into the "go test" runner +func Test(t *testing.T) { TestingT(t) } + +type SnapdirTestSuite struct { +} + +var _ = Suite(&SnapdirTestSuite{}) + +func (s *SnapdirTestSuite) TestReadFile(c *C) { + d := c.MkDir() + needle := []byte(`stuff`) + err := ioutil.WriteFile(filepath.Join(d, "foo"), needle, 0644) + c.Assert(err, IsNil) + + snap := snapdir.New(d) + content, err := snap.ReadFile("foo") + c.Assert(err, IsNil) + c.Assert(content, DeepEquals, needle) +} + +func (s *SnapdirTestSuite) TestInstall(c *C) { + tryBaseDir := c.MkDir() + snap := snapdir.New(tryBaseDir) + + varLibSnapd := c.MkDir() + targetPath := filepath.Join(varLibSnapd, "foo_1.0.snap") + err := snap.Install(targetPath, "unused-mount-dir") + c.Assert(err, IsNil) + symlinkTarget, err := filepath.EvalSymlinks(targetPath) + c.Assert(err, IsNil) + c.Assert(symlinkTarget, Equals, tryBaseDir) +} diff -Nru snapd-2.0.5/snap/snapenv/snapenv.go snapd-2.0.8/snap/snapenv/snapenv.go --- snapd-2.0.5/snap/snapenv/snapenv.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/snapenv/snapenv.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,7 +24,7 @@ "strings" "text/template" - "github.com/ubuntu-core/snappy/logger" + "github.com/snapcore/snapd/logger" ) // MakeMapFromEnvList takes a string list of the form "key=value" diff -Nru snapd-2.0.5/snap/snaptest/build.go snapd-2.0.8/snap/snaptest/build.go --- snapd-2.0.5/snap/snaptest/build.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/snaptest/build.go 2016-06-08 05:58:01.000000000 +0000 @@ -31,9 +31,9 @@ "strings" "syscall" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/squashfs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/squashfs" ) // from click's click.build.ClickBuilderBase, and there from diff -Nru snapd-2.0.5/snap/snaptest/build_test.go snapd-2.0.8/snap/snaptest/build_test.go --- snapd-2.0.5/snap/snaptest/build_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/snaptest/build_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -29,10 +29,10 @@ "strings" "syscall" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/testutil" . "gopkg.in/check.v1" ) diff -Nru snapd-2.0.5/snap/snaptest/snaptest.go snapd-2.0.8/snap/snaptest/snaptest.go --- snapd-2.0.5/snap/snaptest/snaptest.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/snaptest/snaptest.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,7 +27,7 @@ "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/snap" ) // MockSnap puts a snap.yaml file on disk so to mock an installed snap, based on the provided arguments. diff -Nru snapd-2.0.5/snap/snaptest/snaptest_test.go snapd-2.0.8/snap/snaptest/snaptest_test.go --- snapd-2.0.5/snap/snaptest/snaptest_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/snaptest/snaptest_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,9 +26,9 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" ) func TestSnapTest(t *testing.T) { TestingT(t) } @@ -57,11 +57,11 @@ } func (s *snapTestSuite) TestMockSnap(c *C) { - snapInfo := snaptest.MockSnap(c, sampleYaml, &snap.SideInfo{Revision: 42}) + snapInfo := snaptest.MockSnap(c, sampleYaml, &snap.SideInfo{Revision: snap.R(42)}) // Data from YAML is used c.Check(snapInfo.Name(), Equals, "sample") // Data from SideInfo is used - c.Check(snapInfo.Revision, Equals, 42) + c.Check(snapInfo.Revision, Equals, snap.R(42)) // The YAML is placed on disk cont, err := ioutil.ReadFile(filepath.Join(dirs.SnapSnapsDir, "sample", "42", "meta", "snap.yaml")) c.Assert(err, IsNil) diff -Nru snapd-2.0.5/snap/squashfs/squashfs.go snapd-2.0.8/snap/squashfs/squashfs.go --- snapd-2.0.5/snap/squashfs/squashfs.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/squashfs/squashfs.go 2016-06-08 05:58:01.000000000 +0000 @@ -29,7 +29,7 @@ "path/filepath" "strings" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/osutil" ) // Magic is the magic prefix of squashfs snap files. diff -Nru snapd-2.0.5/snap/squashfs/squashfs_test.go snapd-2.0.8/snap/squashfs/squashfs_test.go --- snapd-2.0.5/snap/squashfs/squashfs_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/squashfs/squashfs_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -32,7 +32,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/osutil" ) // Hook up check.v1 into the "go test" runner diff -Nru snapd-2.0.5/snap/types.go snapd-2.0.8/snap/types.go --- snapd-2.0.5/snap/types.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/types.go 2016-06-08 05:58:01.000000000 +0000 @@ -21,6 +21,7 @@ import ( "encoding/json" + "fmt" ) // Type represents the kind of snap (app, core, gadget, os, kernel) @@ -34,11 +35,6 @@ TypeKernel Type = "kernel" ) -// MarshalJSON returns *m as the JSON encoding of m. -func (m Type) MarshalJSON() ([]byte, error) { - return json.Marshal(string(m)) -} - // UnmarshalJSON sets *m to a copy of data. func (m *Type) UnmarshalJSON(data []byte) error { var str string @@ -46,13 +42,75 @@ return err } + return m.fromString(str) +} + +// UnmarshalYAML so ConfinementType implements yaml's Unmarshaler interface +func (m *Type) UnmarshalYAML(unmarshal func(interface{}) error) error { + var str string + if err := unmarshal(&str); err != nil { + return err + } + + return m.fromString(str) +} + +// fromString converts str to Type and sets *m to it if validations pass +func (m *Type) fromString(str string) error { + t := Type(str) + // this is a workaround as the store sends "application" but snappy uses // "app" for TypeApp if str == "application" { - *m = TypeApp - } else { - *m = Type(str) + t = TypeApp + } + + if t != TypeApp && t != TypeGadget && t != TypeOS && t != TypeKernel { + return fmt.Errorf("invalid snap type: %q", str) + } + + *m = t + + return nil +} + +// ConfinementType represents the kind of confinement supported by the snap +// (devmode only, or strict confinement) +type ConfinementType string + +// The various confinement types we support +const ( + DevmodeConfinement ConfinementType = "devmode" + StrictConfinement ConfinementType = "strict" +) + +// UnmarshalJSON sets *confinementType to a copy of data, assuming validation passes +func (confinementType *ConfinementType) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err } + return confinementType.fromString(s) +} + +// UnmarshalYAML so ConfinementType implements yaml's Unmarshaler interface +func (confinementType *ConfinementType) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + if err := unmarshal(&s); err != nil { + return err + } + + return confinementType.fromString(s) +} + +func (confinementType *ConfinementType) fromString(str string) error { + c := ConfinementType(str) + if c != DevmodeConfinement && c != StrictConfinement { + return fmt.Errorf("invalid confinement type: %q", str) + } + + *confinementType = c + return nil } diff -Nru snapd-2.0.5/snap/types_test.go snapd-2.0.8/snap/types_test.go --- snapd-2.0.5/snap/types_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/types_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -21,6 +21,8 @@ import ( "encoding/json" + "fmt" + "gopkg.in/yaml.v2" . "gopkg.in/check.v1" ) @@ -35,7 +37,7 @@ c.Assert(err, NotNil) } -func (s *typeSuite) TestMarshalTypes(c *C) { +func (s *typeSuite) TestJsonMarshalTypes(c *C) { out, err := json.Marshal(TypeApp) c.Assert(err, IsNil) c.Check(string(out), Equals, "\"app\"") @@ -43,9 +45,17 @@ out, err = json.Marshal(TypeGadget) c.Assert(err, IsNil) c.Check(string(out), Equals, "\"gadget\"") + + out, err = json.Marshal(TypeOS) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "\"os\"") + + out, err = json.Marshal(TypeKernel) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "\"kernel\"") } -func (s *typeSuite) TestUnmarshalTypes(c *C) { +func (s *typeSuite) TestJsonUnmarshalTypes(c *C) { var st Type err := json.Unmarshal([]byte("\"application\""), &st) @@ -59,4 +69,136 @@ err = json.Unmarshal([]byte("\"gadget\""), &st) c.Assert(err, IsNil) c.Check(st, Equals, TypeGadget) + + err = json.Unmarshal([]byte("\"os\""), &st) + c.Assert(err, IsNil) + c.Check(st, Equals, TypeOS) + + err = json.Unmarshal([]byte("\"kernel\""), &st) + c.Assert(err, IsNil) + c.Check(st, Equals, TypeKernel) +} + +func (s *typeSuite) TestJsonUnmarshalInvalidTypes(c *C) { + invalidTypes := []string{"foo", "-app", "gadget_"} + var st Type + for _, invalidType := range invalidTypes { + err := json.Unmarshal([]byte(fmt.Sprintf("%q", invalidType)), &st) + c.Assert(err, NotNil, Commentf("Expected '%s' to be an invalid type", invalidType)) + } +} + +func (s *typeSuite) TestYamlMarshalTypes(c *C) { + out, err := yaml.Marshal(TypeApp) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "app\n") + + out, err = yaml.Marshal(TypeGadget) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "gadget\n") + + out, err = yaml.Marshal(TypeOS) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "os\n") + + out, err = yaml.Marshal(TypeKernel) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "kernel\n") +} + +func (s *typeSuite) TestYamlUnmarshalTypes(c *C) { + var st Type + + err := yaml.Unmarshal([]byte("application"), &st) + c.Assert(err, IsNil) + c.Check(st, Equals, TypeApp) + + err = yaml.Unmarshal([]byte("app"), &st) + c.Assert(err, IsNil) + c.Check(st, Equals, TypeApp) + + err = yaml.Unmarshal([]byte("gadget"), &st) + c.Assert(err, IsNil) + c.Check(st, Equals, TypeGadget) + + err = yaml.Unmarshal([]byte("os"), &st) + c.Assert(err, IsNil) + c.Check(st, Equals, TypeOS) + + err = yaml.Unmarshal([]byte("kernel"), &st) + c.Assert(err, IsNil) + c.Check(st, Equals, TypeKernel) +} + +func (s *typeSuite) TestYamlUnmarshalInvalidTypes(c *C) { + invalidTypes := []string{"foo", "-app", "gadget_"} + var st Type + for _, invalidType := range invalidTypes { + err := yaml.Unmarshal([]byte(invalidType), &st) + c.Assert(err, NotNil, Commentf("Expected '%s' to be an invalid type", invalidType)) + } +} + +func (s *typeSuite) TestYamlMarshalConfinementTypes(c *C) { + out, err := yaml.Marshal(DevmodeConfinement) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "devmode\n") + + out, err = yaml.Marshal(StrictConfinement) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "strict\n") +} + +func (s *typeSuite) TestYamlUnmarshalConfinementTypes(c *C) { + var confinementType ConfinementType + err := yaml.Unmarshal([]byte("devmode"), &confinementType) + c.Assert(err, IsNil) + c.Check(confinementType, Equals, DevmodeConfinement) + + err = yaml.Unmarshal([]byte("strict"), &confinementType) + c.Assert(err, IsNil) + c.Check(confinementType, Equals, StrictConfinement) +} + +func (s *typeSuite) TestYamlUnmarshalInvalidConfinementTypes(c *C) { + var invalidConfinementTypes = []string{ + "foo", "strict-", "_devmode", + } + var confinementType ConfinementType + for _, thisConfinementType := range invalidConfinementTypes { + err := yaml.Unmarshal([]byte(thisConfinementType), &confinementType) + c.Assert(err, NotNil, Commentf("Expected '%s' to be an invalid confinement type", thisConfinementType)) + } +} + +func (s *typeSuite) TestJsonMarshalConfinementTypes(c *C) { + out, err := json.Marshal(DevmodeConfinement) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "\"devmode\"") + + out, err = json.Marshal(StrictConfinement) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "\"strict\"") +} + +func (s *typeSuite) TestJsonUnmarshalConfinementTypes(c *C) { + var confinementType ConfinementType + err := json.Unmarshal([]byte("\"devmode\""), &confinementType) + c.Assert(err, IsNil) + c.Check(confinementType, Equals, DevmodeConfinement) + + err = json.Unmarshal([]byte("\"strict\""), &confinementType) + c.Assert(err, IsNil) + c.Check(confinementType, Equals, StrictConfinement) +} + +func (s *typeSuite) TestJsonUnmarshalInvalidConfinementTypes(c *C) { + var invalidConfinementTypes = []string{ + "foo", "strict-", "_devmode", + } + var confinementType ConfinementType + for _, thisConfinementType := range invalidConfinementTypes { + err := json.Unmarshal([]byte(fmt.Sprintf("%q", thisConfinementType)), &confinementType) + c.Assert(err, NotNil, Commentf("Expected '%s' to be an invalid confinement type", thisConfinementType)) + } } diff -Nru snapd-2.0.5/snap/validate.go snapd-2.0.8/snap/validate.go --- snapd-2.0.5/snap/validate.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/validate.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,6 +26,8 @@ // Regular expression describing correct identifiers. var validName = regexp.MustCompile("^[a-z](?:-?[a-z0-9])*$") +var validEpoch = regexp.MustCompile("^(?:0|[1-9][0-9]*[*]?)$") +var validHookName = regexp.MustCompile(`^[a-z](?:-?[a-z])*$`) // ValidateName checks if a string can be used as a snap name. func ValidateName(name string) error { @@ -36,6 +38,24 @@ return nil } +// ValidateEpoch checks if a string can be used as a snap epoch. +func ValidateEpoch(epoch string) error { + valid := validEpoch.MatchString(epoch) + if !valid { + return fmt.Errorf("invalid snap epoch: %q", epoch) + } + return nil +} + +// ValidateHook validates the content of the given HookInfo +func ValidateHook(hook *HookInfo) error { + valid := validHookName.MatchString(hook.Name) + if !valid { + return fmt.Errorf("invalid hook name: %q", hook.Name) + } + return nil +} + // Validate verifies the content in the info. func Validate(info *Info) error { name := info.Name() @@ -47,6 +67,15 @@ return err } + epoch := info.Epoch + if epoch == "" { + return fmt.Errorf("snap epoch cannot be empty") + } + err = ValidateEpoch(epoch) + if err != nil { + return err + } + // validate app entries for _, app := range info.Apps { err := ValidateApp(app) @@ -54,6 +83,14 @@ return err } } + + // validate hook entries + for _, hook := range info.Hooks { + err := ValidateHook(hook) + if err != nil { + return err + } + } return nil } @@ -68,6 +105,7 @@ // appContentWhitelist is the whitelist of legal chars in the "apps" // section of snap.yaml var appContentWhitelist = regexp.MustCompile(`^[A-Za-z0-9/. _#:-]*$`) +var validAppName = regexp.MustCompile("^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$") // ValidateApp verifies the content in the app info. func ValidateApp(app *AppInfo) error { @@ -78,8 +116,13 @@ return fmt.Errorf(`"daemon" field contains invalid value %q`, app.Daemon) } + // Validate app name + if !validAppName.MatchString(app.Name) { + return fmt.Errorf("cannot have %q as app name - use letters, digits, and dash as separator", app.Name) + } + + // Validate the rest of the app info checks := map[string]string{ - "name": app.Name, "command": app.Command, "stop-command": app.StopCommand, "post-stop-command": app.PostStopCommand, diff -Nru snapd-2.0.5/snap/validate_test.go snapd-2.0.8/snap/validate_test.go --- snapd-2.0.5/snap/validate_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snap/validate_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( . "gopkg.in/check.v1" - . "github.com/ubuntu-core/snappy/snap" + . "github.com/snapcore/snapd/snap" ) type ValidateSuite struct{} @@ -61,35 +61,99 @@ } } +func (s *ValidateSuite) TestValidateEpoch(c *C) { + validEpochs := []string{ + "0", "1*", "1", "400*", "1234", + } + for _, epoch := range validEpochs { + err := ValidateEpoch(epoch) + c.Assert(err, IsNil) + } + invalidEpochs := []string{ + "0*", "_", "1-", "1+", "-1", "+1", "-1*", "a", "1a", "1**", + } + for _, epoch := range invalidEpochs { + err := ValidateEpoch(epoch) + c.Assert(err, ErrorMatches, `invalid snap epoch: ".*"`) + } +} + +func (s *ValidateSuite) TestValidateHook(c *C) { + validHooks := []*HookInfo{ + &HookInfo{Name: "a"}, + &HookInfo{Name: "aaa"}, + &HookInfo{Name: "a-a"}, + &HookInfo{Name: "aa-a"}, + &HookInfo{Name: "a-aa"}, + &HookInfo{Name: "a-b-c"}, + } + for _, hook := range validHooks { + err := ValidateHook(hook) + c.Assert(err, IsNil) + } + invalidHooks := []*HookInfo{ + &HookInfo{Name: ""}, + &HookInfo{Name: "a a"}, + &HookInfo{Name: "a--a"}, + &HookInfo{Name: "-a"}, + &HookInfo{Name: "a-"}, + &HookInfo{Name: "0"}, + &HookInfo{Name: "123"}, + &HookInfo{Name: "abc0"}, + &HookInfo{Name: "日本語"}, + } + for _, hook := range invalidHooks { + err := ValidateHook(hook) + c.Assert(err, ErrorMatches, `invalid hook name: ".*"`) + } +} + // ValidateApp +func (s *ValidateSuite) TestValidateAppName(c *C) { + validAppNames := []string{ + "1", "a", "aa", "aaa", "aaaa", "Aa", "aA", "1a", "a1", "1-a", "a-1", + "a-a", "aa-a", "a-aa", "a-b-c", "0a-a", "a-0a", + } + for _, name := range validAppNames { + c.Check(ValidateApp(&AppInfo{Name: name}), IsNil) + } + invalidAppNames := []string{ + "", "-", "--", "a--a", "a-", "a ", " a", "a a", "日本語", "한글", + "ру́сский язы́к", "ໄຂ່​ອີ​ສ​ເຕີ້", ":a", "a:", "a:a", "_a", "a_", "a_a", + } + for _, name := range invalidAppNames { + err := ValidateApp(&AppInfo{Name: name}) + c.Assert(err, ErrorMatches, `cannot have ".*" as app name.*`) + } +} + func (s *ValidateSuite) TestAppWhitelistSimple(c *C) { - c.Check(ValidateApp(&AppInfo{Name: "foo"}), IsNil) - c.Check(ValidateApp(&AppInfo{Command: "foo"}), IsNil) - c.Check(ValidateApp(&AppInfo{StopCommand: "foo"}), IsNil) - c.Check(ValidateApp(&AppInfo{PostStopCommand: "foo"}), IsNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", Command: "foo"}), IsNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", StopCommand: "foo"}), IsNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", PostStopCommand: "foo"}), IsNil) } func (s *ValidateSuite) TestAppWhitelistIllegal(c *C) { c.Check(ValidateApp(&AppInfo{Name: "x\n"}), NotNil) c.Check(ValidateApp(&AppInfo{Name: "test!me"}), NotNil) - c.Check(ValidateApp(&AppInfo{Command: "foo\n"}), NotNil) - c.Check(ValidateApp(&AppInfo{StopCommand: "foo\n"}), NotNil) - c.Check(ValidateApp(&AppInfo{PostStopCommand: "foo\n"}), NotNil) - c.Check(ValidateApp(&AppInfo{SocketMode: "foo\n"}), NotNil) - c.Check(ValidateApp(&AppInfo{ListenStream: "foo\n"}), NotNil) - c.Check(ValidateApp(&AppInfo{BusName: "foo\n"}), NotNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", Command: "foo\n"}), NotNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", StopCommand: "foo\n"}), NotNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", PostStopCommand: "foo\n"}), NotNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", SocketMode: "foo\n"}), NotNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", ListenStream: "foo\n"}), NotNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", BusName: "foo\n"}), NotNil) } func (s *ValidateSuite) TestAppDaemonValue(c *C) { - c.Check(ValidateApp(&AppInfo{Daemon: "oneshot"}), IsNil) - c.Check(ValidateApp(&AppInfo{Daemon: "nono"}), ErrorMatches, `"daemon" field contains invalid value "nono"`) + c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "oneshot"}), IsNil) + c.Check(ValidateApp(&AppInfo{Name: "foo", Daemon: "nono"}), ErrorMatches, `"daemon" field contains invalid value "nono"`) } func (s *ValidateSuite) TestAppWhitelistError(c *C) { - err := ValidateApp(&AppInfo{Name: "x\n"}) + err := ValidateApp(&AppInfo{Name: "foo", Command: "x\n"}) c.Assert(err, NotNil) - c.Check(err.Error(), Equals, `app description field 'name' contains illegal "x\n" (legal: '^[A-Za-z0-9/. _#:-]*$')`) + c.Check(err.Error(), Equals, `app description field 'command' contains illegal "x\n" (legal: '^[A-Za-z0-9/. _#:-]*$')`) } // Validate @@ -140,3 +204,34 @@ err = Validate(info) c.Check(err, ErrorMatches, `snap name cannot be empty`) } + +func (s *ValidateSuite) TestIllegalSnapEpoch(c *C) { + info, err := InfoFromSnapYaml([]byte(`name: foo +version: 1.0 +epoch: 0* +`)) + c.Assert(err, IsNil) + + err = Validate(info) + c.Check(err, ErrorMatches, `invalid snap epoch: "0\*"`) +} + +func (s *ValidateSuite) TestMissingSnapEpochIsOkay(c *C) { + info, err := InfoFromSnapYaml([]byte(`name: foo +version: 1.0 +`)) + c.Assert(err, IsNil) + c.Assert(Validate(info), IsNil) +} + +func (s *ValidateSuite) TestIllegalHookName(c *C) { + info, err := InfoFromSnapYaml([]byte(`name: foo +version: 1.0 +hooks: + abc123: +`)) + c.Assert(err, IsNil) + + err = Validate(info) + c.Check(err, ErrorMatches, `invalid hook name: "abc123"`) +} diff -Nru snapd-2.0.5/snappy/common_test.go snapd-2.0.8/snappy/common_test.go --- snapd-2.0.5/snappy/common_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/common_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -29,9 +29,9 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" ) const ( @@ -71,7 +71,7 @@ if err != nil { return "", err } - info.SideInfo = snap.SideInfo{Revision: revno} + info.SideInfo = snap.SideInfo{Revision: snap.R(revno)} metaDir := filepath.Join(info.MountDir(), "meta") if err := os.MkdirAll(metaDir, 0775); err != nil { @@ -84,7 +84,7 @@ si := snap.SideInfo{ OfficialName: info.Name(), - Revision: revno, + Revision: snap.R(revno), Developer: testDeveloper, Channel: "remote-channel", EditedSummary: "hello in summary", @@ -177,20 +177,20 @@ foo10 := &snap.SideInfo{ OfficialName: "foo", Developer: testDeveloper, - Revision: 100, + Revision: snap.R(100), Channel: "remote-channel", } - info1, err := (&Overlord{}).InstallWithSideInfo(snapPath, foo10, AllowUnauthenticated|AllowGadget, inter) + info1, err := (&Overlord{}).InstallWithSideInfo(snapPath, foo10, LegacyAllowUnauthenticated|LegacyAllowGadget, inter) c.Assert(err, IsNil) snapPath = makeTestSnapPackage(c, snapYamlContent+"version: 2.0") foo20 := &snap.SideInfo{ OfficialName: "foo", Developer: testDeveloper, - Revision: 200, + Revision: snap.R(200), Channel: "remote-channel", } - info2, err := (&Overlord{}).InstallWithSideInfo(snapPath, foo20, AllowUnauthenticated|AllowGadget, inter) + info2, err := (&Overlord{}).InstallWithSideInfo(snapPath, foo20, LegacyAllowUnauthenticated|LegacyAllowGadget, inter) c.Assert(err, IsNil) installed, err := (&Overlord{}).Installed() diff -Nru snapd-2.0.5/snappy/desktop_test.go snapd-2.0.8/snappy/desktop_test.go --- snapd-2.0.5/snappy/desktop_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/desktop_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" ) var desktopAppYaml = ` @@ -62,7 +62,7 @@ c.Assert(string(content), Matches, "(?s).*Name=foo\n.*") // unlink (deactivate) removes it again - err = UnlinkSnap(snap.Info(), nil) + err = unlinkSnap(snap.Info(), nil) c.Assert(err, IsNil) c.Assert(osutil.FileExists(mockDesktopFilePath), Equals, false) } diff -Nru snapd-2.0.5/snappy/errors.go snapd-2.0.8/snappy/errors.go --- snapd-2.0.5/snappy/errors.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/errors.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,7 +24,7 @@ "fmt" "strings" - "github.com/ubuntu-core/snappy/arch" + "github.com/snapcore/snapd/arch" ) var ( diff -Nru snapd-2.0.5/snappy/firstboot.go snapd-2.0.8/snappy/firstboot.go --- snapd-2.0.5/snappy/firstboot.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/firstboot.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,9 +26,9 @@ "os/exec" "path/filepath" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/progress" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/progress" "gopkg.in/yaml.v2" ) diff -Nru snapd-2.0.5/snappy/firstboot_test.go snapd-2.0.8/snappy/firstboot_test.go --- snapd-2.0.5/snappy/firstboot_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/firstboot_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/systemd" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/systemd" ) type FirstBootTestSuite struct { diff -Nru snapd-2.0.5/snappy/gadget.go snapd-2.0.8/snappy/gadget.go --- snapd-2.0.5/snappy/gadget.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/gadget.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2014-2015 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 . - * - */ - -// TODO this should be it's own package, but depends on splitting out -// snap.yaml's - -package snappy - -import ( - "errors" - - "github.com/ubuntu-core/snappy/snap" -) - -// getGadget is a convenience function to not go into the details for -// the business logic for a gadget package in every other function -var getGadget = getGadgetImpl - -func getGadgetImpl() (*snap.Info, error) { - gadgets, _ := ActiveSnapsByType(snap.TypeGadget) - if len(gadgets) == 1 { - return gadgets[0].Info(), nil - } - - return nil, errors.New("no gadget snap") -} diff -Nru snapd-2.0.5/snappy/info.go snapd-2.0.8/snappy/info.go --- snapd-2.0.5/snappy/info.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/info.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,10 +27,10 @@ "gopkg.in/yaml.v2" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" ) const ( @@ -159,7 +159,7 @@ // FindSnapsByNameAndRevision returns the snaps with the name/version in the // given slice of snaps -func FindSnapsByNameAndRevision(needle string, revision int, haystack []*Snap) []*Snap { +func FindSnapsByNameAndRevision(needle string, revision snap.Revision, haystack []*Snap) []*Snap { name, developer := SplitDeveloper(needle) ignorens := developer == "" var found []*Snap @@ -199,14 +199,14 @@ } // manifestPath returns the would be path for the snap manifest. -func manifestPath(name string, revno int) string { - return filepath.Join(dirs.SnapMetaDir, fmt.Sprintf("%s_%d.manifest", name, revno)) +func manifestPath(name string, revno snap.Revision) string { + return filepath.Join(dirs.SnapMetaDir, fmt.Sprintf("%s_%s.manifest", name, revno.String())) } // SaveManifest saves the manifest at the designated location for the snap containing information not in the snap.yaml. func SaveManifest(rsnap *snap.Info) error { - if rsnap.Revision == 0 { - return fmt.Errorf("internal error: should not be storring manifests for sideloaded snaps") + if !rsnap.Revision.Store() { + return fmt.Errorf("internal error: should not be storing manifests for local snaps") } // XXX: we store OfficialName though it may not be the blessed one later diff -Nru snapd-2.0.5/snappy/info_test.go snapd-2.0.8/snappy/info_test.go --- snapd-2.0.5/snappy/info_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/info_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,8 +26,8 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" ) func (s *SnapTestSuite) TestActiveSnapByType(c *C) { @@ -85,7 +85,7 @@ // now remove the channel si := snap.SideInfo{ OfficialName: "app", - Revision: 11, + Revision: snap.R(11), Developer: testDeveloper, Channel: "", EditedSummary: "hello", @@ -165,7 +165,7 @@ c.Assert(ActivateSnap(snap, ag), IsNil) c.Check(PackageNameActive("hello-snap"), Equals, true) - c.Assert(UnlinkSnap(snap.Info(), ag), IsNil) + c.Assert(unlinkSnap(snap.Info(), ag), IsNil) c.Check(PackageNameActive("hello-snap"), Equals, false) } @@ -217,19 +217,19 @@ installed, err := repo.Installed() c.Assert(err, IsNil) - snaps := FindSnapsByNameAndRevision("hello-snap."+testDeveloper, 11, installed) + snaps := FindSnapsByNameAndRevision("hello-snap."+testDeveloper, snap.R(11), installed) c.Check(snaps, HasLen, 1) - snaps = FindSnapsByNameAndRevision("bad-app."+testDeveloper, 11, installed) + snaps = FindSnapsByNameAndRevision("bad-app."+testDeveloper, snap.R(11), installed) c.Check(snaps, HasLen, 0) - snaps = FindSnapsByNameAndRevision("hello-snap.badDeveloper", 11, installed) + snaps = FindSnapsByNameAndRevision("hello-snap.badDeveloper", snap.R(11), installed) c.Check(snaps, HasLen, 0) - snaps = FindSnapsByNameAndRevision("hello-snap."+testDeveloper, 22, installed) + snaps = FindSnapsByNameAndRevision("hello-snap."+testDeveloper, snap.R(22), installed) c.Check(snaps, HasLen, 0) - snaps = FindSnapsByNameAndRevision("hello-snap", 11, installed) + snaps = FindSnapsByNameAndRevision("hello-snap", snap.R(11), installed) c.Check(snaps, HasLen, 1) - snaps = FindSnapsByNameAndRevision("bad-app", 11, installed) + snaps = FindSnapsByNameAndRevision("bad-app", snap.R(11), installed) c.Check(snaps, HasLen, 0) - snaps = FindSnapsByNameAndRevision("hello-snap", 22, installed) + snaps = FindSnapsByNameAndRevision("hello-snap", snap.R(22), installed) c.Check(snaps, HasLen, 0) } diff -Nru snapd-2.0.5/snappy/install.go snapd-2.0.8/snappy/install.go --- snapd-2.0.5/snappy/install.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/install.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,31 +24,31 @@ "os" "sort" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/provisioning" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/store" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/provisioning" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/store" ) // SetupFlags can be used to pass additional flags to the install of a // snap -type InstallFlags uint +type LegacyInstallFlags uint const ( // AllowUnauthenticated allows to install a snap even if it cannot be authenticated - AllowUnauthenticated InstallFlags = 1 << iota + LegacyAllowUnauthenticated LegacyInstallFlags = 1 << iota // InhibitHooks will ensure that the hooks are not run - InhibitHooks + LegacyInhibitHooks // DoInstallGC will ensure that garbage collection is done - DoInstallGC + LegacyDoInstallGC // AllowGadget allows the installation of Gadget packages, this does not affect updates. - AllowGadget - // DeveloperMode will install the snap without confinement - DeveloperMode + LegacyAllowGadget + + // Do not add new flags here! this is all going away soon! just kept alive as long as we may need to quickly patch up u-d-f. + DO_NOT_ADD_NEW_FLAGS_HERE ) -func installRemote(mStore *store.SnapUbuntuStoreRepository, remoteSnap *snap.Info, flags InstallFlags, meter progress.Meter) (string, error) { +func installRemote(mStore *store.SnapUbuntuStoreRepository, remoteSnap *snap.Info, flags LegacyInstallFlags, meter progress.Meter) (string, error) { downloadedSnap, err := mStore.Download(remoteSnap, meter, nil) if err != nil { return "", fmt.Errorf("cannot download %s: %s", remoteSnap.Name(), err) @@ -63,82 +63,12 @@ return localSnap.Name(), nil } -func doUpdate(mStore *store.SnapUbuntuStoreRepository, rsnap *snap.Info, flags InstallFlags, meter progress.Meter) error { - _, err := installRemote(mStore, rsnap, flags, meter) - if err == ErrSideLoaded { - logger.Noticef("Skipping sideloaded package: %s", rsnap.Name()) - return nil - } else if err != nil { - return err - } - - if err := GarbageCollect(rsnap.Name(), flags, meter); err != nil { - return err - } - - return nil -} - -// FIXME: This needs to go (and will go). We will have something -// like: -// remoteSnapType = GetUpdatesFromServer() -// localSnapType = DoUpdate(remoteSnaps) -// ShowUpdates(localSnaps) -// By using the different types (instead of the same interface) -// it will not be possilbe to pass remote snaps into the -// ShowUpdates() output. -// -// -// convertToInstalledSnaps takes a slice of remote snaps that got -// updated and returns the corresponding local snaps -func convertToInstalledSnaps(remoteUpdates []*snap.Info) ([]*Snap, error) { - installed, err := (&Overlord{}).Installed() - if err != nil { - return nil, err - } - - installedUpdates := make([]*Snap, 0, len(remoteUpdates)) - for _, snap := range remoteUpdates { - for _, installed := range installed { - if snap.Name() == installed.Name() && snap.Version == installed.Version() { - installedUpdates = append(installedUpdates, installed) - } - } - } - - return installedUpdates, nil -} - -// snapUpdates identifies which snaps have updates in the store. -func snapUpdates(repo *store.SnapUbuntuStoreRepository) (snaps []*snap.Info, err error) { - // TODO: this should eventually be snap-id based - // NOTE this *will* send .sideload apps to the store. - installed, err := ActiveSnapIterByType(fullNameWithChannel, snap.TypeApp, snap.TypeGadget, snap.TypeOS, snap.TypeKernel) - if err != nil || len(installed) == 0 { - return nil, err - } - - rsnaps, err := repo.Updates(installed, nil) - if err != nil { - return nil, err - } - - for _, rsnap := range rsnaps { - current := ActiveSnapByName(rsnap.Name()) - if current == nil || current.Revision() != rsnap.Revision { - snaps = append(snaps, rsnap) - } - } - - return snaps, nil -} - var storeConfig = (*store.SnapUbuntuStoreConfig)(nil) // TODO: kill this function once fewer places make a store on the fly -// NewConfiguredUbuntuStoreSnapRepository creates a new fully configured store.SnapUbuntuStoreRepository with the store id selected by the gadget. -func NewConfiguredUbuntuStoreSnapRepository() *store.SnapUbuntuStoreRepository { +// newConfiguredUbuntuStoreSnapRepository creates a new fully configured store.SnapUbuntuStoreRepository with the store id selected by the gadget. +func newConfiguredUbuntuStoreSnapRepository() *store.SnapUbuntuStoreRepository { storeID := "" // TODO: set the store-id here from the model information if cand := os.Getenv("UBUNTU_STORE_ID"); cand != "" { @@ -148,75 +78,9 @@ return store.NewUbuntuStoreSnapRepository(storeConfig, storeID) } -// Update updates the selected name -func Update(name string, flags InstallFlags, meter progress.Meter) ([]*Snap, error) { - installed, err := (&Overlord{}).Installed() - if err != nil { - return nil, err - } - cur := FindSnapsByName(name, installed) - if len(cur) != 1 { - return nil, ErrNotInstalled - } - - mStore := NewConfiguredUbuntuStoreSnapRepository() - // zomg :-( - // TODO: query the store for just this package, instead of this - updates, err := snapUpdates(mStore) - if err != nil { - return nil, fmt.Errorf("cannot get updates: %s", err) - } - var update *snap.Info - for _, upd := range updates { - if cur[0].Name() == upd.Name() { - update = upd - break - } - } - if update == nil { - return nil, fmt.Errorf("cannot find any update for %q", name) - } - - if err := doUpdate(mStore, update, flags, meter); err != nil { - return nil, err - } - - installedUpdates, err := convertToInstalledSnaps([]*snap.Info{update}) - if err != nil { - return nil, err - } - - return installedUpdates, nil -} - -// UpdateAll the installed snappy packages, it returns the updated Snaps -// if updates where available and an error and nil if any of the updates -// fail to apply. -func UpdateAll(flags InstallFlags, meter progress.Meter) ([]*Snap, error) { - mStore := NewConfiguredUbuntuStoreSnapRepository() - updates, err := snapUpdates(mStore) - if err != nil { - return nil, err - } - - for _, snap := range updates { - meter.Notify(fmt.Sprintf("Updating %s (%s)", snap.Name(), snap.Version)) - if err := doUpdate(mStore, snap, flags, meter); err != nil { - return nil, err - } - } - - installedUpdates, err := convertToInstalledSnaps(updates) - if err != nil { - return nil, err - } - - return installedUpdates, nil -} - // Install the givens snap names provided via args. This can be local // files or snaps that are queried from the store -func Install(name, channel string, flags InstallFlags, meter progress.Meter) (string, error) { +func Install(name, channel string, flags LegacyInstallFlags, meter progress.Meter) (string, error) { name, err := doInstall(name, channel, flags, meter) if err != nil { return "", err @@ -225,7 +89,7 @@ return name, GarbageCollect(name, flags, meter) } -func doInstall(name, channel string, flags InstallFlags, meter progress.Meter) (snapName string, err error) { +func doInstall(name, channel string, flags LegacyInstallFlags, meter progress.Meter) (snapName string, err error) { defer func() { if err != nil { err = &ErrInstallFailed{Snap: name, OrigErr: err} @@ -237,7 +101,7 @@ // we allow unauthenticated package when in developer // mode if provisioning.InDeveloperMode() { - flags |= AllowUnauthenticated + flags |= LegacyAllowUnauthenticated } snap, err := (&Overlord{}).Install(name, flags, meter) @@ -249,7 +113,7 @@ } // check repos next - mStore := NewConfiguredUbuntuStoreSnapRepository() + mStore := newConfiguredUbuntuStoreSnapRepository() installed, err := (&Overlord{}).Installed() if err != nil { return "", err @@ -274,10 +138,10 @@ // GarbageCollect removes all versions two older than the current active // version, as long as NeedsReboot() is false on all the versions found, and // DoInstallGC is set. -func GarbageCollect(name string, flags InstallFlags, pb progress.Meter) error { +func GarbageCollect(name string, flags LegacyInstallFlags, pb progress.Meter) error { var snaps BySnapVersion - if (flags & DoInstallGC) == 0 { + if (flags & LegacyDoInstallGC) == 0 { return nil } diff -Nru snapd-2.0.5/snappy/install_test.go snapd-2.0.8/snappy/install_test.go --- snapd-2.0.5/snappy/install_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/install_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -31,13 +31,13 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/progress" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/progress" ) func (s *SnapTestSuite) TestInstallInstall(c *C) { snapPath := makeTestSnapPackage(c, "") - name, err := Install(snapPath, "channel", AllowUnauthenticated|DoInstallGC, &progress.NullProgress{}) + name, err := Install(snapPath, "channel", LegacyAllowUnauthenticated|LegacyDoInstallGC, &progress.NullProgress{}) c.Assert(err, IsNil) c.Check(name, Equals, "foo") @@ -51,7 +51,7 @@ func (s *SnapTestSuite) TestInstallNoHook(c *C) { snapPath := makeTestSnapPackage(c, "") - name, err := Install(snapPath, "", AllowUnauthenticated|DoInstallGC|InhibitHooks, &progress.NullProgress{}) + name, err := Install(snapPath, "", LegacyAllowUnauthenticated|LegacyDoInstallGC|LegacyInhibitHooks, &progress.NullProgress{}) c.Assert(err, IsNil) c.Check(name, Equals, "foo") @@ -63,7 +63,7 @@ c.Check(snap.IsActive(), Equals, false) // c.f. TestInstallInstall } -func (s *SnapTestSuite) installThree(c *C, flags InstallFlags) { +func (s *SnapTestSuite) installThree(c *C, flags LegacyInstallFlags) { c.Skip("can't really install 3 separate snap version just through the old snappy.Install interface, they all get revision 0!") dirs.SnapDataHomeGlob = filepath.Join(s.tempdir, "home", "*", "snaps") homeDir := filepath.Join(s.tempdir, "home", "user1", "snaps") @@ -88,7 +88,7 @@ // check that on install we remove all but the two newest package versions func (s *SnapTestSuite) TestClickInstallGCSimple(c *C) { - s.installThree(c, AllowUnauthenticated|DoInstallGC) + s.installThree(c, LegacyAllowUnauthenticated|LegacyDoInstallGC) globs, err := filepath.Glob(filepath.Join(dirs.SnapSnapsDir, "foo", "*")) c.Check(err, IsNil) @@ -111,7 +111,7 @@ // check that if flags does not include DoInstallGC, no gc is done func (s *SnapTestSuite) TestClickInstallGCSuppressed(c *C) { - s.installThree(c, AllowUnauthenticated) + s.installThree(c, LegacyAllowUnauthenticated) globs, err := filepath.Glob(filepath.Join(dirs.SnapSnapsDir, "foo", "*")) c.Assert(err, IsNil) @@ -213,79 +213,3 @@ _, err = Install("hello-snap", "ch", 0, ag) c.Assert(err, ErrorMatches, ".*"+ErrPackageNameAlreadyInstalled.Error()) } - -func (s *SnapTestSuite) TestUpdate(c *C) { - yamlPath, err := makeInstalledMockSnap("name: foo\nversion: 1", 25) - c.Assert(err, IsNil) - makeSnapActive(yamlPath) - installed, err := (&Overlord{}).Installed() - c.Assert(err, IsNil) - c.Assert(installed, HasLen, 1) - c.Assert(ActiveSnapByName("foo"), NotNil) - - snapPackagev2 := makeTestSnapPackage(c, "name: foo\nversion: 2") - - snapR, err := os.Open(snapPackagev2) - c.Assert(err, IsNil) - defer snapR.Close() - - // details - var dlURL, iconURL string - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/search": - io.WriteString(w, `{"_embedded": {"clickindex:package": [{ -"package_name": "foo", -"version": "2", -"revision": 27, -"developer": "`+testDeveloper+`", -"anon_download_url": "`+dlURL+`", -"icon_url": "`+iconURL+`" -}]}}`) - case "/dl": - snapR.Seek(0, 0) - io.Copy(w, snapR) - case "/icon": - fmt.Fprintf(w, "") - default: - panic("unexpected url path: " + r.URL.Path) - } - })) - c.Assert(mockServer, NotNil) - defer mockServer.Close() - - dlURL = mockServer.URL + "/dl" - iconURL = mockServer.URL + "/icon" - - s.storeCfg.SearchURI, err = url.Parse(mockServer.URL + "/search") - c.Assert(err, IsNil) - - // bulk - mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, `[{ - "package_name": "foo", - "version": "2", - "revision": 3, - "origin": "`+testDeveloper+`", - "anon_download_url": "`+dlURL+`", - "download_url": "`+dlURL+`", - "icon_url": "`+iconURL+`" -}]`) - })) - - s.storeCfg.BulkURI, err = url.Parse(mockServer.URL) - c.Assert(err, IsNil) - - c.Assert(mockServer, NotNil) - defer mockServer.Close() - - // the test - updates, err := UpdateAll(0, &progress.NullProgress{}) - c.Assert(err, IsNil) - c.Assert(updates, HasLen, 1) - c.Check(updates[0].Name(), Equals, "foo") - c.Check(updates[0].Version(), Equals, "2") - c.Check(updates[0].Revision(), Equals, 3) - // ensure that we get a "local" snap back - not a remote one - c.Check(updates[0], FitsTypeOf, &Snap{}) -} diff -Nru snapd-2.0.5/snappy/kernel_os.go snapd-2.0.8/snappy/kernel_os.go --- snapd-2.0.5/snappy/kernel_os.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/kernel_os.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,14 +24,14 @@ "os" "os/exec" "path/filepath" - "strconv" "strings" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/partition" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/partition" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) // override in tests @@ -65,7 +65,7 @@ // extractKernelAssets extracts kernel/initrd/dtb data from the given // Snap to a versionized bootloader directory so that the bootloader // can use it. -func extractKernelAssets(s *snap.Info, snapf snap.File, flags InstallFlags, inter progress.Meter) error { +func extractKernelAssets(s *snap.Info, flags LegacyInstallFlags, inter progress.Meter) error { if s.Type != snap.TypeKernel { return fmt.Errorf("cannot extract kernel assets from snap type %q", s.Type) } @@ -122,6 +122,9 @@ // SetNextBoot will schedule the given os or kernel snap to be used in // the next boot func SetNextBoot(s *snap.Info) error { + if release.OnClassic { + return nil + } if s.Type != snap.TypeOS && s.Type != snap.TypeKernel { return nil } @@ -188,14 +191,14 @@ return false } -func nameAndRevnoFromSnap(snap string) (string, int) { - name := strings.Split(snap, "_")[0] - revnoNSuffix := strings.Split(snap, "_")[1] - revno, err := strconv.Atoi(strings.Split(revnoNSuffix, ".snap")[0]) +func nameAndRevnoFromSnap(sn string) (string, snap.Revision) { + name := strings.Split(sn, "_")[0] + revnoNSuffix := strings.Split(sn, "_")[1] + rev, err := snap.ParseRevision(strings.Split(revnoNSuffix, ".snap")[0]) if err != nil { - return "", -1 + return "", snap.Revision{} } - return name, revno + return name, rev } // SyncBoot synchronizes the active kernel and OS snap versions with @@ -206,6 +209,9 @@ // misleading. This code will check what kernel/os booted and set // those versions active. func SyncBoot() error { + if release.OnClassic { + return nil + } bootloader, err := findBootloader() if err != nil { return fmt.Errorf("cannot run SyncBoot: %s", err) @@ -224,7 +230,7 @@ name, revno := nameAndRevnoFromSnap(snap) found := FindSnapsByNameAndRevision(name, revno, installed) if len(found) != 1 { - return fmt.Errorf("cannot SyncBoot, expected 1 snap %q (revno=%d) found %d", snap, revno, len(found)) + return fmt.Errorf("cannot SyncBoot, expected 1 snap %q (revision %s) found %d", snap, revno, len(found)) } if err := overlord.SetActive(found[0], true, nil); err != nil { return fmt.Errorf("cannot SyncBoot, cannot make %s active: %s", found[0].Name(), err) diff -Nru snapd-2.0.5/snappy/kernel_os_test.go snapd-2.0.8/snappy/kernel_os_test.go --- snapd-2.0.5/snappy/kernel_os_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/kernel_os_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,8 +20,11 @@ package snappy import ( - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/partition" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/partition" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" . "gopkg.in/check.v1" ) @@ -43,11 +46,11 @@ func (s *kernelTestSuite) TestNameAndRevnoFromSnap(c *C) { name, revno := nameAndRevnoFromSnap("canonical-pc-linux.canonical_101.snap") c.Check(name, Equals, "canonical-pc-linux.canonical") - c.Check(revno, Equals, 101) + c.Check(revno, Equals, snap.R(101)) name, revno = nameAndRevnoFromSnap("ubuntu-core.canonical_103.snap") c.Check(name, Equals, "ubuntu-core.canonical") - c.Check(revno, Equals, 103) + c.Check(revno, Equals, snap.R(103)) } var kernelYaml = `name: linux @@ -59,6 +62,9 @@ ` func (s *kernelTestSuite) TestSyncBoot(c *C) { + restore := release.MockOnClassic(false) + defer restore() + // make an OS _, err := makeInstalledMockSnap(osYaml+"version: v1", 10) c.Assert(err, IsNil) @@ -77,10 +83,10 @@ c.Assert(err, IsNil) c.Assert(installed, HasLen, 3) // ensure that v2 is the active one - found := FindSnapsByNameAndRevision("linux", 21, installed) + found := FindSnapsByNameAndRevision("linux", snap.R(21), installed) c.Assert(found, HasLen, 1) c.Assert(found[0].Name(), Equals, "linux") - c.Assert(found[0].Revision(), Equals, 21) + c.Assert(found[0].Revision(), Equals, snap.R(21)) c.Assert(found[0].Version(), Equals, "v2") c.Assert(found[0].IsActive(), Equals, true) @@ -102,7 +108,18 @@ found = FindSnapsByNameAndVersion("linux", "v1", installed) c.Assert(found, HasLen, 1) c.Assert(found[0].Name(), Equals, "linux") - c.Assert(found[0].Revision(), Equals, 20) + c.Assert(found[0].Revision(), Equals, snap.R(20)) c.Assert(found[0].Version(), Equals, "v1") c.Assert(found[0].IsActive(), Equals, true) } + +// SetNextBoot should do nothing on classic LP: #1580403 +func (s *kernelTestSuite) TestSetNextBootOnClassic(c *C) { + restore := release.MockOnClassic(true) + defer restore() + + // Create a fake OS snap that we try to update + snapInfo := snaptest.MockSnap(c, "type: os", &snap.SideInfo{Revision: snap.R(42)}) + err := SetNextBoot(snapInfo) + c.Assert(err, IsNil) +} diff -Nru snapd-2.0.5/snappy/overlord.go snapd-2.0.8/snappy/overlord.go --- snapd-2.0.5/snappy/overlord.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/overlord.go 2016-06-08 05:58:01.000000000 +0000 @@ -27,14 +27,14 @@ "strings" "time" - "github.com/ubuntu-core/snappy/arch" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/systemd" - "github.com/ubuntu-core/snappy/wrappers" + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/systemd" + "github.com/snapcore/snapd/wrappers" ) // Overlord is responsible for the overall system state. @@ -61,10 +61,10 @@ return nil } -// CheckSnap ensures that the snap can be installed -func CheckSnap(snapFilePath string, curInfo *snap.Info, flags InstallFlags, meter progress.Meter) error { - allowGadget := (flags & AllowGadget) != 0 - allowUnauth := (flags & AllowUnauthenticated) != 0 +// checkSnap ensures that the snap can be installed +func checkSnap(snapFilePath string, curInfo *snap.Info, flags LegacyInstallFlags, meter progress.Meter) error { + allowGadget := (flags & LegacyAllowGadget) != 0 + allowUnauth := (flags & LegacyAllowUnauthenticated) != 0 // we do not Verify() the package here. This is done earlier in // openSnapFile() to ensure that we do not mount/inspect @@ -89,9 +89,9 @@ // SetupSnap does prepare and mount the snap for further processing // It returns the installed path and an error -func SetupSnap(snapFilePath string, sideInfo *snap.SideInfo, flags InstallFlags, meter progress.Meter) (snap.PlaceInfo, error) { - inhibitHooks := (flags & InhibitHooks) != 0 - allowUnauth := (flags & AllowUnauthenticated) != 0 +func SetupSnap(snapFilePath string, sideInfo *snap.SideInfo, flags LegacyInstallFlags, meter progress.Meter) (snap.PlaceInfo, error) { + inhibitHooks := (flags & LegacyInhibitHooks) != 0 + allowUnauth := (flags & LegacyAllowUnauthenticated) != 0 s, snapf, err := openSnapFile(snapFilePath, allowUnauth, sideInfo) if err != nil { @@ -109,13 +109,13 @@ } // generate the mount unit for the squashfs - if err := addSquashfsMount(s, inhibitHooks, meter); err != nil { + if err := addMountUnit(s, inhibitHooks, meter); err != nil { return s, err } // FIXME: special handling is bad 'mkay if s.Type == snap.TypeKernel { - if err := extractKernelAssets(s, snapf, flags, meter); err != nil { + if err := extractKernelAssets(s, flags, meter); err != nil { return s, fmt.Errorf("cannot install kernel: %s", err) } } @@ -127,7 +127,7 @@ Notify(status string) } -func addSquashfsMount(s *snap.Info, inhibitHooks bool, inter interacter) error { +func addMountUnit(s *snap.Info, noMount bool, inter interacter) error { squashfsPath := stripGlobalRootDir(s.MountFile()) whereDir := stripGlobalRootDir(s.MountDir()) @@ -142,14 +142,14 @@ return err } - if !inhibitHooks { + if !noMount { return sysd.Start(mountUnitName) } return nil } -func removeSquashfsMount(baseDir string, inter interacter) error { +func removeMountUnit(baseDir string, inter interacter) error { sysd := systemd.New(dirs.GlobalRootDir, inter) unit := systemd.MountUnitPath(stripGlobalRootDir(baseDir), "mount") if osutil.FileExists(unit) { @@ -195,7 +195,7 @@ // and can only be used during install right now } -func CopyData(newSnap, oldSnap *snap.Info, flags InstallFlags, meter progress.Meter) error { +func copyData(newSnap, oldSnap *snap.Info, flags LegacyInstallFlags, meter progress.Meter) error { // deal with the old data or // otherwise just create a empty data dir @@ -212,16 +212,16 @@ return copySnapData(oldSnap, newSnap) } -func UndoCopyData(newInfo *snap.Info, flags InstallFlags, meter progress.Meter) { +func undoCopyData(newInfo *snap.Info, flags LegacyInstallFlags, meter progress.Meter) { // XXX we were copying data, assume InhibitHooks was false - if err := RemoveSnapData(newInfo); err != nil { + if err := removeSnapData(newInfo); err != nil { logger.Noticef("When cleaning up data for %s %s: %v", newInfo.Name(), newInfo.Version, err) } } -func GenerateWrappers(s *snap.Info, inter interacter) error { +func generateWrappers(s *snap.Info, inter interacter) error { // add the CLI apps from the snap.yaml if err := wrappers.AddSnapBinaries(s); err != nil { return err @@ -238,10 +238,9 @@ return nil } -// RemoveGeneratedWrappers removes the generated services, binaries, desktop +// removeGeneratedWrappers removes the generated services, binaries, desktop // wrappers -func RemoveGeneratedWrappers(s *snap.Info, inter interacter) error { - +func removeGeneratedWrappers(s *snap.Info, inter interacter) error { err1 := wrappers.RemoveSnapBinaries(s) if err1 != nil { logger.Noticef("Cannot remove binaries for %q: %v", s.Name(), err1) @@ -260,8 +259,7 @@ return firstErr(err1, err2, err3) } -// XXX: would really like not to expose this but used in daemon tests atm -func UpdateCurrentSymlink(info *snap.Info, inter interacter) error { +func updateCurrentSymlink(info *snap.Info, inter interacter) error { mountDir := info.MountDir() currentActiveSymlink := filepath.Join(mountDir, "..", "current") @@ -353,19 +351,19 @@ // security setup was done here! - return LinkSnap(s.Info(), inter) + return linkSnap(s.Info(), inter) } -func LinkSnap(s *snap.Info, inter interacter) error { - if err := GenerateWrappers(s, inter); err != nil { +func linkSnap(s *snap.Info, inter interacter) error { + if err := generateWrappers(s, inter); err != nil { return err } - return UpdateCurrentSymlink(s, inter) + return updateCurrentSymlink(s, inter) } -// UnlinkSnap deactivates the given active snap. -func UnlinkSnap(info *snap.Info, inter interacter) error { +// unlinkSnap deactivates the given active snap. +func unlinkSnap(info *snap.Info, inter interacter) error { mountDir := info.MountDir() currentSymlink := filepath.Join(mountDir, "..", "current") @@ -381,7 +379,7 @@ } // remove generated services, binaries, security policy - err1 := RemoveGeneratedWrappers(info, inter) + err1 := removeGeneratedWrappers(info, inter) // removing security setup move here! @@ -395,7 +393,7 @@ // Install installs the given snap file to the system. // // It returns the local snap file or an error -func (o *Overlord) Install(snapFilePath string, flags InstallFlags, meter progress.Meter) (sp *snap.Info, err error) { +func (o *Overlord) Install(snapFilePath string, flags LegacyInstallFlags, meter progress.Meter) (sp *snap.Info, err error) { return o.InstallWithSideInfo(snapFilePath, nil, flags, meter) } @@ -403,7 +401,7 @@ // considering the provided side info. // // It returns the local snap file or an error -func (o *Overlord) InstallWithSideInfo(snapFilePath string, sideInfo *snap.SideInfo, flags InstallFlags, meter progress.Meter) (sp *snap.Info, err error) { +func (o *Overlord) InstallWithSideInfo(snapFilePath string, sideInfo *snap.SideInfo, flags LegacyInstallFlags, meter progress.Meter) (sp *snap.Info, err error) { var oldInfo *snap.Info if sideInfo != nil { @@ -413,7 +411,7 @@ } } - if err := CheckSnap(snapFilePath, oldInfo, flags, meter); err != nil { + if err := checkSnap(snapFilePath, oldInfo, flags, meter); err != nil { return nil, err } @@ -429,7 +427,7 @@ return nil, err } - allowUnauth := (flags & AllowUnauthenticated) != 0 + allowUnauth := (flags & LegacyAllowUnauthenticated) != 0 newInfo, _, err := openSnapFile(snapFilePath, allowUnauth, sideInfo) if err != nil { return nil, err @@ -438,7 +436,7 @@ // XXX: this is still done for now for this legacy Install to // keep unit tests as they are working and as strawman // behavior for current u-d-f - if newInfo.Revision != 0 { // not sideloaded + if newInfo.Revision.Store() { if err := SaveManifest(newInfo); err != nil { return nil, err } @@ -448,10 +446,10 @@ // we need to stop any services and make the commands unavailable // so that copying data and later activating the new revision // can work - err = UnlinkSnap(oldInfo, meter) + err = unlinkSnap(oldInfo, meter) defer func() { if err != nil { - if err := LinkSnap(oldInfo, meter); err != nil { + if err := linkSnap(oldInfo, meter); err != nil { logger.Noticef("When linking old revision: %v", newInfo.Name(), err) } } @@ -462,10 +460,10 @@ } // deal with the data - err = CopyData(newInfo, oldInfo, flags, meter) + err = copyData(newInfo, oldInfo, flags, meter) defer func() { if err != nil { - UndoCopyData(newInfo, flags, meter) + undoCopyData(newInfo, flags, meter) } }() if err != nil { @@ -474,15 +472,15 @@ // and finally make active - if (flags & InhibitHooks) != 0 { + if (flags & LegacyInhibitHooks) != 0 { // XXX: kill InhibitHooks flag but used by u-d-f atm return newInfo, nil } - err = LinkSnap(newInfo, meter) + err = linkSnap(newInfo, meter) defer func() { if err != nil { - if err := UnlinkSnap(newInfo, meter); err != nil { + if err := unlinkSnap(newInfo, meter); err != nil { logger.Noticef("When unlinking failed new snap revision: %v", newInfo.Name(), err) } } @@ -495,29 +493,18 @@ } // CanInstall checks whether the Snap passes a series of tests required for installation -func canInstall(s *snap.Info, snapf snap.File, curInfo *snap.Info, allowGadget bool, inter interacter) error { +func canInstall(s *snap.Info, snapf snap.Container, curInfo *snap.Info, allowGadget bool, inter interacter) error { // verify we have a valid architecture if !arch.IsSupportedArchitecture(s.Architectures) { return &ErrArchitectureNotSupported{s.Architectures} } - if s.Type == snap.TypeGadget { - if !allowGadget { - if currentGadget, err := getGadget(); err == nil { - if currentGadget.Name() != s.Name() { - return ErrGadgetPackageInstall - } - } else { - // there should always be a gadget package now - return ErrGadgetPackageInstall - } - } - } + // assume allowGadget, this is only used by u-d-f now return nil } -func CanRemove(s *snap.Info, active bool) bool { +func canRemove(s *snap.Info, active bool) bool { // Gadget snaps should not be removed as they are a key // building block for Gadgets. Prunning non active ones // is acceptible. @@ -549,7 +536,7 @@ snapPath := s.MountFile() // this also ensures that the mount unit stops - if err := removeSquashfsMount(mountDir, meter); err != nil { + if err := removeMountUnit(mountDir, meter); err != nil { return err } @@ -579,11 +566,11 @@ // // It returns an error on failure func (o *Overlord) Uninstall(s *Snap, meter progress.Meter) error { - if !CanRemove(s.Info(), s.IsActive()) { + if !canRemove(s.Info(), s.IsActive()) { return ErrPackageNotRemovable } - if err := UnlinkSnap(s.Info(), meter); err != nil && err != ErrSnapNotActive { + if err := unlinkSnap(s.Info(), meter); err != nil && err != ErrSnapNotActive { return err } @@ -591,7 +578,7 @@ return err } - return RemoveSnapData(s.Info()) + return removeSnapData(s.Info()) } // SetActive sets the active state of the given snap @@ -601,14 +588,14 @@ if active { // deactivate current first if current := ActiveSnapByName(s.Name()); current != nil { - if err := UnlinkSnap(current.Info(), meter); err != nil { + if err := unlinkSnap(current.Info(), meter); err != nil { return err } } return ActivateSnap(s, meter) } - return UnlinkSnap(s.Info(), meter) + return unlinkSnap(s.Info(), meter) } // Installed returns the installed snaps from this repository diff -Nru snapd-2.0.5/snappy/overlord_test.go snapd-2.0.8/snappy/overlord_test.go --- snapd-2.0.5/snappy/overlord_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/overlord_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,10 +28,10 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/systemd" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/systemd" ) var helloAppYaml = `name: hello-snap @@ -61,19 +61,19 @@ func (s *SnapTestSuite) TestLocalSnapInstall(c *C) string { snapPath := makeTestSnapPackage(c, "") - // revision will be 0 + // XXX Broken test: revision will be unset snap, err := (&Overlord{}).Install(snapPath, 0, nil) c.Assert(err, IsNil) c.Check(snap.Name(), Equals, "foo") - baseDir := filepath.Join(dirs.SnapSnapsDir, fooComposedName, "0") + baseDir := filepath.Join(dirs.SnapSnapsDir, fooComposedName, "unset") c.Assert(osutil.FileExists(baseDir), Equals, true) snapEntries := listDir(c, filepath.Join(dirs.SnapSnapsDir, fooComposedName)) - c.Check(snapEntries, DeepEquals, []string{"0", "current"}) + c.Check(snapEntries, DeepEquals, []string{"current", "unset"}) snapDataEntries := listDir(c, filepath.Join(dirs.SnapDataDir, fooComposedName)) - c.Check(snapDataEntries, DeepEquals, []string{"0", "common", "current"}) + c.Check(snapDataEntries, DeepEquals, []string{"common", "current", "unset"}) return snapPath } @@ -83,13 +83,13 @@ si := &snap.SideInfo{ OfficialName: "foo", - Revision: 40, + Revision: snap.R(40), } - snap, err := (&Overlord{}).InstallWithSideInfo(snapPath, si, 0, nil) + sn, err := (&Overlord{}).InstallWithSideInfo(snapPath, si, 0, nil) c.Assert(err, IsNil) - c.Check(snap.Name(), Equals, "foo") - c.Check(snap.Revision, Equals, 40) + c.Check(sn.Name(), Equals, "foo") + c.Check(sn.Revision, Equals, snap.R(40)) baseDir := filepath.Join(dirs.SnapSnapsDir, fooComposedName, "40") c.Assert(osutil.FileExists(baseDir), Equals, true) @@ -106,13 +106,13 @@ si := &snap.SideInfo{ OfficialName: "bar", - Revision: 55, + Revision: snap.R(55), } - snap, err := (&Overlord{}).InstallWithSideInfo(snapPath, si, 0, nil) + sn, err := (&Overlord{}).InstallWithSideInfo(snapPath, si, 0, nil) c.Assert(err, IsNil) - c.Check(snap.Name(), Equals, "bar") - c.Check(snap.Revision, Equals, 55) + c.Check(sn.Name(), Equals, "bar") + c.Check(sn.Revision, Equals, snap.R(55)) baseDir := filepath.Join(dirs.SnapSnapsDir, "bar", "55") c.Assert(osutil.FileExists(baseDir), Equals, true) @@ -171,71 +171,25 @@ version: 1.0 type: gadget `) - // revision will be 0 - _, err := (&Overlord{}).Install(snapPath, AllowGadget, nil) - c.Assert(err, IsNil) - - contentFile := filepath.Join(dirs.SnapSnapsDir, "foo", "0", "bin", "foo") - _, err = os.Stat(contentFile) - c.Assert(err, IsNil) -} - -func (s *SnapTestSuite) TestLocalGadgetSnapInstallVariants(c *C) { - snapPath := makeTestSnapPackage(c, `name: foo -version: 1.0 -type: gadget -`) - - foo10 := &snap.SideInfo{ - OfficialName: "foo", - Developer: testDeveloper, - Revision: 100, - Channel: "remote-channel", - } - _, err := (&Overlord{}).InstallWithSideInfo(snapPath, foo10, AllowGadget, nil) + // XXX Broken test: revision will be unset + _, err := (&Overlord{}).Install(snapPath, LegacyAllowGadget, nil) c.Assert(err, IsNil) - contentFile := filepath.Join(dirs.SnapSnapsDir, "foo", "100", "bin", "foo") + contentFile := filepath.Join(dirs.SnapSnapsDir, "foo", "unset", "bin", "foo") _, err = os.Stat(contentFile) c.Assert(err, IsNil) - - // a package update - snapPath = makeTestSnapPackage(c, `name: foo -version: 2.0 -type: gadget -`) - foo20 := &snap.SideInfo{ - OfficialName: "foo", - Developer: testDeveloper, - Revision: 200, - Channel: "remote-channel", - } - _, err = (&Overlord{}).InstallWithSideInfo(snapPath, foo20, 0, nil) - c.Check(err, IsNil) - - // a package name fork, IOW, a different Gadget package. - snapPath = makeTestSnapPackage(c, `name: foo-fork -version: 2.0 -type: gadget -`) - _, err = (&Overlord{}).Install(snapPath, 0, nil) - c.Check(err, Equals, ErrGadgetPackageInstall) - - // this will cause chaos, but let's test if it works - _, err = (&Overlord{}).Install(snapPath, AllowGadget, nil) - c.Check(err, IsNil) } // sideinfos var ( fooSI10 = &snap.SideInfo{ OfficialName: "foo", - Revision: 10, + Revision: snap.R(10), } fooSI20 = &snap.SideInfo{ OfficialName: "foo", - Revision: 20, + Revision: snap.R(20), } ) @@ -243,11 +197,11 @@ snapYamlContent := `name: foo ` snapPath := makeTestSnapPackage(c, snapYamlContent+"version: 1.0") - _, err := (&Overlord{}).InstallWithSideInfo(snapPath, fooSI10, AllowUnauthenticated, nil) + _, err := (&Overlord{}).InstallWithSideInfo(snapPath, fooSI10, LegacyAllowUnauthenticated, nil) c.Assert(err, IsNil) snapPath = makeTestSnapPackage(c, snapYamlContent+"version: 2.0") - _, err = (&Overlord{}).InstallWithSideInfo(snapPath, fooSI20, AllowUnauthenticated, nil) + _, err = (&Overlord{}).InstallWithSideInfo(snapPath, fooSI20, LegacyAllowUnauthenticated, nil) c.Assert(err, IsNil) // ensure v2 is active @@ -260,7 +214,7 @@ c.Assert(snaps[1].IsActive(), Equals, true) // deactivate v2 - err = UnlinkSnap(snaps[1].Info(), nil) + err = unlinkSnap(snaps[1].Info(), nil) // set v1 active err = ActivateSnap(snaps[0], nil) snaps, err = (&Overlord{}).Installed() @@ -272,94 +226,6 @@ } -func (s *SnapTestSuite) TestCopyData(c *C) { - dirs.SnapDataHomeGlob = filepath.Join(s.tempdir, "home", "*", "snap") - homeDir := filepath.Join(s.tempdir, "home", "user1", "snap") - appDir := "foo" - homeData := filepath.Join(homeDir, appDir, "10") - err := os.MkdirAll(homeData, 0755) - c.Assert(err, IsNil) - homeCommonData := filepath.Join(homeDir, appDir, "common") - err = os.MkdirAll(homeCommonData, 0755) - c.Assert(err, IsNil) - - snapYamlContent := `name: foo -` - canaryData := []byte("ni ni ni") - - snapPath := makeTestSnapPackage(c, snapYamlContent+"version: 1.0") - _, err = (&Overlord{}).InstallWithSideInfo(snapPath, fooSI10, AllowUnauthenticated, nil) - c.Assert(err, IsNil) - canaryDataFile := filepath.Join(dirs.SnapDataDir, appDir, "10", "canary.txt") - err = ioutil.WriteFile(canaryDataFile, canaryData, 0644) - c.Assert(err, IsNil) - canaryDataFile = filepath.Join(dirs.SnapDataDir, appDir, "common", "canary.common") - err = ioutil.WriteFile(canaryDataFile, canaryData, 0644) - c.Assert(err, IsNil) - err = ioutil.WriteFile(filepath.Join(homeData, "canary.home"), canaryData, 0644) - c.Assert(err, IsNil) - err = ioutil.WriteFile(filepath.Join(homeCommonData, "canary.common_home"), canaryData, 0644) - c.Assert(err, IsNil) - - snapPath = makeTestSnapPackage(c, snapYamlContent+"version: 2.0") - _, err = (&Overlord{}).InstallWithSideInfo(snapPath, fooSI20, AllowUnauthenticated, nil) - c.Assert(err, IsNil) - newCanaryDataFile := filepath.Join(dirs.SnapDataDir, appDir, "20", "canary.txt") - content, err := ioutil.ReadFile(newCanaryDataFile) - c.Assert(err, IsNil) - c.Assert(content, DeepEquals, canaryData) - - // ensure common data file is still there (even though it didn't get copied) - newCanaryDataFile = filepath.Join(dirs.SnapDataDir, appDir, "common", "canary.common") - content, err = ioutil.ReadFile(newCanaryDataFile) - c.Assert(err, IsNil) - c.Assert(content, DeepEquals, canaryData) - - newCanaryDataFile = filepath.Join(homeDir, appDir, "20", "canary.home") - content, err = ioutil.ReadFile(newCanaryDataFile) - c.Assert(err, IsNil) - c.Assert(content, DeepEquals, canaryData) - - // ensure home common data file is still there (even though it didn't get copied) - newCanaryDataFile = filepath.Join(homeDir, appDir, "common", "canary.common_home") - content, err = ioutil.ReadFile(newCanaryDataFile) - c.Assert(err, IsNil) - c.Assert(content, DeepEquals, canaryData) -} - -// ensure that even with no home dir there is no error and the -// system data gets copied -func (s *SnapTestSuite) TestCopyDataNoUserHomes(c *C) { - // this home dir path does not exist - oldSnapDataHomeGlob := dirs.SnapDataHomeGlob - defer func() { dirs.SnapDataHomeGlob = oldSnapDataHomeGlob }() - dirs.SnapDataHomeGlob = filepath.Join(s.tempdir, "no-such-home", "*", "snap") - - snapYamlContent := `name: foo -` - snapPath := makeTestSnapPackage(c, snapYamlContent+"version: 1.0") - snap, err := (&Overlord{}).InstallWithSideInfo(snapPath, fooSI10, AllowUnauthenticated, nil) - c.Assert(err, IsNil) - canaryDataFile := filepath.Join(snap.DataDir(), "canary.txt") - err = ioutil.WriteFile(canaryDataFile, []byte(""), 0644) - c.Assert(err, IsNil) - canaryDataFile = filepath.Join(snap.CommonDataDir(), "canary.common") - err = ioutil.WriteFile(canaryDataFile, []byte(""), 0644) - c.Assert(err, IsNil) - - snapPath = makeTestSnapPackage(c, snapYamlContent+"version: 2.0") - snap2, err := (&Overlord{}).InstallWithSideInfo(snapPath, fooSI20, AllowUnauthenticated, nil) - c.Assert(err, IsNil) - _, err = os.Stat(filepath.Join(snap2.DataDir(), "canary.txt")) - c.Assert(err, IsNil) - _, err = os.Stat(filepath.Join(snap2.CommonDataDir(), "canary.common")) - c.Assert(err, IsNil) - - // sanity atm - c.Check(snap.DataDir(), Not(Equals), snap2.DataDir()) - c.Check(snap.CommonDataDir(), Equals, snap2.CommonDataDir()) -} - func (s *SnapTestSuite) TestSnappyHandleBinariesOnUpgrade(c *C) { snapYamlContent := `name: foo apps: @@ -367,7 +233,7 @@ command: bin/bar ` snapPath := makeTestSnapPackage(c, snapYamlContent+"version: 1.0") - _, err := (&Overlord{}).InstallWithSideInfo(snapPath, fooSI10, AllowUnauthenticated, nil) + _, err := (&Overlord{}).InstallWithSideInfo(snapPath, fooSI10, LegacyAllowUnauthenticated, nil) c.Assert(err, IsNil) // ensure that the binary wrapper file got generated with the right @@ -380,7 +246,7 @@ // and that it gets updated on upgrade snapPath = makeTestSnapPackage(c, snapYamlContent+"version: 2.0") - _, err = (&Overlord{}).InstallWithSideInfo(snapPath, fooSI20, AllowUnauthenticated, nil) + _, err = (&Overlord{}).InstallWithSideInfo(snapPath, fooSI20, LegacyAllowUnauthenticated, nil) c.Assert(err, IsNil) newSnapBin := filepath.Join(dirs.SnapSnapsDir[len(dirs.GlobalRootDir):], "foo", "20", "bin", "bar") content, err = ioutil.ReadFile(binaryWrapper) @@ -397,12 +263,12 @@ ` si := &snap.SideInfo{ OfficialName: "foo", - Revision: 32, + Revision: snap.R(32), } snapPath := makeTestSnapPackage(c, snapYamlContent+"version: 1.0") - // revision will be 0 - _, err := (&Overlord{}).InstallWithSideInfo(snapPath, si, AllowUnauthenticated, nil) + // XXX Broken test: revision will be unset + _, err := (&Overlord{}).InstallWithSideInfo(snapPath, si, LegacyAllowUnauthenticated, nil) c.Assert(err, IsNil) servicesFile := filepath.Join(dirs.SnapServicesDir, "snap.foo.service.service") @@ -439,7 +305,7 @@ daemon: forking ` snapPath := makeTestSnapPackage(c, snapYamlContent+"version: 1.0") - _, err := (&Overlord{}).Install(snapPath, InhibitHooks, nil) + _, err := (&Overlord{}).Install(snapPath, LegacyInhibitHooks, nil) c.Assert(err, IsNil) c.Assert(allSystemctl, HasLen, 0) @@ -453,8 +319,8 @@ command: bin/bar ` snapPath := makeTestSnapPackage(c, snapYamlContent+"version: 1.0") - // revision will be 0 - _, err := (&Overlord{}).Install(snapPath, AllowUnauthenticated, nil) + // XXX Broken test: revision will be unset + _, err := (&Overlord{}).Install(snapPath, LegacyAllowUnauthenticated, nil) c.Assert(err, IsNil) // ensure that the binary wrapper file go generated with the right @@ -463,7 +329,7 @@ c.Assert(osutil.FileExists(binaryWrapper), Equals, true) // and that it gets removed on remove - snapDir := filepath.Join(dirs.SnapSnapsDir, "foo", "0") + snapDir := filepath.Join(dirs.SnapSnapsDir, "foo", "unset") yamlPath := filepath.Join(snapDir, "meta", "snap.yaml") snap, err := NewInstalledSnap(yamlPath) c.Assert(err, IsNil) @@ -484,7 +350,7 @@ si := &snap.SideInfo{ OfficialName: "bar", - Revision: 55, + Revision: snap.R(55), } _, err := (&Overlord{}).InstallWithSideInfo(snapPath, si, 0, &MockProgressMeter{}) @@ -505,7 +371,7 @@ ` snapPath := makeTestSnapPackage(c, snapYamlContent+"version: 1.0") // Use InstallWithSideInfo, this is just a cheap way to call openSnapFile - snapInfo, err := (&Overlord{}).InstallWithSideInfo(snapPath, fooSI10, AllowUnauthenticated, nil) + snapInfo, err := (&Overlord{}).InstallWithSideInfo(snapPath, fooSI10, LegacyAllowUnauthenticated, nil) c.Assert(err, IsNil) // Ensure that side info is correctly stored diff -Nru snapd-2.0.5/snappy/remove.go snapd-2.0.8/snappy/remove.go --- snapd-2.0.5/snappy/remove.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/remove.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2014-2015 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 snappy - -// RemoveFlags can be used to pass additional flags to the snap removal request -type RemoveFlags uint - -const ( - // DoRemoveGC will ensure that garbage collection is done, unless a - // version is specified. - DoRemoveGC RemoveFlags = 1 << iota -) diff -Nru snapd-2.0.5/snappy/remove_test.go snapd-2.0.8/snappy/remove_test.go --- snapd-2.0.5/snappy/remove_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/remove_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2014-2015 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 snappy - -import ( - . "gopkg.in/check.v1" - - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" -) - -func (s *SnapTestSuite) TestUnlinkSnapActiveVsNotActive(c *C) { - foo1, foo2 := makeTwoTestSnaps(c, snap.TypeApp) - - err := UnlinkSnap(foo2, &progress.NullProgress{}) - c.Assert(err, IsNil) - - err = UnlinkSnap(foo1, &progress.NullProgress{}) - c.Assert(err, Equals, ErrSnapNotActive) -} - -func (s *SnapTestSuite) TestCanRemoveGadget(c *C) { - foo1, foo2 := makeTwoTestSnaps(c, snap.TypeGadget) - - c.Check(CanRemove(foo2, true), Equals, false) - - c.Check(CanRemove(foo1, false), Equals, true) -} diff -Nru snapd-2.0.5/snappy/snapdata.go snapd-2.0.8/snappy/snapdata.go --- snapd-2.0.5/snappy/snapdata.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/snapdata.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,12 +24,12 @@ "os/exec" "path/filepath" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" ) // RemoveSnapData removes the data for the given version of the given snap -func RemoveSnapData(snap *snap.Info) error { +func removeSnapData(snap *snap.Info) error { dirs, err := snapDataDirs(snap) if err != nil { return err @@ -39,7 +39,7 @@ } // RemoveSnapCommonData removes the data common between versions of the given snap -func RemoveSnapCommonData(snap *snap.Info) error { +func removeSnapCommonData(snap *snap.Info) error { dirs, err := snapCommonDataDirs(snap) if err != nil { return err diff -Nru snapd-2.0.5/snappy/snap_file.go snapd-2.0.8/snappy/snap_file.go --- snapd-2.0.5/snappy/snap_file.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/snap_file.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,12 +20,12 @@ package snappy import ( - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/snap" ) // openSnapFile opens a snap blob returning both a snap.Info completed -// with sideInfo (if not nil) and a corresponding snap.File. -func openSnapFile(snapPath string, unsignedOk bool, sideInfo *snap.SideInfo) (*snap.Info, snap.File, error) { +// with sideInfo (if not nil) and a corresponding snap.Container. +func openSnapFile(snapPath string, unsignedOk bool, sideInfo *snap.SideInfo) (*snap.Info, snap.Container, error) { // TODO: what precautions to take if unsignedOk == false ? snapf, err := snap.Open(snapPath) diff -Nru snapd-2.0.5/snappy/snap_local.go snapd-2.0.8/snappy/snap_local.go --- snapd-2.0.5/snappy/snap_local.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/snap_local.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,12 +24,11 @@ "io/ioutil" "os" "path/filepath" - "strconv" "gopkg.in/yaml.v2" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" ) // Snap represents a generic snap type @@ -46,10 +45,9 @@ // XXX: hack the name and revision out of the path for now // snapstate primitives shouldn't need this name := filepath.Base(filepath.Dir(mountDir)) - revnoStr := filepath.Base(mountDir) - revno, err := strconv.Atoi(revnoStr) + revno, err := snap.ParseRevision(filepath.Base(mountDir)) if err != nil { - return nil, fmt.Errorf("broken snap directory path: %q", mountDir) + return nil, fmt.Errorf("broken snap directory path, bad revision: %q", mountDir) } s := &Snap{} @@ -77,7 +75,7 @@ s.info = info - if revno != 0 { + if revno.Store() { mfPath := manifestPath(name, revno) if osutil.FileExists(mfPath) { content, err := ioutil.ReadFile(mfPath) @@ -120,7 +118,7 @@ } // Revision returns the revision -func (s *Snap) Revision() int { +func (s *Snap) Revision() snap.Revision { return s.info.Revision } diff -Nru snapd-2.0.5/snappy/snapp_snapfs_test.go snapd-2.0.8/snappy/snapp_snapfs_test.go --- snapd-2.0.5/snappy/snapp_snapfs_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/snapp_snapfs_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,13 +24,14 @@ "os" "path/filepath" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/partition" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/squashfs" - "github.com/ubuntu-core/snappy/systemd" - "github.com/ubuntu-core/snappy/testutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/partition" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/squashfs" + "github.com/snapcore/snapd/systemd" + "github.com/snapcore/snapd/testutil" . "gopkg.in/check.v1" ) @@ -119,20 +120,20 @@ func (s *SquashfsTestSuite) TestOpenSnapFilebSideInfo(c *C) { snapPkg := makeTestSnapPackage(c, packageHello) - si := snap.SideInfo{OfficialName: "blessed", Revision: 42} + si := snap.SideInfo{OfficialName: "blessed", Revision: snap.R(42)} info, _, err := openSnapFile(snapPkg, true, &si) c.Assert(err, IsNil) // check side info c.Check(info.Name(), Equals, "blessed") - c.Check(info.Revision, Equals, 42) + c.Check(info.Revision, Equals, snap.R(42)) } func (s *SquashfsTestSuite) TestInstallViaSquashfsWorks(c *C) { snapPkg := makeTestSnapPackage(c, packageHello) si := &snap.SideInfo{ OfficialName: "hello-snap", - Revision: 16, + Revision: snap.R(16), } _, err := (&Overlord{}).InstallWithSideInfo(snapPkg, si, 0, &MockProgressMeter{}) c.Assert(err, IsNil) @@ -148,24 +149,24 @@ c.Assert(string(content), Matches, "(?ms).*^What=/var/lib/snapd/snaps/hello-snap_16.snap") } -func (s *SquashfsTestSuite) TestAddSquashfsMount(c *C) { +func (s *SquashfsTestSuite) TestAddMountUnit(c *C) { info := &snap.Info{ SideInfo: snap.SideInfo{ OfficialName: "foo", - Revision: 13, + Revision: snap.R(13), }, Version: "1.1", Architectures: []string{"all"}, } inter := &MockProgressMeter{} - err := addSquashfsMount(info, true, inter) + err := addMountUnit(info, true, inter) c.Assert(err, IsNil) // ensure correct mount unit mount, err := ioutil.ReadFile(filepath.Join(dirs.SnapServicesDir, "snap-foo-13.mount")) c.Assert(err, IsNil) c.Assert(string(mount), Equals, `[Unit] -Description=Squashfs mount unit for foo +Description=Mount unit for foo [Mount] What=/var/lib/snapd/snaps/foo_13.snap @@ -177,17 +178,17 @@ } -func (s *SquashfsTestSuite) TestRemoveSquashfsMountUnit(c *C) { +func (s *SquashfsTestSuite) TestRemoveMountUnit(c *C) { info := &snap.Info{ SideInfo: snap.SideInfo{ OfficialName: "foo", - Revision: 13, + Revision: snap.R(13), }, Version: "1.1", Architectures: []string{"all"}, } inter := &MockProgressMeter{} - err := addSquashfsMount(info, true, inter) + err := addMountUnit(info, true, inter) c.Assert(err, IsNil) // ensure we have the files @@ -195,7 +196,7 @@ c.Assert(osutil.FileExists(p), Equals, true) // now call remove and ensure they are gone - err = removeSquashfsMount(info.MountDir(), inter) + err = removeMountUnit(info.MountDir(), inter) c.Assert(err, IsNil) p = filepath.Join(dirs.SnapServicesDir, "snaps-foo-13.mount") c.Assert(osutil.FileExists(p), Equals, false) @@ -205,7 +206,7 @@ snapPath := makeTestSnapPackage(c, packageHello) si := &snap.SideInfo{ OfficialName: "hello-snap", - Revision: 16, + Revision: snap.R(16), } snap, err := (&Overlord{}).InstallWithSideInfo(snapPath, si, 0, &MockProgressMeter{}) c.Assert(err, IsNil) @@ -230,10 +231,13 @@ ` func (s *SquashfsTestSuite) TestInstallOsSnapUpdatesBootloader(c *C) { + restore := release.MockOnClassic(false) + defer restore() + snapPkg := makeTestSnapPackage(c, packageOS) si := &snap.SideInfo{ OfficialName: "ubuntu-core", - Revision: 160, + Revision: snap.R(160), } _, err := (&Overlord{}).InstallWithSideInfo(snapPkg, si, 0, &MockProgressMeter{}) c.Assert(err, IsNil) @@ -252,6 +256,9 @@ ` func (s *SquashfsTestSuite) TestInstallKernelSnapUpdatesBootloader(c *C) { + restore := release.MockOnClassic(false) + defer restore() + files := [][]string{ {"kernel.img", "I'm a kernel"}, {"initrd.img", "...and I'm an initrd"}, @@ -260,7 +267,7 @@ snapPkg := makeTestSnapPackageWithFiles(c, packageKernel, files) si := &snap.SideInfo{ OfficialName: "ubuntu-kernel", - Revision: 40, + Revision: snap.R(40), } _, err := (&Overlord{}).InstallWithSideInfo(snapPkg, si, 0, &MockProgressMeter{}) c.Assert(err, IsNil) @@ -283,7 +290,7 @@ snapPkg := makeTestSnapPackageWithFiles(c, packageKernel, files) si := &snap.SideInfo{ OfficialName: "ubuntu-kernel", - Revision: 42, + Revision: snap.R(42), } _, err := (&Overlord{}).InstallWithSideInfo(snapPkg, si, 0, &MockProgressMeter{}) c.Assert(err, IsNil) @@ -311,7 +318,7 @@ snapPkg := makeTestSnapPackageWithFiles(c, packageKernel, files) si := &snap.SideInfo{ OfficialName: "ubuntu-kernel", - Revision: 42, + Revision: snap.R(42), } snap, err := (&Overlord{}).InstallWithSideInfo(snapPkg, si, 0, &MockProgressMeter{}) c.Assert(err, IsNil) @@ -341,10 +348,10 @@ func (s *SquashfsTestSuite) TestInstallKernelSnapUnpacksKernelErrors(c *C) { snapPkg := makeTestSnapPackage(c, packageHello) - snap, snapf, err := openSnapFile(snapPkg, true, nil) + snap, _, err := openSnapFile(snapPkg, true, nil) c.Assert(err, IsNil) - err = extractKernelAssets(snap, snapf, 0, nil) + err = extractKernelAssets(snap, 0, nil) c.Assert(err, ErrorMatches, `cannot extract kernel assets from snap type "app"`) } diff -Nru snapd-2.0.5/snappy/snapp_test.go snapd-2.0.8/snappy/snapp_test.go --- snapd-2.0.5/snappy/snapp_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/snapp_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,20 +22,19 @@ import ( "fmt" "io" - "io/ioutil" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" - "github.com/ubuntu-core/snappy/arch" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/policy" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snapenv" - "github.com/ubuntu-core/snappy/store" - "github.com/ubuntu-core/snappy/systemd" + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/policy" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snapenv" + "github.com/snapcore/snapd/store" + "github.com/snapcore/snapd/systemd" . "gopkg.in/check.v1" ) @@ -100,17 +99,17 @@ snapYaml, err := makeInstalledMockSnap("", 15) c.Assert(err, IsNil) - snap, err := NewInstalledSnap(snapYaml) + sn, err := NewInstalledSnap(snapYaml) c.Assert(err, IsNil) - c.Assert(snap, NotNil) - c.Check(snap.Name(), Equals, "hello-snap") - c.Check(snap.Version(), Equals, "1.10") - c.Check(snap.IsActive(), Equals, false) - c.Check(snap.Info().Summary(), Equals, "hello in summary") - c.Check(snap.Info().Description(), Equals, "Hello...") - c.Check(snap.Info().Revision, Equals, 15) + c.Assert(sn, NotNil) + c.Check(sn.Name(), Equals, "hello-snap") + c.Check(sn.Version(), Equals, "1.10") + c.Check(sn.IsActive(), Equals, false) + c.Check(sn.Info().Summary(), Equals, "hello in summary") + c.Check(sn.Info().Description(), Equals, "Hello...") + c.Check(sn.Info().Revision, Equals, snap.R(15)) - mountDir := snap.Info().MountDir() + mountDir := sn.Info().MountDir() _, err = os.Stat(mountDir) c.Assert(err, IsNil) @@ -152,80 +151,6 @@ funkyAppName = "8nzc1x4iim2xj1g2ul64" ) -/* acquired via: -curl -s --data-binary '{"name":["8nzc1x4iim2xj1g2ul64.chipaca"]}' -H 'content-type: application/json' https://search.apps.ubuntu.com/api/v1/click-metadata -*/ -const MockUpdatesJSON = `[ - { - "status": "Published", - "name": "8nzc1x4iim2xj1g2ul64.chipaca", - "package_name": "8nzc1x4iim2xj1g2ul64", - "origin": "chipaca", - "changelog": "", - "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/04/hello.svg_Dlrd3L4.png", - "title": "Returns for store credit only.", - "binary_filesize": 65375, - "anon_download_url": "https://public.apps.ubuntu.com/anon/download/chipaca/8nzc1x4iim2xj1g2ul64.chipaca/8nzc1x4iim2xj1g2ul64.chipaca_42_all.snap", - "allow_unauthenticated": true, - "revision": 3, - "version": "42", - "download_url": "https://public.apps.ubuntu.com/download/chipaca/8nzc1x4iim2xj1g2ul64.chipaca/8nzc1x4iim2xj1g2ul64.chipaca_42_all.snap", - "download_sha512": "5364253e4a988f4f5c04380086d542f410455b97d48cc6c69ca2a5877d8aef2a6b2b2f83ec4f688cae61ebc8a6bf2cdbd4dbd8f743f0522fc76540429b79df42" - } -]` - -func mockActiveSnapIterByType(mockSnaps []string) { - ActiveSnapIterByType = func(f func(*snap.Info) string, snapTs ...snap.Type) (res []string, err error) { - return mockSnaps, nil - } -} - -func (s *SnapTestSuite) TestUbuntuStoreRepositoryUpdates(c *C) { - mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - jsonReq, err := ioutil.ReadAll(r.Body) - c.Assert(err, IsNil) - c.Assert(string(jsonReq), Equals, `{"name":["`+funkyAppName+`"]}`) - io.WriteString(w, MockUpdatesJSON) - })) - - c.Assert(mockServer, NotNil) - defer mockServer.Close() - - var err error - s.storeCfg.BulkURI, err = url.Parse(mockServer.URL + "/updates/") - c.Assert(err, IsNil) - repo := store.NewUbuntuStoreSnapRepository(s.storeCfg, "") - c.Assert(repo, NotNil) - - // override the real ActiveSnapIterByType to return our - // mock data - mockActiveSnapIterByType([]string{funkyAppName}) - - // the actual test - results, err := snapUpdates(repo) - c.Assert(err, IsNil) - c.Assert(results, HasLen, 1) - c.Assert(results[0].Name(), Equals, funkyAppName) - c.Assert(results[0].Revision, Equals, 3) - c.Assert(results[0].Version, Equals, "42") -} - -func (s *SnapTestSuite) TestUbuntuStoreRepositoryUpdatesNoSnaps(c *C) { - - var err error - s.storeCfg.SearchURI, err = url.Parse("https://some-uri") - c.Assert(err, IsNil) - repo := store.NewUbuntuStoreSnapRepository(s.storeCfg, "") - c.Assert(repo, NotNil) - - mockActiveSnapIterByType([]string{}) - - // the actual test - results, err := snapUpdates(repo) - c.Assert(err, IsNil) - c.Assert(results, HasLen, 0) -} - func (s *SnapTestSuite) TestMakeConfigEnv(c *C) { yamlFile, err := makeInstalledMockSnap("", 11) c.Assert(err, IsNil) @@ -266,7 +191,7 @@ r := &snap.Info{} r.OfficialName = "foo" - r.Revision = 42 + r.Revision = snap.R(42) r.Developer = "bar" r.EditedDescription = "this is a description" r.Version = "1.0" @@ -287,7 +212,7 @@ c.Assert(err, IsNil) c.Assert(installed, HasLen, 1) - c.Check(installed[0].Info().Revision, Equals, 42) + c.Check(installed[0].Info().Revision, Equals, snap.R(42)) c.Check(installed[0].Developer(), Equals, "bar") c.Check(installed[0].Info().Description(), Equals, "this is a description") diff -Nru snapd-2.0.5/snappy/sort.go snapd-2.0.8/snappy/sort.go --- snapd-2.0.5/snappy/sort.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/sort.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,7 +24,7 @@ "strconv" "strings" - "github.com/ubuntu-core/snappy/logger" + "github.com/snapcore/snapd/logger" ) const ( diff -Nru snapd-2.0.5/snappy/sort_test.go snapd-2.0.8/snappy/sort_test.go --- snapd-2.0.5/snappy/sort_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/sort_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,7 +24,7 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/snap" ) type SortTestSuite struct { diff -Nru snapd-2.0.5/snappy/undo_test.go snapd-2.0.8/snappy/undo_test.go --- snapd-2.0.5/snappy/undo_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/undo_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,18 +20,17 @@ package snappy import ( - "io/ioutil" "os" "path/filepath" . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/partition" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/systemd" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/partition" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/systemd" ) type undoTestSuite struct { @@ -63,7 +62,7 @@ si := snap.SideInfo{ OfficialName: "hello-snap", - Revision: 14, + Revision: snap.R(14), } minInfo, err := SetupSnap(snapPath, &si, 0, &s.meter) @@ -99,7 +98,7 @@ si := snap.SideInfo{ OfficialName: "kernel-snap", - Revision: 140, + Revision: snap.R(140), } instDir, err := SetupSnap(snapPath, &si, 0, &s.meter) @@ -112,125 +111,3 @@ l, _ = filepath.Glob(filepath.Join(bootloader.Dir(), "*")) c.Assert(l, HasLen, 0) } - -func (s *undoTestSuite) TestUndoForCopyData(c *C) { - v1, err := makeInstalledMockSnap(`name: hello -version: 1.0`, 11) - - c.Assert(err, IsNil) - makeSnapActive(v1) - // add some data - datadir := filepath.Join(dirs.SnapDataDir, "hello/11") - subdir := filepath.Join(datadir, "random-subdir") - err = os.MkdirAll(subdir, 0755) - c.Assert(err, IsNil) - err = ioutil.WriteFile(filepath.Join(subdir, "random-file"), nil, 0644) - c.Assert(err, IsNil) - - // pretend we install a new version - v2, err := makeInstalledMockSnap(`name: hello -version: 2.0`, 12) - - c.Assert(err, IsNil) - - sn1, err := NewInstalledSnap(v1) - c.Assert(err, IsNil) - - sn, err := NewInstalledSnap(v2) - c.Assert(err, IsNil) - - // copy data - err = CopyData(sn.Info(), sn1.Info(), 0, &s.meter) - c.Assert(err, IsNil) - v2data := filepath.Join(dirs.SnapDataDir, "hello/12") - l, _ := filepath.Glob(filepath.Join(v2data, "*")) - c.Assert(l, HasLen, 1) - - UndoCopyData(sn.Info(), 0, &s.meter) - l, _ = filepath.Glob(filepath.Join(v2data, "*")) - c.Assert(l, HasLen, 0) - -} - -func (s *undoTestSuite) TestUndoForGenerateWrappers(c *C) { - yaml, err := makeInstalledMockSnap(`name: hello -version: 1.0 -apps: - bin: - command: bin - svc: - command: svc - daemon: simple -`, 11) - - c.Assert(err, IsNil) - - sn, err := NewInstalledSnap(yaml) - c.Assert(err, IsNil) - - err = GenerateWrappers(sn.Info(), &s.meter) - c.Assert(err, IsNil) - - l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*")) - c.Assert(err, IsNil) - c.Assert(l, HasLen, 1) - l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service")) - c.Assert(err, IsNil) - c.Assert(l, HasLen, 1) - - // undo via remove - err = RemoveGeneratedWrappers(sn.Info(), &s.meter) - l, err = filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*")) - c.Assert(err, IsNil) - c.Assert(l, HasLen, 0) - l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service")) - c.Assert(err, IsNil) - c.Assert(l, HasLen, 0) -} - -func (s *undoTestSuite) TestUndoForUpdateCurrentSymlink(c *C) { - v1yaml, err := makeInstalledMockSnap(`name: hello -version: 1.0 -`, 11) - - c.Assert(err, IsNil) - makeSnapActive(v1yaml) - - v2yaml, err := makeInstalledMockSnap(`name: hello -version: 2.0 -`, 22) - - c.Assert(err, IsNil) - - v1, err := NewInstalledSnap(v1yaml) - c.Assert(err, IsNil) - v2, err := NewInstalledSnap(v2yaml) - c.Assert(err, IsNil) - - err = UpdateCurrentSymlink(v2.Info(), &s.meter) - c.Assert(err, IsNil) - - v1MountDir := v1.Info().MountDir() - v2MountDir := v2.Info().MountDir() - v2DataDir := v2.Info().DataDir() - currentActiveSymlink := filepath.Join(v2MountDir, "..", "current") - currentActiveDir, err := filepath.EvalSymlinks(currentActiveSymlink) - c.Assert(err, IsNil) - c.Assert(currentActiveDir, Equals, v2MountDir) - - currentDataSymlink := filepath.Join(filepath.Dir(v2DataDir), "current") - currentDataDir, err := filepath.EvalSymlinks(currentDataSymlink) - c.Assert(err, IsNil) - c.Assert(currentDataDir, Matches, `.*/22`) - - // undo is just update again - err = UpdateCurrentSymlink(v1.Info(), &s.meter) - currentActiveDir, err = filepath.EvalSymlinks(currentActiveSymlink) - c.Assert(err, IsNil) - c.Assert(currentActiveDir, Equals, v1MountDir) - - currentDataDir, err = filepath.EvalSymlinks(currentDataSymlink) - c.Assert(err, IsNil) - c.Assert(currentDataDir, Matches, `.*/11`) - -} diff -Nru snapd-2.0.5/snappy/utils.go snapd-2.0.8/snappy/utils.go --- snapd-2.0.5/snappy/utils.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/snappy/utils.go 2016-06-08 05:58:01.000000000 +0000 @@ -23,9 +23,10 @@ "fmt" "os" - "github.com/ubuntu-core/snappy/arch" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/snap/snapenv" + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snapenv" ) // takes a directory and removes the global root, this is needed @@ -47,21 +48,21 @@ // The returned environment contains additional SNAP_* variables that // are required when calling a meta/hook/ script and that will override // any already existing SNAP_* variables in os.Environment() -func makeSnapHookEnv(snap *Snap) (env []string) { +func makeSnapHookEnv(sn *Snap) (env []string) { desc := struct { SnapName string SnapArch string SnapPath string Version string - Revision int + Revision snap.Revision UdevAppName string }{ - snap.Name(), + sn.Name(), arch.UbuntuArchitecture(), - snap.Info().MountDir(), - snap.Version(), - snap.Revision(), - snap.Name(), + sn.Info().MountDir(), + sn.Version(), + sn.Revision(), + sn.Name(), } vars := snapenv.GetBasicSnapEnvVars(desc) diff -Nru snapd-2.0.5/store/details.go snapd-2.0.8/store/details.go --- snapd-2.0.5/store/details.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/store/details.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,7 +20,7 @@ package store import ( - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/snap" ) // snapDetails encapsulates the data sent to us from the store. @@ -42,7 +42,7 @@ Prices map[string]float64 `json:"prices,omitempty"` Publisher string `json:"publisher,omitempty"` RatingsAverage float64 `json:"ratings_average,omitempty"` - Revision int `json:"revision"` + Revision snap.Revision `json:"revision"` SnapID string `json:"snap_id"` SupportURL string `json:"support_url"` Title string `json:"title"` @@ -51,6 +51,7 @@ // FIXME: the store should return "developer" to us instead of // origin - Developer string `json:"origin" yaml:"origin"` - Private bool `json:"private" yaml:"private"` + Developer string `json:"origin" yaml:"origin"` + Private bool `json:"private" yaml:"private"` + Confinement string `json:"confinement" yaml:"confinement"` } diff -Nru snapd-2.0.5/store/store.go snapd-2.0.8/store/store.go --- snapd-2.0.5/store/store.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/store/store.go 2016-06-08 05:58:01.000000000 +0000 @@ -34,12 +34,12 @@ "strings" "sync" - "github.com/ubuntu-core/snappy/arch" - "github.com/ubuntu-core/snappy/asserts" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/release" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) // TODO: better/shorter names are probably in order once fewer legacy places are using this @@ -56,6 +56,7 @@ info.Architectures = d.Architectures info.Type = d.Type info.Version = d.Version + info.Epoch = "0" info.OfficialName = d.Name info.SnapID = d.SnapID info.Revision = d.Revision @@ -167,7 +168,7 @@ v.Set("fields", strings.Join(getStructFields(snapDetails{}), ",")) defaultConfig.SearchURI.RawQuery = v.Encode() - defaultConfig.BulkURI, err = storeBaseURI.Parse("click-metadata") + defaultConfig.BulkURI, err = storeBaseURI.Parse("metadata") if err != nil { panic(err) } @@ -212,20 +213,20 @@ } // small helper that sets the correct http headers for the ubuntu store -func (s *SnapUbuntuStoreRepository) applyUbuntuStoreHeaders(req *http.Request, accept string, auther Authenticator) { +func (s *SnapUbuntuStoreRepository) setUbuntuStoreHeaders(req *http.Request, channel string, auther Authenticator) { if auther != nil { auther.Authenticate(req) } - if accept == "" { - accept = "application/hal+json" - } - req.Header.Set("Accept", accept) - + req.Header.Set("Accept", "application/hal+json,application/json") req.Header.Set("X-Ubuntu-Architecture", string(arch.UbuntuArchitecture())) req.Header.Set("X-Ubuntu-Release", release.Series) req.Header.Set("X-Ubuntu-Wire-Protocol", UbuntuCoreWireProtocol) + if channel != "" { + req.Header.Set("X-Ubuntu-Device-Channel", channel) + } + if s.storeID != "" { req.Header.Set("X-Ubuntu-Store", s.storeID) } @@ -295,8 +296,7 @@ return nil, err } - s.applyUbuntuStoreHeaders(req, "", auther) - req.Header.Set("X-Ubuntu-Device-Channel", channel) + s.setUbuntuStoreHeaders(req, channel, auther) resp, err := s.client.Do(req) if err != nil { @@ -424,8 +424,7 @@ } // set headers - s.applyUbuntuStoreHeaders(req, "", auther) - req.Header.Set("X-Ubuntu-Device-Channel", channel) + s.setUbuntuStoreHeaders(req, channel, auther) resp, err := s.client.Do(req) if err != nil { @@ -476,9 +475,9 @@ } -// FindSnaps finds (installable) snaps from the store, matching the +// Find finds (installable) snaps from the store, matching the // given search term. -func (s *SnapUbuntuStoreRepository) FindSnaps(searchTerm string, channel string, auther Authenticator) ([]*snap.Info, error) { +func (s *SnapUbuntuStoreRepository) Find(searchTerm string, channel string, auther Authenticator) ([]*snap.Info, error) { if channel == "" { channel = "stable" } @@ -494,8 +493,7 @@ } // set headers - s.applyUbuntuStoreHeaders(req, "", auther) - req.Header.Set("X-Ubuntu-Device-Channel", channel) + s.setUbuntuStoreHeaders(req, channel, auther) resp, err := s.client.Do(req) if err != nil { @@ -533,11 +531,73 @@ return snaps, nil } -// Updates returns the available updates for a list of snap identified by fullname with channel. -func (s *SnapUbuntuStoreRepository) Updates(installed []string, auther Authenticator) (snaps []*snap.Info, err error) { - // XXX: uses obsolete end point! +// RefreshCandidate contains information for the store about the currently +// installed snap so that the store can decide what update we should see +type RefreshCandidate struct { + SnapID string + Revision snap.Revision + Epoch string + DevMode bool + + // the desired channel + Channel string +} + +// the exact bits that we need to send to the store +type currentSnapJson struct { + SnapID string `json:"snap_id"` + Channel string `json:"channel"` + Revision int `json:"revision,omitempty"` + Epoch string `json:"epoch"` + + // The store expects a "confinement" value {"strict", "devmode"}. + // We map this accordingly from our devmode bool, we do not + // use the value of the current snap as we are interested in the + // users intention, not the actual value of the snap itself. + Confinement snap.ConfinementType `json:"confinement"` +} + +type metadataWrapper struct { + Snaps []currentSnapJson `json:"snaps"` + Fields []string `json:"fields"` +} + +// ListRefresh returns the available updates for a list of snap identified by fullname with channel. +func (s *SnapUbuntuStoreRepository) ListRefresh(installed []*RefreshCandidate, auther Authenticator) (snaps []*snap.Info, err error) { + + candidateMap := map[string]*RefreshCandidate{} + currentSnaps := make([]currentSnapJson, 0, len(installed)) + for _, cs := range installed { + revision := cs.Revision.N + if !cs.Revision.Store() { + revision = 0 + } + // the store gets confused if we send snaps without a snapid + // (like local ones) + if cs.SnapID == "" { + continue + } - jsonData, err := json.Marshal(map[string][]string{"name": installed}) + confinement := snap.StrictConfinement + if cs.DevMode { + confinement = snap.DevmodeConfinement + } + + currentSnaps = append(currentSnaps, currentSnapJson{ + SnapID: cs.SnapID, + Channel: cs.Channel, + Confinement: confinement, + Epoch: cs.Epoch, + Revision: revision, + }) + candidateMap[cs.SnapID] = cs + } + + // build input for the updates endpoint + jsonData, err := json.Marshal(metadataWrapper{ + Snaps: currentSnaps, + Fields: []string{"snap_id", "package_name", "revision", "version", "download_url"}, + }) if err != nil { return nil, err } @@ -549,7 +609,7 @@ // set headers // the updates call is a special snowflake right now // (see LP: #1427155) - s.applyUbuntuStoreHeaders(req, "application/json", auther) + s.setUbuntuStoreHeaders(req, "", auther) resp, err := s.client.Do(req) if err != nil { @@ -557,15 +617,20 @@ } defer resp.Body.Close() - var updateData []snapDetails + var updateData searchResults dec := json.NewDecoder(resp.Body) if err := dec.Decode(&updateData); err != nil { return nil, err } - res := make([]*snap.Info, len(updateData)) - for i, rsnap := range updateData { - res[i] = infoFromRemote(rsnap) + res := make([]*snap.Info, 0, len(updateData.Payload.Packages)) + for _, rsnap := range updateData.Payload.Packages { + // the store also gives us identical revisions, filter those + // out, we are not interested + if rsnap.Revision == candidateMap[rsnap.SnapID].Revision { + continue + } + res = append(res, infoFromRemote(rsnap)) } s.checkStoreResponse(resp) @@ -600,7 +665,7 @@ if err != nil { return "", err } - s.applyUbuntuStoreHeaders(req, "", auther) + s.setUbuntuStoreHeaders(req, "", auther) if err := download(remoteSnap.Name(), w, req, pbar); err != nil { return "", err diff -Nru snapd-2.0.5/store/store_test.go snapd-2.0.8/store/store_test.go --- snapd-2.0.5/store/store_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/store/store_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -33,12 +33,12 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/asserts" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" ) type remoteRepoTestSuite struct { @@ -172,15 +172,15 @@ req, err := http.NewRequest("GET", "http://example.com", nil) c.Assert(err, IsNil) - t.store.applyUbuntuStoreHeaders(req, "", nil) + t.store.setUbuntuStoreHeaders(req, "", nil) - c.Assert(req.Header.Get("X-Ubuntu-Release"), Equals, "16") - c.Check(req.Header.Get("Accept"), Equals, "application/hal+json") + c.Check(req.Header.Get("X-Ubuntu-Release"), Equals, "16") + c.Check(req.Header.Get("X-Ubuntu-Device-Channel"), Equals, "") - t.store.applyUbuntuStoreHeaders(req, "application/json", nil) + t.store.setUbuntuStoreHeaders(req, "chan", nil) - c.Check(req.Header.Get("Accept"), Equals, "application/json") - c.Assert(req.Header.Get("Authorization"), Equals, "") + c.Check(req.Header.Get("Authorization"), Equals, "") + c.Check(req.Header.Get("X-Ubuntu-Device-Channel"), Equals, "chan") } const ( @@ -331,7 +331,7 @@ c.Assert(err, IsNil) c.Check(result.Name(), Equals, "hello-world") c.Check(result.Architectures, DeepEquals, []string{"all"}) - c.Check(result.Revision, Equals, 25) + c.Check(result.Revision, Equals, snap.R(25)) c.Check(result.SnapID, Equals, helloWorldSnapID) c.Check(result.Developer, Equals, "canonical") c.Check(result.Version, Equals, "6.0") @@ -343,8 +343,13 @@ c.Assert(result.Prices, DeepEquals, map[string]float64{"USD": 1.23}) c.Check(result.MustBuy, Equals, true) + // Make sure the epoch (currently not sent by the store) defaults to "0" + c.Check(result.Epoch, Equals, "0") + c.Check(repo.SuggestedCurrency(), Equals, "GBP") c.Check(result.Private, Equals, true) + + c.Check(snap.Validate(result), IsNil) } func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetailsSetsAuth(c *C) { @@ -558,7 +563,7 @@ repo := NewUbuntuStoreSnapRepository(&cfg, "") c.Assert(repo, NotNil) - snaps, err := repo.FindSnaps("hello", "", nil) + snaps, err := repo.Find("hello", "", nil) c.Assert(err, IsNil) c.Assert(snaps, HasLen, 1) c.Check(snaps[0].Name(), Equals, "hello-world") @@ -583,7 +588,7 @@ repo := NewUbuntuStoreSnapRepository(&cfg, "") c.Assert(repo, NotNil) - snaps, err := repo.FindSnaps("hello", "", nil) + snaps, err := repo.Find("hello", "", nil) c.Check(err, ErrorMatches, `received an unexpected http response code \(418 I'm a teapot\) when trying to search via "http://[^?]+\?q=hello"`) c.Check(snaps, HasLen, 0) } @@ -605,7 +610,7 @@ repo := NewUbuntuStoreSnapRepository(&cfg, "") c.Assert(repo, NotNil) - snaps, err := repo.FindSnaps("hello", "", nil) + snaps, err := repo.Find("hello", "", nil) c.Check(err, ErrorMatches, `received an unexpected content type \("text/plain[^"]+"\) when trying to search via "http://[^?]+\?q=hello"`) c.Check(snaps, HasLen, 0) } @@ -628,7 +633,7 @@ repo := NewUbuntuStoreSnapRepository(&cfg, "") c.Assert(repo, NotNil) - snaps, err := repo.FindSnaps("hello", "", nil) + snaps, err := repo.Find("hello", "", nil) c.Check(err, ErrorMatches, `cannot decode reply \(got invalid character.*\) when trying to search via "http://[^?]+\?q=hello"`) c.Check(snaps, HasLen, 0) } @@ -669,7 +674,7 @@ c.Assert(repo, NotNil) authenticator := &fakeAuthenticator{} - snaps, err := repo.FindSnaps("foo", "", authenticator) + snaps, err := repo.Find("foo", "", authenticator) c.Assert(err, IsNil) c.Assert(snaps, HasLen, 1) c.Check(snaps[0].SnapID, Equals, helloWorldSnapID) @@ -713,7 +718,7 @@ c.Assert(repo, NotNil) authenticator := &fakeAuthenticator{} - snaps, err := repo.FindSnaps("foo", "", authenticator) + snaps, err := repo.Find("foo", "", authenticator) c.Assert(err, IsNil) // Check that we log an error. @@ -727,33 +732,47 @@ } /* acquired via: -curl -s --data-binary '{"name":["8nzc1x4iim2xj1g2ul64.chipaca"]}' -H 'content-type: application/json' https://search.apps.ubuntu.com/api/v1/click-metadata +(against staging "hello-world"): +$ $ curl -s --data-binary '{"snaps":[{"snap_id":"JtwEnisYi8Mmk51vNLZPSOwSOFLwGdhs","channel":"stable","revision":6}],"fields":["snap_id","package_name","revision","version","download_url"]}' -H 'content-type: application/json' https://search.apps.staging.ubuntu.com/api/v1/metadata|python -m json.tool + +(against production "hello-world") +$ curl -s --data-binary '{"snaps":[{"snap_id":"buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ","channel":"stable","revision":25,"confinement":"strict"}],"fields":["snap_id","package_name","revision","version","download_url"]}' -H 'content-type: application/json' https://search.apps.ubuntu.com/api/v1/metadata */ -const MockUpdatesJSON = `[ - { - "status": "Published", - "name": "8nzc1x4iim2xj1g2ul64.chipaca", - "package_name": "8nzc1x4iim2xj1g2ul64", - "snap_id": "1e21e12ex4iim2xj1g2ul6f12f1", - "origin": "chipaca", - "changelog": "", - "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/04/hello.svg_Dlrd3L4.png", - "title": "Returns for store credit only.", - "binary_filesize": 65375, - "anon_download_url": "https://public.apps.ubuntu.com/anon/download/chipaca/8nzc1x4iim2xj1g2ul64.chipaca/8nzc1x4iim2xj1g2ul64.chipaca_42_all.snap", - "allow_unauthenticated": true, - "revision": 3, - "version": "42", - "download_url": "https://public.apps.ubuntu.com/download/chipaca/8nzc1x4iim2xj1g2ul64.chipaca/8nzc1x4iim2xj1g2ul64.chipaca_42_all.snap", - "download_sha512": "5364253e4a988f4f5c04380086d542f410455b97d48cc6c69ca2a5877d8aef2a6b2b2f83ec4f688cae61ebc8a6bf2cdbd4dbd8f743f0522fc76540429b79df42" +var MockUpdatesJSON = fmt.Sprintf(` +{ + "_embedded": { + "clickindex:package": [ + { + "_links": { + "self": { + "href": "https://search.apps.staging.ubuntu.com/api/v1/package/%[1]s" + } + }, + "download_url": "https://public.apps.staging.ubuntu.com/download-snap/%[1].snap", + "package_name": "hello-world", + "revision": 6, + "snap_id": "%[1]s", + "version": "16.04-1" + } + ] + }, + "_links": { + "curies": [ + { + "href": "https://wiki.ubuntu.com/AppStore/Interfaces/ClickPackageIndex#reltype_{rel}", + "name": "clickindex", + "templated": true + } + ] } -]` +} +`, helloWorldSnapID) -func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryUpdates(c *C) { +func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryListRefresh(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { jsonReq, err := ioutil.ReadAll(r.Body) c.Assert(err, IsNil) - c.Assert(string(jsonReq), Equals, `{"name":["`+funkyAppName+`"]}`) + c.Assert(string(jsonReq), Equals, `{"snaps":[{"snap_id":"`+helloWorldSnapID+`","channel":"stable","revision":1,"epoch":"0","confinement":"strict"}],"fields":["snap_id","package_name","revision","version","download_url"]}`) io.WriteString(w, MockUpdatesJSON) })) @@ -769,12 +788,52 @@ repo := NewUbuntuStoreSnapRepository(&cfg, "") c.Assert(repo, NotNil) - results, err := repo.Updates([]string{funkyAppName}, nil) + results, err := repo.ListRefresh([]*RefreshCandidate{ + { + SnapID: helloWorldSnapID, + Channel: "stable", + Revision: snap.R(1), + Epoch: "0", + DevMode: false, + }, + }, nil) c.Assert(err, IsNil) c.Assert(results, HasLen, 1) - c.Assert(results[0].Name(), Equals, funkyAppName) - c.Assert(results[0].Revision, Equals, 3) - c.Assert(results[0].Version, Equals, "42") + c.Assert(results[0].Name(), Equals, "hello-world") + c.Assert(results[0].Revision, Equals, snap.R(6)) + c.Assert(results[0].Version, Equals, "16.04-1") +} + +func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryUpdateNotSendLocalRevs(c *C) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + jsonReq, err := ioutil.ReadAll(r.Body) + c.Assert(err, IsNil) + c.Assert(string(jsonReq), Equals, `{"snaps":[{"snap_id":"`+helloWorldSnapID+`","channel":"stable","epoch":"0","confinement":"devmode"}],"fields":["snap_id","package_name","revision","version","download_url"]}`) + io.WriteString(w, MockUpdatesJSON) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + var err error + bulkURI, err := url.Parse(mockServer.URL + "/updates/") + c.Assert(err, IsNil) + cfg := SnapUbuntuStoreConfig{ + BulkURI: bulkURI, + } + repo := NewUbuntuStoreSnapRepository(&cfg, "") + c.Assert(repo, NotNil) + + _, err = repo.ListRefresh([]*RefreshCandidate{ + { + SnapID: helloWorldSnapID, + Channel: "stable", + Revision: snap.R(-2), + Epoch: "0", + DevMode: true, + }, + }, nil) + c.Assert(err, IsNil) } func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryUpdatesSetsAuth(c *C) { @@ -785,7 +844,7 @@ jsonReq, err := ioutil.ReadAll(r.Body) c.Assert(err, IsNil) - c.Assert(string(jsonReq), Equals, `{"name":["`+funkyAppName+`"]}`) + c.Assert(string(jsonReq), Equals, `{"snaps":[{"snap_id":"`+helloWorldSnapID+`","channel":"stable","revision":1,"epoch":"0","confinement":"strict"}],"fields":["snap_id","package_name","revision","version","download_url"]}`) io.WriteString(w, MockUpdatesJSON) })) @@ -802,7 +861,15 @@ c.Assert(repo, NotNil) authenticator := &fakeAuthenticator{} - _, err = repo.Updates([]string{funkyAppName}, authenticator) + _, err = repo.ListRefresh([]*RefreshCandidate{ + { + SnapID: helloWorldSnapID, + Channel: "stable", + Revision: snap.R(1), + Epoch: "0", + DevMode: false, + }, + }, authenticator) c.Assert(err, IsNil) } @@ -860,7 +927,7 @@ func (t *remoteRepoTestSuite) TestDefaultConfig(c *C) { c.Check(strings.HasPrefix(defaultConfig.SearchURI.String(), "https://search.apps.ubuntu.com/api/v1/search?"), Equals, true) - c.Check(strings.HasPrefix(defaultConfig.BulkURI.String(), "https://search.apps.ubuntu.com/api/v1/click-metadata?"), Equals, true) + c.Check(strings.HasPrefix(defaultConfig.BulkURI.String(), "https://search.apps.ubuntu.com/api/v1/metadata?"), Equals, true) c.Check(defaultConfig.AssertionsURI.String(), Equals, "https://assertions.ubuntu.com/v1/assertions/") } diff -Nru snapd-2.0.5/systemd/escape_test.go snapd-2.0.8/systemd/escape_test.go --- snapd-2.0.5/systemd/escape_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/systemd/escape_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -22,7 +22,7 @@ import ( . "gopkg.in/check.v1" - . "github.com/ubuntu-core/snappy/systemd" + . "github.com/snapcore/snapd/systemd" ) func (s *SystemdTestSuite) TestEscape(c *C) { diff -Nru snapd-2.0.5/systemd/systemd.go snapd-2.0.8/systemd/systemd.go --- snapd-2.0.5/systemd/systemd.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/systemd/systemd.go 2016-06-08 05:58:01.000000000 +0000 @@ -31,8 +31,8 @@ "strconv" "time" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" ) var ( @@ -412,16 +412,21 @@ } func (s *systemd) WriteMountUnitFile(name, what, where string) (string, error) { + extra := "" + if osutil.IsDirectory(what) { + extra = "Options=bind\nType=none\n" + } + c := fmt.Sprintf(`[Unit] -Description=Squashfs mount unit for %s +Description=Mount unit for %s [Mount] What=%s Where=%s - +%s [Install] WantedBy=multi-user.target -`, name, what, where) +`, name, what, where, extra) mu := MountUnitPath(where, "mount") return filepath.Base(mu), osutil.AtomicWriteFile(mu, []byte(c), 0644, 0) diff -Nru snapd-2.0.5/systemd/systemd_test.go snapd-2.0.8/systemd/systemd_test.go --- snapd-2.0.5/systemd/systemd_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/systemd/systemd_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -20,6 +20,7 @@ package systemd_test import ( + "fmt" "io/ioutil" "os" "path/filepath" @@ -29,8 +30,8 @@ . "gopkg.in/check.v1" "gopkg.in/yaml.v2" - "github.com/ubuntu-core/snappy/dirs" - . "github.com/ubuntu-core/snappy/systemd" + "github.com/snapcore/snapd/dirs" + . "github.com/snapcore/snapd/systemd" ) type testreporter struct { @@ -278,22 +279,51 @@ } func (s *SystemdTestSuite) TestWriteMountUnit(c *C) { - mountUnitName, err := New("", nil).WriteMountUnitFile("foo", "/var/lib/snappy/snaps/foo_1.0.snap", "/apps/foo/1.0") + mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap") + err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(mockSnapPath, nil, 0644) + c.Assert(err, IsNil) + + mountUnitName, err := New("", nil).WriteMountUnitFile("foo", mockSnapPath, "/apps/foo/1.0") + c.Assert(err, IsNil) + defer os.Remove(mountUnitName) + + mount, err := ioutil.ReadFile(filepath.Join(dirs.SnapServicesDir, mountUnitName)) + c.Assert(err, IsNil) + c.Assert(string(mount), Equals, fmt.Sprintf(`[Unit] +Description=Mount unit for foo + +[Mount] +What=%s +Where=/apps/foo/1.0 + +[Install] +WantedBy=multi-user.target +`, mockSnapPath)) +} + +func (s *SystemdTestSuite) TestWriteMountUnitForDirs(c *C) { + // a directory instead of a file produces a different output + snapDir := c.MkDir() + mountUnitName, err := New("", nil).WriteMountUnitFile("foodir", snapDir, "/apps/foo/1.0") c.Assert(err, IsNil) defer os.Remove(mountUnitName) mount, err := ioutil.ReadFile(filepath.Join(dirs.SnapServicesDir, mountUnitName)) c.Assert(err, IsNil) - c.Assert(string(mount), Equals, `[Unit] -Description=Squashfs mount unit for foo + c.Assert(string(mount), Equals, fmt.Sprintf(`[Unit] +Description=Mount unit for foodir [Mount] -What=/var/lib/snappy/snaps/foo_1.0.snap +What=%s Where=/apps/foo/1.0 +Options=bind +Type=none [Install] WantedBy=multi-user.target -`) +`, snapDir)) } func (s *SystemdTestSuite) TestRestartCondUnmarshal(c *C) { diff -Nru snapd-2.0.5/.tarmac.sh snapd-2.0.8/.tarmac.sh --- snapd-2.0.5/.tarmac.sh 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/.tarmac.sh 2016-06-08 05:58:01.000000000 +0000 @@ -7,8 +7,8 @@ trap 'rm -rf "$GOPATH"' EXIT # this is a hack, but not sure tarmac is golang friendly -mkdir -p $GOPATH/src/github.com/ubuntu-core/snappy -cp -a . $GOPATH/src/github.com/ubuntu-core/snappy/ -cd $GOPATH/src/github.com/ubuntu-core/snappy +mkdir -p $GOPATH/src/github.com/snapcore/snapd +cp -a . $GOPATH/src/github.com/snapcore/snapd/ +cd $GOPATH/src/github.com/snapcore/snapd sh -v ./run-checks diff -Nru snapd-2.0.5/.travis.yml snapd-2.0.8/.travis.yml --- snapd-2.0.5/.travis.yml 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/.travis.yml 2016-06-08 05:58:01.000000000 +0000 @@ -7,7 +7,7 @@ global: # Encrypted Coveralls token. The secret token for your repository can be found at the bottom of # your repository's page on Coveralls. To encrypt it, install the travis gem and run: - # travis encrypt -r ubuntu-core/snappy COVERALLS_TOKEN=$secret_token + # travis encrypt -r snapcore/snapd COVERALLS_TOKEN=$secret_token - secure: "bHMAmu1F32uc3EXYuK8VnxK3x57aZTr0L4f4J1SKLKKkCy9khvwfBRXP+mlBtNzTjzN9TPYYXc5M6JYJKlM9a8ylu+kzjJBzpW6oI01yKskJuzSr7QbQl9SPoSxYvIZC38df+pJV7dCBtiK/ikXWixRtf316VaQOkaVYcLYs5lYvSrPTGYrEr90CHH2ZlVgUASdee3t6Ew7jIbs0MKRz1AOtrU2N2OZj9SmLa3PBB3syMpamf5aJW/kDR7JrRCLHdbTMDtGk+B8klPKofhOEGzplAqnzeBdMS2yqZ+qBH85w40NX3HEyQryzdidod1VmMfj9AUIgsy8rdfwWjjp0GxdSG9sX4KBi8sgtBfq4S7VtluWPQwe0/l6y3spZv7agNzVej6fM+hongFlpvuXMxGHuiqbkln4cx8KWD+jr5eljdinbxQKXHVn/xOiGLl4rcVMgVaYna/t6tVSBmOD0G45Q/UBu1b+AxdchDzMzv+A4ASwRJwYHUPj1SIlducd+VfvucVDiczF9CqnquCwgoJKN5vufxyz4wPgqnwhXwTd1+f+lROoB3gOE0aQya8wSGIulqZO5DbHkCO/gFMKRTxNIk9FM+CZwwFgPiqlOeY88nnHK1tY/ShIihK1jV6BSl1UkZEn91h64/0zh22YJ3/yp3mP30L8Q5MwgsXdeZNI=" matrix: - TEST_SUITE="--static" @@ -16,6 +16,7 @@ before_install: - sudo apt-get update -qq - sudo apt-get install -qq squashfs-tools + - sudo apt-get install -qq gnupg install: - echo "Skip. Install is done by the test script." script: sh -v ./run-checks $TEST_SUITE diff -Nru snapd-2.0.5/update-pot snapd-2.0.8/update-pot --- snapd-2.0.5/update-pot 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/update-pot 2016-06-08 05:58:01.000000000 +0000 @@ -11,7 +11,7 @@ fi # ensure we have our xgettext-go -go install github.com/ubuntu-core/snappy/i18n/xgettext-go +go install github.com/snapcore/snapd/i18n/xgettext-go $GOPATH/bin/xgettext-go \ -o "$OUTPUT" \ diff -Nru snapd-2.0.5/wrappers/binaries_gen_test.go snapd-2.0.8/wrappers/binaries_gen_test.go --- snapd-2.0.5/wrappers/binaries_gen_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/wrappers/binaries_gen_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,9 +24,9 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/arch" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/wrappers" + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/wrappers" ) type binariesWrapperGenSuite struct{} @@ -61,7 +61,7 @@ info := &snap.Info{} info.SuggestedName = "pastebinit" info.Version = "1.4.0.0.1" - info.Revision = 44 + info.Revision = snap.R(44) binary := &snap.AppInfo{ Snap: info, Name: "pastebinit", diff -Nru snapd-2.0.5/wrappers/binaries.go snapd-2.0.8/wrappers/binaries.go --- snapd-2.0.5/wrappers/binaries.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/wrappers/binaries.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,12 +26,12 @@ "strings" "text/template" - "github.com/ubuntu-core/snappy/arch" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snapenv" + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snapenv" ) // Doesn't need to handle complications like internal quotes, just needs to @@ -72,7 +72,7 @@ SnapArch string SnapPath string Version string - Revision int + Revision snap.Revision Home string }{ App: app, diff -Nru snapd-2.0.5/wrappers/binaries_test.go snapd-2.0.8/wrappers/binaries_test.go --- snapd-2.0.5/wrappers/binaries_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/wrappers/binaries_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,11 +28,11 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" - "github.com/ubuntu-core/snappy/wrappers" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/wrappers" ) func TestWrappers(t *testing.T) { TestingT(t) } @@ -67,7 +67,7 @@ ` func (s *binariesTestSuite) TestAddSnapBinariesAndRemove(c *C) { - info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: 11}) + info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(11)}) err := wrappers.AddSnapBinaries(info) c.Assert(err, IsNil) diff -Nru snapd-2.0.5/wrappers/desktop.go snapd-2.0.8/wrappers/desktop.go --- snapd-2.0.5/wrappers/desktop.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/wrappers/desktop.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,9 +28,9 @@ "path/filepath" "strings" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" ) // valid simple prefixes diff -Nru snapd-2.0.5/wrappers/desktop_test.go snapd-2.0.8/wrappers/desktop_test.go --- snapd-2.0.5/wrappers/desktop_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/wrappers/desktop_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -26,11 +26,11 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" - "github.com/ubuntu-core/snappy/wrappers" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/wrappers" ) type desktopSuite struct { @@ -62,7 +62,7 @@ expectedDesktopFilePath := filepath.Join(dirs.SnapDesktopFilesDir, "foo_foobar.desktop") c.Assert(osutil.FileExists(expectedDesktopFilePath), Equals, false) - info := snaptest.MockSnap(c, desktopAppYaml, &snap.SideInfo{Revision: 11}) + info := snaptest.MockSnap(c, desktopAppYaml, &snap.SideInfo{Revision: snap.R(11)}) // generate .desktop file in the package baseDir baseDir := info.MountDir() @@ -99,7 +99,7 @@ var _ = Suite(&sanitizeDesktopFileSuite{}) func (s *sanitizeDesktopFileSuite) TestSanitizeIgnoreNotWhitelisted(c *C) { - snap := &snap.Info{SideInfo: snap.SideInfo{OfficialName: "foo", Revision: 12}} + snap := &snap.Info{SideInfo: snap.SideInfo{OfficialName: "foo", Revision: snap.R(12)}} desktopContent := []byte(`[Desktop Entry] Name=foo UnknownKey=baz diff -Nru snapd-2.0.5/wrappers/services_gen_test.go snapd-2.0.8/wrappers/services_gen_test.go --- snapd-2.0.5/wrappers/services_gen_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/wrappers/services_gen_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -24,11 +24,11 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/arch" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/systemd" - "github.com/ubuntu-core/snappy/timeout" - "github.com/ubuntu-core/snappy/wrappers" + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/systemd" + "github.com/snapcore/snapd/timeout" + "github.com/snapcore/snapd/wrappers" ) type servicesWrapperGenSuite struct{} @@ -98,7 +98,7 @@ ` info, err := snap.InfoFromSnapYaml([]byte(yamlText)) c.Assert(err, IsNil) - info.Revision = 44 + info.Revision = snap.R(44) app := info.Apps["app"] generatedWrapper, err := wrappers.GenerateSnapServiceFile(app) @@ -118,7 +118,7 @@ info, err := snap.InfoFromSnapYaml([]byte(yamlText)) c.Assert(err, IsNil) - info.Revision = 44 + info.Revision = snap.R(44) app := info.Apps["app"] wrapperText, err := wrappers.GenerateSnapServiceFile(app) @@ -133,7 +133,7 @@ Snap: &snap.Info{ SuggestedName: "xkcd-webserver", Version: "0.3.4", - SideInfo: snap.SideInfo{Revision: 44}, + SideInfo: snap.SideInfo{Revision: snap.R(44)}, }, Name: "xkcd-webserver", Command: "bin/foo start", @@ -153,7 +153,7 @@ Snap: &snap.Info{ SuggestedName: "xkcd-webserver", Version: "0.3.4", - SideInfo: snap.SideInfo{Revision: 44}, + SideInfo: snap.SideInfo{Revision: snap.R(44)}, }, Name: "xkcd-webserver", Command: "bin/foo start\n", @@ -184,7 +184,7 @@ info, err := snap.InfoFromSnapYaml([]byte(yamlText)) c.Assert(err, IsNil) - info.Revision = 44 + info.Revision = snap.R(44) app := info.Apps["app"] wrapperText, err := wrappers.GenerateSnapServiceFile(app) @@ -198,7 +198,7 @@ Snap: &snap.Info{ SideInfo: snap.SideInfo{ OfficialName: "xkcd-webserver", - Revision: 44, + Revision: snap.R(44), }, Version: "0.3.4", }, @@ -232,7 +232,7 @@ Snap: &snap.Info{ SideInfo: snap.SideInfo{ OfficialName: "xkcd-webserver", - Revision: 44, + Revision: snap.R(44), }, Version: "0.3.4", }, @@ -253,7 +253,7 @@ Snap: &snap.Info{ SideInfo: snap.SideInfo{ OfficialName: "xkcd-webserver", - Revision: 44, + Revision: snap.R(44), }, Version: "0.3.4", }, @@ -273,6 +273,7 @@ func (s *servicesWrapperGenSuite) TestGenerateSnapSocketFileMode(c *C) { srv := &snap.AppInfo{ + Name: "foo", Snap: &snap.Info{}, } diff -Nru snapd-2.0.5/wrappers/services.go snapd-2.0.8/wrappers/services.go --- snapd-2.0.5/wrappers/services.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/wrappers/services.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,14 +28,14 @@ "text/template" "time" - "github.com/ubuntu-core/snappy/arch" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/logger" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snapenv" - "github.com/ubuntu-core/snappy/systemd" - "github.com/ubuntu-core/snappy/timeout" + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snapenv" + "github.com/snapcore/snapd/systemd" + "github.com/snapcore/snapd/timeout" ) type interacter interface { @@ -231,7 +231,7 @@ SnapArch string SnapPath string Version string - Revision int + Revision snap.Revision Home string }{ App: appInfo, diff -Nru snapd-2.0.5/wrappers/services_test.go snapd-2.0.8/wrappers/services_test.go --- snapd-2.0.5/wrappers/services_test.go 2016-05-19 13:34:50.000000000 +0000 +++ snapd-2.0.8/wrappers/services_test.go 2016-06-08 05:58:01.000000000 +0000 @@ -28,13 +28,13 @@ . "gopkg.in/check.v1" - "github.com/ubuntu-core/snappy/dirs" - "github.com/ubuntu-core/snappy/osutil" - "github.com/ubuntu-core/snappy/progress" - "github.com/ubuntu-core/snappy/snap" - "github.com/ubuntu-core/snappy/snap/snaptest" - "github.com/ubuntu-core/snappy/systemd" - "github.com/ubuntu-core/snappy/wrappers" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/systemd" + "github.com/snapcore/snapd/wrappers" ) type servicesTestSuite struct { @@ -66,7 +66,7 @@ return []byte("ActiveState=inactive\n"), nil } - info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: 12}) + info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)}) err := wrappers.AddSnapServices(info, nil) c.Assert(err, IsNil) @@ -119,7 +119,7 @@ command: wat stop-timeout: 250ms daemon: forking -`, &snap.SideInfo{Revision: 11}) +`, &snap.SideInfo{Revision: snap.R(11)}) err := wrappers.AddSnapServices(info, nil) c.Assert(err, IsNil)