summaryrefslogtreecommitdiff
diff options
authorMichael Vogt <mvo@ubuntu.com>2016-04-14 08:42:46 +0200
committerMichael Vogt <mvo@ubuntu.com>2016-04-14 08:43:19 +0200
commitc28e18f0d1ea2c3ca823dc7784e5676a9a9b67be (patch)
tree3f2730bdd6090f8a78f2b7c9642aaf385d06a614
parent8b6bbdecaafb2ed1abd833697383a0ed88babdcd (diff)
parent5cb104ff73bd2895b48e5f9aef88944cb819b539 (diff)
Merge remote-tracking branch 'upstream/master' into feature/change-progressfeature/change-progress
-rw-r--r--cmd/snap/cmd_snap_op.go44
-rw-r--r--daemon/api.go55
-rw-r--r--daemon/api_test.go51
-rw-r--r--debian/tests/integrationtests2
-rw-r--r--integration-tests/tests/snap_example_test.go4
-rw-r--r--integration-tests/tests/snap_op_test.go10
-rw-r--r--integration-tests/testutils/cli/cli.go2
-rw-r--r--oauth/oauth.go80
-rw-r--r--oauth/oauth_test.go69
-rw-r--r--overlord/auth/auth.go30
-rw-r--r--overlord/auth/auth_test.go26
-rw-r--r--overlord/ifacestate/ifacemgr.go144
-rw-r--r--overlord/ifacestate/ifacemgr_test.go238
-rw-r--r--overlord/overlord.go2
-rw-r--r--overlord/overlord_test.go3
-rw-r--r--overlord/snapstate/backend.go129
-rw-r--r--overlord/snapstate/backend_test.go99
-rw-r--r--overlord/snapstate/export_test.go14
-rw-r--r--overlord/snapstate/progress.go11
-rw-r--r--overlord/snapstate/snapmgr.go273
-rw-r--r--overlord/snapstate/snapmgr_test.go435
-rw-r--r--overlord/snapstate/snapstate.go185
-rw-r--r--po/snappy.pot28
-rw-r--r--snappy/desktop_test.go2
-rw-r--r--snappy/info_test.go2
-rw-r--r--snappy/install_test.go24
-rw-r--r--snappy/kernel_os.go8
-rw-r--r--snappy/overlord.go165
-rw-r--r--snappy/overlord_test.go8
-rw-r--r--snappy/snapp_test.go9
-rw-r--r--snappy/undo_test.go15
-rw-r--r--store/auth.go115
-rw-r--r--store/auth_test.go110
-rw-r--r--store/snap_remote_repo.go74
-rw-r--r--store/snap_remote_repo_test.go245
35 files changed, 1531 insertions, 1180 deletions
diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go
index 73fd3fc020..d7a005a8b3 100644
--- a/cmd/snap/cmd_snap_op.go
+++ b/cmd/snap/cmd_snap_op.go
@@ -32,35 +32,45 @@ import (
)
func wait(client *client.Client, id string) error {
+ // FIXME: progress is all a bit simplistic, however its ok
+ // for now because the only meaningful progress
+ // we have is the download progress
+
+ // we may have multiple downloads in a single change
+ lastTotal := 0
pb := progress.NewTextProgress()
- defer pb.Finished()
- started := false
+ defer func() {
+ pb.Set(float64(lastTotal))
+ pb.Finished()
+ }()
for {
chg, err := client.Change(id)
if err != nil {
return err
}
-
- // FIXME: a bit simplistic
+ total := 1
msg := ""
- cur := 0
- total := 0
for _, t := range chg.Tasks {
- cur = t.Progress.Done
- total = t.Progress.Total
- if total > 1 && cur != total {
- break
+ if t.Status == "Doing" {
+ msg := t.Summary
+ // this will break once we have multiple
+ // downloads in parallel in a single change
+ if t.Progress.Total > 1 {
+ cur := t.Progress.Done
+ total = t.Progress.Total
+ if t.Progress.Total != lastTotal {
+ pb.Start(msg, float64(total))
+ lastTotal = total
+ }
+ pb.Set(float64(cur))
+ }
}
}
+ // we have no meaningful progress, just show spinner for
+ // last doing task
if total == 1 {
- pb.Spin("")
- } else {
- if !started {
- pb.Start(msg, float64(total))
- started = true
- }
- pb.Set(float64(cur))
+ pb.Spin(msg)
}
// XXX move this to a method of client.Change
diff --git a/daemon/api.go b/daemon/api.go
index 819bc51d18..830b1561dd 100644
--- a/daemon/api.go
+++ b/daemon/api.go
@@ -514,6 +514,7 @@ func (inst *snapInstruction) Agreed(intro, license string) bool {
}
var snapstateInstall = snapstate.Install
+var snapstateGet = snapstate.Get
func waitChange(chg *state.Change) error {
select {
@@ -526,30 +527,66 @@ func waitChange(chg *state.Change) error {
return chg.Err()
}
+func ensureUbuntuCore(chg *state.Change) error {
+ var ss snapstate.SnapState
+
+ ubuntuCore := "ubuntu-core"
+ err := snapstateGet(chg.State(), ubuntuCore, &ss)
+ if err != state.ErrNoState {
+ return err
+ }
+
+ // FIXME: workaround because we are not fully state based yet
+ installed, err := (&snappy.Overlord{}).Installed()
+ snaps := snappy.FindSnapsByName(ubuntuCore, installed)
+ if len(snaps) > 0 {
+ return nil
+ }
+
+ return installSnap(chg, ubuntuCore, "stable", 0)
+}
+
+func installSnap(chg *state.Change, name, channel string, flags snappy.InstallFlags) error {
+ st := chg.State()
+ ts, err := snapstateInstall(st, name, channel, flags)
+ if err != nil {
+ return err
+ }
+
+ // ensure that each of our task runs after the existing tasks
+ chgts := state.NewTaskSet(chg.Tasks()...)
+ for _, t := range ts.Tasks() {
+ t.WaitAll(chgts)
+ }
+ chg.AddAll(ts)
+
+ return nil
+}
+
func (inst *snapInstruction) install() (*state.Change, error) {
flags := snappy.DoInstallGC
if inst.LeaveOld {
flags = 0
}
- state := inst.overlord.State()
- state.Lock()
msg := fmt.Sprintf(i18n.G("Install %q snap"), inst.pkg)
if inst.Channel != "stable" {
msg = fmt.Sprintf(i18n.G("Install %q snap from %q channel"), inst.pkg, inst.Channel)
}
- chg := state.NewChange("install-snap", msg)
- ts, err := snapstateInstall(state, inst.pkg, inst.Channel, flags)
+
+ st := inst.overlord.State()
+ st.Lock()
+ chg := st.NewChange("install-snap", msg)
+ err := ensureUbuntuCore(chg)
if err == nil {
- chg.AddAll(ts)
+ err = installSnap(chg, inst.pkg, inst.Channel, flags)
}
- state.Unlock()
+ st.Unlock()
if err != nil {
return nil, err
}
- state.EnsureBefore(0)
-
- return chg, nil
+ st.EnsureBefore(0)
+ return chg, err
// FIXME: handle license agreement need to happen in the above
// code
/*
diff --git a/daemon/api_test.go b/daemon/api_test.go
index 811db3bf7c..46960a38ac 100644
--- a/daemon/api_test.go
+++ b/daemon/api_test.go
@@ -91,6 +91,7 @@ func (s *apiSuite) TearDownSuite(c *check.C) {
newRemoteRepo = nil
muxVars = nil
snapstateInstall = snapstate.Install
+ snapstateGet = snapstate.Get
}
func (s *apiSuite) SetUpTest(c *check.C) {
@@ -176,7 +177,7 @@ version: %s
c.Assert(err, check.IsNil)
if active {
- err := snappy.UpdateCurrentSymlink(localSnap, nil)
+ err := snappy.UpdateCurrentSymlink(localSnap.Info(), nil)
c.Assert(err, check.IsNil)
}
@@ -362,6 +363,7 @@ func (s *apiSuite) TestListIncludesAll(c *check.C) {
"pkgActionDispatch",
// snapInstruction vars:
"snapstateInstall",
+ "snapstateGet",
"getConfigurator",
}
c.Check(found, check.Equals, len(api)+len(exceptions),
@@ -1181,9 +1183,15 @@ func (s *apiSuite) TestPkgInstructionMismatch(c *check.C) {
func (s *apiSuite) TestInstall(c *check.C) {
calledFlags := snappy.InstallFlags(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, flags snappy.InstallFlags) (*state.TaskSet, error) {
calledFlags = flags
+ installQueue = append(installQueue, name)
t := s.NewTask("fake-install-snap", "Doing a fake install")
return state.NewTaskSet(t), nil
@@ -1193,6 +1201,7 @@ func (s *apiSuite) TestInstall(c *check.C) {
inst := &snapInstruction{
overlord: d.overlord,
Action: "install",
+ pkg: "some-snap",
}
d.overlord.Loop()
@@ -1201,6 +1210,46 @@ func (s *apiSuite) TestInstall(c *check.C) {
c.Check(calledFlags, check.Equals, snappy.DoInstallGC)
c.Check(err, check.IsNil)
+ c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
+}
+
+func (s *apiSuite) TestInstallMissingUbuntuCore(c *check.C) {
+ installQueue := []*state.Task{}
+
+ snapstateGet = func(s *state.State, name string, snapst *snapstate.SnapState) error {
+ // pretend we do not have a state for ubuntu-core
+ return state.ErrNoState
+ }
+ snapstateInstall = func(s *state.State, name, channel string, flags snappy.InstallFlags) (*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)
+ return state.NewTaskSet(t1, t2), nil
+ }
+
+ d := s.daemon(c)
+ inst := &snapInstruction{
+ overlord: d.overlord,
+ Action: "install",
+ pkg: "some-snap",
+ }
+
+ d.overlord.Loop()
+ defer d.overlord.Stop()
+ _, err := inst.dispatch()()
+ c.Check(err, check.IsNil)
+
+ d.overlord.State().Lock()
+ defer d.overlord.State().Unlock()
+ c.Check(installQueue, check.HasLen, 4)
+ // the two "ubuntu-core" install tasks
+ c.Check(installQueue[0].Summary(), check.Equals, "ubuntu-core")
+ c.Check(installQueue[0].WaitTasks(), check.HasLen, 0)
+ c.Check(installQueue[1].WaitTasks(), check.HasLen, 0)
+ // the two "some-snap" install tasks
+ c.Check(installQueue[2].Summary(), check.Equals, "some-snap")
+ c.Check(installQueue[2].WaitTasks(), check.HasLen, 2)
+ c.Check(installQueue[3].WaitTasks(), check.HasLen, 2)
}
func (s *apiSuite) TestInstallFails(c *check.C) {
diff --git a/debian/tests/integrationtests b/debian/tests/integrationtests
index 90a7b6664f..23e6ece417 100644
--- a/debian/tests/integrationtests
+++ b/debian/tests/integrationtests
@@ -8,4 +8,4 @@ mkdir $GOPATH/src/github.com/ubuntu-core/snappy/integration-tests/data/output
cp debian/tests/testconfig.json $GOPATH/src/github.com/ubuntu-core/snappy/integration-tests/data/output/
cd $GOPATH/src/github.com/ubuntu-core/snappy
go test -c ./integration-tests/tests
-./tests.test -check.v -check.f buildSuite
+./tests.test -check.v -check.f snapHelloWorldExampleSuite
diff --git a/integration-tests/tests/snap_example_test.go b/integration-tests/tests/snap_example_test.go
index 3fb1984a3b..f290eec4b6 100644
--- a/integration-tests/tests/snap_example_test.go
+++ b/integration-tests/tests/snap_example_test.go
@@ -58,8 +58,10 @@ func (s *snapHelloWorldExampleSuite) TestCallHelloWorldBinary(c *check.C) {
removeSnap(c, "hello-world")
})
+ // note that this also checks that we have a working ubuntu-core
+ // snap installed, without the ubuntu-core snap the launcher will
+ // not work and no "Hello World!\n" output
echoOutput := cli.ExecCommand(c, "hello-world.echo")
-
c.Assert(echoOutput, check.Equals, "Hello World!\n",
check.Commentf("Wrong output from hello-world binary"))
}
diff --git a/integration-tests/tests/snap_op_test.go b/integration-tests/tests/snap_op_test.go
index d6e74d8854..f3702f2823 100644
--- a/integration-tests/tests/snap_op_test.go
+++ b/integration-tests/tests/snap_op_test.go
@@ -33,12 +33,12 @@ type snapOpSuite struct {
common.SnappySuite
}
-func (s *snapOpSuite) testInstallRemove(c *check.C, snapName, displayName, displayDeveloper string) {
+func (s *snapOpSuite) testInstallRemove(c *check.C, snapName, displayName string) {
installOutput := installSnap(c, snapName)
expected := "(?ms)" +
"Name +Version +Developer\n" +
".*" +
- displayName + " +.* +" + displayDeveloper + "\n" +
+ displayName + " +.*\n" +
".*"
c.Assert(installOutput, check.Matches, expected)
@@ -47,9 +47,5 @@ func (s *snapOpSuite) testInstallRemove(c *check.C, snapName, displayName, displ
}
func (s *snapOpSuite) TestInstallRemoveAliasWorks(c *check.C) {
- s.testInstallRemove(c, "hello-world", "hello-world", "canonical")
-}
-
-func (s *snapOpSuite) TestInstallRemoveFullNameWorks(c *check.C) {
- s.testInstallRemove(c, "hello-world.canonical", "hello-world", "canonical")
+ s.testInstallRemove(c, "hello-world", "hello-world")
}
diff --git a/integration-tests/testutils/cli/cli.go b/integration-tests/testutils/cli/cli.go
index 933f47b0f0..a38577dac5 100644
--- a/integration-tests/testutils/cli/cli.go
+++ b/integration-tests/testutils/cli/cli.go
@@ -39,7 +39,7 @@ var execCommand = exec.Command
// of the command. In case of error, it will fail the test.
func ExecCommand(c *check.C, cmds ...string) string {
output, err := ExecCommandErr(cmds...)
- c.Assert(err, check.IsNil, check.Commentf("Error: %v", output))
+ c.Assert(err, check.IsNil, check.Commentf("Error for %v: %v", cmds, output))
return output
}
diff --git a/oauth/oauth.go b/oauth/oauth.go
deleted file mode 100644
index 7c663de006..0000000000
--- a/oauth/oauth.go
+++ /dev/null
@@ -1,80 +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 <http://www.gnu.org/licenses/>.
- *
- */
-
-package oauth
-
-import (
- "bytes"
- "fmt"
- "time"
-
- "github.com/ubuntu-core/snappy/strutil"
-)
-
-// Token contains the sso token
-type Token struct {
- TokenKey string `json:"token_key"`
- TokenSecret string `json:"token_secret"`
- ConsumerSecret string `json:"consumer_secret"`
- ConsumerKey string `json:"consumer_key"`
-}
-
-// see https://dev.twitter.com/oauth/overview/percent-encoding-parameters
-func needsEscape(c byte) bool {
- return !(('A' <= c && c <= 'Z') ||
- ('a' <= c && c <= 'z') ||
- ('0' <= c && c <= '9') ||
- (c == '-') ||
- (c == '.') ||
- (c == '_') ||
- (c == '~'))
-}
-
-// quote will quote all bytes in the input string that oauth requries to
-// be quoted
-func quote(s string) string {
- buf := bytes.NewBuffer(nil)
- // set to worst case max size, to avoid reallocs
- sin := []byte(s)
- buf.Grow(len(sin) * 3)
-
- for _, c := range sin {
- if needsEscape(c) {
- fmt.Fprintf(buf, "%%%02X", c)
- } else {
- fmt.Fprintf(buf, "%c", c)
- }
- }
-
- return buf.String()
-}
-
-// FIXME: replace with a real oauth1 library - or wait until oauth2 becomes
-// available
-
-// MakePlaintextSignature makes a oauth v1 plaintext signature
-func MakePlaintextSignature(token *Token) string {
- // hrm, rfc5849 says that nonce, timestamp are not used for PLAINTEXT
- // but our sso server is unhappy without, so
- nonce := strutil.MakeRandomString(60)
- timestamp := time.Now().Unix()
-
- s := fmt.Sprintf(`OAuth oauth_nonce="%s", oauth_timestamp="%v", oauth_version="1.0", oauth_signature_method="PLAINTEXT", oauth_consumer_key="%s", oauth_token="%s", oauth_signature="%s%%26%s"`, nonce, timestamp, quote(token.ConsumerKey), quote(token.TokenKey), quote(token.ConsumerSecret), quote(token.TokenSecret))
- return s
-}
diff --git a/oauth/oauth_test.go b/oauth/oauth_test.go
deleted file mode 100644
index 366ea8d789..0000000000
--- a/oauth/oauth_test.go
+++ /dev/null
@@ -1,69 +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 <http://www.gnu.org/licenses/>.
- *
- */
-
-package oauth
-
-import (
- "testing"
-
- . "gopkg.in/check.v1"
-)
-
-func Test(t *testing.T) { TestingT(t) }
-
-type OAuthTestSuite struct{}
-
-var _ = Suite(&OAuthTestSuite{})
-
-func (s *OAuthTestSuite) TestMakePlaintextSignature(c *C) {
- mockToken := Token{
- ConsumerKey: "consumer-key+",
- ConsumerSecret: "consumer-secret+",
- TokenKey: "token-key+",
- TokenSecret: "token-secret+",
- }
- sig := MakePlaintextSignature(&mockToken)
- c.Assert(sig, Matches, `OAuth oauth_nonce="[a-zA-Z0-9]+", oauth_timestamp="[0-9]+", oauth_version="1.0", oauth_signature_method="PLAINTEXT", oauth_consumer_key="consumer-key%2B", oauth_token="token-key%2B", oauth_signature="consumer-secret%2B%26token-secret%2B"`)
-}
-
-func (s *OAuthTestSuite) TestQuote(c *C) {
- // see http://wiki.oauth.net/w/page/12238556/TestCases
- c.Check(quote("abcABC123"), Equals, "abcABC123")
- c.Check(quote("-._~"), Equals, "-._~")
- c.Check(quote("%"), Equals, "%25")
- c.Check(quote("+"), Equals, "%2B")
- c.Check(quote("&=*"), Equals, "%26%3D%2A")
- c.Check(quote("\u000A"), Equals, "%0A")
- c.Check(quote("\u0020"), Equals, "%20")
- c.Check(quote("\u007F"), Equals, "%7F")
- c.Check(quote("\u0080"), Equals, "%C2%80")
- c.Check(quote("\u3001"), Equals, "%E3%80%81")
-}
-
-func (s *OAuthTestSuite) TestNeedsEscape(c *C) {
- for _, needed := range []byte{'?', '/', ':'} {
- c.Check(needsEscape(needed), Equals, true)
- }
-}
-
-func (s *OAuthTestSuite) TestNeedsNoEscape(c *C) {
- for _, no := range []byte{'a', 'z', 'A', 'Z', '-', '.', '_', '~'} {
- c.Check(needsEscape(no), Equals, false)
- }
-}
diff --git a/overlord/auth/auth.go b/overlord/auth/auth.go
index d92f306f2a..061f76449c 100644
--- a/overlord/auth/auth.go
+++ b/overlord/auth/auth.go
@@ -20,7 +20,9 @@
package auth
import (
+ "bytes"
"fmt"
+ "net/http"
"github.com/ubuntu-core/snappy/overlord/state"
)
@@ -81,3 +83,31 @@ func User(st *state.State, id int) (*UserState, error) {
}
return nil, fmt.Errorf("invalid user")
}
+
+// Authenticator returns MacaroonAuthenticator for current authenticated user represented by UserState
+func (us *UserState) Authenticator() *MacaroonAuthenticator {
+ return newMacaroonAuthenticator(us.Macaroon, us.Discharges)
+}
+
+// MacaroonAuthenticator is a store authenticator based on macaroons
+type MacaroonAuthenticator struct {
+ Macaroon string
+ Discharges []string
+}
+
+func newMacaroonAuthenticator(macaroon string, discharges []string) *MacaroonAuthenticator {
+ return &MacaroonAuthenticator{
+ Macaroon: macaroon,
+ Discharges: discharges,
+ }
+}
+
+// Authenticate will add the store expected Authorization header for macaroons
+func (ma *MacaroonAuthenticator) Authenticate(r *http.Request) {
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, `Macaroon root="%s"`, ma.Macaroon)
+ for _, discharge := range ma.Discharges {
+ fmt.Fprintf(&buf, `, discharge="%s"`, discharge)
+ }
+ r.Header.Set("Authorization", buf.String())
+}
diff --git a/overlord/auth/auth_test.go b/overlord/auth/auth_test.go
index 2be81581d2..bd6ccd9acf 100644
--- a/overlord/auth/auth_test.go
+++ b/overlord/auth/auth_test.go
@@ -20,6 +20,7 @@
package auth_test
import (
+ "net/http"
"testing"
. "gopkg.in/check.v1"
@@ -127,3 +128,28 @@ func (as *authSuite) TestUser(c *C) {
c.Check(err, IsNil)
c.Check(userFromState, DeepEquals, user)
}
+
+func (as *authSuite) TestGetAuthenticatorFromUser(c *C) {
+ as.state.Lock()
+ user, err := auth.NewUser(as.state, "username", "macaroon", []string{"discharge"})
+ as.state.Unlock()
+ c.Check(err, IsNil)
+
+ authenticator := user.Authenticator()
+ c.Check(authenticator.Macaroon, Equals, user.Macaroon)
+ c.Check(authenticator.Discharges, DeepEquals, user.Discharges)
+}
+
+func (as *authSuite) TestAuthenticatorSetHeaders(c *C) {
+ as.state.Lock()
+ user, err := auth.NewUser(as.state, "username", "macaroon", []string{"discharge"})
+ as.state.Unlock()
+ c.Check(err, IsNil)
+
+ req, _ := http.NewRequest("GET", "http://example.com", nil)
+ authenticator := user.Authenticator()
+ authenticator.Authenticate(req)
+
+ authorization := req.Header.Get("Authorization")
+ c.Check(authorization, Equals, `Macaroon root="macaroon", discharge="discharge"`)
+}
diff --git a/overlord/ifacestate/ifacemgr.go b/overlord/ifacestate/ifacemgr.go
index 8f416f52ca..8fafd4a422 100644
--- a/overlord/ifacestate/ifacemgr.go
+++ b/overlord/ifacestate/ifacemgr.go
@@ -23,6 +23,7 @@ package ifacestate
import (
"fmt"
+ "strings"
"gopkg.in/tomb.v2"
@@ -37,7 +38,6 @@ import (
"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/snappy"
)
// InterfaceManager is responsible for the maintenance of interfaces in
@@ -50,20 +50,15 @@ type InterfaceManager struct {
}
// Manager returns a new InterfaceManager.
-func Manager(s *state.State) (*InterfaceManager, error) {
- repo := interfaces.NewRepository()
- for _, iface := range builtin.Interfaces() {
- if err := repo.AddInterface(iface); err != nil {
- return nil, err
- }
- }
+// Extra interfaces can be provided for testing.
+func Manager(s *state.State, extra []interfaces.Interface) (*InterfaceManager, error) {
runner := state.NewTaskRunner(s)
m := &InterfaceManager{
state: s,
runner: runner,
- repo: repo,
+ repo: interfaces.NewRepository(),
}
- if err := m.addSnaps(); err != nil {
+ if err := m.initialize(extra); err != nil {
return nil, err
}
runner.AddHandler("connect", m.doConnect, nil)
@@ -73,8 +68,38 @@ func Manager(s *state.State) (*InterfaceManager, error) {
return m, nil
}
+func (m *InterfaceManager) initialize(extra []interfaces.Interface) error {
+ m.state.Lock()
+ defer m.state.Unlock()
+
+ if err := m.addInterfaces(extra); err != nil {
+ return err
+ }
+ if err := m.addSnaps(); err != nil {
+ return err
+ }
+ if err := m.reloadConnections(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (m *InterfaceManager) addInterfaces(extra []interfaces.Interface) error {
+ for _, iface := range builtin.Interfaces() {
+ if err := m.repo.AddInterface(iface); err != nil {
+ return err
+ }
+ }
+ for _, iface := range extra {
+ if err := m.repo.AddInterface(iface); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
func (m *InterfaceManager) addSnaps() error {
- snaps, err := xxxHackyInstalledSnaps()
+ snaps, err := snapstate.ActiveInfos(m.state)
if err != nil {
return err
}
@@ -87,16 +112,22 @@ func (m *InterfaceManager) addSnaps() error {
return nil
}
-func xxxHackyInstalledSnaps() ([]*snap.Info, error) {
- installed, err := (&snappy.Overlord{}).Installed()
+func (m *InterfaceManager) reloadConnections() error {
+ conns, err := getConns(m.state)
if err != nil {
- return nil, err
+ return err
}
- snaps := make([]*snap.Info, len(installed))
- for i, legacySnap := range installed {
- snaps[i] = legacySnap.Info()
+ for id := range conns {
+ plugRef, slotRef, err := parseConnID(id)
+ if err != nil {
+ return err
+ }
+ err = m.repo.Connect(plugRef.Snap, plugRef.Name, slotRef.Snap, slotRef.Name)
+ if err != nil {
+ return err
+ }
}
- return snaps, nil
+ return nil
}
func (m *InterfaceManager) doSetupSnapSecurity(task *state.Task, _ *tomb.Tomb) error {
@@ -164,6 +195,25 @@ type connState struct {
Interface string `json:"interface,omitempty"`
}
+func connID(plug *interfaces.PlugRef, slot *interfaces.SlotRef) string {
+ return fmt.Sprintf("%s:%s %s:%s", plug.Snap, plug.Name, slot.Snap, slot.Name)
+}
+
+func parseConnID(conn string) (*interfaces.PlugRef, *interfaces.SlotRef, error) {
+ parts := strings.SplitN(conn, " ", 2)
+ if len(parts) != 2 {
+ return nil, nil, fmt.Errorf("malformed connection identifier: %q", conn)
+ }
+ plugParts := strings.SplitN(parts[0], ":", 2)
+ slotParts := strings.SplitN(parts[1], ":", 2)
+ if len(plugParts) != 2 || len(slotParts) != 2 {
+ return nil, nil, fmt.Errorf("malformed connection identifier: %q", conn)
+ }
+ plugRef := &interfaces.PlugRef{Snap: plugParts[0], Name: plugParts[1]}
+ slotRef := &interfaces.SlotRef{Snap: slotParts[0], Name: slotParts[1]}
+ return plugRef, slotRef, nil
+}
+
func (m *InterfaceManager) autoConnect(task *state.Task, snapName string) error {
var conns map[string]connState
err := task.State().Get("conns", &conns)
@@ -277,26 +327,72 @@ func getPlugAndSlotRefs(task *state.Task) (*interfaces.PlugRef, *interfaces.Slot
return &plugRef, &slotRef, nil
}
+func getConns(st *state.State) (map[string]connState, error) {
+ // Get information about connections from the state
+ var conns map[string]connState
+ err := st.Get("conns", &conns)
+ if err != nil && err != state.ErrNoState {
+ return nil, fmt.Errorf("cannot obtain data about existing connections: %s", err)
+ }
+ if conns == nil {
+ conns = make(map[string]connState)
+ }
+ return conns, nil
+}
+
+func setConns(st *state.State, conns map[string]connState) {
+ st.Set("conns", conns)
+}
+
func (m *InterfaceManager) doConnect(task *state.Task, _ *tomb.Tomb) error {
- task.State().Lock()
- defer task.State().Unlock()
+ st := task.State()
+ st.Lock()
+ defer st.Unlock()
plugRef, slotRef, err := getPlugAndSlotRefs(task)
if err != nil {
return err
}
- return m.repo.Connect(plugRef.Snap, plugRef.Name, slotRef.Snap, slotRef.Name)
+
+ conns, err := getConns(st)
+ if err != nil {
+ return err
+ }
+
+ err = m.repo.Connect(plugRef.Snap, plugRef.Name, slotRef.Snap, slotRef.Name)
+ if err != nil {
+ return err
+ }
+
+ plug := m.repo.Plug(plugRef.Snap, plugRef.Name)
+ conns[connID(plugRef, slotRef)] = connState{Interface: plug.Interface}
+ setConns(st, conns)
+ return nil
}
func (m *InterfaceManager) doDisconnect(task *state.Task, _ *tomb.Tomb) error {
- task.State().Lock()
- defer task.State().Unlock()
+ st := task.State()
+ st.Lock()
+ defer st.Unlock()
plugRef, slotRef, err := getPlugAndSlotRefs(task)
if err != nil {
return err
}
- return m.repo.Disconnect(plugRef.Snap, plugRef.Name, slotRef.Snap, slotRef.Name)
+
+ conns, err := getConns(st)
+ if err != nil {
+ return err
+ }
+
+ err = m.repo.Disconnect(plugRef.Snap, plugRef.Name, slotRef.Snap, slotRef.Name)
+ if err != nil {
+ return err
+ }
+
+ delete(conns, connID(plugRef, slotRef))
+ setConns(st, conns)
+ return nil
}
// Ensure implements StateManager.Ensure.
diff --git a/overlord/ifacestate/ifacemgr_test.go b/overlord/ifacestate/ifacemgr_test.go
index 2b0f0ee330..0b4cdbe05f 100644
--- a/overlord/ifacestate/ifacemgr_test.go
+++ b/overlord/ifacestate/ifacemgr_test.go
@@ -40,7 +40,8 @@ func TestInterfaceManager(t *testing.T) { TestingT(t) }
type interfaceManagerSuite struct {
state *state.State
- mgr *ifacestate.InterfaceManager
+ privateMgr *ifacestate.InterfaceManager
+ extraIfaces []interfaces.Interface
restoreBackends func()
}
@@ -49,24 +50,35 @@ var _ = Suite(&interfaceManagerSuite{})
func (s *interfaceManagerSuite) SetUpTest(c *C) {
dirs.SetRootDir(c.MkDir())
state := state.New(nil)
- mgr, err := ifacestate.Manager(state)
- c.Assert(err, IsNil)
s.state = state
- s.mgr = mgr
+ s.privateMgr = nil
+ s.extraIfaces = nil
s.restoreBackends = ifacestate.MockSecurityBackendsForSnap(
func(snapInfo *snap.Info) []interfaces.SecurityBackend { return nil },
)
}
func (s *interfaceManagerSuite) TearDownTest(c *C) {
- s.mgr.Stop()
+ if s.privateMgr != nil {
+ s.privateMgr.Stop()
+ }
dirs.SetRootDir("")
s.restoreBackends()
}
+func (s *interfaceManagerSuite) manager(c *C) *ifacestate.InterfaceManager {
+ if s.privateMgr == nil {
+ mgr, err := ifacestate.Manager(s.state, s.extraIfaces)
+ c.Assert(err, IsNil)
+ s.privateMgr = mgr
+ }
+ return s.privateMgr
+}
+
func (s *interfaceManagerSuite) TestSmoke(c *C) {
- s.mgr.Ensure()
- s.mgr.Wait()
+ mgr := s.manager(c)
+ mgr.Ensure()
+ mgr.Wait()
}
func (s *interfaceManagerSuite) TestConnectTask(c *C) {
@@ -91,39 +103,36 @@ func (s *interfaceManagerSuite) TestConnectTask(c *C) {
}
func (s *interfaceManagerSuite) TestEnsureProcessesConnectTask(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+ s.mockIface(c, &interfaces.TestInterface{InterfaceName: "test"})
+ s.mockSnap(c, consumerYaml)
+ s.mockSnap(c, producerYaml)
- s.addPlugSlotAndInterface(c)
+ s.state.Lock()
change := s.state.NewChange("kind", "summary")
ts, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
c.Assert(err, IsNil)
change.AddAll(ts)
-
s.state.Unlock()
- s.mgr.Ensure()
- s.mgr.Wait()
+
+ mgr := s.manager(c)
+ mgr.Ensure()
+ mgr.Wait()
+
s.state.Lock()
+ defer s.state.Unlock()
task := change.Tasks()[0]
c.Check(task.Kind(), Equals, "connect")
c.Check(task.Status(), Equals, state.DoneStatus)
c.Check(change.Status(), Equals, state.DoneStatus)
- repo := s.mgr.Repository()
- c.Check(repo.Interfaces(), DeepEquals, &interfaces.Interfaces{
- Slots: []*interfaces.Slot{{
- SlotInfo: &snap.SlotInfo{
- Snap: &snap.Info{SuggestedName: "producer"}, Name: "slot", Interface: "test",
- },
- Connections: []interfaces.PlugRef{{Snap: "consumer", Name: "plug"}},
- }},
- Plugs: []*interfaces.Plug{{
- PlugInfo: &snap.PlugInfo{
- Snap: &snap.Info{SuggestedName: "consumer"}, Name: "plug", Interface: "test",
- },
- Connections: []interfaces.SlotRef{{Snap: "producer", Name: "slot"}},
- }},
- })
+
+ repo := mgr.Repository()
+ plug := repo.Plug("consumer", "plug")
+ slot := repo.Slot("producer", "slot")
+ c.Assert(plug.Connections, HasLen, 1)
+ c.Assert(slot.Connections, HasLen, 1)
+ c.Check(plug.Connections[0], DeepEquals, interfaces.SlotRef{Snap: "producer", Name: "slot"})
+ c.Check(slot.Connections[0], DeepEquals, interfaces.PlugRef{Snap: "consumer", Name: "plug"})
}
func (s *interfaceManagerSuite) TestDisconnectTask(c *C) {
@@ -148,46 +157,45 @@ func (s *interfaceManagerSuite) TestDisconnectTask(c *C) {
}
func (s *interfaceManagerSuite) TestEnsureProcessesDisconnectTask(c *C) {
+ s.mockIface(c, &interfaces.TestInterface{InterfaceName: "test"})
+ s.mockSnap(c, consumerYaml)
+ s.mockSnap(c, producerYaml)
+
s.state.Lock()
- defer s.state.Unlock()
+ s.state.Set("conns", map[string]interface{}{
+ "consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
+ })
+ s.state.Unlock()
- s.addPlugSlotAndInterface(c)
- repo := s.mgr.Repository()
- err := repo.Connect("consumer", "plug", "producer", "slot")
- c.Assert(err, IsNil)
+ s.state.Lock()
change := s.state.NewChange("kind", "summary")
ts, err := ifacestate.Disconnect(s.state, "consumer", "plug", "producer", "slot")
c.Assert(err, IsNil)
change.AddAll(ts)
-
s.state.Unlock()
- s.mgr.Ensure()
- s.mgr.Wait()
+
+ mgr := s.manager(c)
+ mgr.Ensure()
+ mgr.Wait()
+
s.state.Lock()
+ defer s.state.Unlock()
task := change.Tasks()[0]
c.Check(task.Kind(), Equals, "disconnect")
c.Check(task.Status(), Equals, state.DoneStatus)
c.Check(change.Status(), Equals, state.DoneStatus)
- c.Check(repo.Interfaces(), DeepEquals, &interfaces.Interfaces{
- // NOTE: the connection is gone now.
- Slots: []*interfaces.Slot{{SlotInfo: &snap.SlotInfo{
- Snap: &snap.Info{SuggestedName: "producer"}, Name: "slot", Interface: "test"}}},
- Plugs: []*interfaces.Plug{{PlugInfo: &snap.PlugInfo{
- Snap: &snap.Info{SuggestedName: "consumer"}, Name: "plug", Interface: "test"}}},
- })
+
+ // The connection is gone
+ repo := mgr.Repository()
+ plug := repo.Plug("consumer", "plug")
+ slot := repo.Slot("producer", "slot")
+ c.Assert(plug.Connections, HasLen, 0)
+ c.Assert(slot.Connections, HasLen, 0)
}
-func (s *interfaceManagerSuite) addPlugSlotAndInterface(c *C) {
- repo := s.mgr.Repository()
- err := repo.AddInterface(&interfaces.TestInterface{InterfaceName: "test"})
- c.Assert(err, IsNil)
- err = repo.AddSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{
- Snap: &snap.Info{SuggestedName: "producer"}, Name: "slot", Interface: "test"}})
- c.Assert(err, IsNil)
- err = repo.AddPlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{
- Snap: &snap.Info{SuggestedName: "consumer"}, Name: "plug", Interface: "test"}})
- c.Assert(err, IsNil)
+func (s *interfaceManagerSuite) mockIface(c *C, iface interfaces.Interface) {
+ s.extraIfaces = append(s.extraIfaces, iface)
}
func (s *interfaceManagerSuite) mockSnap(c *C, yamlText string) *snap.Info {
@@ -210,12 +218,9 @@ func (s *interfaceManagerSuite) mockSnap(c *C, yamlText string) *snap.Info {
// Put a side info into the state
snapstate.Set(s.state, snapInfo.Name(), &snapstate.SnapState{
+ Active: true,
Sequence: []*snap.SideInfo{{Revision: snapInfo.Revision}},
})
-
- // Add it to the repository
- s.mgr.Repository().AddSnap(snapInfo)
-
return snapInfo
}
@@ -249,15 +254,33 @@ plugs:
interface: network
`
+var consumerYaml = `
+name: consumer
+version: 1
+plugs:
+ plug:
+ interface: test
+`
+
+var producerYaml = `
+name: producer
+version: 1
+slots:
+ slot:
+ interface: test
+`
+
func (s *interfaceManagerSuite) TestDoSetupSnapSecuirty(c *C) {
s.mockSnap(c, osSnapYaml)
snapInfo := s.mockSnap(c, sampleSnapYaml)
+ mgr := s.manager(c)
+
// Run the setup-snap-security task
change := s.addSetupSnapSecurityChange(c, snapInfo.Name())
- s.mgr.Ensure()
- s.mgr.Wait()
- s.mgr.Stop()
+ mgr.Ensure()
+ mgr.Wait()
+ mgr.Stop()
s.state.Lock()
defer s.state.Unlock()
@@ -278,6 +301,8 @@ func (s *interfaceManagerSuite) TestDoSetupSnapSecuirtyKeepsExistingConnectionSt
s.mockSnap(c, osSnapYaml)
snapInfo := s.mockSnap(c, sampleSnapYaml)
+ mgr := s.manager(c)
+
// Put information about connections for another snap into the state
s.state.Lock()
s.state.Set("conns", map[string]interface{}{
@@ -289,9 +314,9 @@ func (s *interfaceManagerSuite) TestDoSetupSnapSecuirtyKeepsExistingConnectionSt
// Run the setup-snap-security task
change := s.addSetupSnapSecurityChange(c, snapInfo.Name())
- s.mgr.Ensure()
- s.mgr.Wait()
- s.mgr.Stop()
+ mgr.Ensure()
+ mgr.Wait()
+ mgr.Stop()
s.state.Lock()
defer s.state.Unlock()
@@ -310,3 +335,90 @@ func (s *interfaceManagerSuite) TestDoSetupSnapSecuirtyKeepsExistingConnectionSt
},
})
}
+
+func (s *interfaceManagerSuite) TestConnectTracksConnectionsInState(c *C) {
+ s.mockIface(c, &interfaces.TestInterface{InterfaceName: "test"})
+ s.mockSnap(c, consumerYaml)
+ s.mockSnap(c, producerYaml)
+
+ mgr := s.manager(c)
+
+ s.state.Lock()
+ ts, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
+ c.Assert(err, IsNil)
+ change := s.state.NewChange("connect", "")
+ change.AddAll(ts)
+ s.state.Unlock()
+
+ mgr.Ensure()
+ mgr.Wait()
+ mgr.Stop()
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ c.Check(change.Status(), Equals, state.DoneStatus)
+ var conns map[string]interface{}
+ err = s.state.Get("conns", &conns)
+ c.Assert(err, IsNil)
+ c.Check(conns, DeepEquals, map[string]interface{}{
+ "consumer:plug producer:slot": map[string]interface{}{
+ "interface": "test",
+ },
+ })
+}
+
+func (s *interfaceManagerSuite) TestDisconnectTracksConnectionsInState(c *C) {
+ s.mockIface(c, &interfaces.TestInterface{InterfaceName: "test"})
+ s.mockSnap(c, consumerYaml)
+ s.mockSnap(c, producerYaml)
+ s.state.Lock()
+ s.state.Set("conns", map[string]interface{}{
+ "consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
+ })
+ s.state.Unlock()
+
+ mgr := s.manager(c)
+
+ s.state.Lock()
+ ts, err := ifacestate.Disconnect(s.state, "consumer", "plug", "producer", "slot")
+ c.Assert(err, IsNil)
+ change := s.state.NewChange("disconnect", "")
+ change.AddAll(ts)
+ s.state.Unlock()
+
+ mgr.Ensure()
+ mgr.Wait()
+ mgr.Stop()
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ c.Check(change.Status(), Equals, state.DoneStatus)
+ var conns map[string]interface{}
+ err = s.state.Get("conns", &conns)
+ c.Assert(err, IsNil)
+ c.Check(conns, DeepEquals, map[string]interface{}{})
+}
+
+func (s *interfaceManagerSuite) TestManagerReloadsConnections(c *C) {
+ s.mockIface(c, &interfaces.TestInterface{InterfaceName: "test"})
+ s.mockSnap(c, consumerYaml)
+ s.mockSnap(c, producerYaml)
+
+ s.state.Lock()
+ s.state.Set("conns", map[string]interface{}{
+ "consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
+ })
+ s.state.Unlock()
+
+ mgr := s.manager(c)
+ repo := mgr.Repository()
+
+ plug := repo.Plug("consumer", "plug")
+ slot := repo.Slot("producer", "slot")
+ c.Assert(plug.Connections, HasLen, 1)
+ c.Assert(slot.Connections, HasLen, 1)
+ c.Check(plug.Connections[0], DeepEquals, interfaces.SlotRef{Snap: "producer", Name: "slot"})
+ c.Check(slot.Connections[0], DeepEquals, interfaces.PlugRef{Snap: "consumer", Name: "plug"})
+}
diff --git a/overlord/overlord.go b/overlord/overlord.go
index 42760a71bd..22e3f0d753 100644
--- a/overlord/overlord.go
+++ b/overlord/overlord.go
@@ -85,7 +85,7 @@ func New() (*Overlord, error) {
o.assertMgr = assertMgr
o.stateEng.AddManager(o.assertMgr)
- ifaceMgr, err := ifacestate.Manager(s)
+ ifaceMgr, err := ifacestate.Manager(s, nil)
if err != nil {
return nil, err
}
diff --git a/overlord/overlord_test.go b/overlord/overlord_test.go
index 8006f0bd03..cc6d4b708f 100644
--- a/overlord/overlord_test.go
+++ b/overlord/overlord_test.go
@@ -221,9 +221,6 @@ func (ovs *overlordSuite) TestCheckpoint(c *C) {
o, err := overlord.New()
c.Assert(err, IsNil)
- _, err = os.Stat(dirs.SnapStateFile)
- c.Check(os.IsNotExist(err), Equals, true)
-
s := o.State()
s.Lock()
s.Set("mark", 1)
diff --git a/overlord/snapstate/backend.go b/overlord/snapstate/backend.go
index 31467b8947..55c59e5178 100644
--- a/overlord/snapstate/backend.go
+++ b/overlord/snapstate/backend.go
@@ -20,11 +20,6 @@
package snapstate
import (
- "fmt"
- "path/filepath"
- "strconv"
-
- "github.com/ubuntu-core/snappy/dirs"
"github.com/ubuntu-core/snappy/progress"
"github.com/ubuntu-core/snappy/snap"
"github.com/ubuntu-core/snappy/snappy"
@@ -33,30 +28,24 @@ import (
type managerBackend interface {
// install releated
Download(name, channel string, meter progress.Meter) (*snap.Info, string, error)
- CheckSnap(snapFilePath string, flags int) error
+ CheckSnap(snapFilePath string, curInfo *snap.Info, flags int) error
SetupSnap(snapFilePath string, si *snap.SideInfo, flags int) error
- CopySnapData(instSnapPath string, flags int) error
- LinkSnap(instSnapPath string) error
- GarbageCollect(snap string, flags int, meter progress.Meter) error
+ CopySnapData(newSnap, oldSnap *snap.Info, flags int) error
+ LinkSnap(info *snap.Info) error
// the undoers for install
UndoSetupSnap(s snap.PlaceInfo) error
- UndoCopySnapData(instSnapPath string, flags int) error
- UndoLinkSnap(oldInstSnapPath, instSnapPath string) error
+ UndoCopySnapData(newSnap *snap.Info, flags int) error
// remove releated
- CanRemove(instSnapPath string) error
- UnlinkSnap(instSnapPath string, meter progress.Meter) error
+ CanRemove(info *snap.Info, active bool) bool
+ UnlinkSnap(info *snap.Info, meter progress.Meter) error
RemoveSnapFiles(s snap.PlaceInfo, meter progress.Meter) error
- RemoveSnapData(name string, revision int) error
+ RemoveSnapData(info *snap.Info) error
// TODO: need to be split into fine grained tasks
- Update(name, channel string, flags int, meter progress.Meter) error
Activate(name string, active bool, meter progress.Meter) error
- // XXX: this one needs to be revno based as well
- Rollback(name, ver string, meter progress.Meter) (string, error)
// info
- ActiveSnap(name string) *snap.Info
SnapByNameAndVersion(name, version string) *snap.Info
// testing helpers
@@ -67,13 +56,6 @@ type defaultBackend struct{}
func (b *defaultBackend) Candidate(*snap.SideInfo) {}
-func (b *defaultBackend) ActiveSnap(name string) *snap.Info {
- if snap := snappy.ActiveSnapByName(name); snap != nil {
- return snap.Info()
- }
- return nil
-}
-
func (b *defaultBackend) SnapByNameAndVersion(name, version string) *snap.Info {
// XXX: use snapstate stuff!
installed, err := (&snappy.Overlord{}).Installed()
@@ -88,16 +70,6 @@ func (b *defaultBackend) SnapByNameAndVersion(name, version string) *snap.Info {
return found[0].Info()
}
-func (b *defaultBackend) Update(name, channel string, flags int, meter progress.Meter) error {
- // FIXME: support "channel" in snappy.Update()
- _, err := snappy.Update(name, snappy.InstallFlags(flags), meter)
- return err
-}
-
-func (b *defaultBackend) Rollback(name, ver string, meter progress.Meter) (string, error) {
- return snappy.Rollback(name, ver, meter)
-}
-
func (b *defaultBackend) Activate(name string, active bool, meter progress.Meter) error {
return snappy.SetActive(name, active, meter)
}
@@ -117,9 +89,9 @@ func (b *defaultBackend) Download(name, channel string, meter progress.Meter) (*
return snap, downloadedSnapFile, nil
}
-func (b *defaultBackend) CheckSnap(snapFilePath string, flags int) error {
+func (b *defaultBackend) CheckSnap(snapFilePath string, curInfo *snap.Info, flags int) error {
meter := &progress.NullProgress{}
- return snappy.CheckSnap(snapFilePath, snappy.InstallFlags(flags), meter)
+ return snappy.CheckSnap(snapFilePath, curInfo, snappy.InstallFlags(flags), meter)
}
func (b *defaultBackend) SetupSnap(snapFilePath string, sideInfo *snap.SideInfo, flags int) error {
@@ -128,26 +100,14 @@ func (b *defaultBackend) SetupSnap(snapFilePath string, sideInfo *snap.SideInfo,
return err
}
-func (b *defaultBackend) CopySnapData(snapInstPath string, flags int) error {
- sn, err := snappy.NewInstalledSnap(filepath.Join(snapInstPath, "meta", "snap.yaml"))
- if err != nil {
- return err
- }
+func (b *defaultBackend) CopySnapData(newInfo, oldInfo *snap.Info, flags int) error {
meter := &progress.NullProgress{}
- return snappy.CopyData(sn.Info(), snappy.InstallFlags(flags), meter)
+ return snappy.CopyData(newInfo, oldInfo, snappy.InstallFlags(flags), meter)
}
-func (b *defaultBackend) LinkSnap(snapInstPath string) error {
- sn, err := snappy.NewInstalledSnap(filepath.Join(snapInstPath, "meta", "snap.yaml"))
- if err != nil {
- return err
- }
+func (b *defaultBackend) LinkSnap(info *snap.Info) error {
meter := &progress.NullProgress{}
- if err := snappy.GenerateWrappers(sn, meter); err != nil {
- return err
- }
-
- return snappy.UpdateCurrentSymlink(sn, meter)
+ return snappy.LinkSnap(info, meter)
}
func (b *defaultBackend) UndoSetupSnap(s snap.PlaceInfo) error {
@@ -156,71 +116,24 @@ func (b *defaultBackend) UndoSetupSnap(s snap.PlaceInfo) error {
return nil
}
-func (b *defaultBackend) UndoCopySnapData(instSnapPath string, flags int) error {
- sn, err := snappy.NewInstalledSnap(filepath.Join(instSnapPath, "meta", "snap.yaml"))
- if err != nil {
- return err
- }
+func (b *defaultBackend) UndoCopySnapData(newInfo *snap.Info, flags int) error {
meter := &progress.NullProgress{}
- snappy.UndoCopyData(sn.Info(), snappy.InstallFlags(flags), meter)
+ snappy.UndoCopyData(newInfo, snappy.InstallFlags(flags), meter)
return nil
}
-func (b *defaultBackend) UndoLinkSnap(oldInstSnapPath, instSnapPath string) error {
- new, err := snappy.NewInstalledSnap(filepath.Join(instSnapPath, "meta", "snap.yaml"))
- if err != nil {
- return err
- }
- old, err := snappy.NewInstalledSnap(filepath.Join(oldInstSnapPath, "meta", "snap.yaml"))
- if err != nil {
- return err
- }
-
- meter := &progress.NullProgress{}
- err1 := snappy.RemoveGeneratedWrappers(new, meter)
- err2 := snappy.UndoUpdateCurrentSymlink(old, new, meter)
-
- // return firstErr
- if err1 != nil {
- return err1
- }
- return err2
-}
-
-func (b *defaultBackend) CanRemove(instSnapPath string) error {
- sn, err := snappy.NewInstalledSnap(filepath.Join(instSnapPath, "meta", "snap.yaml"))
- if err != nil {
- return err
- }
- if !snappy.CanRemove(sn) {
- return fmt.Errorf("snap %q is not removable", sn.Name())
- }
- return nil
+func (b *defaultBackend) CanRemove(info *snap.Info, active bool) bool {
+ return snappy.CanRemove(info, active)
}
-func (b *defaultBackend) UnlinkSnap(instSnapPath string, meter progress.Meter) error {
- sn, err := snappy.NewInstalledSnap(filepath.Join(instSnapPath, "meta", "snap.yaml"))
- if err != nil {
- return err
- }
-
- return snappy.UnlinkSnap(sn, meter)
+func (b *defaultBackend) UnlinkSnap(info *snap.Info, meter progress.Meter) error {
+ return snappy.UnlinkSnap(info, meter)
}
func (b *defaultBackend) RemoveSnapFiles(s snap.PlaceInfo, meter progress.Meter) error {
return snappy.RemoveSnapFiles(s, meter)
}
-func (b *defaultBackend) RemoveSnapData(name string, revision int) error {
- // XXX: hack for now
- sn, err := snappy.NewInstalledSnap(filepath.Join(dirs.SnapSnapsDir, name, strconv.Itoa(revision), "meta", "snap.yaml"))
- if err != nil {
- return err
- }
-
- return snappy.RemoveSnapData(sn.Info())
-}
-
-func (b *defaultBackend) GarbageCollect(snap string, flags int, meter progress.Meter) error {
- return snappy.GarbageCollect(snap, snappy.InstallFlags(flags), meter)
+func (b *defaultBackend) RemoveSnapData(info *snap.Info) error {
+ return snappy.RemoveSnapData(info)
}
diff --git a/overlord/snapstate/backend_test.go b/overlord/snapstate/backend_test.go
index f923fe2495..95bbe5db7f 100644
--- a/overlord/snapstate/backend_test.go
+++ b/overlord/snapstate/backend_test.go
@@ -20,6 +20,7 @@
package snapstate_test
import (
+ "errors"
"strings"
"github.com/ubuntu-core/snappy/progress"
@@ -36,7 +37,7 @@ type fakeOp struct {
active bool
sinfo snap.SideInfo
- rollback string
+ old string
}
type fakeSnappyBackend struct {
@@ -45,7 +46,7 @@ type fakeSnappyBackend struct {
fakeCurrentProgress int
fakeTotalProgress int
- activeSnaps map[string]*snap.Info
+ linkSnapFailTrigger string
}
func (f *fakeSnappyBackend) InstallLocal(path string, flags int, p progress.Meter) error {
@@ -77,31 +78,6 @@ func (f *fakeSnappyBackend) Download(name, channel string, p progress.Meter) (*s
return info, "downloaded-snap-path", nil
}
-func (f *fakeSnappyBackend) Update(name, channel string, flags int, p progress.Meter) error {
- f.ops = append(f.ops, fakeOp{
- op: "update",
- name: name,
- channel: channel,
- })
- return nil
-}
-
-func (f *fakeSnappyBackend) Remove(name string, flags int, p progress.Meter) error {
- f.ops = append(f.ops, fakeOp{
- op: "remove",
- name: name,
- })
- return nil
-}
-func (f *fakeSnappyBackend) Rollback(name, ver string, p progress.Meter) (string, error) {
- f.ops = append(f.ops, fakeOp{
- op: "rollback",
- name: name,
- rollback: ver,
- })
- return "", nil
-}
-
func (f *fakeSnappyBackend) Activate(name string, active bool, p progress.Meter) error {
f.ops = append(f.ops, fakeOp{
op: "activate",
@@ -111,10 +87,15 @@ func (f *fakeSnappyBackend) Activate(name string, active bool, p progress.Meter)
return nil
}
-func (f *fakeSnappyBackend) CheckSnap(snapFilePath string, flags int) error {
+func (f *fakeSnappyBackend) CheckSnap(snapFilePath string, curInfo *snap.Info, flags int) error {
+ cur := "<no-current>"
+ if curInfo != nil {
+ cur = curInfo.MountDir()
+ }
f.ops = append(f.ops, fakeOp{
op: "check-snap",
name: snapFilePath,
+ old: cur,
flags: flags,
})
return nil
@@ -134,19 +115,37 @@ func (f *fakeSnappyBackend) SetupSnap(snapFilePath string, si *snap.SideInfo, fl
return nil
}
-func (f *fakeSnappyBackend) CopySnapData(instSnapPath string, flags int) error {
+func (f *fakeSnappyBackend) RetrieveInfo(name string, si *snap.SideInfo) (*snap.Info, error) {
+ // naive emulation for now, always works
+ return &snap.Info{SideInfo: *si}, nil
+}
+
+func (f *fakeSnappyBackend) CopySnapData(newInfo, oldInfo *snap.Info, flags int) error {
+ old := "<no-old>"
+ if oldInfo != nil {
+ old = oldInfo.MountDir()
+ }
f.ops = append(f.ops, fakeOp{
op: "copy-data",
- name: instSnapPath,
+ name: newInfo.MountDir(),
flags: flags,
+ old: old,
})
return nil
}
-func (f *fakeSnappyBackend) LinkSnap(instSnapPath string) error {
+func (f *fakeSnappyBackend) LinkSnap(info *snap.Info) error {
+ if info.MountDir() == f.linkSnapFailTrigger {
+ f.ops = append(f.ops, fakeOp{
+ op: "link-snap.failed",
+ name: info.MountDir(),
+ })
+ return errors.New("fail")
+ }
+
f.ops = append(f.ops, fakeOp{
op: "link-snap",
- name: instSnapPath,
+ name: info.MountDir(),
})
return nil
}
@@ -159,26 +158,14 @@ func (f *fakeSnappyBackend) UndoSetupSnap(s snap.PlaceInfo) error {
return nil
}
-func (f *fakeSnappyBackend) UndoCopySnapData(instSnapPath string, flags int) error {
+func (f *fakeSnappyBackend) UndoCopySnapData(newInfo *snap.Info, flags int) error {
f.ops = append(f.ops, fakeOp{
op: "undo-copy-snap-data",
- name: instSnapPath,
- })
- return nil
-}
-
-func (f *fakeSnappyBackend) UndoLinkSnap(oldInstSnapPath, instSnapPath string) error {
- f.ops = append(f.ops, fakeOp{
- op: "undo-link-snap",
- name: instSnapPath,
+ name: newInfo.MountDir(),
})
return nil
}
-func (f *fakeSnappyBackend) ActiveSnap(name string) *snap.Info {
- return f.activeSnaps[name]
-}
-
func (f *fakeSnappyBackend) SnapByNameAndVersion(name, version string) *snap.Info {
return &snap.Info{
SideInfo: snap.SideInfo{
@@ -190,18 +177,19 @@ func (f *fakeSnappyBackend) SnapByNameAndVersion(name, version string) *snap.Inf
}
}
-func (f *fakeSnappyBackend) CanRemove(instSnapPath string) error {
+func (f *fakeSnappyBackend) CanRemove(info *snap.Info, active bool) bool {
f.ops = append(f.ops, fakeOp{
- op: "can-remove",
- name: instSnapPath,
+ op: "can-remove",
+ name: info.MountDir(),
+ active: active,
})
- return nil
+ return true
}
-func (f *fakeSnappyBackend) UnlinkSnap(instSnapPath string, meter progress.Meter) error {
+func (f *fakeSnappyBackend) UnlinkSnap(info *snap.Info, meter progress.Meter) error {
f.ops = append(f.ops, fakeOp{
op: "unlink-snap",
- name: instSnapPath,
+ name: info.MountDir(),
})
return nil
}
@@ -214,11 +202,10 @@ func (f *fakeSnappyBackend) RemoveSnapFiles(s snap.PlaceInfo, meter progress.Met
return nil
}
-func (f *fakeSnappyBackend) RemoveSnapData(name string, revno int) error {
+func (f *fakeSnappyBackend) RemoveSnapData(info *snap.Info) error {
f.ops = append(f.ops, fakeOp{
- op: "remove-snap-data",
- name: name,
- revno: revno,
+ op: "remove-snap-data",
+ name: info.MountDir(),
})
return nil
}
diff --git a/overlord/snapstate/export_test.go b/overlord/snapstate/export_test.go
index 25455ccd5b..f11f0052fb 100644
--- a/overlord/snapstate/export_test.go
+++ b/overlord/snapstate/export_test.go
@@ -20,9 +20,12 @@
package snapstate
import (
+ "errors"
+
"gopkg.in/tomb.v2"
"github.com/ubuntu-core/snappy/overlord/state"
+ "github.com/ubuntu-core/snappy/snap"
)
type ManagerBackend managerBackend
@@ -41,4 +44,15 @@ func (m *SnapManager) AddForeignTaskHandlers() {
fakeHandler := func(task *state.Task, _ *tomb.Tomb) error { return nil }
m.runner.AddHandler("setup-snap-security", fakeHandler, fakeHandler)
m.runner.AddHandler("remove-snap-security", fakeHandler, fakeHandler)
+
+ // Add handler to test full aborting of changes
+ erroringHandler := func(task *state.Task, _ *tomb.Tomb) error {
+ return errors.New("error out")
+ }
+ m.runner.AddHandler("error-trigger", erroringHandler, nil)
+}
+
+func ChangeRetrieveInfo(retrieve func(name string, si *snap.SideInfo) (*snap.Info, error)) func() {
+ retrieveInfo = retrieve
+ return func() { retrieveInfo = retrieveInfoImpl }
}
diff --git a/overlord/snapstate/progress.go b/overlord/snapstate/progress.go
index fa0a28eef3..30ad32cd7b 100644
--- a/overlord/snapstate/progress.go
+++ b/overlord/snapstate/progress.go
@@ -26,8 +26,9 @@ import (
// TaskProgressAdapter adapts the progress.Meter to the task progress
// until we have native install/update/remove.
type TaskProgressAdapter struct {
- task *state.Task
- total float64
+ task *state.Task
+ total float64
+ current float64
}
// Start sets total
@@ -37,6 +38,7 @@ func (t *TaskProgressAdapter) Start(pkg string, total float64) {
// Set sets the current progress
func (t *TaskProgressAdapter) Set(current float64) {
+ t.current = current
t.task.State().Lock()
defer t.task.State().Unlock()
t.task.SetProgress(int(current), int(t.total))
@@ -56,6 +58,11 @@ func (t *TaskProgressAdapter) Finished() {
// Write does nothing
func (t *TaskProgressAdapter) Write(p []byte) (n int, err error) {
+ t.task.State().Lock()
+ defer t.task.State().Unlock()
+
+ t.current += float64(len(p))
+ t.task.SetProgress(int(t.current), int(t.total))
return len(p), nil
}
diff --git a/overlord/snapstate/snapmgr.go b/overlord/snapstate/snapmgr.go
index 912c44e2bd..b5149d5862 100644
--- a/overlord/snapstate/snapmgr.go
+++ b/overlord/snapstate/snapmgr.go
@@ -39,19 +39,23 @@ type SnapManager struct {
// SnapSetup holds the necessary snap details to perform most snap manager tasks.
type SnapSetup struct {
- Name string `json:"name"`
- Developer string `json:"developer,omitempty"`
- Revision int `json:"revision,omitempty"`
- Channel string `json:"channel,omitempty"`
-
- // XXX: should be switched to use Revision instead
- RollbackVersion string `json:"rollback-version,omitempty"`
+ Name string `json:"name"`
+ Revision int `json:"revision,omitempty"`
+ Channel string `json:"channel,omitempty"`
Flags int `json:"flags,omitempty"`
SnapPath string `json:"snap-path,omitempty"`
}
+func (ss *SnapSetup) placeInfo() snap.PlaceInfo {
+ return snap.MinimalPlaceInfo(ss.Name, ss.Revision)
+}
+
+func (ss *SnapSetup) MountDir() string {
+ return snap.MountDir(ss.Name, ss.Revision)
+}
+
// SnapState holds the state for a snap installed in the system.
type SnapState struct {
Sequence []*snap.SideInfo `json:"sequence"` // Last is current
@@ -61,12 +65,13 @@ type SnapState struct {
DevMode bool `json:"dev-mode,omitempty"`
}
-func (ss *SnapSetup) placeInfo() snap.PlaceInfo {
- return snap.MinimalPlaceInfo(ss.Name, ss.Revision)
-}
-
-func (ss *SnapSetup) MountDir() string {
- return snap.MountDir(ss.Name, ss.Revision)
+// Current returns the side info for the current revision in the snap revision sequence if there is one.
+func (snapst *SnapState) Current() *snap.SideInfo {
+ n := len(snapst.Sequence)
+ if n == 0 {
+ return nil
+ }
+ return snapst.Sequence[n-1]
}
// Manager returns a new snap manager.
@@ -88,6 +93,7 @@ func Manager(s *state.State) (*SnapManager, error) {
runner.AddHandler("prepare-snap", m.doPrepareSnap, m.undoPrepareSnap)
runner.AddHandler("download-snap", m.doDownloadSnap, m.undoPrepareSnap)
runner.AddHandler("mount-snap", m.doMountSnap, m.undoMountSnap)
+ runner.AddHandler("unlink-current-snap", m.doUnlinkCurrentSnap, m.undoUnlinkCurrentSnap)
runner.AddHandler("copy-snap-data", m.doCopySnapData, m.undoCopySnapData)
runner.AddHandler("link-snap", m.doLinkSnap, m.undoLinkSnap)
// FIXME: port to native tasks and rename
@@ -95,11 +101,10 @@ func Manager(s *state.State) (*SnapManager, error) {
// remove releated
runner.AddHandler("unlink-snap", m.doUnlinkSnap, nil)
- runner.AddHandler("remove-snap-files", m.doRemoveSnapFiles, nil)
- runner.AddHandler("remove-snap-data", m.doRemoveSnapData, nil)
+ runner.AddHandler("clear-snap", m.doClearSnapData, nil)
+ runner.AddHandler("discard-snap", m.doDiscardSnap, nil)
// FIXME: work on those
- runner.AddHandler("rollback-snap", m.doRollbackSnap, nil)
runner.AddHandler("activate-snap", m.doActivateSnap, nil)
runner.AddHandler("deactivate-snap", m.doDeactivateSnap, nil)
@@ -170,13 +175,8 @@ func (m *SnapManager) doDownloadSnap(t *state.Task, _ *tomb.Tomb) error {
return err
}
- // construct the store name
- name := ss.Name
- if ss.Developer != "" {
- name = fmt.Sprintf("%s.%s", ss.Name, ss.Developer)
- }
pb := &TaskProgressAdapter{task: t}
- storeInfo, downloadedSnapFile, err := m.backend.Download(name, ss.Channel, pb)
+ storeInfo, downloadedSnapFile, err := m.backend.Download(ss.Name, ss.Channel, pb)
if err != nil {
return err
}
@@ -194,20 +194,39 @@ func (m *SnapManager) doDownloadSnap(t *state.Task, _ *tomb.Tomb) error {
}
func (m *SnapManager) doUnlinkSnap(t *state.Task, _ *tomb.Tomb) error {
- var ss SnapSetup
+ // invoked only if snap has a current active revision
- t.State().Lock()
- err := t.Get("snap-setup", &ss)
- t.State().Unlock()
+ st := t.State()
+
+ // Hold the lock for the full duration of the task here so
+ // nobody observes a world where the state engine and
+ // the file system are reporting different things.
+ st.Lock()
+ defer st.Unlock()
+
+ ss, snapst, err := snapSetupAndState(t)
+ if err != nil {
+ return err
+ }
+
+ info, err := Info(t.State(), ss.Name, ss.Revision)
if err != nil {
return err
}
pb := &TaskProgressAdapter{task: t}
- return m.backend.UnlinkSnap(ss.MountDir(), pb)
+ err = m.backend.UnlinkSnap(info, pb)
+ if err != nil {
+ return err
+ }
+
+ // mark as inactive
+ snapst.Active = false
+ Set(st, ss.Name, snapst)
+ return nil
}
-func (m *SnapManager) doRemoveSnapFiles(t *state.Task, _ *tomb.Tomb) error {
+func (m *SnapManager) doClearSnapData(t *state.Task, _ *tomb.Tomb) error {
t.State().Lock()
ss, err := TaskSnapSetup(t)
t.State().Unlock()
@@ -215,34 +234,52 @@ func (m *SnapManager) doRemoveSnapFiles(t *state.Task, _ *tomb.Tomb) error {
return err
}
- pb := &TaskProgressAdapter{task: t}
- return m.backend.RemoveSnapFiles(ss.placeInfo(), pb)
-}
-
-func (m *SnapManager) doRemoveSnapData(t *state.Task, _ *tomb.Tomb) error {
t.State().Lock()
- ss, err := TaskSnapSetup(t)
+ info, err := Info(t.State(), ss.Name, ss.Revision)
t.State().Unlock()
if err != nil {
return err
}
- return m.backend.RemoveSnapData(ss.Name, ss.Revision)
+ return m.backend.RemoveSnapData(info)
}
-func (m *SnapManager) doRollbackSnap(t *state.Task, _ *tomb.Tomb) error {
- var ss SnapSetup
+func (m *SnapManager) doDiscardSnap(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
- t.State().Lock()
- err := t.Get("snap-setup", &ss)
- t.State().Unlock()
+ st.Lock()
+ ss, snapst, err := snapSetupAndState(t)
+ st.Unlock()
if err != nil {
return err
}
+ st.Lock()
+ if len(snapst.Sequence) == 1 {
+ snapst.Sequence = nil
+ } else {
+ newSeq := make([]*snap.SideInfo, 0, len(snapst.Sequence))
+ for _, si := range snapst.Sequence {
+ if si.Revision == ss.Revision {
+ // leave out
+ continue
+ }
+ newSeq = append(newSeq, si)
+ }
+ snapst.Sequence = newSeq
+ }
+ st.Unlock()
+
pb := &TaskProgressAdapter{task: t}
- _, err = m.backend.Rollback(ss.Name, ss.RollbackVersion, pb)
- return err
+ err = m.backend.RemoveSnapFiles(ss.placeInfo(), pb)
+ if err != nil {
+ return err
+ }
+
+ st.Lock()
+ Set(st, ss.Name, snapst)
+ st.Unlock()
+ return nil
}
func (m *SnapManager) doActivateSnap(t *state.Task, _ *tomb.Tomb) error {
@@ -345,7 +382,17 @@ func (m *SnapManager) doMountSnap(t *state.Task, _ *tomb.Tomb) error {
return err
}
- if err := m.backend.CheckSnap(ss.SnapPath, ss.Flags); err != nil {
+ var curInfo *snap.Info
+ if cur := snapst.Current(); cur != nil {
+ var err error
+ curInfo, err = retrieveInfo(ss.Name, cur)
+ if err != nil {
+ return err
+ }
+
+ }
+
+ if err := m.backend.CheckSnap(ss.SnapPath, curInfo, ss.Flags); err != nil {
return err
}
@@ -354,26 +401,109 @@ func (m *SnapManager) doMountSnap(t *state.Task, _ *tomb.Tomb) error {
return m.backend.SetupSnap(ss.SnapPath, snapst.Candidate, ss.Flags)
}
+func (m *SnapManager) undoUnlinkCurrentSnap(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+
+ // Hold the lock for the full duration of the task here so
+ // nobody observes a world where the state engine and
+ // the file system are reporting different things.
+ st.Lock()
+ defer st.Unlock()
+
+ ss, snapst, err := snapSetupAndState(t)
+ if err != nil {
+ return err
+ }
+
+ oldInfo, err := retrieveInfo(ss.Name, snapst.Current())
+ if err != nil {
+ return err
+ }
+
+ snapst.Active = true
+ err = m.backend.LinkSnap(oldInfo)
+ if err != nil {
+ return err
+ }
+
+ // mark as active again
+ Set(st, ss.Name, snapst)
+ return nil
+
+}
+
+func (m *SnapManager) doUnlinkCurrentSnap(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+
+ // Hold the lock for the full duration of the task here so
+ // nobody observes a world where the state engine and
+ // the file system are reporting different things.
+ st.Lock()
+ defer st.Unlock()
+
+ ss, snapst, err := snapSetupAndState(t)
+ if err != nil {
+ return err
+ }
+
+ oldInfo, err := retrieveInfo(ss.Name, snapst.Current())
+ if err != nil {
+ return err
+ }
+
+ snapst.Active = false
+
+ pb := &TaskProgressAdapter{task: t}
+ err = m.backend.UnlinkSnap(oldInfo, pb)
+ if err != nil {
+ return err
+ }
+
+ // mark as inactive
+ Set(st, ss.Name, snapst)
+ return nil
+}
+
func (m *SnapManager) undoCopySnapData(t *state.Task, _ *tomb.Tomb) error {
t.State().Lock()
- ss, err := TaskSnapSetup(t)
+ ss, snapst, err := snapSetupAndState(t)
t.State().Unlock()
if err != nil {
return err
}
- return m.backend.UndoCopySnapData(ss.MountDir(), ss.Flags)
+ newInfo, err := retrieveInfo(ss.Name, snapst.Candidate)
+ if err != nil {
+ return err
+ }
+
+ return m.backend.UndoCopySnapData(newInfo, ss.Flags)
}
func (m *SnapManager) doCopySnapData(t *state.Task, _ *tomb.Tomb) error {
t.State().Lock()
- ss, err := TaskSnapSetup(t)
+ ss, snapst, err := snapSetupAndState(t)
t.State().Unlock()
if err != nil {
return err
}
- return m.backend.CopySnapData(ss.MountDir(), ss.Flags)
+ newInfo, err := retrieveInfo(ss.Name, snapst.Candidate)
+ if err != nil {
+ return err
+ }
+
+ var oldInfo *snap.Info
+ if cur := snapst.Current(); cur != nil {
+ var err error
+ oldInfo, err = retrieveInfo(ss.Name, cur)
+ if err != nil {
+ return err
+ }
+
+ }
+
+ return m.backend.CopySnapData(newInfo, oldInfo, ss.Flags)
}
func (m *SnapManager) doLinkSnap(t *state.Task, _ *tomb.Tomb) error {
@@ -389,12 +519,20 @@ func (m *SnapManager) doLinkSnap(t *state.Task, _ *tomb.Tomb) error {
if err != nil {
return err
}
+
+ cand := snapst.Candidate
+
m.backend.Candidate(snapst.Candidate)
snapst.Sequence = append(snapst.Sequence, snapst.Candidate)
snapst.Candidate = nil
snapst.Active = true
- err = m.backend.LinkSnap(ss.MountDir())
+ newInfo, err := retrieveInfo(ss.Name, cand)
+ if err != nil {
+ return err
+ }
+
+ err = m.backend.LinkSnap(newInfo)
if err != nil {
return err
}
@@ -405,32 +543,37 @@ func (m *SnapManager) doLinkSnap(t *state.Task, _ *tomb.Tomb) error {
}
func (m *SnapManager) undoLinkSnap(t *state.Task, _ *tomb.Tomb) error {
- t.State().Lock()
+ st := t.State()
+
+ // Hold the lock for the full duration of the task here so
+ // nobody observes a world where the state engine and
+ // the file system are reporting different things.
+ st.Lock()
+ defer st.Unlock()
+
ss, snapst, err := snapSetupAndState(t)
- t.State().Unlock()
if err != nil {
return err
}
- // No need to undo "snaps" in state here. The only chance of
- // having the new state there is a working doLinkSnap call.
- newDir := ss.MountDir()
- oldDir := ""
- if len(snapst.Sequence) > 0 {
- latest := snapst.Sequence[len(snapst.Sequence)-1]
- oldDir = snap.MountDir(ss.Name, latest.Revision)
- }
- return m.backend.UndoLinkSnap(oldDir, newDir)
-}
+ // relinking of the old snap is done in the undo of unlink-current-snap
-func (m *SnapManager) doGarbageCollect(t *state.Task, _ *tomb.Tomb) error {
- t.State().Lock()
- ss, err := TaskSnapSetup(t)
- t.State().Unlock()
+ snapst.Candidate = snapst.Sequence[len(snapst.Sequence)-1]
+ snapst.Sequence = snapst.Sequence[:len(snapst.Sequence)-1]
+ snapst.Active = false
+
+ newInfo, err := retrieveInfo(ss.Name, snapst.Candidate)
if err != nil {
return err
}
pb := &TaskProgressAdapter{task: t}
- return m.backend.GarbageCollect(ss.Name, ss.Flags, pb)
+ err = m.backend.UnlinkSnap(newInfo, pb)
+ if err != nil {
+ return err
+ }
+
+ // mark as inactive
+ Set(st, ss.Name, snapst)
+ return nil
}
diff --git a/overlord/snapstate/snapmgr_test.go b/overlord/snapstate/snapmgr_test.go
index f1b8fc48ae..d537c06b13 100644
--- a/overlord/snapstate/snapmgr_test.go
+++ b/overlord/snapstate/snapmgr_test.go
@@ -42,6 +42,8 @@ type snapmgrTestSuite struct {
snapmgr *snapstate.SnapManager
fakeBackend *fakeSnappyBackend
+
+ reset func()
}
func (s *snapmgrTestSuite) settle() {
@@ -58,8 +60,6 @@ func (s *snapmgrTestSuite) SetUpTest(c *C) {
s.fakeBackend = &fakeSnappyBackend{
fakeCurrentProgress: 75,
fakeTotalProgress: 100,
-
- activeSnaps: make(map[string]*snap.Info),
}
s.state = state.New(nil)
@@ -67,17 +67,35 @@ func (s *snapmgrTestSuite) SetUpTest(c *C) {
s.snapmgr, err = snapstate.Manager(s.state)
c.Assert(err, IsNil)
s.snapmgr.AddForeignTaskHandlers()
+
+ // XXX: have just one, reset!
snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend)
snapstate.SetSnapstateBackend(s.fakeBackend)
+
+ s.reset = snapstate.ChangeRetrieveInfo(s.fakeBackend.RetrieveInfo)
}
-func verifyInstallUpdateTasks(c *C, ts *state.TaskSet) {
+func (s *snapmgrTestSuite) TearDownTest(c *C) {
+ s.reset()
+}
+
+func verifyInstallUpdateTasks(c *C, curActive bool, ts *state.TaskSet, st *state.State) {
i := 0
- c.Assert(ts.Tasks(), HasLen, 5)
+ n := 5
+ if curActive {
+ n++
+ }
+ c.Assert(ts.Tasks(), HasLen, n)
+ // all tasks are accounted
+ c.Assert(st.Tasks(), HasLen, n)
c.Assert(ts.Tasks()[i].Kind(), Equals, "download-snap")
i++
c.Assert(ts.Tasks()[i].Kind(), Equals, "mount-snap")
i++
+ if curActive {
+ c.Assert(ts.Tasks()[i].Kind(), Equals, "unlink-current-snap")
+ i++
+ }
c.Assert(ts.Tasks()[i].Kind(), Equals, "copy-snap-data")
i++
c.Assert(ts.Tasks()[i].Kind(), Equals, "setup-snap-security")
@@ -91,42 +109,46 @@ func (s *snapmgrTestSuite) TestInstallTasks(c *C) {
ts, err := snapstate.Install(s.state, "some-snap", "some-channel", 0)
c.Assert(err, IsNil)
- verifyInstallUpdateTasks(c, ts)
+ verifyInstallUpdateTasks(c, false, ts, s.state)
}
func (s *snapmgrTestSuite) TestUpdateTasks(c *C) {
- s.fakeBackend.activeSnaps["some-snap"] = &snap.Info{
- SuggestedName: "some-snap",
- }
-
s.state.Lock()
defer s.state.Unlock()
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{{OfficialName: "some-snap", Revision: 11}},
+ })
+
ts, err := snapstate.Update(s.state, "some-snap", "some-channel", 0)
c.Assert(err, IsNil)
- verifyInstallUpdateTasks(c, ts)
+ verifyInstallUpdateTasks(c, true, ts, s.state)
}
func (s *snapmgrTestSuite) TestRemoveTasks(c *C) {
- s.fakeBackend.activeSnaps["foo"] = &snap.Info{
- SuggestedName: "foo",
- }
-
s.state.Lock()
defer s.state.Unlock()
+ snapstate.Set(s.state, "foo", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{{OfficialName: "foo"}},
+ })
+
ts, err := snapstate.Remove(s.state, "foo", 0)
c.Assert(err, IsNil)
i := 0
c.Assert(ts.Tasks(), HasLen, 4)
+ // all tasks are accounted
+ c.Assert(s.state.Tasks(), HasLen, 4)
c.Assert(ts.Tasks()[i].Kind(), Equals, "unlink-snap")
i++
c.Assert(ts.Tasks()[i].Kind(), Equals, "remove-snap-security")
i++
- c.Assert(ts.Tasks()[i].Kind(), Equals, "remove-snap-data")
+ c.Assert(ts.Tasks()[i].Kind(), Equals, "clear-snap")
i++
- c.Assert(ts.Tasks()[i].Kind(), Equals, "remove-snap-files")
+ c.Assert(ts.Tasks()[i].Kind(), Equals, "discard-snap")
}
func (s *snapmgrTestSuite) TestInstallIntegration(c *C) {
@@ -134,25 +156,26 @@ func (s *snapmgrTestSuite) TestInstallIntegration(c *C) {
defer s.state.Unlock()
chg := s.state.NewChange("install", "install a snap")
- ts, err := snapstate.Install(s.state, "some-snap.mvo", "some-channel", 0)
+ ts, err := snapstate.Install(s.state, "some-snap", "some-channel", 0)
c.Assert(err, IsNil)
chg.AddAll(ts)
s.state.Unlock()
- s.settle()
defer s.snapmgr.Stop()
+ s.settle()
s.state.Lock()
// ensure all our tasks ran
c.Assert(s.fakeBackend.ops, DeepEquals, []fakeOp{
fakeOp{
op: "download",
- name: "some-snap.mvo",
+ name: "some-snap",
channel: "some-channel",
},
fakeOp{
op: "check-snap",
name: "downloaded-snap-path",
+ old: "<no-current>",
},
fakeOp{
op: "setup-snap",
@@ -162,6 +185,7 @@ func (s *snapmgrTestSuite) TestInstallIntegration(c *C) {
fakeOp{
op: "copy-data",
name: "/snap/some-snap/11",
+ old: "<no-old>",
},
fakeOp{
op: "candidate",
@@ -188,11 +212,10 @@ func (s *snapmgrTestSuite) TestInstallIntegration(c *C) {
err = task.Get("snap-setup", &ss)
c.Assert(err, IsNil)
c.Assert(ss, DeepEquals, snapstate.SnapSetup{
- Name: "some-snap",
- Revision: 11,
- Developer: "mvo",
- Channel: "some-channel",
- SnapPath: "downloaded-snap-path",
+ Name: "some-snap",
+ Revision: 11,
+ Channel: "some-channel",
+ SnapPath: "downloaded-snap-path",
})
// verify snaps in the system state
@@ -211,37 +234,40 @@ func (s *snapmgrTestSuite) TestInstallIntegration(c *C) {
}
func (s *snapmgrTestSuite) TestUpdateIntegration(c *C) {
- s.fakeBackend.activeSnaps["some-snap"] = &snap.Info{
- SideInfo: snap.SideInfo{
- OfficialName: "some-snap",
- Revision: 7,
- },
+ si := snap.SideInfo{
+ OfficialName: "some-snap",
+ Revision: 7,
}
s.state.Lock()
defer s.state.Unlock()
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{&si},
+ })
+
chg := s.state.NewChange("install", "install a snap")
- ts, err := snapstate.Update(s.state, "some-snap.mvo", "some-channel", snappy.DoInstallGC)
+ ts, err := snapstate.Update(s.state, "some-snap", "some-channel", snappy.DoInstallGC)
c.Assert(err, IsNil)
chg.AddAll(ts)
s.state.Unlock()
- s.settle()
defer s.snapmgr.Stop()
+ s.settle()
s.state.Lock()
- // ensure all our tasks ran
- c.Assert(s.fakeBackend.ops, DeepEquals, []fakeOp{
+ expected := []fakeOp{
fakeOp{
op: "download",
- name: "some-snap.mvo",
+ name: "some-snap",
channel: "some-channel",
},
fakeOp{
op: "check-snap",
name: "downloaded-snap-path",
flags: int(snappy.DoInstallGC),
+ old: "/snap/some-snap/7",
},
fakeOp{
op: "setup-snap",
@@ -250,9 +276,14 @@ func (s *snapmgrTestSuite) TestUpdateIntegration(c *C) {
revno: 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",
},
fakeOp{
op: "candidate",
@@ -266,7 +297,10 @@ func (s *snapmgrTestSuite) TestUpdateIntegration(c *C) {
op: "link-snap",
name: "/snap/some-snap/11",
},
- })
+ }
+
+ // ensure all our tasks ran
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected)
// check progress
task := ts.Tasks()[0]
@@ -279,15 +313,240 @@ func (s *snapmgrTestSuite) TestUpdateIntegration(c *C) {
err = task.Get("snap-setup", &ss)
c.Assert(err, IsNil)
c.Assert(ss, DeepEquals, snapstate.SnapSetup{
- Name: "some-snap",
- Developer: "mvo",
- Channel: "some-channel",
- Flags: int(snappy.DoInstallGC),
+ Name: "some-snap",
+ Channel: "some-channel",
+ Flags: int(snappy.DoInstallGC),
Revision: 11,
SnapPath: "downloaded-snap-path",
})
+
+ // verify snaps in the system state
+ var snapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap", &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.Sequence[0], DeepEquals, &snap.SideInfo{
+ OfficialName: "some-snap",
+ Channel: "",
+ Revision: 7,
+ })
+ c.Assert(snapst.Sequence[1], DeepEquals, &snap.SideInfo{
+ OfficialName: "some-snap",
+ Channel: "some-channel",
+ Revision: 11,
+ })
+}
+
+func (s *snapmgrTestSuite) TestUpdateUndoIntegration(c *C) {
+ si := snap.SideInfo{
+ OfficialName: "some-snap",
+ Revision: 7,
+ }
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{&si},
+ })
+
+ chg := s.state.NewChange("install", "install a snap")
+ ts, err := snapstate.Update(s.state, "some-snap", "some-channel", snappy.DoInstallGC)
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ s.fakeBackend.linkSnapFailTrigger = "/snap/some-snap/11"
+
+ s.state.Unlock()
+ defer s.snapmgr.Stop()
+ s.settle()
+ s.state.Lock()
+
+ expected := []fakeOp{
+ {
+ op: "download",
+ name: "some-snap",
+ channel: "some-channel",
+ },
+ {
+ op: "check-snap",
+ name: "downloaded-snap-path",
+ flags: int(snappy.DoInstallGC),
+ old: "/snap/some-snap/7",
+ },
+ {
+ op: "setup-snap",
+ name: "downloaded-snap-path",
+ flags: int(snappy.DoInstallGC),
+ revno: 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: "candidate",
+ sinfo: snap.SideInfo{
+ OfficialName: "some-snap",
+ Channel: "some-channel",
+ Revision: 11,
+ },
+ },
+ {
+ op: "link-snap.failed",
+ name: "/snap/some-snap/11",
+ },
+ // no unlink-snap here is expected!
+ {
+ op: "undo-copy-snap-data",
+ name: "/snap/some-snap/11",
+ },
+ {
+ op: "link-snap",
+ name: "/snap/some-snap/7",
+ },
+ {
+ op: "undo-setup-snap",
+ name: "/snap/some-snap/11",
+ },
+ }
+
+ // ensure all our tasks ran
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected)
+
+ // verify snaps in the system state
+ var snapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.Candidate, IsNil)
+ c.Assert(snapst.Sequence, HasLen, 1)
+ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ OfficialName: "some-snap",
+ Channel: "",
+ Revision: 7,
+ })
+}
+
+func (s *snapmgrTestSuite) TestUpdateTotalUndoIntegration(c *C) {
+ si := snap.SideInfo{
+ OfficialName: "some-snap",
+ Revision: 7,
+ }
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{&si},
+ })
+
+ chg := s.state.NewChange("install", "install a snap")
+ ts, err := snapstate.Update(s.state, "some-snap", "some-channel", snappy.DoInstallGC)
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ tasks := ts.Tasks()
+ last := tasks[len(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()
+
+ expected := []fakeOp{
+ {
+ op: "download",
+ name: "some-snap",
+ channel: "some-channel",
+ },
+ {
+ op: "check-snap",
+ name: "downloaded-snap-path",
+ flags: int(snappy.DoInstallGC),
+ old: "/snap/some-snap/7",
+ },
+ {
+ op: "setup-snap",
+ name: "downloaded-snap-path",
+ flags: int(snappy.DoInstallGC),
+ revno: 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: "candidate",
+ sinfo: snap.SideInfo{
+ OfficialName: "some-snap",
+ Channel: "some-channel",
+ Revision: 11,
+ },
+ },
+ {
+ op: "link-snap",
+ name: "/snap/some-snap/11",
+ },
+ // undoing everything from here down...
+ {
+ op: "unlink-snap",
+ name: "/snap/some-snap/11",
+ },
+ {
+ op: "undo-copy-snap-data",
+ name: "/snap/some-snap/11",
+ },
+ {
+ op: "link-snap",
+ name: "/snap/some-snap/7",
+ },
+ {
+ op: "undo-setup-snap",
+ name: "/snap/some-snap/11",
+ },
+ }
+
+ // ensure all our tasks ran
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected)
+
+ // verify snaps in the system state
+ var snapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap", &snapst)
+ c.Assert(err, IsNil)
+
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.Candidate, IsNil)
+ c.Assert(snapst.Sequence, HasLen, 1)
+ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ OfficialName: "some-snap",
+ Channel: "",
+ Revision: 7,
+ })
}
func makeTestSnap(c *C, snapYamlContent string) (snapFilePath string) {
@@ -318,8 +577,8 @@ version: 1.0`)
chg.AddAll(ts)
s.state.Unlock()
- s.settle()
defer s.snapmgr.Stop()
+ s.settle()
s.state.Lock()
// ensure only local install was run, i.e. first action is check-snap
@@ -340,79 +599,86 @@ version: 1.0`)
Revision: 0,
SnapPath: mockSnap,
})
+
+ // verify snaps in the system state
+ 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[0], DeepEquals, &snap.SideInfo{
+ OfficialName: "", // XXX: do we want this state of things?
+ Channel: "",
+ Revision: 0,
+ })
}
func (s *snapmgrTestSuite) TestRemoveIntegration(c *C) {
- s.fakeBackend.activeSnaps["some-snap"] = &snap.Info{
- SideInfo: snap.SideInfo{
- OfficialName: "some-name",
- Developer: "mvo",
- Revision: 7,
- },
+ si := snap.SideInfo{
+ OfficialName: "some-snap",
+ Revision: 7,
}
s.state.Lock()
defer s.state.Unlock()
+
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{&si},
+ })
+
chg := s.state.NewChange("remove", "remove a snap")
- ts, err := snapstate.Remove(s.state, "some-snap.mvo", 0)
+ ts, err := snapstate.Remove(s.state, "some-snap", 0)
c.Assert(err, IsNil)
chg.AddAll(ts)
s.state.Unlock()
- s.settle()
defer s.snapmgr.Stop()
+ s.settle()
s.state.Lock()
c.Assert(s.fakeBackend.ops, HasLen, 4)
- c.Assert(s.fakeBackend.ops, DeepEquals, []fakeOp{
+ expected := []fakeOp{
fakeOp{
- op: "can-remove",
- name: "/snap/some-snap/7",
+ op: "can-remove",
+ name: "/snap/some-snap/7",
+ active: true,
},
fakeOp{
op: "unlink-snap",
name: "/snap/some-snap/7",
},
fakeOp{
- op: "remove-snap-data",
- name: "some-snap",
- revno: 7,
+ op: "remove-snap-data",
+ name: "/snap/some-snap/7",
},
fakeOp{
op: "remove-snap-files",
name: "/snap/some-snap/7",
},
- })
+ }
+ c.Assert(s.fakeBackend.ops, DeepEquals, expected)
// verify snapSetup info
- task := ts.Tasks()[0]
+ tasks := ts.Tasks()
+ task := tasks[len(tasks)-1]
var ss snapstate.SnapSetup
err = task.Get("snap-setup", &ss)
c.Assert(err, IsNil)
c.Assert(ss, DeepEquals, snapstate.SnapSetup{
- Name: "some-snap",
- Developer: "mvo",
- Revision: 7,
+ Name: "some-snap",
+ Revision: 7,
})
-}
-
-func (s *snapmgrTestSuite) TestRollbackIntegration(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
- chg := s.state.NewChange("rollback", "rollback a snap")
- ts, err := snapstate.Rollback(s.state, "some-snap-to-rollback", "1.0")
+ // verify snaps in the system state
+ var snapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap", &snapst)
c.Assert(err, IsNil)
- chg.AddAll(ts)
- s.state.Unlock()
- s.settle()
- defer s.snapmgr.Stop()
- s.state.Lock()
-
- c.Assert(s.fakeBackend.ops[0].op, Equals, "rollback")
- c.Assert(s.fakeBackend.ops[0].name, Equals, "some-snap-to-rollback")
- c.Assert(s.fakeBackend.ops[0].rollback, Equals, "1.0")
+ c.Assert(snapst.Sequence, HasLen, 0)
+ c.Assert(snapst.Active, Equals, false)
+ c.Assert(snapst.Candidate, IsNil)
}
func (s *snapmgrTestSuite) TestActivate(c *C) {
@@ -424,8 +690,8 @@ func (s *snapmgrTestSuite) TestActivate(c *C) {
chg.AddAll(ts)
s.state.Unlock()
- s.settle()
defer s.snapmgr.Stop()
+ s.settle()
s.state.Lock()
c.Assert(s.fakeBackend.ops[0].op, Equals, "activate")
@@ -442,8 +708,8 @@ func (s *snapmgrTestSuite) TestSetInactive(c *C) {
chg.AddAll(ts)
s.state.Unlock()
- s.settle()
defer s.snapmgr.Stop()
+ s.settle()
s.state.Lock()
c.Assert(s.fakeBackend.ops[0].op, Equals, "activate")
@@ -451,9 +717,14 @@ func (s *snapmgrTestSuite) TestSetInactive(c *C) {
c.Assert(s.fakeBackend.ops[0].active, Equals, false)
}
-func (s *snapmgrTestSuite) TestInfo(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
+type snapmgrQuerySuite struct{}
+
+var _ = Suite(&snapmgrQuerySuite{})
+
+func (s *snapmgrQuerySuite) TestInfo(c *C) {
+ st := state.New(nil)
+ st.Lock()
+ defer st.Unlock()
dirs.SetRootDir(c.MkDir())
defer dirs.SetRootDir("")
@@ -470,14 +741,14 @@ description: |
Lots of text`), 0644)
c.Assert(err, IsNil)
- snapstate.Set(s.state, "name1", &snapstate.SnapState{
+ snapstate.Set(st, "name1", &snapstate.SnapState{
Sequence: []*snap.SideInfo{
{OfficialName: "name1", Revision: 11, EditedSummary: "s11"},
{OfficialName: "name1", Revision: 12, EditedSummary: "s12"},
},
})
- info, err := snapstate.Info(s.state, "name1", 11)
+ info, err := snapstate.Info(st, "name1", 11)
c.Assert(err, IsNil)
c.Check(info.Name(), Equals, "name1")
diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go
index 404609344e..f6673f081d 100644
--- a/overlord/snapstate/snapstate.go
+++ b/overlord/snapstate/snapstate.go
@@ -29,6 +29,7 @@ import (
"strings"
"github.com/ubuntu-core/snappy/i18n"
+ "github.com/ubuntu-core/snappy/logger"
"github.com/ubuntu-core/snappy/osutil"
"github.com/ubuntu-core/snappy/overlord/state"
"github.com/ubuntu-core/snappy/snap"
@@ -38,7 +39,7 @@ import (
// allow exchange in the tests
var backend managerBackend = &defaultBackend{}
-func doInstall(s *state.State, snapName, channel string, flags snappy.InstallFlags) (*state.TaskSet, error) {
+func doInstall(s *state.State, curActive bool, snapName, channel string, flags snappy.InstallFlags) (*state.TaskSet, error) {
// download
var prepare *state.Task
ss := SnapSetup{
@@ -49,58 +50,77 @@ func doInstall(s *state.State, snapName, channel string, flags snappy.InstallFla
ss.SnapPath = snapName
prepare = s.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q"), snapName))
} else {
- name, developer := snappy.SplitDeveloper(snapName)
- ss.Name = name
- ss.Developer = developer
+ ss.Name = snapName
prepare = s.NewTask("download-snap", fmt.Sprintf(i18n.G("Download snap %q"), snapName))
}
prepare.Set("snap-setup", ss)
+ tasks := []*state.Task{prepare}
+ addTask := func(t *state.Task) {
+ t.Set("snap-setup-task", prepare.ID())
+ tasks = append(tasks, t)
+ }
+
// mount
mount := s.NewTask("mount-snap", fmt.Sprintf(i18n.G("Mount snap %q"), snapName))
- mount.Set("snap-setup-task", prepare.ID())
+ addTask(mount)
mount.WaitFor(prepare)
+ precopy := mount
+
+ if curActive {
+ // unlink-current-snap (will stop services for copy-data)
+ unlink := s.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), snapName))
+ addTask(unlink)
+ unlink.WaitFor(mount)
+ precopy = unlink
+ }
- // copy-data (needs to stop services)
+ // copy-data (needs stopped services by unlink)
copyData := s.NewTask("copy-snap-data", fmt.Sprintf(i18n.G("Copy snap %q data"), snapName))
- copyData.Set("snap-setup-task", prepare.ID())
- copyData.WaitFor(mount)
+ addTask(copyData)
+ copyData.WaitFor(precopy)
// security
setupSecurity := s.NewTask("setup-snap-security", fmt.Sprintf(i18n.G("Setup snap %q security profiles"), snapName))
- setupSecurity.Set("snap-setup-task", prepare.ID())
+ addTask(setupSecurity)
setupSecurity.WaitFor(copyData)
// finalize (wrappers+current symlink)
linkSnap := s.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q available to the system"), snapName))
- linkSnap.Set("snap-setup-task", prepare.ID())
+ addTask(linkSnap)
linkSnap.WaitFor(setupSecurity)
- return state.NewTaskSet(prepare, mount, copyData, setupSecurity, linkSnap), nil
+ return state.NewTaskSet(tasks...), nil
}
// Install returns a set of tasks for installing snap.
// Note that the state must be locked by the caller.
-func Install(s *state.State, snap, channel string, flags snappy.InstallFlags) (*state.TaskSet, error) {
- name, _ := snappy.SplitDeveloper(snap)
- info := backend.ActiveSnap(name)
- if info != nil {
- return nil, fmt.Errorf("snap %q already installed", snap)
+func Install(s *state.State, name, channel string, flags snappy.InstallFlags) (*state.TaskSet, error) {
+ var snapst SnapState
+ err := Get(s, name, &snapst)
+ if err != nil && err != state.ErrNoState {
+ return nil, err
+ }
+ if snapst.Current() != nil {
+ return nil, fmt.Errorf("snap %q already installed", name)
}
- return doInstall(s, snap, channel, flags)
+ return doInstall(s, false, name, channel, flags)
}
// Update initiates a change updating a snap.
// Note that the state must be locked by the caller.
-func Update(s *state.State, snap, channel string, flags snappy.InstallFlags) (*state.TaskSet, error) {
- name, _ := snappy.SplitDeveloper(snap)
- info := backend.ActiveSnap(name)
- if info == nil {
- return nil, fmt.Errorf("cannot find snap %q", snap)
+func Update(s *state.State, name, channel string, flags snappy.InstallFlags) (*state.TaskSet, error) {
+ var snapst SnapState
+ err := Get(s, name, &snapst)
+ if err != nil && err != state.ErrNoState {
+ return nil, err
+ }
+ if snapst.Current() == nil {
+ return nil, fmt.Errorf("cannot find snap %q", name)
}
- return doInstall(s, snap, channel, flags)
+ return doInstall(s, snapst.Active, name, channel, flags)
}
// parseSnapspec parses a string like: name[.developer][=version]
@@ -118,15 +138,27 @@ func Remove(s *state.State, snapSpec string, flags snappy.RemoveFlags) (*state.T
// allow remove by version so that we can remove snaps that are
// not active
name, version := parseSnapSpec(snapSpec)
- name, developer := snappy.SplitDeveloper(name)
+
+ var snapst SnapState
+ err := Get(s, name, &snapst)
+ if err != nil && err != state.ErrNoState {
+ return nil, err
+ }
+
+ cur := snapst.Current()
+ if cur == nil {
+ return nil, fmt.Errorf("cannot find snap %q", name)
+ }
+
revision := 0
+ active := false
if version == "" {
- info := backend.ActiveSnap(name)
- if info == nil {
+ if !snapst.Active {
return nil, fmt.Errorf("cannot find active snap for %q", name)
}
- revision = info.Revision
+ revision = snapst.Current().Revision
} else {
+ // XXX: change this to use snapstate stuff
info := backend.SnapByNameAndVersion(name, version)
if info == nil {
return nil, fmt.Errorf("cannot find snap for %q and version %q", name, version)
@@ -134,46 +166,69 @@ func Remove(s *state.State, snapSpec string, flags snappy.RemoveFlags) (*state.T
revision = info.Revision
}
+ // removing active?
+ if snapst.Active && cur.Revision == revision {
+ active = true
+ }
+
+ info, err := Info(s, name, revision)
+ if err != nil {
+ return nil, err
+ }
+
ss := SnapSetup{
- Name: name,
- Developer: developer,
- Revision: revision,
- Flags: int(flags),
+ Name: name,
+ Revision: revision,
+ Flags: int(flags),
}
+
// check if this is something that can be removed
- if err := backend.CanRemove(ss.MountDir()); err != nil {
- return nil, err
+ if !backend.CanRemove(info, active) {
+ return nil, fmt.Errorf("snap %q is not removable", ss.Name)
}
// trigger remove
- unlink := s.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Deactivating %q"), snapSpec))
- unlink.Set("snap-setup", ss)
- removeSecurity := s.NewTask("remove-snap-security", fmt.Sprintf(i18n.G("Removing security profile for %q"), snapSpec))
- removeSecurity.WaitFor(unlink)
- removeSecurity.Set("snap-setup-task", unlink.ID())
+ // last task but the one holding snap-setup
+ discardSnap := s.NewTask("discard-snap", fmt.Sprintf(i18n.G("Remove snap %q from the system"), snapSpec))
+ discardSnap.Set("snap-setup", ss)
+
+ discardSnapID := discardSnap.ID()
+ tasks := ([]*state.Task)(nil)
+ var chain *state.Task
+ addNext := func(t *state.Task) {
+ if chain != nil {
+ t.WaitFor(chain)
+ }
+ if t.ID() != discardSnapID {
+ t.Set("snap-setup-task", discardSnapID)
+ }
+ tasks = append(tasks, t)
+ chain = t
+ }
+
+ if active {
+ unlink := s.NewTask("unlink-snap", fmt.Sprintf(i18n.G("Make snap %q unavailable to the system"), snapSpec))
+
+ addNext(unlink)
+ }
+
+ removeSecurity := s.NewTask("remove-snap-security", fmt.Sprintf(i18n.G("Remove security profile for snap %q"), snapSpec))
+ addNext(removeSecurity)
- removeData := s.NewTask("remove-snap-data", fmt.Sprintf(i18n.G("Removing data for %q"), snapSpec))
- removeData.Set("snap-setup-task", unlink.ID())
- removeData.WaitFor(removeSecurity)
+ clearData := s.NewTask("clear-snap", fmt.Sprintf(i18n.G("Remove data for snap %q"), snapSpec))
+ addNext(clearData)
- removeFiles := s.NewTask("remove-snap-files", fmt.Sprintf(i18n.G("Removing files for %q"), snapSpec))
- removeFiles.Set("snap-setup-task", unlink.ID())
- removeFiles.WaitFor(removeData)
+ // discard is last
+ addNext(discardSnap)
- return state.NewTaskSet(unlink, removeSecurity, removeData, removeFiles), nil
+ return state.NewTaskSet(tasks...), nil
}
// Rollback returns a set of tasks for rolling back a snap.
// Note that the state must be locked by the caller.
func Rollback(s *state.State, snap, ver string) (*state.TaskSet, error) {
- t := s.NewTask("rollback-snap", fmt.Sprintf(i18n.G("Rolling back %q"), snap))
- t.Set("snap-setup", SnapSetup{
- Name: snap,
- RollbackVersion: ver,
- })
-
- return state.NewTaskSet(t), nil
+ return nil, fmt.Errorf("rollback not implemented")
}
// Activate returns a set of tasks for activating a snap.
@@ -202,7 +257,7 @@ func Deactivate(s *state.State, snap string) (*state.TaskSet, error) {
// Retrieval functions
-func retrieveInfo(name string, si *snap.SideInfo) (*snap.Info, error) {
+func retrieveInfoImpl(name string, si *snap.SideInfo) (*snap.Info, error) {
// XXX: move some of this in snap as helper?
snapYamlFn := filepath.Join(snap.MountDir(name, si.Revision), "meta", "snap.yaml")
meta, err := ioutil.ReadFile(snapYamlFn)
@@ -223,6 +278,8 @@ func retrieveInfo(name string, si *snap.SideInfo) (*snap.Info, error) {
return info, nil
}
+var retrieveInfo = retrieveInfoImpl
+
// 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) {
@@ -285,3 +342,25 @@ func Set(s *state.State, name string, snapst *SnapState) {
snaps[name] = &raw
s.Set("snaps", snaps)
}
+
+// ActiveInfos returns information about all active snaps.
+func ActiveInfos(s *state.State) ([]*snap.Info, error) {
+ var stateMap map[string]*SnapState
+ var infos []*snap.Info
+ if err := s.Get("snaps", &stateMap); err != nil && err != state.ErrNoState {
+ return nil, err
+ }
+ for snapName, snapState := range stateMap {
+ if !snapState.Active {
+ continue
+ }
+ sideInfo := snapState.Sequence[len(snapState.Sequence)-1]
+ snapInfo, err := retrieveInfo(snapName, sideInfo)
+ if err != nil {
+ logger.Noticef("cannot retrieve info for snap %q: %s", snapName, err)
+ continue
+ }
+ infos = append(infos, snapInfo)
+ }
+ return infos, nil
+}
diff --git a/po/snappy.pot b/po/snappy.pot
index 0d4a1ceecb..64bf2a7f72 100644
--- a/po/snappy.pot
+++ b/po/snappy.pot
@@ -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-04-12 10:31+0200\n"
+ "POT-Creation-Date: 2016-04-13 21:37+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -103,10 +103,6 @@ msgid "Deactivate an installed active snap"
msgstr ""
#, c-format
-msgid "Deactivating %q"
-msgstr ""
-
-#, c-format
msgid "Disconnect %s:%s from %s:%s"
msgstr ""
@@ -185,10 +181,18 @@ msgid "Login successful"
msgstr ""
#, c-format
+msgid "Make current revision for snap %q unavailable"
+msgstr ""
+
+#, c-format
msgid "Make snap %q available to the system"
msgstr ""
#, c-format
+msgid "Make snap %q unavailable to the system"
+msgstr ""
+
+#, c-format
msgid "Mount snap %q"
msgstr ""
@@ -261,21 +265,21 @@ msgstr ""
msgid "Remove a snapp part"
msgstr ""
-#. TRANSLATORS: the %s is a pkgname
#, c-format
-msgid "Removing %s\n"
+msgid "Remove data for snap %q"
msgstr ""
#, c-format
-msgid "Removing data for %q"
+msgid "Remove security profile for snap %q"
msgstr ""
#, c-format
-msgid "Removing files for %q"
+msgid "Remove snap %q from the system"
msgstr ""
+#. TRANSLATORS: the %s is a pkgname
#, c-format
-msgid "Removing security profile for %q"
+msgid "Removing %s\n"
msgstr ""
#, c-format
@@ -288,10 +292,6 @@ msgstr ""
msgid "Rollback to a previous version of a package"
msgstr ""
-#, c-format
-msgid "Rolling back %q"
-msgstr ""
-
msgid "Runs unsupported experimental commands"
msgstr ""
diff --git a/snappy/desktop_test.go b/snappy/desktop_test.go
index 62c052e550..b485a0ac2b 100644
--- a/snappy/desktop_test.go
+++ b/snappy/desktop_test.go
@@ -104,7 +104,7 @@ Name=foo
Icon=/snap/foo/11/foo.png`)
// unlink (deactivate) removes it again
- err = UnlinkSnap(snap, nil)
+ err = UnlinkSnap(snap.Info(), nil)
c.Assert(err, IsNil)
c.Assert(osutil.FileExists(mockDesktopFilePath), Equals, false)
}
diff --git a/snappy/info_test.go b/snappy/info_test.go
index 400934c822..95ffd22b20 100644
--- a/snappy/info_test.go
+++ b/snappy/info_test.go
@@ -165,7 +165,7 @@ func (s *SnapTestSuite) TestPackageNameInstalled(c *C) {
c.Assert(ActivateSnap(snap, ag), IsNil)
c.Check(PackageNameActive("hello-snap"), Equals, true)
- c.Assert(UnlinkSnap(snap, ag), IsNil)
+ c.Assert(UnlinkSnap(snap.Info(), ag), IsNil)
c.Check(PackageNameActive("hello-snap"), Equals, false)
}
diff --git a/snappy/install_test.go b/snappy/install_test.go
index 7da5f04ce7..71474b6aeb 100644
--- a/snappy/install_test.go
+++ b/snappy/install_test.go
@@ -151,15 +151,15 @@ func (s *SnapTestSuite) TestInstallAppTwiceFails(c *C) {
var dlURL, iconURL string
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
- case "/details/foo/ch":
- io.WriteString(w, `{
+ case "/search":
+ io.WriteString(w, `{"_embedded": {"clickindex:package": [{
"package_name": "foo",
"version": "2",
"developer": "test",
"anon_download_url": "`+dlURL+`",
"download_url": "`+dlURL+`",
"icon_url": "`+iconURL+`"
-}`)
+}]}}`)
case "/dl":
snapR.Seek(0, 0)
io.Copy(w, snapR)
@@ -175,7 +175,7 @@ func (s *SnapTestSuite) TestInstallAppTwiceFails(c *C) {
dlURL = mockServer.URL + "/dl"
iconURL = mockServer.URL + "/icon"
- s.storeCfg.DetailsURI, err = url.Parse(mockServer.URL + "/details/")
+ s.storeCfg.SearchURI, err = url.Parse(mockServer.URL + "/search")
c.Assert(err, IsNil)
name, err := Install("foo", "ch", 0, &progress.NullProgress{})
@@ -203,19 +203,19 @@ func (s *SnapTestSuite) TestInstallAppPackageNameFails(c *C) {
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
- case "/details/hello-snap.potato/ch":
- io.WriteString(w, `{
+ case "/search":
+ io.WriteString(w, `{"_embedded": {"clickindex:package": [{
"developer": "potato",
"package_name": "hello-snap",
"version": "2",
"anon_download_url": "blah"
-}`)
+}]}}`)
default:
panic("unexpected url path: " + r.URL.Path)
}
}))
- s.storeCfg.DetailsURI, err = url.Parse(mockServer.URL + "/details/")
+ s.storeCfg.SearchURI, err = url.Parse(mockServer.URL + "/search")
c.Assert(err, IsNil)
c.Assert(mockServer, NotNil)
@@ -244,15 +244,15 @@ func (s *SnapTestSuite) TestUpdate(c *C) {
var dlURL, iconURL string
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
- case "/details/foo." + testDeveloper:
- io.WriteString(w, `{
+ 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)
@@ -268,7 +268,7 @@ func (s *SnapTestSuite) TestUpdate(c *C) {
dlURL = mockServer.URL + "/dl"
iconURL = mockServer.URL + "/icon"
- s.storeCfg.DetailsURI, err = url.Parse(mockServer.URL + "/details/")
+ s.storeCfg.SearchURI, err = url.Parse(mockServer.URL + "/search")
c.Assert(err, IsNil)
// bulk
diff --git a/snappy/kernel_os.go b/snappy/kernel_os.go
index 9308c9575d..85f38b67ad 100644
--- a/snappy/kernel_os.go
+++ b/snappy/kernel_os.go
@@ -122,8 +122,8 @@ func extractKernelAssets(s *snap.Info, snapf snap.File, flags InstallFlags, inte
// setNextBoot will schedule the given os or kernel snap to be used in
// the next boot
-func setNextBoot(s *Snap) error {
- if s.m.Type != snap.TypeOS && s.m.Type != snap.TypeKernel {
+func setNextBoot(s *snap.Info) error {
+ if s.Type != snap.TypeOS && s.Type != snap.TypeKernel {
return nil
}
@@ -133,13 +133,13 @@ func setNextBoot(s *Snap) error {
}
var bootvar string
- switch s.m.Type {
+ switch s.Type {
case snap.TypeOS:
bootvar = "snappy_os"
case snap.TypeKernel:
bootvar = "snappy_kernel"
}
- blobName := filepath.Base(s.Info().MountFile())
+ blobName := filepath.Base(s.MountFile())
if err := bootloader.SetBootVar(bootvar, blobName); err != nil {
return err
}
diff --git a/snappy/overlord.go b/snappy/overlord.go
index b42cbea470..5679aeb1ec 100644
--- a/snappy/overlord.go
+++ b/snappy/overlord.go
@@ -40,7 +40,7 @@ type Overlord struct {
}
// CheckSnap ensures that the snap can be installed
-func CheckSnap(snapFilePath string, flags InstallFlags, meter progress.Meter) error {
+func CheckSnap(snapFilePath string, curInfo *snap.Info, flags InstallFlags, meter progress.Meter) error {
allowGadget := (flags & AllowGadget) != 0
allowUnauth := (flags & AllowUnauthenticated) != 0
@@ -57,7 +57,7 @@ func CheckSnap(snapFilePath string, flags InstallFlags, meter progress.Meter) er
// This is done earlier in
// openSnapFile() to ensure that we do not mount/inspect
// potentially dangerous snaps
- return canInstall(s, snapf, allowGadget, meter)
+ return canInstall(s, snapf, curInfo, allowGadget, meter)
}
// SetupSnap does prepare and mount the snap for further processing
@@ -179,71 +179,39 @@ func UndoSetupSnap(s snap.PlaceInfo, meter progress.Meter) {
// and can only be used during install right now
}
-// XXX: ideally should go from Info to Info, likely we will move to something else anyway
-func currentSnap(newSnap *snap.Info) *Snap {
- currentActiveDir, _ := filepath.EvalSymlinks(filepath.Join(newSnap.MountDir(), "..", "current"))
- if currentActiveDir == "" {
- return nil
- }
-
- currentSnap, err := NewInstalledSnap(filepath.Join(currentActiveDir, "meta", "snap.yaml"))
- if err != nil {
- return nil
- }
- return currentSnap
-}
-
-func CopyData(newSnap *snap.Info, flags InstallFlags, meter progress.Meter) error {
+func CopyData(newSnap, oldSnap *snap.Info, flags InstallFlags, meter progress.Meter) error {
dataDir := newSnap.DataDir()
- // deal with the data:
- //
- // if there was a previous version, stop it
- // from being active so that it stops running and can no longer be
- // started then copy the data
- //
+ // deal with the old data or
// otherwise just create a empty data dir
- oldSnap := currentSnap(newSnap)
+
if oldSnap == nil {
return os.MkdirAll(dataDir, 0755)
}
- // we need to stop any services and make the commands unavailable
- // so that the data can be safely copied
- if err := UnlinkSnap(oldSnap, meter); err != nil {
- return err
- }
-
- return copySnapData(oldSnap.Info(), newSnap)
+ return copySnapData(oldSnap, newSnap)
}
func UndoCopyData(newInfo *snap.Info, flags InstallFlags, meter progress.Meter) {
// XXX we were copying data, assume InhibitHooks was false
- oldSnap := currentSnap(newInfo)
- if oldSnap != nil {
- // reactivate the previously inactivated snap
- if err := ActivateSnap(oldSnap, meter); err != nil {
- logger.Noticef("Setting old version back to active failed: %v", err)
- }
- }
-
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, inter interacter) error {
+func GenerateWrappers(s *snap.Info, inter interacter) error {
// add the CLI apps from the snap.yaml
- if err := addPackageBinaries(s.Info()); err != nil {
+ if err := addPackageBinaries(s); err != nil {
return err
}
// add the daemons from the snap.yaml
- if err := addPackageServices(s.Info(), inter); err != nil {
+ if err := addPackageServices(s, inter); err != nil {
return err
}
// add the desktop files
- if err := addPackageDesktopFiles(s.Info()); err != nil {
+ if err := addPackageDesktopFiles(s); err != nil {
return err
}
@@ -252,19 +220,19 @@ func GenerateWrappers(s *Snap, inter interacter) error {
// RemoveGeneratedWrappers removes the generated services, binaries, desktop
// wrappers
-func RemoveGeneratedWrappers(s *Snap, inter interacter) error {
+func RemoveGeneratedWrappers(s *snap.Info, inter interacter) error {
- err1 := removePackageBinaries(s.Info())
+ err1 := removePackageBinaries(s)
if err1 != nil {
logger.Noticef("Failed to remove binaries for %q: %v", s.Name(), err1)
}
- err2 := removePackageServices(s.Info(), inter)
+ err2 := removePackageServices(s, inter)
if err2 != nil {
logger.Noticef("Failed to remove services for %q: %v", s.Name(), err2)
}
- err3 := removePackageDesktopFiles(s.Info())
+ err3 := removePackageDesktopFiles(s)
if err3 != nil {
logger.Noticef("Failed to remove desktop files for %q: %v", s.Name(), err3)
}
@@ -272,8 +240,8 @@ func RemoveGeneratedWrappers(s *Snap, inter interacter) error {
return firstErr(err1, err2, err3)
}
-func UpdateCurrentSymlink(s *Snap, inter interacter) error {
- info := s.Info()
+// XXX: would really like not to expose this but used in daemon tests atm
+func UpdateCurrentSymlink(info *snap.Info, inter interacter) error {
mountDir := info.MountDir()
currentActiveSymlink := filepath.Join(mountDir, "..", "current")
@@ -299,23 +267,15 @@ func UpdateCurrentSymlink(s *Snap, inter interacter) error {
// FIXME: create {Os,Kernel}Snap type instead of adding special
// cases here
- if err := setNextBoot(s); err != nil {
+ if err := setNextBoot(info); err != nil {
return err
}
return os.Symlink(filepath.Base(dataDir), currentDataSymlink)
}
-func UndoUpdateCurrentSymlink(oldSnap, newSnap *Snap, inter interacter) error {
- if err := removeCurrentSymlink(newSnap, inter); err != nil {
- return err
- }
- return UpdateCurrentSymlink(oldSnap, inter)
-}
-
-func removeCurrentSymlink(s *Snap, inter interacter) error {
+func removeCurrentSymlink(info snap.PlaceInfo, inter interacter) error {
var err1, err2 error
- info := s.Info()
// the snap "current" symlink
currentActiveSymlink := filepath.Join(info.MountDir(), "..", "current")
@@ -371,6 +331,10 @@ func ActivateSnap(s *Snap, inter interacter) error {
// security setup was done here!
+ return LinkSnap(s.Info(), inter)
+}
+
+func LinkSnap(s *snap.Info, inter interacter) error {
if err := GenerateWrappers(s, inter); err != nil {
return err
}
@@ -379,8 +343,7 @@ func ActivateSnap(s *Snap, inter interacter) error {
}
// UnlinkSnap deactivates the given active snap.
-func UnlinkSnap(s *Snap, inter interacter) error {
- info := s.Info()
+func UnlinkSnap(info *snap.Info, inter interacter) error {
mountDir := info.MountDir()
currentSymlink := filepath.Join(mountDir, "..", "current")
@@ -396,12 +359,12 @@ func UnlinkSnap(s *Snap, inter interacter) error {
}
// remove generated services, binaries, security policy
- err1 := RemoveGeneratedWrappers(s, inter)
+ err1 := RemoveGeneratedWrappers(info, inter)
// removing security setup move here!
// and finally remove current symlink
- err2 := removeCurrentSymlink(s, inter)
+ err2 := removeCurrentSymlink(info, inter)
// FIXME: aggregate errors instead
return firstErr(err1, err2)
@@ -419,7 +382,16 @@ func (o *Overlord) Install(snapFilePath string, flags InstallFlags, meter progre
//
// 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) {
- if err := CheckSnap(snapFilePath, flags, meter); err != nil {
+ var oldInfo *snap.Info
+
+ if sideInfo != nil {
+ oldSnap := ActiveSnapByName(sideInfo.OfficialName)
+ if oldSnap != nil {
+ oldInfo = oldSnap.Info()
+ }
+ }
+
+ if err := CheckSnap(snapFilePath, oldInfo, flags, meter); err != nil {
return nil, err
}
@@ -442,10 +414,26 @@ func (o *Overlord) InstallWithSideInfo(snapFilePath string, sideInfo *snap.SideI
}
// we need this for later
- oldSnap := currentSnap(newInfo)
+
+ if oldInfo != nil {
+ // 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)
+ defer func() {
+ if err != nil {
+ if err := LinkSnap(oldInfo, meter); err != nil {
+ logger.Noticef("When linking old revision: %v", newInfo.Name(), err)
+ }
+ }
+ }()
+ if err != nil {
+ return nil, err
+ }
+ }
// deal with the data
- err = CopyData(newInfo, flags, meter)
+ err = CopyData(newInfo, oldInfo, flags, meter)
defer func() {
if err != nil {
UndoCopyData(newInfo, flags, meter)
@@ -462,19 +450,11 @@ func (o *Overlord) InstallWithSideInfo(snapFilePath string, sideInfo *snap.SideI
return newInfo, nil
}
- // if get this far we know the snap is actually mounted.
- // XXX: use infos further but anyway this is going away mostly
- // once we simplify u-d-f
- newSnap, err := NewInstalledSnap(filepath.Join(newInfo.MountDir(), "meta", "snap.yaml"))
- if err != nil {
- return nil, err
- }
-
- err = ActivateSnap(newSnap, meter)
+ err = LinkSnap(newInfo, meter)
defer func() {
- if err != nil && oldSnap != nil {
- if err := ActivateSnap(oldSnap, meter); err != nil {
- logger.Noticef("When setting old %s version back to active: %v", newSnap.Name(), err)
+ if err != nil {
+ if err := UnlinkSnap(newInfo, meter); err != nil {
+ logger.Noticef("When unlinking failed new snap revision: %v", newInfo.Name(), err)
}
}
}()
@@ -482,11 +462,11 @@ func (o *Overlord) InstallWithSideInfo(snapFilePath string, sideInfo *snap.SideI
return nil, err
}
- return newSnap.Info(), nil
+ return newInfo, nil
}
// CanInstall checks whether the Snap passes a series of tests required for installation
-func canInstall(s *snap.Info, snapf snap.File, allowGadget bool, inter interacter) error {
+func canInstall(s *snap.Info, snapf snap.File, curInfo *snap.Info, allowGadget bool, inter interacter) error {
// verify we have a valid architecture
if !arch.IsSupportedArchitecture(s.Architectures) {
return &ErrArchitectureNotSupported{s.Architectures}
@@ -505,14 +485,7 @@ func canInstall(s *snap.Info, snapf snap.File, allowGadget bool, inter interacte
}
}
- // XXX: can be cleaner later
- currSnap := currentSnap(s)
- var curr *snap.Info
- if currSnap != nil {
- curr = currSnap.Info()
- }
-
- if err := checkLicenseAgreement(s, snapf, curr, inter); err != nil {
+ if err := checkLicenseAgreement(s, snapf, curInfo, inter); err != nil {
return err
}
@@ -552,20 +525,20 @@ func checkLicenseAgreement(s *snap.Info, snapf snap.File, cur *snap.Info, ag agr
return nil
}
-func CanRemove(s *Snap) 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.
- if s.m.Type == snap.TypeGadget && s.IsActive() {
+ if s.Type == snap.TypeGadget && active {
return false
}
// You never want to remove an active kernel or OS
- if (s.m.Type == snap.TypeKernel || s.m.Type == snap.TypeOS) && s.IsActive() {
+ if (s.Type == snap.TypeKernel || s.Type == snap.TypeOS) && active {
return false
}
- if IsBuiltInSoftware(s.Name()) && s.IsActive() {
+ if IsBuiltInSoftware(s.Name()) && active {
return false
}
return true
@@ -617,11 +590,11 @@ func RemoveSnapFiles(s snap.PlaceInfo, meter progress.Meter) error {
//
// It returns an error on failure
func (o *Overlord) Uninstall(s *Snap, meter progress.Meter) error {
- if !CanRemove(s) {
+ if !CanRemove(s.Info(), s.IsActive()) {
return ErrPackageNotRemovable
}
- if err := UnlinkSnap(s, meter); err != nil && err != ErrSnapNotActive {
+ if err := UnlinkSnap(s.Info(), meter); err != nil && err != ErrSnapNotActive {
return err
}
@@ -639,14 +612,14 @@ func (o *Overlord) SetActive(s *Snap, active bool, meter progress.Meter) error {
if active {
// deactivate current first
if current := ActiveSnapByName(s.Name()); current != nil {
- if err := UnlinkSnap(current, meter); err != nil {
+ if err := UnlinkSnap(current.Info(), meter); err != nil {
return err
}
}
return ActivateSnap(s, meter)
}
- return UnlinkSnap(s, meter)
+ return UnlinkSnap(s.Info(), meter)
}
// Configure configures the given snap
diff --git a/snappy/overlord_test.go b/snappy/overlord_test.go
index abfabe592e..45098839af 100644
--- a/snappy/overlord_test.go
+++ b/snappy/overlord_test.go
@@ -195,12 +195,12 @@ license-version: 2
pkgdir := filepath.Dir(filepath.Dir(yamlFile))
c.Assert(os.MkdirAll(filepath.Join(pkgdir, ".click", "info"), 0755), IsNil)
c.Assert(ioutil.WriteFile(filepath.Join(pkgdir, ".click", "info", "foox."+testDeveloper+".manifest"), []byte(`{"name": "foox"}`), 0644), IsNil)
- snap, err := NewInstalledSnap(yamlFile)
+ installedSnap, err := NewInstalledSnap(yamlFile)
c.Assert(err, IsNil)
- c.Assert(ActivateSnap(snap, ag), IsNil)
+ c.Assert(ActivateSnap(installedSnap, ag), IsNil)
pkg := makeTestSnapPackage(c, yaml+"version: 2")
- _, err = (&Overlord{}).Install(pkg, 0, ag)
+ _, err = (&Overlord{}).InstallWithSideInfo(pkg, &snap.SideInfo{OfficialName: "foox"}, 0, ag)
c.Assert(err, Equals, nil)
c.Check(IsLicenseNotAccepted(err), Equals, false)
c.Check(ag.intro, Equals, "")
@@ -376,7 +376,7 @@ func (s *SnapTestSuite) TestClickSetActive(c *C) {
c.Assert(snaps[1].IsActive(), Equals, true)
// deactivate v2
- err = UnlinkSnap(snaps[1], nil)
+ err = UnlinkSnap(snaps[1].Info(), nil)
// set v1 active
err = ActivateSnap(snaps[0], nil)
snaps, err = (&Overlord{}).Installed()
diff --git a/snappy/snapp_test.go b/snappy/snapp_test.go
index 216ca1f9d8..b8bff9649a 100644
--- a/snappy/snapp_test.go
+++ b/snappy/snapp_test.go
@@ -78,9 +78,8 @@ func (s *SnapTestSuite) SetUpTest(c *C) {
// do not attempt to hit the real store servers in the tests
nowhereURI, _ := url.Parse("")
s.storeCfg = &store.SnapUbuntuStoreConfig{
- SearchURI: nowhereURI,
- DetailsURI: nowhereURI,
- BulkURI: nowhereURI,
+ SearchURI: nowhereURI,
+ BulkURI: nowhereURI,
}
storeConfig = s.storeCfg
@@ -236,7 +235,7 @@ func (s *SnapTestSuite) TestUbuntuStoreRepositoryUpdates(c *C) {
func (s *SnapTestSuite) TestUbuntuStoreRepositoryUpdatesNoSnaps(c *C) {
var err error
- s.storeCfg.DetailsURI, err = url.Parse("https://some-uri")
+ s.storeCfg.SearchURI, err = url.Parse("https://some-uri")
c.Assert(err, IsNil)
repo := store.NewUbuntuStoreSnapRepository(s.storeCfg, "")
c.Assert(repo, NotNil)
@@ -513,7 +512,7 @@ type: gadget
c.Assert(err, IsNil)
makeSnapActive(snapYamlFn)
- s.storeCfg.DetailsURI, err = url.Parse(mockServer.URL)
+ s.storeCfg.SearchURI, err = url.Parse(mockServer.URL)
c.Assert(err, IsNil)
repo := NewConfiguredUbuntuStoreSnapRepository()
c.Assert(repo, NotNil)
diff --git a/snappy/undo_test.go b/snappy/undo_test.go
index 42453e2dc0..4f0ce5a2f8 100644
--- a/snappy/undo_test.go
+++ b/snappy/undo_test.go
@@ -137,11 +137,14 @@ 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(), 0, &s.meter)
+ 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, "*"))
@@ -169,7 +172,7 @@ apps:
sn, err := NewInstalledSnap(yaml)
c.Assert(err, IsNil)
- err = GenerateWrappers(sn, &s.meter)
+ err = GenerateWrappers(sn.Info(), &s.meter)
c.Assert(err, IsNil)
l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
@@ -180,7 +183,7 @@ apps:
c.Assert(l, HasLen, 1)
// undo via remove
- err = RemoveGeneratedWrappers(sn, &s.meter)
+ err = RemoveGeneratedWrappers(sn.Info(), &s.meter)
l, err = filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
c.Assert(err, IsNil)
c.Assert(l, HasLen, 0)
@@ -208,7 +211,7 @@ version: 2.0
v2, err := NewInstalledSnap(v2yaml)
c.Assert(err, IsNil)
- err = UpdateCurrentSymlink(v2, &s.meter)
+ err = UpdateCurrentSymlink(v2.Info(), &s.meter)
c.Assert(err, IsNil)
v1MountDir := v1.Info().MountDir()
@@ -224,8 +227,8 @@ version: 2.0
c.Assert(err, IsNil)
c.Assert(currentDataDir, Matches, `.*/22`)
- // undo sets the symlink back
- err = UndoUpdateCurrentSymlink(v1, v2, &s.meter)
+ // 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)
diff --git a/store/auth.go b/store/auth.go
index f606a37232..c10e385dab 100644
--- a/store/auth.go
+++ b/store/auth.go
@@ -23,12 +23,7 @@ import (
"encoding/json"
"fmt"
"net/http"
- "os"
- "path/filepath"
"strings"
-
- "github.com/ubuntu-core/snappy/oauth"
- "github.com/ubuntu-core/snappy/osutil"
)
var (
@@ -36,20 +31,13 @@ var (
// MyAppsPackageAccessAPI points to MyApps endpoint to get a package access macaroon
MyAppsPackageAccessAPI = myappsAPIBase + "/acl/package_access/"
ubuntuoneAPIBase = authURL()
- ubuntuoneOauthAPI = ubuntuoneAPIBase + "/tokens/oauth"
// UbuntuoneDischargeAPI points to SSO endpoint to discharge a macaroon
UbuntuoneDischargeAPI = ubuntuoneAPIBase + "/tokens/discharge"
)
-// StoreToken contains the personal token to access the store
-type StoreToken struct {
- OpenID string `json:"openid"`
- TokenName string `json:"token_name"`
- DateUpdated string `json:"date_updated"`
- DateCreated string `json:"date_created"`
- Href string `json:"href"`
-
- oauth.Token
+// Authenticator interface to set required authorization headers for requests to the store
+type Authenticator interface {
+ Authenticate(r *http.Request)
}
type ssoMsg struct {
@@ -67,103 +55,6 @@ func httpStatusCodeClientError(httpStatusCode int) bool {
return httpStatusCode/100 == 4
}
-// RequestStoreToken requests a token for accessing the ubuntu store
-func RequestStoreToken(username, password, tokenName, otp string) (*StoreToken, error) {
- data := map[string]string{
- "email": username,
- "password": password,
- "token_name": tokenName,
- }
- if otp != "" {
- data["otp"] = otp
- }
- jsonData, err := json.Marshal(data)
- if err != nil {
- return nil, err
- }
-
- req, err := http.NewRequest("POST", ubuntuoneOauthAPI, strings.NewReader(string(jsonData)))
- if err != nil {
- return nil, err
- }
- req.Header.Set("content-type", "application/json")
-
- client := &http.Client{}
- resp, err := client.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
-
- // check return code, error on 4xx and anything !200
- switch {
- case httpStatusCodeClientError(resp.StatusCode):
- // we get a error code, check json details
- var msg ssoMsg
- dec := json.NewDecoder(resp.Body)
- if err := dec.Decode(&msg); err != nil {
- return nil, err
- }
- if msg.Code == "TWOFACTOR_REQUIRED" {
- return nil, ErrAuthenticationNeeds2fa
- }
-
- // XXX: maybe return msg.Message as well to the client?
- return nil, ErrInvalidCredentials
-
- case !httpStatusCodeSuccess(resp.StatusCode):
- // unexpected result, bail
- return nil, fmt.Errorf("failed to get store token: %v (%v)", resp.StatusCode, resp)
- }
-
- var token StoreToken
- dec := json.NewDecoder(resp.Body)
- if err := dec.Decode(&token); err != nil {
- return nil, err
- }
-
- return &token, nil
-}
-
-// FIXME: maybe use a name in /var/lib/users/$user/snappy instead?
-// as sabdfl prefers $HOME to be for user created data?
-func storeTokenFilename() string {
- homeDir, _ := osutil.CurrentHomeDir()
- return filepath.Join(homeDir, "snaps", "snappy", "auth", "sso.json")
-}
-
-// WriteStoreToken takes the token and stores it on the filesystem for
-// later reading via ReadStoreToken()
-func WriteStoreToken(token StoreToken) error {
- targetFile := storeTokenFilename()
- if err := os.MkdirAll(filepath.Dir(targetFile), 0750); err != nil {
- return err
- }
- outStr, err := json.MarshalIndent(token, "", " ")
- if err != nil {
- return nil
- }
-
- return osutil.AtomicWriteFile(targetFile, []byte(outStr), 0600, 0)
-}
-
-// ReadStoreToken reads a token previously write via WriteStoreToken
-func ReadStoreToken() (*StoreToken, error) {
- targetFile := storeTokenFilename()
- f, err := os.Open(targetFile)
- if err != nil {
- return nil, err
- }
-
- var readStoreToken StoreToken
- dec := json.NewDecoder(f)
- if err := dec.Decode(&readStoreToken); err != nil {
- return nil, err
- }
-
- return &readStoreToken, nil
-}
-
// RequestPackageAccessMacaroon requests a macaroon for accessing package data from the ubuntu store.
func RequestPackageAccessMacaroon() (string, error) {
const errorPrefix = "cannot get package access macaroon from store: "
diff --git a/store/auth_test.go b/store/auth_test.go
index 559099d9c1..e73a772119 100644
--- a/store/auth_test.go
+++ b/store/auth_test.go
@@ -21,28 +21,16 @@ package store
import (
"io"
- "io/ioutil"
"net/http"
"net/http/httptest"
- "os"
- "path/filepath"
-
- "github.com/ubuntu-core/snappy/oauth"
- "github.com/ubuntu-core/snappy/osutil"
. "gopkg.in/check.v1"
)
-type authTestSuite struct {
- tempdir string
-}
+type authTestSuite struct{}
var _ = Suite(&authTestSuite{})
-func (s *authTestSuite) SetUpTest(c *C) {
- s.tempdir = c.MkDir()
-}
-
const mockStoreInvalidLoginCode = 401
const mockStoreInvalidLogin = `
{
@@ -61,20 +49,6 @@ const mockStoreNeeds2fa = `
}
`
-const mockStoreReturnToken = `
-{
- "openid": "the-open-id-string-that-is-also-the-consumer-key-in-our-store",
- "token_name": "some-token-name",
- "date_updated": "2015-02-27T15:00:55.062",
- "token_key": "the-token-key",
- "consumer_secret": "the-consumer-secret",
- "href": "/api/v2/tokens/oauth/something",
- "date_created": "2015-02-27T14:54:30.863",
- "consumer_key": "the-consumer-key",
- "token_secret": "the-token-secret"
-}
-`
-
const mockStoreReturnMacaroon = `
{
"macaroon": "the-root-macaroon-serialized-data"
@@ -89,48 +63,6 @@ const mockStoreReturnDischarge = `
const mockStoreReturnNoMacaroon = `{}`
-func (s *authTestSuite) TestRequestStoreToken(c *C) {
- mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- io.WriteString(w, mockStoreReturnToken)
- }))
- c.Assert(mockServer, NotNil)
- defer mockServer.Close()
- ubuntuoneOauthAPI = mockServer.URL + "/token/oauth"
-
- token, err := RequestStoreToken("guy@example.com", "passwd", "some-token-name", "")
- c.Assert(err, IsNil)
- c.Assert(token.TokenKey, Equals, "the-token-key")
- c.Assert(token.TokenSecret, Equals, "the-token-secret")
- c.Assert(token.ConsumerSecret, Equals, "the-consumer-secret")
- c.Assert(token.ConsumerKey, Equals, "the-consumer-key")
-}
-
-func (s *authTestSuite) TestRequestStoreTokenNeeds2fa(c *C) {
- mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(mockStoreNeeds2faHTTPCode)
- io.WriteString(w, mockStoreNeeds2fa)
- }))
- c.Assert(mockServer, NotNil)
- defer mockServer.Close()
- ubuntuoneOauthAPI = mockServer.URL + "/token/oauth"
-
- _, err := RequestStoreToken("foo@example.com", "passwd", "some-token-name", "")
- c.Assert(err, Equals, ErrAuthenticationNeeds2fa)
-}
-
-func (s *authTestSuite) TestRequestStoreTokenInvalidLogin(c *C) {
- mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(mockStoreInvalidLoginCode)
- io.WriteString(w, mockStoreInvalidLogin)
- }))
- c.Assert(mockServer, NotNil)
- defer mockServer.Close()
- ubuntuoneOauthAPI = mockServer.URL + "/token/oauth"
-
- _, err := RequestStoreToken("foo@example.com", "passwd", "some-token-name", "")
- c.Assert(err, Equals, ErrInvalidCredentials)
-}
-
func (s *authTestSuite) TestRequestPackageAccessMacaroon(c *C) {
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, mockStoreReturnMacaroon)
@@ -228,43 +160,3 @@ func (s *authTestSuite) TestDischargeAuthCaveatError(c *C) {
c.Assert(err, ErrorMatches, "cannot get discharge macaroon from store: server returned status 500")
c.Assert(discharge, Equals, "")
}
-
-func (s *authTestSuite) TestWriteStoreToken(c *C) {
- os.Setenv("HOME", s.tempdir)
- mockStoreToken := StoreToken{TokenName: "meep"}
- err := WriteStoreToken(mockStoreToken)
-
- c.Assert(err, IsNil)
- outFile := filepath.Join(s.tempdir, "snaps", "snappy", "auth", "sso.json")
- c.Assert(osutil.FileExists(outFile), Equals, true)
- content, err := ioutil.ReadFile(outFile)
- c.Assert(err, IsNil)
- c.Assert(string(content), Equals, `{
- "openid": "",
- "token_name": "meep",
- "date_updated": "",
- "date_created": "",
- "href": "",
- "token_key": "",
- "token_secret": "",
- "consumer_secret": "",
- "consumer_key": ""
-}`)
-}
-
-func (s *authTestSuite) TestReadStoreToken(c *C) {
- os.Setenv("HOME", s.tempdir)
- mockStoreToken := StoreToken{
- TokenName: "meep",
- Token: oauth.Token{
- TokenKey: "token-key",
- TokenSecret: "token-secret",
- },
- }
- err := WriteStoreToken(mockStoreToken)
- c.Assert(err, IsNil)
-
- readToken, err := ReadStoreToken()
- c.Assert(err, IsNil)
- c.Assert(readToken, DeepEquals, &mockStoreToken)
-}
diff --git a/store/snap_remote_repo.go b/store/snap_remote_repo.go
index a0f02858d8..c1b8189802 100644
--- a/store/snap_remote_repo.go
+++ b/store/snap_remote_repo.go
@@ -36,7 +36,7 @@ import (
"github.com/ubuntu-core/snappy/arch"
"github.com/ubuntu-core/snappy/asserts"
- "github.com/ubuntu-core/snappy/oauth"
+ "github.com/ubuntu-core/snappy/logger"
"github.com/ubuntu-core/snappy/progress"
"github.com/ubuntu-core/snappy/release"
"github.com/ubuntu-core/snappy/snap"
@@ -72,7 +72,6 @@ func infoFromRemote(d snapDetails) *snap.Info {
// SnapUbuntuStoreConfig represents the configuration to access the snap store
type SnapUbuntuStoreConfig struct {
SearchURI *url.URL
- DetailsURI *url.URL
BulkURI *url.URL
AssertionsURI *url.URL
}
@@ -81,7 +80,6 @@ type SnapUbuntuStoreConfig struct {
type SnapUbuntuStoreRepository struct {
storeID string
searchURI *url.URL
- detailsURI *url.URL
bulkURI *url.URL
assertionsURI *url.URL
// reused http client
@@ -163,11 +161,6 @@ func init() {
v.Set("fields", strings.Join(getStructFields(snapDetails{}), ","))
defaultConfig.SearchURI.RawQuery = v.Encode()
- defaultConfig.DetailsURI, err = storeBaseURI.Parse("package/")
- if err != nil {
- panic(err)
- }
-
defaultConfig.BulkURI, err = storeBaseURI.Parse("click-metadata")
if err != nil {
panic(err)
@@ -201,28 +194,12 @@ func NewUbuntuStoreSnapRepository(cfg *SnapUbuntuStoreConfig, storeID string) *S
return &SnapUbuntuStoreRepository{
storeID: storeID,
searchURI: cfg.SearchURI,
- detailsURI: cfg.DetailsURI,
bulkURI: cfg.BulkURI,
assertionsURI: cfg.AssertionsURI,
client: &http.Client{},
}
}
-// setAuthHeader sets the authorization header.
-func setAuthHeader(req *http.Request, token *StoreToken) {
- if token != nil {
- req.Header.Set("Authorization", oauth.MakePlaintextSignature(&token.Token))
- }
-}
-
-// configureAuthHeader optionally sets the auth header if a token is available.
-func configureAuthHeader(req *http.Request) {
- ssoToken, err := ReadStoreToken()
- if err == nil {
- setAuthHeader(req, ssoToken)
- }
-}
-
// small helper that sets the correct http headers for the ubuntu store
func (s *SnapUbuntuStoreRepository) applyUbuntuStoreHeaders(req *http.Request, accept string) {
if accept == "" {
@@ -239,12 +216,6 @@ func (s *SnapUbuntuStoreRepository) applyUbuntuStoreHeaders(req *http.Request, a
}
}
-// small helper that sets the correct http headers for a store request including auth
-func (s *SnapUbuntuStoreRepository) configureStoreReq(req *http.Request, accept string) {
- configureAuthHeader(req)
- s.applyUbuntuStoreHeaders(req, accept)
-}
-
// read all the available metadata from the store response and cache
func (s *SnapUbuntuStoreRepository) checkStoreResponse(resp *http.Response) {
suggestedCurrency := resp.Header.Get("X-Suggested-Currency")
@@ -259,18 +230,20 @@ func (s *SnapUbuntuStoreRepository) checkStoreResponse(resp *http.Response) {
// Snap returns the snap.Info for the store hosted snap with the given name or an error.
func (s *SnapUbuntuStoreRepository) Snap(name, channel string) (*snap.Info, error) {
- url, err := s.detailsURI.Parse(path.Join(name, channel))
- if err != nil {
- return nil, err
- }
+ u := *s.searchURI // make a copy, so we can mutate it
- req, err := http.NewRequest("GET", url.String(), nil)
+ q := u.Query()
+ q.Set("q", "package_name:\""+name+"\"")
+ u.RawQuery = q.Encode()
+
+ req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, err
}
// set headers
- s.configureStoreReq(req, "")
+ s.applyUbuntuStoreHeaders(req, "")
+ req.Header.Set("X-Ubuntu-Device-Channel", channel)
resp, err := s.client.Do(req)
if err != nil {
@@ -283,19 +256,28 @@ func (s *SnapUbuntuStoreRepository) Snap(name, channel string) (*snap.Info, erro
case resp.StatusCode == 404:
return nil, ErrSnapNotFound
case resp.StatusCode != 200:
- return nil, fmt.Errorf("SnapUbuntuStoreRepository: unexpected HTTP status code %d while looking forsnap %q/%q", resp.StatusCode, name, channel)
+ return nil, fmt.Errorf("SnapUbuntuStoreRepository: unexpected HTTP status code %d while looking for snap %q/%q", resp.StatusCode, name, channel)
}
// and decode json
- var detailsData snapDetails
+ var searchData searchResults
dec := json.NewDecoder(resp.Body)
- if err := dec.Decode(&detailsData); err != nil {
+ if err := dec.Decode(&searchData); err != nil {
return nil, err
}
+ switch len(searchData.Payload.Packages) {
+ case 0:
+ return nil, ErrSnapNotFound
+ case 1:
+ // whee
+ default:
+ logger.Noticef("expected at most one result from this search, got %d. Using first one.", len(searchData.Payload.Packages))
+ }
+
s.checkStoreResponse(resp)
- return infoFromRemote(detailsData), nil
+ return infoFromRemote(searchData.Payload.Packages[0]), nil
}
// FindSnaps finds (installable) snaps from the store, matching the
@@ -319,8 +301,8 @@ func (s *SnapUbuntuStoreRepository) FindSnaps(searchTerm string, channel string)
}
// set headers
- s.configureStoreReq(req, "")
- req.Header.Set("X-Ubuntu-Device-Channnel", channel)
+ s.applyUbuntuStoreHeaders(req, "")
+ req.Header.Set("X-Ubuntu-Device-Channel", channel)
resp, err := s.client.Do(req)
if err != nil {
@@ -359,7 +341,7 @@ func (s *SnapUbuntuStoreRepository) Updates(installed []string) (snaps []*snap.I
// set headers
// the updates call is a special snowflake right now
// (see LP: #1427155)
- s.configureStoreReq(req, "application/json")
+ s.applyUbuntuStoreHeaders(req, "application/json")
resp, err := s.client.Do(req)
if err != nil {
@@ -401,10 +383,8 @@ func (s *SnapUbuntuStoreRepository) Download(remoteSnap *snap.Info, pbar progres
}
}()
- ssoToken, _ := ReadStoreToken()
-
url := remoteSnap.AnonDownloadURL
- if url == "" || ssoToken != nil {
+ if url == "" {
url = remoteSnap.DownloadURL
}
@@ -412,7 +392,6 @@ func (s *SnapUbuntuStoreRepository) Download(remoteSnap *snap.Info, pbar progres
if err != nil {
return "", err
}
- setAuthHeader(req, ssoToken)
s.applyUbuntuStoreHeaders(req, "")
if err := download(remoteSnap.Name(), w, req, pbar); err != nil {
@@ -467,7 +446,6 @@ func (s *SnapUbuntuStoreRepository) Assertion(assertType *asserts.AssertionType,
return nil, err
}
- configureAuthHeader(req)
req.Header.Set("Accept", asserts.MediaType)
resp, err := s.client.Do(req)
diff --git a/store/snap_remote_repo_test.go b/store/snap_remote_repo_test.go
index 7331cd51df..982997fbe7 100644
--- a/store/snap_remote_repo_test.go
+++ b/store/snap_remote_repo_test.go
@@ -83,33 +83,34 @@ func (t *remoteRepoTestSuite) TestDownloadOK(c *C) {
c.Assert(string(content), Equals, "I was downloaded")
}
-func (t *remoteRepoTestSuite) TestAuthenticatedDownloadDoesNotUseAnonURL(c *C) {
- home := os.Getenv("HOME")
- os.Setenv("HOME", c.MkDir())
- defer os.Setenv("HOME", home)
- mockStoreToken := StoreToken{TokenName: "meep"}
- err := WriteStoreToken(mockStoreToken)
- c.Assert(err, IsNil)
-
- download = func(name string, w io.Writer, req *http.Request, pbar progress.Meter) error {
- c.Check(req.URL.String(), Equals, "AUTH-URL")
- w.Write([]byte("I was downloaded"))
- return nil
- }
-
- snap := &snap.Info{}
- snap.OfficialName = "foo"
- snap.AnonDownloadURL = "anon-url"
- snap.DownloadURL = "AUTH-URL"
-
- path, err := t.store.Download(snap, nil)
- c.Assert(err, IsNil)
- defer os.Remove(path)
-
- content, err := ioutil.ReadFile(path)
- c.Assert(err, IsNil)
- c.Assert(string(content), Equals, "I was downloaded")
-}
+// TODO: re-enable this test once authenticator support is in place
+// func (t *remoteRepoTestSuite) TestAuthenticatedDownloadDoesNotUseAnonURL(c *C) {
+// home := os.Getenv("HOME")
+// os.Setenv("HOME", c.MkDir())
+// defer os.Setenv("HOME", home)
+// mockStoreToken := StoreToken{TokenName: "meep"}
+// err := WriteStoreToken(mockStoreToken)
+// c.Assert(err, IsNil)
+//
+// download = func(name string, w io.Writer, req *http.Request, pbar progress.Meter) error {
+// c.Check(req.URL.String(), Equals, "AUTH-URL")
+// w.Write([]byte("I was downloaded"))
+// return nil
+// }
+//
+// snap := &snap.Info{}
+// snap.OfficialName = "foo"
+// snap.AnonDownloadURL = "anon-url"
+// snap.DownloadURL = "AUTH-URL"
+//
+// path, err := t.store.Download(snap, nil)
+// c.Assert(err, IsNil)
+// defer os.Remove(path)
+//
+// content, err := ioutil.ReadFile(path)
+// c.Assert(err, IsNil)
+// c.Assert(string(content), Equals, "I was downloaded")
+// }
func (t *remoteRepoTestSuite) TestDownloadFails(c *C) {
var tmpfile *os.File
@@ -157,23 +158,15 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryHeaders(c *C) {
req, err := http.NewRequest("GET", "http://example.com", nil)
c.Assert(err, IsNil)
- t.store.configureStoreReq(req, "")
+ t.store.applyUbuntuStoreHeaders(req, "")
c.Assert(req.Header.Get("X-Ubuntu-Release"), Equals, release.String())
c.Check(req.Header.Get("Accept"), Equals, "application/hal+json")
- t.store.configureStoreReq(req, "application/json")
+ t.store.applyUbuntuStoreHeaders(req, "application/json")
c.Check(req.Header.Get("Accept"), Equals, "application/json")
c.Assert(req.Header.Get("Authorization"), Equals, "")
-
- mockStoreToken := StoreToken{TokenName: "meep"}
- err = WriteStoreToken(mockStoreToken)
- c.Assert(err, IsNil)
-
- t.store.configureStoreReq(req, "")
-
- c.Assert(req.Header.Get("Authorization"), Matches, "OAuth .*")
}
const (
@@ -182,9 +175,39 @@ const (
)
/* acquired via
- curl -s -H "accept: application/hal+json" -H "X-Ubuntu-Release: 15.04-core" https://search.apps.ubuntu.com/api/v1/package/8nzc1x4iim2xj1g2ul64.chipaca | python -m json.tool
+curl -s -H "accept: application/hal+json" -H "X-Ubuntu-Release: rolling-core" -H "X-Ubuntu-Device-Channel: edge" 'https://search.apps.ubuntu.com/api/v1/search?q=package_name:"hello-world"&fields=publisher,package_name,origin,description,summary,title,icon_url,prices,content,ratings_average,version,anon_download_url,download_url,download_sha512,last_updated,binary_filesize,support_url,revision' | python -m json.tool
*/
const MockDetailsJSON = `{
+ "_embedded": {
+ "clickindex:package": [
+ {
+ "_links": {
+ "self": {
+ "href": "https://search.apps.ubuntu.com/api/v1/package/iZvp6HUG9XOQv4vuRQL9MlEgKBgFwsc6"
+ }
+ },
+ "anon_download_url": "https://public.apps.ubuntu.com/anon/download/canonical/hello-world.canonical/hello-world.canonical_5.0_all.snap",
+ "binary_filesize": 20480,
+ "channel": "edge",
+ "content": "application",
+ "description": "This is a simple hello world example.",
+ "download_sha512": "4faffe7e2fee66dbcd1cff629b4f6fa7e5e8e904b4a49b0a908a0ea5518b025bf01f0e913617f6088b30c6c6151eff0a83e89c6b12aea420c4dd0e402bf10c81",
+ "download_url": "https://public.apps.ubuntu.com/download-snap/canonical/hello-world.canonical/hello-world.canonical_5.0_all.snap",
+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/03/hello.svg_NZLfWbh.png",
+ "last_updated": "2016-03-03T19:52:01.075726Z",
+ "origin": "canonical",
+ "package_name": "hello-world",
+ "prices": {},
+ "publisher": "Canonical",
+ "ratings_average": 0.0,
+ "revision": 22,
+ "summary": "Hello world example",
+ "support_url": "mailto:snappy-devel@lists.ubuntu.com",
+ "title": "hello-world",
+ "version": "5.0"
+ }
+ ]
+ },
"_links": {
"curies": [
{
@@ -193,72 +216,16 @@ const MockDetailsJSON = `{
"templated": true
}
],
+ "first": {
+ "href": "https://search.apps.ubuntu.com/api/v1/search?q=package_name%3A%22hello-world%22&fields=channel%2Cpublisher%2Cpackage_name%2Corigin%2Cdescription%2Csummary%2Ctitle%2Cicon_url%2Cprices%2Ccontent%2Cratings_average%2Cversion%2Canon_download_url%2Cdownload_url%2Cdownload_sha512%2Clast_updated%2Cbinary_filesize%2Csupport_url%2Crevision&page=1"
+ },
+ "last": {
+ "href": "https://search.apps.ubuntu.com/api/v1/search?q=package_name%3A%22hello-world%22&fields=channel%2Cpublisher%2Cpackage_name%2Corigin%2Cdescription%2Csummary%2Ctitle%2Cicon_url%2Cprices%2Ccontent%2Cratings_average%2Cversion%2Canon_download_url%2Cdownload_url%2Cdownload_sha512%2Clast_updated%2Cbinary_filesize%2Csupport_url%2Crevision&page=1"
+ },
"self": {
- "href": "https://search.apps.ubuntu.com/api/v1/package/8nzc1x4iim2xj1g2ul64.chipaca"
+ "href": "https://search.apps.ubuntu.com/api/v1/search?q=package_name%3A%22hello-world%22&fields=channel%2Cpublisher%2Cpackage_name%2Corigin%2Cdescription%2Csummary%2Ctitle%2Cicon_url%2Cprices%2Ccontent%2Cratings_average%2Cversion%2Canon_download_url%2Cdownload_url%2Cdownload_sha512%2Clast_updated%2Cbinary_filesize%2Csupport_url%2Crevision&page=1"
}
- },
- "alias": null,
- "allow_unauthenticated": true,
- "anon_download_url": "https://public.apps.ubuntu.com/anon/download/chipaca/8nzc1x4iim2xj1g2ul64.chipaca/8nzc1x4iim2xj1g2ul64.chipaca_42_all.snap",
- "architecture": [
- "all"
- ],
- "binary_filesize": 65375,
- "blacklist_country_codes": [
- "AX"
- ],
- "channel": "edge",
- "changelog": "",
- "click_framework": [],
- "click_version": "0.1",
- "company_name": "",
- "content": "application",
- "date_published": "2015-04-15T18:34:40.060874Z",
- "department": [
- "food-drink"
- ],
- "summary": "hello world example",
- "description": "Returns for store credit only.\nThis is a simple hello world example.",
- "developer_name": "John Lenton",
- "download_sha512": "5364253e4a988f4f5c04380086d542f410455b97d48cc6c69ca2a5877d8aef2a6b2b2f83ec4f688cae61ebc8a6bf2cdbd4dbd8f743f0522fc76540429b79df42",
- "download_url": "https://public.apps.ubuntu.com/download/chipaca/8nzc1x4iim2xj1g2ul64.chipaca/8nzc1x4iim2xj1g2ul64.chipaca_42_all.snap",
- "framework": [],
- "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/04/hello.svg_Dlrd3L4.png",
- "icon_urls": {
- "256": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/04/hello.svg_Dlrd3L4.png"
- },
- "id": 2333,
- "keywords": [],
- "last_updated": "2015-04-15T18:30:16Z",
- "license": "Proprietary",
- "name": "8nzc1x4iim2xj1g2ul64.chipaca",
- "origin": "chipaca",
- "package_name": "8nzc1x4iim2xj1g2ul64",
- "price": 0.0,
- "prices": {},
- "publisher": "John Lenton",
- "ratings_average": 0.0,
- "release": [
- "15.04-core"
- ],
- "revision": 15,
- "screenshot_url": null,
- "screenshot_urls": [],
- "status": "Published",
- "stores": {
- "ubuntu": {
- "status": "Published"
- }
- },
- "support_url": "http://lmgtfy.com",
- "terms_of_service": "",
- "title": "Returns for store credit only.",
- "translations": {},
- "version": "42",
- "video_embedded_html_urls": [],
- "video_urls": [],
- "website": "",
- "whitelist_country_codes": []
+ }
}
`
@@ -268,40 +235,71 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryDetails(c *C) {
storeID := r.Header.Get("X-Ubuntu-Store")
c.Check(storeID, Equals, "")
- c.Check(r.URL.Path, Equals, fmt.Sprintf("/details/%s.%s/edge", funkyAppName, funkyAppDeveloper))
+ c.Check(r.URL.Path, Equals, "/search")
+
+ q := r.URL.Query()
+ c.Check(q.Get("q"), Equals, "package_name:\"hello-world\"")
+ c.Check(r.Header.Get("X-Ubuntu-Device-Channel"), Equals, "edge")
io.WriteString(w, MockDetailsJSON)
}))
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
- detailsURI, err := url.Parse(mockServer.URL + "/details/")
+ searchURI, err := url.Parse(mockServer.URL + "/search")
c.Assert(err, IsNil)
cfg := SnapUbuntuStoreConfig{
- DetailsURI: detailsURI,
+ SearchURI: searchURI,
}
repo := NewUbuntuStoreSnapRepository(&cfg, "")
c.Assert(repo, NotNil)
// the actual test
- result, err := repo.Snap(funkyAppName+"."+funkyAppDeveloper, "edge")
+ result, err := repo.Snap("hello-world", "edge")
c.Assert(err, IsNil)
- c.Check(result.Name(), Equals, funkyAppName)
- c.Check(result.Developer, Equals, funkyAppDeveloper)
- c.Check(result.Version, Equals, "42")
- c.Check(result.Sha512, Equals, "5364253e4a988f4f5c04380086d542f410455b97d48cc6c69ca2a5877d8aef2a6b2b2f83ec4f688cae61ebc8a6bf2cdbd4dbd8f743f0522fc76540429b79df42")
- c.Check(result.Size, Equals, int64(65375))
+ c.Check(result.Name(), Equals, "hello-world")
+ c.Check(result.Developer, Equals, "canonical")
+ c.Check(result.Version, Equals, "5.0")
+ c.Check(result.Sha512, Equals, "4faffe7e2fee66dbcd1cff629b4f6fa7e5e8e904b4a49b0a908a0ea5518b025bf01f0e913617f6088b30c6c6151eff0a83e89c6b12aea420c4dd0e402bf10c81")
+ c.Check(result.Size, Equals, int64(20480))
c.Check(result.Channel, Equals, "edge")
- c.Check(result.Description(), Equals, "Returns for store credit only.\nThis is a simple hello world example.")
- c.Check(result.Summary(), Equals, "hello world example")
+ c.Check(result.Description(), Equals, "This is a simple hello world example.")
+ c.Check(result.Summary(), Equals, "Hello world example")
}
-const MockNoDetailsJSON = `{"errors": ["No such package"], "result": "error"}`
+/*
+acquired via
+curl -s -H "accept: application/hal+json" -H "X-Ubuntu-Release: rolling-core" -H "X-Ubuntu-Device-Channel: edge" 'https://search.apps.ubuntu.com/api/v1/search?q=package_name:""&fields=channel,publisher,package_name,origin,description,summary,title,icon_url,prices,content,ratings_average,version,anon_download_url,download_url,download_sha512,last_updated,binary_filesize,support_url,revision' | python -m json.tool
+*/
+const MockNoDetailsJSON = `{
+ "_links": {
+ "curies": [
+ {
+ "href": "https://wiki.ubuntu.com/AppStore/Interfaces/ClickPackageIndex#reltype_{rel}",
+ "name": "clickindex",
+ "templated": true
+ }
+ ],
+ "first": {
+ "href": "https://search.apps.ubuntu.com/api/v1/search?q=package_name%3A%22%22&fields=publisher%2Cpackage_name%2Corigin%2Cdescription%2Csummary%2Ctitle%2Cicon_url%2Cprices%2Ccontent%2Cratings_average%2Cversion%2Canon_download_url%2Cdownload_url%2Cdownload_sha512%2Clast_updated%2Cbinary_filesize%2Csupport_url%2Crevision&page=1"
+ },
+ "last": {
+ "href": "https://search.apps.ubuntu.com/api/v1/search?q=package_name%3A%22%22&fields=publisher%2Cpackage_name%2Corigin%2Cdescription%2Csummary%2Ctitle%2Cicon_url%2Cprices%2Ccontent%2Cratings_average%2Cversion%2Canon_download_url%2Cdownload_url%2Cdownload_sha512%2Clast_updated%2Cbinary_filesize%2Csupport_url%2Crevision&page=1"
+ },
+ "self": {
+ "href": "https://search.apps.ubuntu.com/api/v1/search?q=package_name%3A%22%22&fields=publisher%2Cpackage_name%2Corigin%2Cdescription%2Csummary%2Ctitle%2Cicon_url%2Cprices%2Ccontent%2Cratings_average%2Cversion%2Canon_download_url%2Cdownload_url%2Cdownload_sha512%2Clast_updated%2Cbinary_filesize%2Csupport_url%2Crevision&page=1"
+ }
+ }
+}
+`
func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryNoDetails(c *C) {
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- c.Assert(r.URL.Path, Equals, "/details/no-such-pkg/edge")
+ c.Check(r.URL.Path, Equals, "/search")
+
+ q := r.URL.Query()
+ c.Check(q.Get("q"), Equals, "package_name:\"no-such-pkg\"")
+ c.Check(r.Header.Get("X-Ubuntu-Device-Channel"), Equals, "edge")
w.WriteHeader(404)
io.WriteString(w, MockNoDetailsJSON)
}))
@@ -309,11 +307,10 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositoryNoDetails(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
- detailsURI, err := url.Parse(mockServer.URL + "/details/")
+ searchURI, err := url.Parse(mockServer.URL + "/search")
c.Assert(err, IsNil)
cfg := SnapUbuntuStoreConfig{
- DetailsURI: detailsURI,
+ SearchURI: searchURI,
}
repo := NewUbuntuStoreSnapRepository(&cfg, "")
c.Assert(repo, NotNil)
@@ -505,7 +502,6 @@ func (t *remoteRepoTestSuite) TestMyAppsURLDependsOnEnviron(c *C) {
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(defaultConfig.DetailsURI.String(), Equals, "https://search.apps.ubuntu.com/api/v1/package/")
c.Check(strings.HasPrefix(defaultConfig.BulkURI.String(), "https://search.apps.ubuntu.com/api/v1/click-metadata?"), Equals, true)
c.Check(defaultConfig.AssertionsURI.String(), Equals, "https://assertions.ubuntu.com/v1/assertions/")
}
@@ -587,11 +583,10 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositorySuggestedCurrency(c *C) {
c.Assert(mockServer, NotNil)
defer mockServer.Close()
- var err error
- detailsURI, err := url.Parse(mockServer.URL + "/details/")
+ searchURI, err := url.Parse(mockServer.URL + "/search")
c.Assert(err, IsNil)
cfg := SnapUbuntuStoreConfig{
- DetailsURI: detailsURI,
+ SearchURI: searchURI,
}
repo := NewUbuntuStoreSnapRepository(&cfg, "")
c.Assert(repo, NotNil)
@@ -600,7 +595,7 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositorySuggestedCurrency(c *C) {
c.Check(repo.SuggestedCurrency(), Equals, "USD")
// we should soon have a suggested currency
- result, err := repo.Snap(funkyAppName+"."+funkyAppDeveloper, "edge")
+ result, err := repo.Snap(funkyAppName, "edge")
c.Assert(err, IsNil)
c.Assert(result, NotNil)
c.Check(repo.SuggestedCurrency(), Equals, "GBP")
@@ -608,7 +603,7 @@ func (t *remoteRepoTestSuite) TestUbuntuStoreRepositorySuggestedCurrency(c *C) {
suggestedCurrency = "EUR"
// checking the currency updates
- result, err = repo.Snap(funkyAppName+"."+funkyAppDeveloper, "edge")
+ result, err = repo.Snap(funkyAppName, "edge")
c.Assert(err, IsNil)
c.Assert(result, NotNil)
c.Check(repo.SuggestedCurrency(), Equals, "EUR")